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>
218 lines
6.4 KiB
TypeScript
218 lines
6.4 KiB
TypeScript
import { z } from 'zod'
|
|
|
|
export const WorktreeSelector = z.object({
|
|
worktree: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(z.string().min(1, 'Missing worktree selector'))
|
|
})
|
|
|
|
export const GitStatusParams = WorktreeSelector.extend({
|
|
includeIgnored: z.boolean().optional()
|
|
})
|
|
|
|
export const GitCheckIgnored = WorktreeSelector.extend({
|
|
paths: z.array(z.string().min(1, 'Missing path')).max(2000)
|
|
})
|
|
|
|
export const GitFilePath = WorktreeSelector.extend({
|
|
filePath: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(z.string().min(1, 'Missing file path'))
|
|
})
|
|
|
|
export const GitDiff = GitFilePath.extend({
|
|
staged: z.boolean(),
|
|
compareAgainstHead: z.boolean().optional()
|
|
})
|
|
|
|
export const GitBranchCompare = WorktreeSelector.extend({
|
|
baseRef: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(
|
|
z
|
|
.string()
|
|
.min(1, 'Missing base ref')
|
|
.refine((value) => !value.startsWith('-'), 'Base ref must not start with -')
|
|
)
|
|
})
|
|
|
|
const FullGitObjectId = z
|
|
.string()
|
|
.regex(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/, 'Expected a full git object id')
|
|
|
|
export const GitCommitCompare = WorktreeSelector.extend({
|
|
commitId: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(FullGitObjectId)
|
|
})
|
|
|
|
export const GitHistory = WorktreeSelector.extend({
|
|
limit: z.number().int().min(1).max(200).optional(),
|
|
baseRef: z.string().nullable().optional()
|
|
})
|
|
|
|
export const GitBranchDiff = GitFilePath.extend({
|
|
compare: z.object({
|
|
baseRef: z.string().optional(),
|
|
baseOid: FullGitObjectId.optional(),
|
|
headOid: FullGitObjectId,
|
|
mergeBase: FullGitObjectId
|
|
}),
|
|
oldPath: z.string().optional()
|
|
})
|
|
|
|
export const GitCommitDiff = GitFilePath.extend({
|
|
commitOid: FullGitObjectId,
|
|
parentOid: FullGitObjectId.nullable().optional(),
|
|
oldPath: z.string().optional()
|
|
})
|
|
|
|
export const GitCommit = WorktreeSelector.extend({
|
|
message: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(z.string().min(1, 'Missing commit message'))
|
|
})
|
|
|
|
const CommitMessageModelCapability = z.object({
|
|
id: z.string(),
|
|
label: z.string(),
|
|
thinkingLevels: z.array(z.object({ id: z.string(), label: z.string() })).optional(),
|
|
defaultThinkingLevel: z.string().optional()
|
|
})
|
|
|
|
const CommitMessageAiSettings = z.object({
|
|
enabled: z.boolean(),
|
|
agentId: z.string().nullable(),
|
|
selectedModelByAgent: z.record(z.string(), z.string()),
|
|
selectedModelByAgentByHost: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
discoveredModelsByAgent: z.record(z.string(), z.array(CommitMessageModelCapability)).optional(),
|
|
discoveredModelsByAgentByHost: z
|
|
.record(z.string(), z.record(z.string(), z.array(CommitMessageModelCapability)))
|
|
.optional(),
|
|
selectedThinkingByModel: z.record(z.string(), z.string()),
|
|
customPrompt: z.string(),
|
|
customAgentCommand: z.string()
|
|
})
|
|
|
|
const SourceControlAiSettings = CommitMessageAiSettings.omit({ customPrompt: true }).extend({
|
|
actions: z
|
|
.record(
|
|
z.string(),
|
|
z.object({
|
|
agentId: z.string().nullable().optional(),
|
|
commandInputTemplate: z.string().optional(),
|
|
agentArgs: z.string().optional()
|
|
})
|
|
)
|
|
.optional(),
|
|
instructionsByOperation: z.record(z.string(), z.string()).optional(),
|
|
modelOverridesByOperation: z
|
|
.record(
|
|
z.string(),
|
|
z.object({
|
|
selectedModelByAgent: z.record(z.string(), z.string()).optional(),
|
|
selectedModelByAgentByHost: z
|
|
.record(z.string(), z.record(z.string(), z.string()))
|
|
.optional(),
|
|
selectedThinkingByModel: z.record(z.string(), z.string()).optional()
|
|
})
|
|
)
|
|
.optional(),
|
|
prCreationDefaults: z
|
|
.object({
|
|
draft: z.boolean().optional(),
|
|
useTemplate: z.boolean().optional(),
|
|
generateDetailsOnOpen: z.boolean().optional(),
|
|
openAfterCreate: z.boolean().optional()
|
|
})
|
|
.optional(),
|
|
launchActionDefaults: z
|
|
.record(
|
|
z.string(),
|
|
z.object({
|
|
agentId: z.string().nullable().optional(),
|
|
commandInputTemplate: z.string().optional(),
|
|
agentArgs: z.string().optional()
|
|
})
|
|
)
|
|
.optional()
|
|
})
|
|
|
|
const ResolvedSourceControlAiGenerationParams = z.object({
|
|
agentId: z.string(),
|
|
model: z.string(),
|
|
thinkingLevel: z.string().optional(),
|
|
customPrompt: z.string().optional(),
|
|
commandInputTemplate: z.string().optional(),
|
|
agentArgs: z.string().optional(),
|
|
customAgentCommand: z.string().optional(),
|
|
agentCommandOverride: z.string().optional()
|
|
})
|
|
|
|
export const GitGenerateCommitMessage = WorktreeSelector.extend({
|
|
commitMessageAi: CommitMessageAiSettings.optional(),
|
|
sourceControlAi: SourceControlAiSettings.optional(),
|
|
sourceControlAiResolvedParams: ResolvedSourceControlAiGenerationParams.optional(),
|
|
agentCmdOverrides: z.record(z.string(), z.string()).optional(),
|
|
enableGitHubAttribution: z.boolean().optional(),
|
|
commitMessageDiscoveryHostKey: z.string().optional()
|
|
})
|
|
|
|
export const GitDiscoverCommitMessageModels = WorktreeSelector.extend({
|
|
agentId: z.string().min(1, 'Missing agent id'),
|
|
agentCmdOverrides: z.record(z.string(), z.string()).optional()
|
|
})
|
|
|
|
export const GitGeneratePullRequestFields = GitGenerateCommitMessage.extend({
|
|
base: z.string().min(1, 'Missing base branch'),
|
|
title: z.string(),
|
|
body: z.string(),
|
|
draft: z.boolean()
|
|
})
|
|
|
|
export const GitBulkPaths = WorktreeSelector.extend({
|
|
filePaths: z.array(z.string().min(1, 'Missing file path'))
|
|
})
|
|
|
|
const GitPushTargetParam = z.object({
|
|
remoteName: z.string(),
|
|
branchName: z.string(),
|
|
remoteUrl: z.string().optional(),
|
|
remoteCreated: z.boolean().optional()
|
|
})
|
|
|
|
export const GitPush = WorktreeSelector.extend({
|
|
publish: z.boolean().optional(),
|
|
forceWithLease: z.boolean().optional(),
|
|
pushTarget: GitPushTargetParam.optional()
|
|
})
|
|
|
|
export const GitTargetedRemote = WorktreeSelector.extend({
|
|
pushTarget: GitPushTargetParam.optional()
|
|
})
|
|
|
|
export const GitRebaseFromBase = WorktreeSelector.extend({
|
|
baseRef: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(
|
|
z
|
|
.string()
|
|
.min(1, 'Missing base ref')
|
|
.refine((value) => !value.startsWith('-'), 'Base ref must not start with -')
|
|
)
|
|
})
|
|
|
|
export const GitRemoteFileUrl = WorktreeSelector.extend({
|
|
relativePath: z
|
|
.unknown()
|
|
.transform((v) => (typeof v === 'string' ? v : ''))
|
|
.pipe(z.string().min(1, 'Missing relative path')),
|
|
line: z.number().int().min(1)
|
|
})
|