mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
* WIP: Changes before auto-review fixes
* Customize Source Control AI action recipes
- Add per-action CLI arguments for generation and launch flows so saved
recipes can select model flags without putting prompts in argv.
- Share the text-generation dialog between commit messages and hosted-review
details, with first-run defaults and repo/global recipe support.
- Launch fix-check agents directly from saved recipes and harden prompt/template
handling for invalid args, blank prompts, and inherited variables.
* Allow custom commands for source control action recipes
- Let text action recipes save and resolve the custom-command sentinel
- Add settings UI for custom command recipes and preserve per-action defaults
- Split large source-control dialogs and direct launch helpers into focused modules
- Keep launch actions from treating custom text agents as runnable TUI agents
* Add per-repo Source Control AI enablement, custom command, and save-targ
- Repo overrides now support `enabled` and `customAgentCommand`, letting
repositories opt in/out of Source Control AI independently and supply a
repo-scoped custom command that takes precedence over the global one.
- Recipe-save dialogs gained a save-target selector ("Don't save / Save for
this repo / Save as global default") replacing the old boolean checkbox,
routing saves through the new `saveSourceControlActionRecipe` helper in
`source-control-ai-recipe-save.ts`.
- `normalizeRepoSourceControlAiOverrides` now returns `undefined` for empty
objects and passes the `null` sentinel through the IPC/RPC layer so the
persistence layer can clear repo overrides cleanly.
- `resolveSourceControlLaunchPlatform` resolves the correct shell platform
for SSH and WSL worktrees so agent launch commands are built correctly.
- Settings UI gained `RepositorySourceControlAiEnablement` and
`RepositorySourceControlAiCustomCommand` rows; draft/label logic was
extracted into focused modules to stay within lint line limits.
* Extract action recipe defaults into own component and use id-prefixed wo
- Move action recipe draft state and UI out of CommitMessageAiPane into SourceControlAiActionRecipeDefaults and source-control-ai-action-recipe-draft.ts to respect the max-lines lint rule
- Use toRuntimeWorktreeSelector() across all runtime git RPC calls so the runtime can resolve worktrees by ID rather than path
- Fix SSH launch platform resolution to use the repo's connection when the newly created worktree isn't hydrated yet
- Add edit and delete handlers for PR conversation comments with confirmation dialog
- Use text-status-success design token instead of hardcoded text-emerald-500
* add more search keyword
* Rename "Enable Source Control AI defaults" to "Show Source Control AI ac
* fix test
* Remove unused imports and variable assignment in launch-work-item-direct
* Fix test mocks to use `mocks.store` instead of `storeState.value` for di
* Extract Source Control AI logic into focused modules with fix-checks dia
---------
Co-authored-by: Orca <help@stably.ai>
319 lines
10 KiB
TypeScript
319 lines
10 KiB
TypeScript
/* eslint-disable max-lines -- Why: this table is the runtime git RPC contract; splitting it would make method coverage harder to audit. */
|
|
import { defineMethod, type RpcMethod } from '../core'
|
|
import type { GlobalSettings } from '../../../../shared/types'
|
|
import type { ResolvedSourceControlAiGenerationParams } from '../../../../shared/source-control-ai'
|
|
import {
|
|
GitBranchCompare,
|
|
GitBranchDiff,
|
|
GitBulkPaths,
|
|
GitCheckIgnored,
|
|
GitCommit,
|
|
GitCommitCompare,
|
|
GitCommitDiff,
|
|
GitDiscoverCommitMessageModels,
|
|
GitDiff,
|
|
GitFilePath,
|
|
GitGenerateCommitMessage,
|
|
GitGeneratePullRequestFields,
|
|
GitHistory,
|
|
GitPush,
|
|
GitRebaseFromBase,
|
|
GitRemoteFileUrl,
|
|
GitStatusParams,
|
|
GitTargetedRemote,
|
|
WorktreeSelector
|
|
} from './git-params'
|
|
|
|
type CommitMessageGenerationOverride = {
|
|
commitMessageAi?: GlobalSettings['commitMessageAi']
|
|
sourceControlAi?: GlobalSettings['sourceControlAi']
|
|
sourceControlAiResolvedParams?: ResolvedSourceControlAiGenerationParams
|
|
agentCmdOverrides?: GlobalSettings['agentCmdOverrides']
|
|
enableGitHubAttribution?: boolean
|
|
commitMessageDiscoveryHostKey?: string
|
|
}
|
|
|
|
// Why: generateCommitMessage and generatePullRequestFields share the same optional
|
|
// override fields; returning undefined when none are set keeps the no-override call path.
|
|
function buildCommitMessageGenerationOverride(params: {
|
|
commitMessageAi?: unknown
|
|
sourceControlAi?: unknown
|
|
sourceControlAiResolvedParams?: unknown
|
|
agentCmdOverrides?: unknown
|
|
enableGitHubAttribution?: boolean
|
|
commitMessageDiscoveryHostKey?: string
|
|
}): CommitMessageGenerationOverride | undefined {
|
|
if (
|
|
params.commitMessageAi === undefined &&
|
|
params.sourceControlAi === undefined &&
|
|
params.sourceControlAiResolvedParams === undefined &&
|
|
params.agentCmdOverrides === undefined &&
|
|
params.enableGitHubAttribution === undefined &&
|
|
params.commitMessageDiscoveryHostKey === undefined
|
|
) {
|
|
return undefined
|
|
}
|
|
return {
|
|
...(params.commitMessageAi !== undefined
|
|
? { commitMessageAi: params.commitMessageAi as GlobalSettings['commitMessageAi'] }
|
|
: {}),
|
|
...(params.sourceControlAi !== undefined
|
|
? { sourceControlAi: params.sourceControlAi as GlobalSettings['sourceControlAi'] }
|
|
: {}),
|
|
...(params.sourceControlAiResolvedParams !== undefined
|
|
? {
|
|
sourceControlAiResolvedParams:
|
|
params.sourceControlAiResolvedParams as ResolvedSourceControlAiGenerationParams
|
|
}
|
|
: {}),
|
|
...(params.agentCmdOverrides !== undefined
|
|
? {
|
|
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
|
|
}
|
|
: {}),
|
|
...(params.enableGitHubAttribution !== undefined
|
|
? { enableGitHubAttribution: params.enableGitHubAttribution }
|
|
: {}),
|
|
...(params.commitMessageDiscoveryHostKey !== undefined
|
|
? { commitMessageDiscoveryHostKey: params.commitMessageDiscoveryHostKey }
|
|
: {})
|
|
}
|
|
}
|
|
|
|
export const GIT_METHODS: RpcMethod[] = [
|
|
defineMethod({
|
|
name: 'git.status',
|
|
params: GitStatusParams,
|
|
handler: async (params, { runtime }) =>
|
|
params.includeIgnored === undefined
|
|
? runtime.getRuntimeGitStatus(params.worktree)
|
|
: runtime.getRuntimeGitStatus(params.worktree, { includeIgnored: params.includeIgnored })
|
|
}),
|
|
defineMethod({
|
|
name: 'git.checkIgnored',
|
|
params: GitCheckIgnored,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.checkRuntimeGitIgnoredPaths(params.worktree, params.paths)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.history',
|
|
params: GitHistory,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitHistory(params.worktree, {
|
|
limit: params.limit,
|
|
baseRef: params.baseRef
|
|
})
|
|
}),
|
|
defineMethod({
|
|
name: 'git.conflictOperation',
|
|
params: WorktreeSelector,
|
|
handler: async (params, { runtime }) => runtime.getRuntimeGitConflictOperation(params.worktree)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.abortMerge',
|
|
params: WorktreeSelector,
|
|
handler: async (params, { runtime }) => runtime.abortRuntimeGitMerge(params.worktree)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.abortRebase',
|
|
params: WorktreeSelector,
|
|
handler: async (params, { runtime }) => runtime.abortRuntimeGitRebase(params.worktree)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.diff',
|
|
params: GitDiff,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitDiff(
|
|
params.worktree,
|
|
params.filePath,
|
|
params.staged,
|
|
params.compareAgainstHead
|
|
)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.branchCompare',
|
|
params: GitBranchCompare,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitBranchCompare(params.worktree, params.baseRef)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.commitCompare',
|
|
params: GitCommitCompare,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitCommitCompare(params.worktree, params.commitId)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.upstreamStatus',
|
|
params: GitTargetedRemote,
|
|
handler: async (params, { runtime }) =>
|
|
params.pushTarget === undefined
|
|
? runtime.getRuntimeGitUpstreamStatus(params.worktree)
|
|
: runtime.getRuntimeGitUpstreamStatus(params.worktree, params.pushTarget)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.fetch',
|
|
params: GitTargetedRemote,
|
|
handler: async (params, { runtime }) =>
|
|
params.pushTarget === undefined
|
|
? runtime.fetchRuntimeGit(params.worktree)
|
|
: runtime.fetchRuntimeGit(params.worktree, params.pushTarget)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.pull',
|
|
params: GitTargetedRemote,
|
|
handler: async (params, { runtime }) =>
|
|
params.pushTarget === undefined
|
|
? runtime.pullRuntimeGit(params.worktree)
|
|
: runtime.pullRuntimeGit(params.worktree, params.pushTarget)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.fastForward',
|
|
params: GitTargetedRemote,
|
|
handler: async (params, { runtime }) =>
|
|
params.pushTarget === undefined
|
|
? runtime.fastForwardRuntimeGit(params.worktree)
|
|
: runtime.fastForwardRuntimeGit(params.worktree, params.pushTarget)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.rebaseFromBase',
|
|
params: GitRebaseFromBase,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.rebaseRuntimeGitFromBase(params.worktree, params.baseRef)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.push',
|
|
params: GitPush,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.pushRuntimeGit(
|
|
params.worktree,
|
|
params.publish,
|
|
params.pushTarget,
|
|
params.forceWithLease
|
|
)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.branchDiff',
|
|
params: GitBranchDiff,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitBranchDiff(
|
|
params.worktree,
|
|
params.compare,
|
|
params.filePath,
|
|
params.oldPath
|
|
)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.commitDiff',
|
|
params: GitCommitDiff,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitCommitDiff(params.worktree, {
|
|
commitOid: params.commitOid,
|
|
parentOid: params.parentOid,
|
|
filePath: params.filePath,
|
|
oldPath: params.oldPath
|
|
})
|
|
}),
|
|
defineMethod({
|
|
name: 'git.commit',
|
|
params: GitCommit,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.commitRuntimeGit(params.worktree, params.message)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.generateCommitMessage',
|
|
params: GitGenerateCommitMessage,
|
|
handler: async (params, { runtime }) => {
|
|
const override = buildCommitMessageGenerationOverride(params)
|
|
if (override === undefined) {
|
|
return runtime.generateRuntimeCommitMessage(params.worktree)
|
|
}
|
|
return runtime.generateRuntimeCommitMessage(params.worktree, override)
|
|
}
|
|
}),
|
|
defineMethod({
|
|
name: 'git.discoverCommitMessageModels',
|
|
params: GitDiscoverCommitMessageModels,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.discoverRuntimeCommitMessageModels(
|
|
params.worktree,
|
|
params.agentId,
|
|
params.agentCmdOverrides !== undefined
|
|
? {
|
|
agentCmdOverrides: params.agentCmdOverrides as GlobalSettings['agentCmdOverrides']
|
|
}
|
|
: {}
|
|
)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.cancelGenerateCommitMessage',
|
|
params: WorktreeSelector,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.cancelRuntimeGenerateCommitMessage(params.worktree)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.generatePullRequestFields',
|
|
params: GitGeneratePullRequestFields,
|
|
handler: async (params, { runtime }) => {
|
|
const input = {
|
|
base: params.base,
|
|
title: params.title,
|
|
body: params.body,
|
|
draft: params.draft
|
|
}
|
|
const override = buildCommitMessageGenerationOverride(params)
|
|
if (override === undefined) {
|
|
return runtime.generateRuntimePullRequestFields(params.worktree, input)
|
|
}
|
|
return runtime.generateRuntimePullRequestFields(params.worktree, input, override)
|
|
}
|
|
}),
|
|
defineMethod({
|
|
name: 'git.cancelGeneratePullRequestFields',
|
|
params: WorktreeSelector,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.cancelRuntimeGeneratePullRequestFields(params.worktree)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.stage',
|
|
params: GitFilePath,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.stageRuntimeGitPath(params.worktree, params.filePath)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.bulkStage',
|
|
params: GitBulkPaths,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.bulkStageRuntimeGitPaths(params.worktree, params.filePaths)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.unstage',
|
|
params: GitFilePath,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.unstageRuntimeGitPath(params.worktree, params.filePath)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.bulkUnstage',
|
|
params: GitBulkPaths,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.bulkUnstageRuntimeGitPaths(params.worktree, params.filePaths)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.discard',
|
|
params: GitFilePath,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.discardRuntimeGitPath(params.worktree, params.filePath)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.bulkDiscard',
|
|
params: GitBulkPaths,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.bulkDiscardRuntimeGitPaths(params.worktree, params.filePaths)
|
|
}),
|
|
defineMethod({
|
|
name: 'git.remoteFileUrl',
|
|
params: GitRemoteFileUrl,
|
|
handler: async (params, { runtime }) =>
|
|
runtime.getRuntimeGitRemoteFileUrl(params.worktree, params.relativePath, params.line)
|
|
})
|
|
]
|