circuschief 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/package.json +1 -1
  2. package/packages/server/src/agents/adapters/CodexAdapter.js +5 -4
  3. package/packages/server/src/api/canvas.js +22 -57
  4. package/packages/server/src/api/commandButtons.js +16 -15
  5. package/packages/server/src/api/index.js +2 -0
  6. package/packages/server/src/api/kanban.js +4 -2
  7. package/packages/server/src/api/projects-commandButtons.js +6 -6
  8. package/packages/server/src/api/projects-helpers.js +20 -3
  9. package/packages/server/src/api/projects-session-create.js +109 -0
  10. package/packages/server/src/api/projects-session-defaults.js +51 -0
  11. package/packages/server/src/api/projects-session-helpers.js +57 -5
  12. package/packages/server/src/api/projects-templates.js +38 -0
  13. package/packages/server/src/api/projects.js +28 -170
  14. package/packages/server/src/api/providers.js +11 -1
  15. package/packages/server/src/api/sessions-commands.js +46 -25
  16. package/packages/server/src/api/sessions-lifecycle.js +10 -10
  17. package/packages/server/src/api/sessions-patch.js +45 -1
  18. package/packages/server/src/api/sessions.js +6 -5
  19. package/packages/server/src/database.js +0 -2
  20. package/packages/server/src/db/DatabaseManager.js +5 -1
  21. package/packages/server/src/db/ProjectDefaultsRepository.js +3 -3
  22. package/packages/server/src/db/ProviderRepository.js +87 -32
  23. package/packages/server/src/db/SessionRepository.js +2 -1
  24. package/packages/server/src/db/SessionTemplateRepository.js +23 -2
  25. package/packages/server/src/db/index.js +0 -3
  26. package/packages/server/src/db/migrations/index.js +60 -7
  27. package/packages/server/src/db/migrations/kanbanMigrations.js +1 -1
  28. package/packages/server/src/db/migrations/miscMigrations.js +59 -184
  29. package/packages/server/src/db/migrations/projectsMigrations.js +31 -0
  30. package/packages/server/src/db/migrations/providerCommitAttributionMigrations.js +30 -0
  31. package/packages/server/src/db/migrations/providerMigrations.js +165 -0
  32. package/packages/server/src/db/migrations/sessionTableRecreate.js +136 -0
  33. package/packages/server/src/db/migrations/sessionsMigrations.js +18 -5
  34. package/packages/server/src/db/seedBaselineData.js +137 -0
  35. package/packages/server/src/db/session-helpers.js +32 -4
  36. package/packages/server/src/middleware/sessionLookup.js +81 -8
  37. package/packages/server/src/schema.sql +153 -132
  38. package/packages/server/src/scripts/backupDatabase.js +21 -0
  39. package/packages/server/src/scripts/dbUtils.js +81 -0
  40. package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
  41. package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
  42. package/packages/server/src/services/codexSpawnHelper.js +9 -0
  43. package/packages/server/src/services/commandButtonPrompts.js +14 -12
  44. package/packages/server/src/services/commandRunner.js +7 -1
  45. package/packages/server/src/services/e2eSpawnCapture.js +147 -0
  46. package/packages/server/src/services/gitCommitAttribution.js +150 -0
  47. package/packages/server/src/services/gitDiff.js +132 -0
  48. package/packages/server/src/services/gitRepoUrl.js +174 -0
  49. package/packages/server/src/services/gitService.js +48 -311
  50. package/packages/server/src/services/gitSessionSetup.js +11 -1
  51. package/packages/server/src/services/gitWorktree.js +127 -0
  52. package/packages/server/src/services/kanbanTriggers.js +6 -3
  53. package/packages/server/src/services/nodeSpawnHelper.js +9 -0
  54. package/packages/server/src/services/prUrlService.js +3 -3
  55. package/packages/server/src/services/queryParamBuilder.js +90 -0
  56. package/packages/server/src/services/sessionDuplicator.js +1 -5
  57. package/packages/server/src/services/sessionExecution.js +56 -108
  58. package/packages/server/src/services/sessionPrompts.js +12 -47
  59. package/packages/server/src/services/sessionProvider.js +10 -0
  60. package/packages/server/src/services/summaryService.js +5 -3
  61. package/packages/server/src/services/summaryStaleCheck.js +23 -4
  62. package/packages/server/src/services/templateTriggerService.js +3 -1
  63. package/packages/shared/src/constants.js +3 -0
  64. package/packages/shared/src/contracts/commandButtons.js +16 -2
  65. package/packages/shared/src/contracts/projects.js +2 -2
  66. package/packages/shared/src/contracts/providers.js +60 -0
  67. package/packages/shared/src/contracts/sessions.js +29 -2
  68. package/packages/shared/src/contracts/templates.js +12 -2
  69. package/packages/shared/src/types.js +1 -9
  70. package/packages/shared/src/utils.js +2 -2
  71. package/packages/web/dist/assets/{ActiveSessionsView-UJsCILDL.js → ActiveSessionsView-Cxh8mHmB.js} +1 -1
  72. package/packages/web/dist/assets/{AgentLogsView-BGFPLjLa.js → AgentLogsView-xdfI2bR6.js} +2 -2
  73. package/packages/web/dist/assets/ApiClient-DfbJwzpz.js +1 -0
  74. package/packages/web/dist/assets/ArchiveConfirmModal-DXZYdzHR.js +1 -0
  75. package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
  76. package/packages/web/dist/assets/CommandButtonDetailView-D8xfqLAp.js +1 -0
  77. package/packages/web/dist/assets/CommandButtonDetailView-D9zjx9ME.css +1 -0
  78. package/packages/web/dist/assets/EffortLevelSelector-D2Hdzc_8.js +1 -0
  79. package/packages/web/dist/assets/{GeneralSettingsView-DsHChEhv.js → GeneralSettingsView-sPXkLlLy.js} +1 -1
  80. package/packages/web/dist/assets/InputWithButton-B-o0DgMH.js +1 -0
  81. package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
  82. package/packages/web/dist/assets/{InterpolationHelp-CIkOSkWX.js → InterpolationHelp-Dxn1li4l.js} +1 -1
  83. package/packages/web/dist/assets/MarkdownEditor-D4Kbb-9l.js +2 -0
  84. package/packages/web/dist/assets/ModelSelector-72C7MUH4.js +1 -0
  85. package/packages/web/dist/assets/{ModelSelector-D8hbTRIt.css → ModelSelector-BNYKujL-.css} +1 -1
  86. package/packages/web/dist/assets/NewSessionView-BR_COfgW.js +3 -0
  87. package/packages/web/dist/assets/NewSessionView-DBl7T2Xp.css +1 -0
  88. package/packages/web/dist/assets/ProjectEditView-DbqTbA0q.css +1 -0
  89. package/packages/web/dist/assets/ProjectEditView-WImU7sNd.js +1 -0
  90. package/packages/web/dist/assets/{ProjectListView-B9FuWESY.js → ProjectListView-CYmmAcBD.js} +1 -1
  91. package/packages/web/dist/assets/{ProjectNewView-D62jYlBL.js → ProjectNewView-DEhqw3Jv.js} +1 -1
  92. package/packages/web/dist/assets/ProvidersView-XZh3jkmH.js +1 -0
  93. package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
  94. package/packages/web/dist/assets/QuickResponsesPanel-BqmnTd-D.js +1 -0
  95. package/packages/web/dist/assets/QuickResponsesPanel-dk-Rj8xx.css +1 -0
  96. package/packages/web/dist/assets/ResizableTextarea-BQNw5e0C.css +1 -0
  97. package/packages/web/dist/assets/ResizableTextarea-DpWdIAP6.js +1 -0
  98. package/packages/web/dist/assets/SessionCard-Bw77-KwD.js +1 -0
  99. package/packages/web/dist/assets/SessionDetailView-B59TEkr-.js +36 -0
  100. package/packages/web/dist/assets/SessionDetailView-CKVBnR4T.css +1 -0
  101. package/packages/web/dist/assets/{SessionFormOptions-DYUISplS.js → SessionFormOptions-hqijxc0S.js} +1 -1
  102. package/packages/web/dist/assets/SessionListView-3-xx6EVs.css +1 -0
  103. package/packages/web/dist/assets/SessionListView-DYXHM9I-.js +1 -0
  104. package/packages/web/dist/assets/{SessionLogStream-DpUE6Xsh.js → SessionLogStream-5NfVr9pF.js} +6 -6
  105. package/packages/web/dist/assets/{SettingsView-BC055tIA.js → SettingsView-DI8ncOAV.js} +1 -1
  106. package/packages/web/dist/assets/{SlashCommandWizard-DmTyNG9O.js → SlashCommandWizard-BQ_rMzn-.js} +1 -1
  107. package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
  108. package/packages/web/dist/assets/{SummarySettingsView-BgnRCwlq.js → SummarySettingsView-C2Qs35mm.js} +1 -1
  109. package/packages/web/dist/assets/TemplateDetailView-B5NI2oTR.css +1 -0
  110. package/packages/web/dist/assets/TemplateDetailView-zVkIvgtu.js +1 -0
  111. package/packages/web/dist/assets/{commandButtons-D4RPpLiu.js → commandButtons-CoU3G4zK.js} +1 -1
  112. package/packages/web/dist/assets/index-9yF1uCCA.js +1 -0
  113. package/packages/web/dist/assets/index-BKstCaYU.js +1 -0
  114. package/packages/web/dist/assets/index-BhbH7eOk.js +1 -0
  115. package/packages/web/dist/assets/{index-BGwH4Cfn.js → index-BjuRttEY.js} +3 -3
  116. package/packages/web/dist/assets/index-Bo7PdwM5.js +1 -0
  117. package/packages/web/dist/assets/index-C2QFVD7d.js +83 -0
  118. package/packages/web/dist/assets/index-C7Ww2auW.js +1 -0
  119. package/packages/web/dist/assets/index-CAGdsDh7.js +1 -0
  120. package/packages/web/dist/assets/index-CLRsVASf.js +3 -0
  121. package/packages/web/dist/assets/{index-Bn5xdGFM.js → index-CP-SxOlV.js} +1 -1
  122. package/packages/web/dist/assets/index-CslU0psO.js +1 -0
  123. package/packages/web/dist/assets/index-DI4NxaWD.js +1 -0
  124. package/packages/web/dist/assets/index-DOzONENy.js +1 -0
  125. package/packages/web/dist/assets/index-DUa7adFh.js +1 -0
  126. package/packages/web/dist/assets/index-DZBpETI5.js +1 -0
  127. package/packages/web/dist/assets/index-DsjWqc6R.js +7 -0
  128. package/packages/web/dist/assets/index-c99Bo3JV.js +1 -0
  129. package/packages/web/dist/assets/index-mT1JpxDc.js +1 -0
  130. package/packages/web/dist/assets/index-rkQx2tso.js +1 -0
  131. package/packages/web/dist/assets/{index-Cs2nxhrT.css → index-uySCcnA_.css} +1 -1
  132. package/packages/web/dist/assets/projectDefaults-B8esIcYq.js +1 -0
  133. package/packages/web/dist/assets/{projects-BUiOGmmb.js → projects-C-8PSxKi.js} +1 -1
  134. package/packages/web/dist/assets/{providers-Bh1ZiiJi.js → providers-oXifvvqN.js} +1 -1
  135. package/packages/web/dist/assets/sessions-Nq5VafSf.js +1 -0
  136. package/packages/web/dist/assets/{settings-Z4AVVmkJ.js → settings-DtpuiyT6.js} +1 -1
  137. package/packages/web/dist/index.html +2 -2
  138. package/packages/server/src/api/sessions-notes.js +0 -51
  139. package/packages/server/src/db/SessionNoteRepository.js +0 -60
  140. package/packages/web/dist/assets/ApiClient-B4YTtyY4.js +0 -1
  141. package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
  142. package/packages/web/dist/assets/ArchiveConfirmModal-OFaj_uX5.js +0 -1
  143. package/packages/web/dist/assets/CommandButtonDetailView-D8S258uP.js +0 -1
  144. package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +0 -1
  145. package/packages/web/dist/assets/EffortLevelSelector-C2378L8e.js +0 -1
  146. package/packages/web/dist/assets/InputWithButton-Ci15ox0a.js +0 -1
  147. package/packages/web/dist/assets/MarkdownEditor-5-bexzUT.js +0 -2
  148. package/packages/web/dist/assets/ModelSelector-BMpR0DPr.js +0 -1
  149. package/packages/web/dist/assets/NewSessionView-BCqtIgWH.js +0 -3
  150. package/packages/web/dist/assets/NewSessionView-CUUdHkfv.css +0 -1
  151. package/packages/web/dist/assets/ProjectEditView-D9sK0fdH.css +0 -1
  152. package/packages/web/dist/assets/ProjectEditView-RFaxHhAX.js +0 -1
  153. package/packages/web/dist/assets/ProvidersView-DDKMIQWZ.js +0 -1
  154. package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
  155. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
  156. package/packages/web/dist/assets/QuickResponseSettings-CDm5vwP7.js +0 -1
  157. package/packages/web/dist/assets/QuickResponsesPanel-BlFDvnZ2.css +0 -1
  158. package/packages/web/dist/assets/QuickResponsesPanel-DZ_Lre_l.js +0 -1
  159. package/packages/web/dist/assets/ResizableTextarea-DiIOEGjN.js +0 -1
  160. package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +0 -1
  161. package/packages/web/dist/assets/SessionCard-DmjnVYWn.js +0 -1
  162. package/packages/web/dist/assets/SessionDetailView-CL7nmfiB.js +0 -36
  163. package/packages/web/dist/assets/SessionDetailView-CupIkI7u.css +0 -1
  164. package/packages/web/dist/assets/SessionListView-BcxGz4aC.js +0 -1
  165. package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
  166. package/packages/web/dist/assets/TemplateDetailView-BlhOmLUX.js +0 -1
  167. package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +0 -1
  168. package/packages/web/dist/assets/index-4rhEeO0B.js +0 -1
  169. package/packages/web/dist/assets/index-9vb2KaAd.js +0 -1
  170. package/packages/web/dist/assets/index-B0CvZXuN.js +0 -7
  171. package/packages/web/dist/assets/index-B6G18FqB.js +0 -82
  172. package/packages/web/dist/assets/index-BUhvkAdF.js +0 -1
  173. package/packages/web/dist/assets/index-BcnkUk2o.js +0 -1
  174. package/packages/web/dist/assets/index-CNwkdB0T.js +0 -1
  175. package/packages/web/dist/assets/index-CfL84oGW.js +0 -1
  176. package/packages/web/dist/assets/index-CkmxO8Mm.js +0 -1
  177. package/packages/web/dist/assets/index-Cpy4-yv3.js +0 -1
  178. package/packages/web/dist/assets/index-CrAQJmoZ.js +0 -1
  179. package/packages/web/dist/assets/index-D6Ky9vJe.js +0 -3
  180. package/packages/web/dist/assets/index-DfrE0gAC.js +0 -1
  181. package/packages/web/dist/assets/index-KwEyz0F3.js +0 -1
  182. package/packages/web/dist/assets/index-OfCywayk.js +0 -1
  183. package/packages/web/dist/assets/index-PDesaJc6.js +0 -1
  184. package/packages/web/dist/assets/index-uB6nhSvz.js +0 -1
  185. package/packages/web/dist/assets/sessions-DH1R-NhV.js +0 -1
@@ -0,0 +1,90 @@
1
+ import { createClaudeCodeSpawner } from './nodeSpawnHelper.js';
2
+ import {
3
+ buildSystemPromptConfig,
4
+ getPermissionModeForSession,
5
+ getSandboxModeForSession,
6
+ } from './sessionPrompts.js';
7
+
8
+ /**
9
+ * Build query parameters for the Claude Code adapter.
10
+ * @returns {Object}
11
+ */
12
+ function buildClaudeCodeQueryParams({
13
+ prompt, workingDirectory, controller, session, sessionId, systemPrompt,
14
+ model, sessionEnv, resumeSessionId = null,
15
+ }) {
16
+ const isVCR = Boolean(process.env.VCR_MODE);
17
+ const effectiveModel = isVCR ? 'claude-haiku-4-5-20251001' : model;
18
+
19
+ return {
20
+ prompt,
21
+ options: {
22
+ cwd: workingDirectory,
23
+ abortController: controller,
24
+ includePartialMessages: true,
25
+ permissionMode: getPermissionModeForSession(session.mode),
26
+ // Match normal Claude Code CLI behavior: load user-level settings
27
+ // such as configured MCP servers, then project/local overrides.
28
+ settingSources: ['user', 'project', 'local'],
29
+ ...(resumeSessionId && { resume: resumeSessionId }),
30
+ env: sessionEnv,
31
+ spawnClaudeCodeProcess: createClaudeCodeSpawner(),
32
+ model: effectiveModel,
33
+ systemPrompt: buildSystemPromptConfig(sessionId, session.projectId, systemPrompt, session.mode),
34
+ },
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Build query parameters for the Codex adapter.
40
+ *
41
+ * Codex in v1 is a simple Chat-Completions-shaped executor. It does not need
42
+ * or accept Claude-specific options such as permissionMode, settingSources,
43
+ * includePartialMessages, spawnClaudeCodeProcess, or resume.
44
+ *
45
+ * @returns {Object}
46
+ */
47
+ function buildCodexQueryParams({
48
+ prompt, workingDirectory, controller, session, sessionId, systemPrompt, model, sessionEnv,
49
+ }) {
50
+ const isVCR = Boolean(process.env.VCR_MODE);
51
+ const effectiveModel = isVCR ? 'gpt-4o-mini' : model;
52
+
53
+ return {
54
+ prompt,
55
+ options: {
56
+ cwd: workingDirectory,
57
+ abortController: controller,
58
+ env: sessionEnv,
59
+ model: effectiveModel,
60
+ effortLevel: session?.effortLevel ?? null,
61
+ systemPrompt: buildSystemPromptConfig(sessionId, session.projectId, systemPrompt, session.mode),
62
+ sandboxMode: getSandboxModeForSession(session?.mode),
63
+ },
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Build query parameters for executing a session via the configured agent.
69
+ * Shared by runSession, continueSession, and continueSessionWithExistingMessage.
70
+ *
71
+ * @param {Object} options
72
+ * @param {string} options.prompt - The prompt text to send
73
+ * @param {string} options.workingDirectory - Session working directory
74
+ * @param {AbortController} options.controller - Abort controller for the session
75
+ * @param {Object} options.session - Session object from DB
76
+ * @param {string} options.sessionId - Session ID
77
+ * @param {string|null} options.systemPrompt - Custom system prompt from project settings
78
+ * @param {string|null} options.model - Model to use
79
+ * @param {Object} options.sessionEnv - Environment variables for the session
80
+ * @param {string|null} [options.resumeSessionId] - Session ID to resume (null for new session)
81
+ * @param {string} [options.agentType] - 'claude-code' (default) | 'codex'
82
+ * @returns {Object} Query parameters for agent.execute()
83
+ */
84
+ export function buildQueryParams(options) {
85
+ const { agentType = 'claude-code' } = options || {};
86
+ if (agentType === 'codex') {
87
+ return buildCodexQueryParams(options);
88
+ }
89
+ return buildClaudeCodeQueryParams(options);
90
+ }
@@ -3,7 +3,6 @@ import {
3
3
  conversations,
4
4
  messages,
5
5
  canvasItems,
6
- sessionNotes,
7
6
  sessionSummaries,
8
7
  projects,
9
8
  } from '../db/index.js';
@@ -47,10 +46,7 @@ export async function duplicateSession(sourceSessionId, options = {}) {
47
46
  // 5. Duplicate canvas items
48
47
  canvasItems.duplicateForSession(sourceSessionId, newSession.id);
49
48
 
50
- // 6. Duplicate session notes
51
- sessionNotes.duplicateForSession(sourceSessionId, newSession.id);
52
-
53
- // 7. Duplicate session summary (if exists)
49
+ // 6. Duplicate session summary (if exists)
54
50
  sessionSummaries.duplicateForSession(sourceSessionId, newSession.id);
55
51
 
56
52
  // Return the updated session
@@ -1,14 +1,13 @@
1
1
  import { sessions, messages, attachments, conversations } from '../database.js';
2
- import { createClaudeCodeSpawner } from './nodeSpawnHelper.js';
3
2
  import { createCodexSpawner } from './codexSpawnHelper.js';
4
- import { resolveProviderFromModel, buildSessionEnv } from './sessionProvider.js';
3
+ import { resolveProviderFromModel, resolveProviderMetadataFromModel, buildSessionEnv } from './sessionProvider.js';
5
4
  import { agentGateway } from '../agents/AgentGateway.js';
6
5
  import { LoggingAgentWrapper } from '../agents/LoggingAgentWrapper.js';
7
6
  import { VCRAgentAdapter } from '../agents/vcr/VCRAgentAdapter.js';
7
+ import { isE2ESpawnCaptureEnabled } from './e2eSpawnCapture.js';
8
+ export { buildQueryParams } from './queryParamBuilder.js';
9
+ import { buildQueryParams } from './queryParamBuilder.js';
8
10
  import {
9
- buildSystemPromptConfig,
10
- getPermissionModeForSession,
11
- getSandboxModeForSession,
12
11
  buildPromptWithAttachments,
13
12
  } from './sessionPrompts.js';
14
13
  import {
@@ -23,6 +22,7 @@ import {
23
22
  import { shouldRescheduleOnError, _checkProactiveReschedule } from './sessionErrors.js';
24
23
  import { schedulerService } from './schedulerService.js';
25
24
  import { buildConversationContextForModelSwitch, buildConversationContextForContinuation } from './conversationContext.js';
25
+ import { ensureWorktreeCommitAttributionHook } from './gitService.js';
26
26
  import { broadcastToSession } from '../websocket.js';
27
27
  import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
28
28
 
@@ -40,6 +40,34 @@ function buildAgentConfig(agentType) {
40
40
  return {};
41
41
  }
42
42
 
43
+ export function buildAgentEnv(sessionEnv, commitAttributionOverride) {
44
+ const env = { ...(sessionEnv || {}) };
45
+ if (commitAttributionOverride) {
46
+ env.CIRCUSCHIEF_COMMIT_ATTRIBUTION = commitAttributionOverride;
47
+ } else {
48
+ delete env.CIRCUSCHIEF_COMMIT_ATTRIBUTION;
49
+ }
50
+ return env;
51
+ }
52
+
53
+ async function resolveInitialSessionModelEnv(session, model) {
54
+ const effectiveModel = model || session.model;
55
+ const provider = resolveProviderFromModel(effectiveModel);
56
+ const providerMetadata = resolveProviderMetadataFromModel(effectiveModel);
57
+ const commitAttributionOverride = providerMetadata?.commitAttributionOverride ?? null;
58
+
59
+ if (session.gitWorktree && commitAttributionOverride) {
60
+ await ensureWorktreeCommitAttributionHook(session.gitWorktree);
61
+ }
62
+
63
+ const baseSessionEnv = buildSessionEnv(provider, session.thinkingEnabled, session.effortLevel);
64
+ return {
65
+ effectiveModel,
66
+ sessionEnv: buildAgentEnv(baseSessionEnv, commitAttributionOverride),
67
+ commitAttributionOverride,
68
+ };
69
+ }
70
+
43
71
  /**
44
72
  * Create the agent for a session, using gateway + logging + VCR.
45
73
  *
@@ -56,7 +84,7 @@ export function createAgentForSession(agentType = 'claude-code', config = {}) {
56
84
  const baseAgent = agentGateway.createAgent(agentType, mergedConfig);
57
85
 
58
86
  // Wrap with VCR adapter if in VCR mode
59
- const agent = process.env.VCR_MODE
87
+ const agent = process.env.VCR_MODE && !isE2ESpawnCaptureEnabled()
60
88
  ? new VCRAgentAdapter(baseAgent, { cassetteDir: 'tests/e2e/cassettes' })
61
89
  : baseAgent;
62
90
 
@@ -64,97 +92,6 @@ export function createAgentForSession(agentType = 'claude-code', config = {}) {
64
92
  return new LoggingAgentWrapper(agent);
65
93
  }
66
94
 
67
- /**
68
- * Build query parameters for the Claude Code adapter.
69
- * @returns {Object}
70
- */
71
- function buildClaudeCodeQueryParams({
72
- prompt, workingDirectory, controller, session, sessionId, systemPrompt,
73
- model, sessionEnv, resumeSessionId = null,
74
- }) {
75
- const isVCR = Boolean(process.env.VCR_MODE);
76
- const effectiveModel = isVCR ? 'claude-haiku-4-5-20251001' : model;
77
-
78
- return {
79
- prompt,
80
- options: {
81
- cwd: workingDirectory,
82
- abortController: controller,
83
- includePartialMessages: true,
84
- permissionMode: getPermissionModeForSession(session.mode),
85
- // Match normal Claude Code CLI behavior: load user-level settings
86
- // such as configured MCP servers, then project/local overrides.
87
- settingSources: ['user', 'project', 'local'],
88
- ...(resumeSessionId && { resume: resumeSessionId }),
89
- env: sessionEnv,
90
- spawnClaudeCodeProcess: createClaudeCodeSpawner(),
91
- model: effectiveModel,
92
- systemPrompt: buildSystemPromptConfig(sessionId, session.projectId, systemPrompt, session.mode),
93
- },
94
- };
95
- }
96
-
97
- /**
98
- * Build query parameters for the Codex adapter.
99
- *
100
- * Codex in v1 is a simple Chat-Completions-shaped executor — it doesn't need
101
- * or accept Claude-specific options (permissionMode, settingSources,
102
- * includePartialMessages, spawnClaudeCodeProcess, resume).
103
- *
104
- * Codex does have its own sandboxing model, driven from {@code session.mode}
105
- * via {@link getSandboxModeForSession}. Codex CLI v0.124.0 also supports
106
- * resume via `codex resume` / `codex exec resume`, but Circus Chief v1
107
- * intentionally does NOT pass a resume token — wiring is deferred to a
108
- * later phase (see canvas plan §Phase 4.5).
109
- *
110
- * @returns {Object}
111
- */
112
- function buildCodexQueryParams({
113
- prompt, workingDirectory, controller, session, sessionId, systemPrompt, model, sessionEnv,
114
- }) {
115
- const isVCR = Boolean(process.env.VCR_MODE);
116
- // In VCR mode, force the cheapest commonly-cassetted OpenAI model.
117
- const effectiveModel = isVCR ? 'gpt-4o-mini' : model;
118
-
119
- return {
120
- prompt,
121
- options: {
122
- cwd: workingDirectory,
123
- abortController: controller,
124
- env: sessionEnv,
125
- model: effectiveModel,
126
- effortLevel: session?.effortLevel ?? null,
127
- systemPrompt: buildSystemPromptConfig(sessionId, session.projectId, systemPrompt, session.mode),
128
- sandboxMode: getSandboxModeForSession(session?.mode),
129
- },
130
- };
131
- }
132
-
133
- /**
134
- * Build query parameters for executing a session via the configured agent.
135
- * Shared by runSession, continueSession, and continueSessionWithExistingMessage.
136
- *
137
- * @param {Object} options
138
- * @param {string} options.prompt - The prompt text to send
139
- * @param {string} options.workingDirectory - Session working directory
140
- * @param {AbortController} options.controller - Abort controller for the session
141
- * @param {Object} options.session - Session object from DB
142
- * @param {string} options.sessionId - Session ID
143
- * @param {string|null} options.systemPrompt - Custom system prompt from project settings
144
- * @param {string|null} options.model - Model to use
145
- * @param {Object} options.sessionEnv - Environment variables for the session
146
- * @param {string|null} [options.resumeSessionId] - Session ID to resume (null for new session)
147
- * @param {string} [options.agentType] - 'claude-code' (default) | 'codex'
148
- * @returns {Object} Query parameters for agent.execute()
149
- */
150
- export function buildQueryParams(options) {
151
- const { agentType = 'claude-code' } = options || {};
152
- if (agentType === 'codex') {
153
- return buildCodexQueryParams(options);
154
- }
155
- return buildClaudeCodeQueryParams(options);
156
- }
157
-
158
95
  /**
159
96
  * Execute the agent stream loop and handle post-turn completion, errors, and cleanup.
160
97
  * This is the shared core of runSession, continueSession, and continueSessionWithExistingMessage.
@@ -260,7 +197,12 @@ function buildContinueModelAndEnv(session, sessionId, model) {
260
197
 
261
198
  // Derive provider from the effective model ID (returns null for Anthropic/SDK defaults)
262
199
  const provider = resolveProviderFromModel(effectiveModel);
263
- const sessionEnv = buildSessionEnv(provider, session.thinkingEnabled, session.effortLevel);
200
+ const providerMetadata = resolveProviderMetadataFromModel(effectiveModel);
201
+ const commitAttributionOverride = providerMetadata?.commitAttributionOverride ?? null;
202
+ const sessionEnv = buildAgentEnv(
203
+ buildSessionEnv(provider, session.thinkingEnabled, session.effortLevel),
204
+ commitAttributionOverride
205
+ );
264
206
 
265
207
  // Check if model changed from the session's last requested model
266
208
  // When model changes, we can't resume the previous session - thinking blocks and
@@ -275,7 +217,13 @@ function buildContinueModelAndEnv(session, sessionId, model) {
275
217
  updatedSession = sessions.getById(sessionId); // refresh
276
218
  }
277
219
 
278
- return { effectiveModel, sessionEnv, modelChanged, session: updatedSession };
220
+ return {
221
+ effectiveModel,
222
+ sessionEnv,
223
+ commitAttributionOverride,
224
+ modelChanged,
225
+ session: updatedSession,
226
+ };
279
227
  }
280
228
 
281
229
  /**
@@ -286,7 +234,7 @@ function buildContinueModelAndEnv(session, sessionId, model) {
286
234
  async function buildContinueParams({
287
235
  sessionId, session, model, systemPrompt, effectiveModel, sessionEnv,
288
236
  modelChanged, activeConversation, promptWithAttachments,
289
- workingDirectory, controller, agentType, agent,
237
+ workingDirectory, controller, agentType, agent, commitAttributionOverride,
290
238
  }) {
291
239
  // Only resume if we have a session ID AND model hasn't changed AND the
292
240
  // agent supports resume.
@@ -308,6 +256,7 @@ async function buildContinueParams({
308
256
  sessionEnv,
309
257
  resumeSessionId: canResume ? activeConversation.claudeSessionId : null,
310
258
  agentType,
259
+ commitAttributionOverride,
311
260
  });
312
261
 
313
262
  // Logging metadata for agent call tracking
@@ -394,11 +343,15 @@ export async function continueSessionCore(sessionId, content, workingDirectory,
394
343
  // Resolve model/provider and detect model changes
395
344
  const modelEnv = buildContinueModelAndEnv(session, sessionId, model);
396
345
  session = modelEnv.session;
346
+ if (session.gitWorktree && modelEnv.commitAttributionOverride) {
347
+ await ensureWorktreeCommitAttributionHook(session.gitWorktree);
348
+ }
397
349
 
398
350
  // Build query params and agent call meta
399
351
  const { queryParams, agentCallMeta } = await buildContinueParams({
400
352
  sessionId, session, model, systemPrompt,
401
353
  effectiveModel: modelEnv.effectiveModel, sessionEnv: modelEnv.sessionEnv,
354
+ commitAttributionOverride: modelEnv.commitAttributionOverride,
402
355
  modelChanged: modelEnv.modelChanged, activeConversation, promptWithAttachments,
403
356
  workingDirectory, controller, agentType, agent,
404
357
  });
@@ -459,14 +412,8 @@ export async function runSessionCore(sessionId, prompt, workingDirectory, config
459
412
  const agentType = session.agentType || 'claude-code';
460
413
  const agent = createAgentForSession(agentType);
461
414
 
462
- // Resolve the effective model: fall back to session.model as defense-in-depth
463
- // (draftSessionService already resolves the model upstream, but this ensures
464
- // correctness if called directly).
465
- const effectiveModel = model || session.model;
466
-
467
- // Derive provider from the effective model ID (returns null for Anthropic/SDK defaults)
468
- const provider = resolveProviderFromModel(effectiveModel);
469
- const sessionEnv = buildSessionEnv(provider, session.thinkingEnabled, session.effortLevel);
415
+ const { effectiveModel, sessionEnv, commitAttributionOverride } =
416
+ await resolveInitialSessionModelEnv(session, model);
470
417
 
471
418
  const queryParams = buildQueryParams({
472
419
  prompt: promptWithAttachments,
@@ -478,6 +425,7 @@ export async function runSessionCore(sessionId, prompt, workingDirectory, config
478
425
  model: effectiveModel,
479
426
  sessionEnv,
480
427
  agentType,
428
+ commitAttributionOverride,
481
429
  });
482
430
 
483
431
  // Log query params for debugging third-party provider issues
@@ -162,10 +162,7 @@ CRITICAL: Do NOT start coding until you have presented a plan and received appro
162
162
  */
163
163
  function buildCanvasWriteSystemPrompt(session) {
164
164
  const apiUrl = getApiBaseUrl();
165
- // Use root session ID, fall back to session.id if getRootSessionId returns null, fall back to 'unknown-session' if session is null
166
- const sessionId = session
167
- ? (sessions.getRootSessionId(session.id) || session.id)
168
- : 'unknown-session';
165
+ const sessionId = session?.id || 'unknown-session';
169
166
  return `When you generate artifacts that should be displayed on the canvas (images, markdown documents, code snippets, data visualizations, PDFs), POST them to:
170
167
 
171
168
  POST ${apiUrl}/api/sessions/${sessionId}/canvas
@@ -187,10 +184,7 @@ The file type is automatically detected from the file extension. Supported forma
187
184
  */
188
185
  function buildCanvasReadSystemPrompt(session) {
189
186
  const apiUrl = getApiBaseUrl();
190
- // Use root session ID, fall back to session.id if getRootSessionId returns null, fall back to 'unknown-session' if session is null
191
- const sessionId = session
192
- ? (sessions.getRootSessionId(session.id) || session.id)
193
- : 'unknown-session';
187
+ const sessionId = session?.id || 'unknown-session';
194
188
  return `## Reading from Canvas
195
189
 
196
190
  To list all files on the canvas:
@@ -225,9 +219,10 @@ function buildSessionCrudOps(apiUrl, projectId) {
225
219
  \`\`\`bash
226
220
  curl -X POST ${apiUrl}/api/projects/${projectId}/sessions \\
227
221
  -H "Content-Type: application/json" \\
228
- -d '{"prompt": "Your task description here", "name": "Optional session name"}'
222
+ -d '{"prompt": "Your task description here"}'
229
223
  \`\`\`
230
- Optional fields: \`name\`, \`mode\`, \`thinkingEnabled\` (boolean), \`effortLevel\` (low/medium/high/max/auto), \`gitBranch\`, \`gitMode\`, \`parentSessionId\` (to create a child session)
224
+ Only \`prompt\` is required. Omitted settings are automatically derived from the project's session defaults, then system defaults, matching UI behavior.
225
+ Optional override fields: \`name\`, \`mode\`, \`thinkingEnabled\` (boolean), \`effortLevel\` (low/medium/high/max/auto), \`model\`, \`providerId\`, \`gitBranch\`, \`gitMode\`, \`templateId\`, \`nextTemplateId\`, \`parentSessionId\` (to create a related follow-up session from the current session), \`startImmediately\`, \`scheduledAt\` (ISO 8601 date-time string with timezone, e.g. \`"2026-06-12T14:00:00Z"\`), \`autoRescheduleEnabled\`, \`rescheduleDelayMinutes\`, \`rescheduleOnTokenLimit\`, \`rescheduleOnServiceError\`, \`maxRescheduleCount\`, \`maxTotalTokens\`, and \`rescheduleAtTokenCount\`.
231
226
 
232
227
  ### Send a Follow-up Message
233
228
  \`\`\`bash
@@ -258,8 +253,8 @@ curl -X PATCH ${apiUrl}/api/sessions/<session_id> \\
258
253
  \`\`\``;
259
254
  }
260
255
 
261
- /** Build project, notes, and summary operations section */
262
- function buildProjectNotesOps(apiUrl) {
256
+ /** Build project and summary operations section */
257
+ function buildProjectOps(apiUrl, sessionId) {
263
258
  return `### Project Operations
264
259
  \`\`\`bash
265
260
  curl ${apiUrl}/api/projects # List all projects
@@ -271,18 +266,10 @@ curl -X POST ${apiUrl}/api/projects \\
271
266
  \`\`\`
272
267
  Optional field: \`systemPrompt\`
273
268
 
274
- ### Session Notes
269
+ ### Workflow Summary
275
270
  \`\`\`bash
276
- curl ${apiUrl}/api/sessions/<session_id>/notes # Get notes
277
- curl -X POST ${apiUrl}/api/sessions/<session_id>/notes \\
278
- -H "Content-Type: application/json" \\
279
- -d '{"content": "Note content"}'
280
- \`\`\`
281
-
282
- ### Session Summary
283
- \`\`\`bash
284
- curl "${apiUrl}/api/sessions/<session_id>/summary?generate=true"
285
- curl -X POST ${apiUrl}/api/sessions/<session_id>/summary # Regenerate
271
+ curl "${apiUrl}/api/sessions/${sessionId}/summary?generate=true"
272
+ curl -X POST ${apiUrl}/api/sessions/${sessionId}/summary # Regenerate
286
273
  \`\`\``;
287
274
  }
288
275
 
@@ -299,7 +286,7 @@ You can create and modify sessions in this system using curl or similar HTTP too
299
286
 
300
287
  ${buildSessionCrudOps(apiUrl, projectId)}
301
288
 
302
- ${buildProjectNotesOps(apiUrl)}`;
289
+ ${buildProjectOps(apiUrl, sessionId)}`;
303
290
  }
304
291
 
305
292
  /**
@@ -336,7 +323,7 @@ ${laneContext}
336
323
  curl ${apiUrl}/api/projects/${projectId}/kanban
337
324
  \`\`\`
338
325
 
339
- ### Add This Session to the Board
326
+ ### Add Current Workflow to the Board
340
327
  \`\`\`bash
341
328
  curl -X POST ${apiUrl}/api/projects/${projectId}/kanban/cards \\
342
329
  -H "Content-Type: application/json" \\
@@ -394,26 +381,6 @@ This session is running in an isolated git worktree:
394
381
  CRITICAL: Do NOT use \`cd\` to navigate to the main repository. Your working directory is already set to the worktree. Running \`cd /home/ubuntu/workspace/circus-chief && ...\` will escape the worktree isolation and affect the main repository instead.`;
395
382
  }
396
383
 
397
- /**
398
- * Build child session context for system prompt
399
- * @param {Object} session - Session object
400
- * @returns {string} Child session context or empty string
401
- */
402
- function buildChildSessionContext(session) {
403
- if (!session || !session.parentSessionId) {
404
- return '';
405
- }
406
-
407
- // Get root session ID using existing method
408
- const rootSessionId = sessions.getRootSessionId(session.id);
409
-
410
- return `## Child Session
411
-
412
- This session is part of a multi-session workflow:
413
- - Parent Session ID: ${session.parentSessionId}
414
- - Root Session ID: ${rootSessionId}`;
415
- }
416
-
417
384
  /**
418
385
  * Build the full system prompt configuration
419
386
  * @param {string} sessionId
@@ -432,7 +399,6 @@ export function buildSystemPromptConfig(sessionId, projectId, customSystemPrompt
432
399
  const kanbanApiInstructions = buildKanbanApiInstructions(sessionId, projectId);
433
400
  const attachmentsContext = getSessionAttachmentsContext(sessionId);
434
401
  const worktreeContext = buildWorktreeContext(session);
435
- const childSessionContext = buildChildSessionContext(session);
436
402
  const basePrompt = customSystemPrompt || DEFAULT_SYSTEM_PROMPT;
437
403
 
438
404
  // Prepend plan mode instructions if in plan mode
@@ -442,7 +408,6 @@ export function buildSystemPromptConfig(sessionId, projectId, customSystemPrompt
442
408
  const parts = [
443
409
  modePrompt,
444
410
  basePrompt,
445
- childSessionContext,
446
411
  worktreeContext,
447
412
  attachmentsContext,
448
413
  canvasWriteInstructions,
@@ -11,6 +11,16 @@ export function resolveProviderFromModel(modelId) {
11
11
  return modelProviders.getProviderByModelId(modelId);
12
12
  }
13
13
 
14
+ export function resolveProviderMetadataFromModel(modelId) {
15
+ if (!modelId) {
16
+ return modelProviders.getById?.('anthropic-default') || null;
17
+ }
18
+ if (typeof modelProviders.getProviderMetadataByModelId === 'function') {
19
+ return modelProviders.getProviderMetadataByModelId(modelId);
20
+ }
21
+ return modelProviders.getProviderByModelId(modelId);
22
+ }
23
+
14
24
  /**
15
25
  * Resolve the agent type (claude-code vs codex) for a given model ID.
16
26
  * Uses the owning provider's kind:
@@ -200,18 +200,19 @@ function updateSessionFromSummary(sessionId, session, summaryData) {
200
200
  if (summaryData.sessionTitle || summaryData.prUrl) {
201
201
  const updateData = {};
202
202
  const freshSession = sessions.getById(sessionId);
203
+ const shouldApplySummaryPrUrl = summaryData.prUrl && !freshSession.prUrlAutoLinkDisabled;
203
204
 
204
205
  if (summaryData.sessionTitle && !freshSession.manuallyNamed) {
205
206
  updateData.name = summaryData.sessionTitle;
206
207
  }
207
208
 
208
- if (summaryData.prUrl) {
209
+ if (shouldApplySummaryPrUrl) {
209
210
  updateData.prUrl = summaryData.prUrl;
210
211
  }
211
212
 
212
213
  const updatedSession = sessions.update(sessionId, updateData);
213
214
 
214
- if (summaryData.prUrl) {
215
+ if (shouldApplySummaryPrUrl) {
215
216
  propagatePrUrlToParent(sessionId, summaryData.prUrl);
216
217
  }
217
218
 
@@ -521,7 +522,7 @@ export function propagatePrUrlToParent(sessionId, prUrl) {
521
522
  if (!rootId || rootId === sessionId) return;
522
523
 
523
524
  const root = sessions.getById(rootId);
524
- if (!root || root.prUrl) return; // Don't overwrite existing PR URL
525
+ if (!root || root.prUrl || root.prUrlAutoLinkDisabled) return; // Don't overwrite existing or user-cleared PR URL
525
526
 
526
527
  sessions.update(root.id, { prUrl });
527
528
 
@@ -536,6 +537,7 @@ export {
536
537
  MAX_MESSAGES, MIN_MESSAGES_FOR_SUMMARY, MAX_RETRIES, DEFAULT_SESSION_TITLE_PROMPT,
537
538
  SUMMARY_SYSTEM_PROMPT, formatMessages, buildIncrementalPrompt, parseSummaryResponse,
538
539
  stripMarkdownCodeBlock as _stripMarkdownCodeBlock, trackMessageMetadata as _trackMessageMetadata,
540
+ updateSessionFromSummary as _updateSessionFromSummary,
539
541
  };
540
542
  export { callSummaryModel };
541
543
  export { callClaude } from './summaryClaudeClient.js';
@@ -3,10 +3,24 @@
3
3
  * Extracted from summaryService.js for modularity.
4
4
  */
5
5
 
6
- import { sessionSummaries, messages } from '../database.js';
6
+ import { sessionSummaries, messages, sessions } from '../database.js';
7
7
 
8
8
  /**
9
- * Check if a summary is stale (message count or last message ID has changed)
9
+ * Check if any descendant session has a summary newer than the given timestamp.
10
+ * @param {string} sessionId
11
+ * @param {number} generatedAt
12
+ * @returns {boolean}
13
+ */
14
+ function hasNewerDescendantSummary(sessionId, generatedAt) {
15
+ const descendantIds = sessions.getAllDescendantIds(sessionId);
16
+ if (descendantIds.length === 0) return false;
17
+
18
+ const descendantSummaries = sessionSummaries.getBySessionIds(descendantIds);
19
+ return descendantSummaries.some(ds => ds.generatedAt > generatedAt);
20
+ }
21
+
22
+ /**
23
+ * Check if a summary is stale (message count, last message ID, or descendant summaries have changed)
10
24
  * @param {string} sessionId
11
25
  * @returns {boolean}
12
26
  */
@@ -27,9 +41,14 @@ export function isSummaryStale(sessionId) {
27
41
  }
28
42
 
29
43
  // Also validate count as a secondary check (defensive programming)
30
- return allMessages.length !== summary.messageCount;
44
+ if (allMessages.length !== summary.messageCount) return true;
31
45
  }
32
46
 
33
47
  // Fallback to count-based staleness detection for old summaries
34
- return allMessages.length !== summary.messageCount;
48
+ if (allMessages.length !== summary.messageCount) return true;
49
+
50
+ // Check if any descendant session has a newer summary
51
+ if (hasNewerDescendantSummary(sessionId, summary.generatedAt)) return true;
52
+
53
+ return false;
35
54
  }
@@ -4,7 +4,7 @@ import { setupGitForSession } from './gitSessionSetup.js';
4
4
  import { runSession } from './sessionManager.js';
5
5
  import { broadcastToProject } from '../websocket.js';
6
6
  import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
7
- import { resolveAgentTypeFromModel } from './sessionProvider.js';
7
+ import { resolveAgentTypeFromModel, resolveProviderMetadataFromModel } from './sessionProvider.js';
8
8
 
9
9
  const liquid = new Liquid();
10
10
 
@@ -100,6 +100,8 @@ async function resolveWorkingDirectory(parentSession, project, settings, newSess
100
100
  gitBranch: settings.gitBranch,
101
101
  sessionId: newSessionId,
102
102
  worktreeBasePath: project.worktreePath || null,
103
+ commitAttributionOverride:
104
+ resolveProviderMetadataFromModel(settings.model)?.commitAttributionOverride ?? null,
103
105
  });
104
106
  return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
105
107
  }
@@ -20,6 +20,9 @@ export const WS_RECONNECT_MAX_DELAY = 30000;
20
20
 
21
21
  export const TOAST_DURATION = 5000;
22
22
 
23
+ /** Default delay (in minutes) before auto-rescheduling a session. */
24
+ export const DEFAULT_RESCHEDULE_DELAY_MINUTES = 60;
25
+
23
26
  export const DEFAULT_SYSTEM_PROMPT = `You are Claude Code, an AI coding assistant. You help users with software engineering tasks including writing code, debugging, refactoring, and explaining code. You have full access to the shell and can execute any commands needed to assist the user. Be helpful, accurate, and thorough.
24
27
 
25
28
  IMPORTANT: Your working directory is already set correctly for this session. NEVER use \`cd\` to change to a hardcoded project path before running commands (e.g., \`cd /path/to/project && git status\`). This bypasses git worktree isolation and causes commands to run in the wrong directory. Always run commands directly without changing directory.
@@ -1,15 +1,29 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ const OPTION_TOKEN_DASHES = /(^|\s)([-\u2010-\u2015\u2212]+)(?=[A-Za-z0-9-])/g;
4
+ const UNICODE_DASHES = /[\u2010-\u2015\u2212]/g;
5
+ const HAS_UNICODE_DASH = /[\u2010-\u2015\u2212]/;
6
+
7
+ export function normalizeCommandOptionDashes(command) {
8
+ return command.replace(OPTION_TOKEN_DASHES, (match, prefix, dashes) => {
9
+ if (!HAS_UNICODE_DASH.test(dashes)) {
10
+ return match;
11
+ }
12
+
13
+ return `${prefix}${dashes.replace(UNICODE_DASHES, '-')}`;
14
+ });
15
+ }
16
+
3
17
  export const CreateCommandButtonRequest = z.object({
4
18
  label: z.string().min(1, 'Label is required'),
5
- command: z.string().min(1, 'Command is required'),
19
+ command: z.string().min(1, 'Command is required').transform(normalizeCommandOptionDashes),
6
20
  sortOrder: z.number().int().optional().default(0),
7
21
  showOnList: z.boolean().optional().default(false),
8
22
  });
9
23
 
10
24
  export const UpdateCommandButtonRequest = z.object({
11
25
  label: z.string().min(1).optional(),
12
- command: z.string().min(1).optional(),
26
+ command: z.string().min(1).transform(normalizeCommandOptionDashes).optional(),
13
27
  sortOrder: z.number().int().optional(),
14
28
  showOnList: z.boolean().optional(),
15
29
  }).refine(obj => Object.keys(obj).length > 0, 'At least one field must be provided for update');
@@ -46,7 +46,7 @@ export const ProjectSessionDefaultsRequest = z.object({
46
46
  thinkingEnabled: z.boolean().nullable().optional(),
47
47
  effortLevel: z.enum(['low', 'medium', 'high', 'max', 'auto']).nullable().optional(),
48
48
  startImmediately: z.boolean().nullable().optional(),
49
- gitMode: z.enum(['branch', 'worktree']).nullable().optional(),
49
+ gitMode: z.enum(['branch', 'worktree', 'current']).nullable().optional(),
50
50
  gitBranch: z.string().nullable().optional(),
51
51
  model: z.string().nullable().optional(),
52
52
  providerId: z.string().nullable().optional(),
@@ -59,7 +59,7 @@ export const ProjectSessionDefaultsResponse = z.object({
59
59
  thinkingEnabled: z.boolean().nullable(),
60
60
  effortLevel: z.enum(['low', 'medium', 'high', 'max', 'auto']).nullable(),
61
61
  startImmediately: z.boolean().nullable(),
62
- gitMode: z.enum(['branch', 'worktree']).nullable(),
62
+ gitMode: z.enum(['branch', 'worktree', 'current']).nullable(),
63
63
  gitBranch: z.string().nullable(),
64
64
  model: z.string().nullable(),
65
65
  providerId: z.string().nullable(),