diff --git a/src/main/text-generation/commit-message-text-generation.test.ts b/src/main/text-generation/commit-message-text-generation.test.ts
index 77665b899..e9e5b5f42 100644
--- a/src/main/text-generation/commit-message-text-generation.test.ts
+++ b/src/main/text-generation/commit-message-text-generation.test.ts
@@ -6,9 +6,9 @@ import type * as ChildProcess from 'child_process'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../shared/constants'
import {
- applyOrcaAttribution,
generateCommitMessageFromContext,
- resolveCommitMessageSettings
+ resolveCommitMessageSettings,
+ trimGeneratedCommitMessage
} from './commit-message-text-generation'
vi.mock('child_process', async (importOriginal) => {
@@ -54,10 +54,25 @@ describe('resolveCommitMessageSettings', () => {
ok: true,
params: {
agentId: 'codex',
- model: 'gpt-5.4-mini',
+ model: 'gpt-5.5',
thinkingLevel: 'low',
- customPrompt: 'Use Conventional Commits.',
- attributionEnabled: true
+ customPrompt: 'Use Conventional Commits.'
+ }
+ })
+ })
+
+ it("uses the user's default agent when the AI setting has no explicit agent", () => {
+ const settings = getDefaultSettings('/tmp')
+ settings.defaultTuiAgent = 'codex'
+
+ const result = resolveCommitMessageSettings(settings)
+
+ expect(result).toMatchObject({
+ ok: true,
+ params: {
+ agentId: 'codex',
+ model: 'gpt-5.5',
+ thinkingLevel: 'low'
}
})
})
@@ -67,8 +82,8 @@ describe('resolveCommitMessageSettings', () => {
settings.commitMessageAi = {
enabled: true,
agentId: 'codex',
- selectedModelByAgent: { codex: 'gpt-5.5' },
- selectedThinkingByModel: { 'gpt-5.5': 'turbo' },
+ selectedModelByAgent: { codex: 'gpt-5.4-mini' },
+ selectedThinkingByModel: { 'gpt-5.4-mini': 'turbo' },
customPrompt: '',
customAgentCommand: ''
}
@@ -79,7 +94,7 @@ describe('resolveCommitMessageSettings', () => {
ok: true,
params: {
agentId: 'codex',
- model: 'gpt-5.5',
+ model: 'gpt-5.4-mini',
thinkingLevel: 'low'
}
})
@@ -239,8 +254,7 @@ describe('generateCommitMessageFromContext', () => {
{
agentId: 'custom',
model: '',
- customAgentCommand: 'agent',
- attributionEnabled: true
+ customAgentCommand: 'agent'
},
{
kind: 'remote',
@@ -257,8 +271,7 @@ describe('generateCommitMessageFromContext', () => {
expect(result).toEqual({
success: true,
- message:
- 'Update README\n\n- Explain the generated commit-message flow\n\nCo-authored-by: Orca ',
+ message: 'Update README\n\n- Explain the generated commit-message flow',
agentLabel: 'agent'
})
})
@@ -464,10 +477,10 @@ describe('generateCommitMessageFromContext', () => {
})
})
-describe('applyOrcaAttribution', () => {
- it('does not duplicate the Orca trailer', () => {
- const message = applyOrcaAttribution('Update docs', true)
+describe('trimGeneratedCommitMessage', () => {
+ it('removes trailing whitespace from generated messages', () => {
+ const message = trimGeneratedCommitMessage('Update docs\n\n')
- expect(applyOrcaAttribution(message, true)).toBe(message)
+ expect(message).toBe('Update docs')
})
})
diff --git a/src/main/text-generation/commit-message-text-generation.ts b/src/main/text-generation/commit-message-text-generation.ts
index e056270f8..cb6aa1c31 100644
--- a/src/main/text-generation/commit-message-text-generation.ts
+++ b/src/main/text-generation/commit-message-text-generation.ts
@@ -15,16 +15,15 @@ import {
} from '../../shared/commit-message-prompt'
import {
CUSTOM_AGENT_ID,
- DEFAULT_COMMIT_MESSAGE_AGENT_ID,
getCommitMessageAgentSpec,
getCommitMessageModel,
- isCustomAgentId
+ isCustomAgentId,
+ resolveCommitMessageAgentChoice
} from '../../shared/commit-message-agent-spec'
import {
planCommitMessageGeneration,
type CommitMessagePlan
} from '../../shared/commit-message-plan'
-import { ORCA_GIT_COMMIT_TRAILER } from '../../shared/orca-attribution'
import { resolveCliCommand } from '../codex-cli/command'
import {
getSpawnArgsForWindows,
@@ -42,8 +41,6 @@ export type GenerateCommitMessageParams = {
customPrompt?: string
customAgentCommand?: string
agentCommandOverride?: string
- /** When true, append `Co-authored-by: Orca ...` after the cleaned message. */
- attributionEnabled?: boolean
}
export type GenerateCommitMessageResult =
@@ -80,33 +77,29 @@ type InternalCommitMessageGenerationResult =
| { success: true; commitMessage: GeneratedCommitMessage; agentLabel?: string }
| { success: false; error: string; canceled?: boolean }
-/** Appends the Orca trailer if the message does not already include it. */
-export function applyOrcaAttribution(message: string, enabled: boolean): string {
- if (!enabled) {
- // Why: trim trailing whitespace even on the no-attribution path so a
- // stray "\n" from the agent's output never reaches the textarea as a
- // visible blank line.
- return message.replace(/\s+$/, '')
- }
- const stripped = message.replace(/\s+$/, '')
- if (stripped.includes(ORCA_GIT_COMMIT_TRAILER)) {
- return stripped
- }
- // Why: a blank line separates the trailer block from the body so `git
- // interpret-trailers` and most parsers treat it as a real trailer instead
- // of a paragraph continuation.
- return `${stripped}\n\n${ORCA_GIT_COMMIT_TRAILER}`
+export function trimGeneratedCommitMessage(message: string): string {
+ return message.replace(/\s+$/, '')
}
export function resolveCommitMessageSettings(
settings: GlobalSettings
): ResolveCommitMessageSettingsResult {
const config = settings.commitMessageAi
- if (!config?.enabled || !config.agentId) {
- return { ok: false, error: 'Enable AI commit messages and choose an agent in Settings -> Git.' }
+ if (!config?.enabled) {
+ return { ok: false, error: 'Enable AI commit messages in Settings -> Git.' }
}
- if (isCustomAgentId(config.agentId)) {
+ const agentChoice = resolveCommitMessageAgentChoice(config.agentId, settings.defaultTuiAgent)
+ if (!agentChoice) {
+ return {
+ ok: false,
+ error:
+ `Default agent "${settings.defaultTuiAgent}" does not support AI commit messages. ` +
+ 'Choose Claude or Codex in Settings -> Git -> AI Commit Messages.'
+ }
+ }
+
+ if (isCustomAgentId(agentChoice)) {
const customAgentCommand = config.customAgentCommand.trim()
if (!customAgentCommand) {
return {
@@ -120,13 +113,12 @@ export function resolveCommitMessageSettings(
agentId: CUSTOM_AGENT_ID,
model: '',
customPrompt: config.customPrompt,
- customAgentCommand,
- attributionEnabled: settings.enableGitHubAttribution === true
+ customAgentCommand
}
}
}
- const agentId = config.agentId ?? DEFAULT_COMMIT_MESSAGE_AGENT_ID
+ const agentId = agentChoice
const spec = getCommitMessageAgentSpec(agentId)
if (!spec) {
return { ok: false, error: `Agent "${agentId}" does not support AI commit messages.` }
@@ -153,8 +145,7 @@ export function resolveCommitMessageSettings(
model: model.id,
thinkingLevel,
customPrompt: config.customPrompt,
- ...(agentCommandOverride ? { agentCommandOverride } : {}),
- attributionEnabled: settings.enableGitHubAttribution === true
+ ...(agentCommandOverride ? { agentCommandOverride } : {})
}
}
}
@@ -418,15 +409,14 @@ async function runRemotePlan(
}
function formatCommitMessageGenerationResult(
- result: InternalCommitMessageGenerationResult,
- attributionEnabled: boolean
+ result: InternalCommitMessageGenerationResult
): GenerateCommitMessageResult {
if (!result.success) {
return result
}
return {
success: true,
- message: applyOrcaAttribution(result.commitMessage.message, attributionEnabled),
+ message: trimGeneratedCommitMessage(result.commitMessage.message),
agentLabel: result.agentLabel
}
}
@@ -446,5 +436,5 @@ export async function generateCommitMessageFromContext(
target.kind === 'remote'
? await runRemotePlan(planned.plan, target)
: await runLocalPlan(planned.plan, target.cwd, target.env)
- return formatCommitMessageGenerationResult(internalResult, params.attributionEnabled === true)
+ return formatCommitMessageGenerationResult(internalResult)
}
diff --git a/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx b/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx
index cce1ecae3..83c9c0872 100644
--- a/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx
+++ b/src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx
@@ -184,6 +184,7 @@ describe('CommitArea AI generation', () => {
const button = findNativeButtonByAriaLabel(element, 'Stop generating commit message')
expect(button.props.title).toBe('Stop generating')
+ expect(hasText(element, 'Generating commit message. Click to stop.')).toBe(true)
;(button.props.onClick as () => void)()
expect(onCancelGenerate).toHaveBeenCalledTimes(1)
})
diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx
index 35536944b..13b490239 100644
--- a/src/renderer/src/components/right-sidebar/SourceControl.tsx
+++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx
@@ -132,6 +132,10 @@ import type {
HostedReviewInfo
} from '../../../../shared/hosted-review'
import { STATUS_COLORS, STATUS_LABELS } from './status-display'
+import {
+ isCustomAgentId,
+ resolveCommitMessageAgentChoice
+} from '../../../../shared/commit-message-agent-spec'
type SourceControlScope = 'all' | 'uncommitted'
type SourceControlViewMode = 'list' | 'tree'
@@ -485,6 +489,10 @@ function SourceControlInner(): React.JSX.Element {
const [createPrDialogOpen, setCreatePrDialogOpen] = useState(false)
const [createPrPushFirst, setCreatePrPushFirst] = useState(false)
const commitMessageAi = useAppStore((s) => s.settings?.commitMessageAi)
+ const effectiveCommitMessageAgentId = useMemo(
+ () => resolveCommitMessageAgentChoice(commitMessageAi?.agentId, settings?.defaultTuiAgent),
+ [commitMessageAi?.agentId, settings?.defaultTuiAgent]
+ )
const filterInputRef = useRef(null)
const commitMessage = readCommitDraftForWorktree(commitDrafts, activeWorktreeId)
const commitError = commitErrors[activeWorktreeId ?? ''] ?? null
@@ -982,11 +990,11 @@ function SourceControlInner(): React.JSX.Element {
if (generateInFlightRef.current[activeWorktreeId]) {
return
}
- if (!commitMessageAi?.enabled || !commitMessageAi.agentId) {
+ if (!commitMessageAi?.enabled || !effectiveCommitMessageAgentId) {
return
}
- if (commitMessageAi.agentId === 'custom') {
+ if (isCustomAgentId(effectiveCommitMessageAgentId)) {
const command = commitMessageAi.customAgentCommand?.trim() ?? ''
if (!command) {
setGenerateErrors((prev) => ({
@@ -1045,7 +1053,7 @@ function SourceControlInner(): React.JSX.Element {
setGenerateInFlightByWorktree((prev) => ({ ...prev, [activeWorktreeId]: false }))
generateInFlightRef.current[activeWorktreeId] = false
}
- }, [activeWorktreeId, commitMessageAi, worktreePath])
+ }, [activeWorktreeId, commitMessageAi, effectiveCommitMessageAgentId, worktreePath])
const handleCancelGenerate = useCallback((): void => {
if (!activeWorktreeId || !worktreePath) {
@@ -2488,11 +2496,11 @@ function SourceControlInner(): React.JSX.Element {
aiEnabled={commitMessageAi?.enabled === true}
aiAgentConfigured={
commitMessageAi?.enabled === true &&
- commitMessageAi.agentId !== null &&
+ effectiveCommitMessageAgentId !== null &&
// Why: 'custom' is configured only once the user types a command.
// Without this guard, Generate would spawn an empty command and
// fail with a confusing error.
- (commitMessageAi.agentId !== 'custom' ||
+ (!isCustomAgentId(effectiveCommitMessageAgentId) ||
(commitMessageAi.customAgentCommand ?? '').trim().length > 0)
}
isGenerating={isGenerating}
@@ -3048,16 +3056,23 @@ export function CommitArea({
// swap to a Square ("stop") with a destructive tint so the user
// sees that clicking will abort the run. Group/group-hover toggles
// keep this stateless on the React side.
-
+
+
+
+
+
+ Generating commit message. Click to stop.
+
+
) : (
-
+
+
+ {unsupportedDefaultAgentLabel ? (
+
+ Your default agent is {unsupportedDefaultAgentLabel}, which does not support commit
+ message generation yet. Choose Claude, Codex, or Custom.
+
+ ) : null}
+
)
}
@@ -413,8 +444,8 @@ export function CommitMessageAiPane({
- Smaller models default to lower latency and cost. Pick a larger one if the diffs you
- review tend to need more reasoning.
+ Defaults to the strongest available model for the selected agent. Pick a smaller one if
+ you prefer lower latency or cost.