Files
orca/src/shared/source-control-ai-settings.ts
T
Brennan Benson 6aa12c30ef Name native Claude and Codex chats after their first message (#25724)
* feat: generate chat names through configured text agents

* Project structured chat names across stored tabs and session rows

* Name native chats from their first live message

* Restore the journal test provider handle import

* Fix first-message naming and live Vault title updates

* Preserve Unicode characters in bounded chat naming prompts

* Read chat naming settings only when a turn needs them

* fix(chat): store only generated conversation names

* fix(chat): keep ordinary labels across unnamed chat surfaces

* fix(chat): preserve naming after fast first turns

* Preserve native chat names across command-first sends and Vault lifecycle

* Route native chat SQLite contracts through the existing Node test pool

* feat(settings): add chat naming controls to Chat page

* Preserve chat naming drafts and configure Custom commands through host settings

* Scope synthetic command output to its journal thread in naming test

* Keep naming test fixtures within their typed project boundaries

* Resolve the real Vault hook directly in its integration test

* Publish chat naming save refs after render commits

* fix: keep Japanese chat naming copy stable during repair

* Update test contracts for the naming integration

* Keep journal fixture reads on the host clock

* Give Chat names a separate settings section
2026-10-06 14:42:59 -07:00

160 lines
5.9 KiB
TypeScript

import { isCustomAgentId } from './commit-message-agent-spec'
import type { CommitMessageAiSettings } from './commit-message-ai-types'
import {
DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES,
AI_ACTION_IDS,
SOURCE_CONTROL_TEXT_ACTION_IDS,
normalizeSourceControlAiActionDefaults,
readSourceControlActionDefault
} from './source-control-ai-actions'
import {
actionRecipeFromLegacyCommitMessageAi,
commandTemplateFromOperationInstruction,
isLegacyBranchInstructionTemplate
} from './source-control-ai-command-template'
import type {
SourceControlAiPrCreationDefaults,
SourceControlAiSettings
} from './source-control-ai-types'
export const DEFAULT_SOURCE_CONTROL_AI_PR_CREATION_DEFAULTS: Required<SourceControlAiPrCreationDefaults> =
{
draft: false,
useTemplate: false,
generateDetailsOnOpen: false,
openAfterCreate: false
}
function copyRecord<T>(value: T | undefined): T | undefined {
return value === undefined ? undefined : structuredClone(value)
}
export function getDefaultSourceControlAiSettings(): SourceControlAiSettings {
const actions: NonNullable<SourceControlAiSettings['actions']> = {}
for (const actionId of AI_ACTION_IDS) {
actions[actionId] = {
commandInputTemplate: DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES[actionId]
}
}
return {
enabled: true,
actions,
agentId: null,
selectedModelByAgent: {},
selectedModelByAgentByHost: {},
discoveredModelsByAgent: {},
discoveredModelsByAgentByHost: {},
selectedThinkingByModel: {},
customAgentCommand: '',
instructionsByOperation: { commitMessage: '', pullRequest: '', branchName: '' },
prCreationDefaults: { ...DEFAULT_SOURCE_CONTROL_AI_PR_CREATION_DEFAULTS },
launchActionDefaults: {}
}
}
export function sourceControlAiSettingsFromLegacy(
legacy: CommitMessageAiSettings | null | undefined
): SourceControlAiSettings {
const defaults = getDefaultSourceControlAiSettings()
if (!legacy) {
return defaults
}
const legacyActionRecipe = actionRecipeFromLegacyCommitMessageAi(legacy)
return {
...defaults,
enabled: legacy.enabled,
agentId: legacy.agentId,
selectedModelByAgent: { ...legacy.selectedModelByAgent },
selectedModelByAgentByHost: copyRecord(legacy.selectedModelByAgentByHost) ?? {},
discoveredModelsByAgent: copyRecord(legacy.discoveredModelsByAgent) ?? {},
discoveredModelsByAgentByHost: copyRecord(legacy.discoveredModelsByAgentByHost) ?? {},
selectedThinkingByModel: { ...legacy.selectedThinkingByModel },
customAgentCommand: legacy.customAgentCommand,
instructionsByOperation: {
commitMessage: legacy.customPrompt ?? '',
pullRequest: '',
branchName: legacy.customPrompt ?? ''
},
actions: {
...defaults.actions,
commitMessage: legacyActionRecipe,
branchName: {
...legacyActionRecipe,
commandInputTemplate: commandTemplateFromOperationInstruction(
'branchName',
legacy.customPrompt
)
}
}
}
}
export function normalizeSourceControlAiSettings(
value: SourceControlAiSettings | null | undefined,
legacy?: CommitMessageAiSettings | null
): SourceControlAiSettings {
const base = value ?? sourceControlAiSettingsFromLegacy(legacy)
const defaults = getDefaultSourceControlAiSettings()
const normalizedLaunchActionDefaults = normalizeSourceControlAiActionDefaults(
base.launchActionDefaults
)
const normalizedActions = {
...normalizedLaunchActionDefaults,
...normalizeSourceControlAiActionDefaults(base.actions)
}
const migratedTextActions = Object.fromEntries(
SOURCE_CONTROL_TEXT_ACTION_IDS.map((actionId) => {
const existing = readSourceControlActionDefault(normalizedActions, actionId)
const instruction = base.instructionsByOperation?.[actionId]
const legacyInstruction = actionId === 'commitMessage' ? legacy?.customPrompt : undefined
const resolvedInstruction = instruction ?? legacyInstruction
const instructionTemplate =
instruction || legacyInstruction
? commandTemplateFromOperationInstruction(actionId, resolvedInstruction)
: undefined
const shouldApplyInstructionTemplate =
instructionTemplate !== undefined &&
(existing.commandInputTemplate === undefined ||
existing.commandInputTemplate ===
DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES[actionId] ||
isLegacyBranchInstructionTemplate(
actionId,
resolvedInstruction,
existing.commandInputTemplate
))
return [
actionId,
{
...defaults.actions?.[actionId],
...(base.agentId && !isCustomAgentId(base.agentId) ? { agentId: base.agentId } : {}),
...existing,
...(shouldApplyInstructionTemplate ? { commandInputTemplate: instructionTemplate } : {})
}
]
})
) as SourceControlAiSettings['actions']
return {
...defaults,
...base,
selectedModelByAgent: { ...defaults.selectedModelByAgent, ...base.selectedModelByAgent },
selectedModelByAgentByHost:
copyRecord(base.selectedModelByAgentByHost) ?? defaults.selectedModelByAgentByHost,
discoveredModelsByAgent:
copyRecord(base.discoveredModelsByAgent) ?? defaults.discoveredModelsByAgent,
discoveredModelsByAgentByHost:
copyRecord(base.discoveredModelsByAgentByHost) ?? defaults.discoveredModelsByAgentByHost,
selectedThinkingByModel: {
...defaults.selectedThinkingByModel,
...base.selectedThinkingByModel
},
instructionsByOperation: {
...defaults.instructionsByOperation,
...base.instructionsByOperation
},
modelOverridesByOperation: copyRecord(base.modelOverridesByOperation),
prCreationDefaults: { ...defaults.prCreationDefaults, ...base.prCreationDefaults },
actions: { ...defaults.actions, ...normalizedActions, ...migratedTextActions },
launchActionDefaults: normalizedLaunchActionDefaults ?? defaults.launchActionDefaults
}
}