@straiffi/archon 1.10.12 → 1.11.1

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 (199) hide show
  1. package/README.md +9 -2
  2. package/dist/client/assets/{ActivityView-BFn_Nhj4.js → ActivityView-DmhZXvTt.js} +1 -1
  3. package/dist/client/assets/{GitHubActionsDialog-iZD86Yju.js → GitHubActionsDialog-cMq_lMn0.js} +1 -1
  4. package/dist/client/assets/{VerificationDialog-CqKE8t1d.js → VerificationDialog-Dj7PwU8r.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-3BPJPVTR-C8KefY67.js → architectureDiagram-3BPJPVTR-Opb9Dl2T.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-GPEHLZMM-BO2Q_Osr.js → blockDiagram-GPEHLZMM-C-aNUCI8.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-AAUBKEIU-DGPkhbrK.js → c4Diagram-AAUBKEIU-DcOXjgMi.js} +1 -1
  8. package/dist/client/assets/channel-Dn6ie6DD.js +1 -0
  9. package/dist/client/assets/{chunk-2J33WTMH-Dtb7Xu8C.js → chunk-2J33WTMH-z0Q9DJGq.js} +1 -1
  10. package/dist/client/assets/{chunk-3OPIFGDE-Bnf4GbOF.js → chunk-3OPIFGDE-B30w2UB3.js} +1 -1
  11. package/dist/client/assets/{chunk-5ZQYHXKU-BWGid3g7.js → chunk-5ZQYHXKU-DxpPdbc3.js} +1 -1
  12. package/dist/client/assets/{chunk-727SXJPM-ScA8IRGP.js → chunk-727SXJPM-VwDemDev.js} +1 -1
  13. package/dist/client/assets/{chunk-AQP2D5EJ-Bygg04Ip.js → chunk-AQP2D5EJ-C8P7-DJE.js} +1 -1
  14. package/dist/client/assets/{chunk-CSCIHK7Q-CxdDDtfN.js → chunk-CSCIHK7Q-mkq1auWV.js} +1 -1
  15. package/dist/client/assets/{chunk-KSCS5N6A-BgpZFsB5.js → chunk-KSCS5N6A-CIJYqs71.js} +1 -1
  16. package/dist/client/assets/{chunk-L5ZTLDWV-CKSew0OL.js → chunk-L5ZTLDWV-DeK_2M2W.js} +1 -1
  17. package/dist/client/assets/{chunk-LZXEDZCA-DVG9Rx0K.js → chunk-LZXEDZCA-CCHfaHPS.js} +2 -2
  18. package/dist/client/assets/{chunk-ND2GUHAM-CHBqNmuF.js → chunk-ND2GUHAM-CsMyaWUU.js} +1 -1
  19. package/dist/client/assets/{chunk-NZK2D7GU-DvZYWn7V.js → chunk-NZK2D7GU-nE5qDOW5.js} +1 -1
  20. package/dist/client/assets/{chunk-O5CBEL6O-BL7lRTSe.js → chunk-O5CBEL6O-tvEe99_5.js} +1 -1
  21. package/dist/client/assets/{chunk-WU5MYG2G-C-il2wHZ.js → chunk-WU5MYG2G-9hNs6xmE.js} +1 -1
  22. package/dist/client/assets/classDiagram-4FO5ZUOK-DpWA3yjM.js +1 -0
  23. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-yBPw_oKO.js +1 -0
  24. package/dist/client/assets/{dagre-BM42HDAG-B00l5tLi.js → dagre-BM42HDAG-B0YGZHur.js} +1 -1
  25. package/dist/client/assets/{diagram-2AECGRRQ-Tum6g1_5.js → diagram-2AECGRRQ-DGqNMs5R.js} +1 -1
  26. package/dist/client/assets/{diagram-5GNKFQAL-BK1GX5jc.js → diagram-5GNKFQAL-COOPM1_4.js} +1 -1
  27. package/dist/client/assets/{diagram-KO2AKTUF-BrzmD5NB.js → diagram-KO2AKTUF-BbF5ZE3I.js} +1 -1
  28. package/dist/client/assets/{diagram-LMA3HP47-BhrlgXiJ.js → diagram-LMA3HP47-BtqHeGuY.js} +1 -1
  29. package/dist/client/assets/{diagram-OG6HWLK6-K-fWsJ3a.js → diagram-OG6HWLK6-Dln0-Z69.js} +1 -1
  30. package/dist/client/assets/{erDiagram-TEJ5UH35-Der6PjHE.js → erDiagram-TEJ5UH35-DpELJ7zd.js} +1 -1
  31. package/dist/client/assets/{flowDiagram-I6XJVG4X-BVWgGhXe.js → flowDiagram-I6XJVG4X-Qvh5Rnlv.js} +1 -1
  32. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DcoZ5ggj.js → ganttDiagram-6RSMTGT7-CFivHpNN.js} +1 -1
  33. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BhhbMTBu.js → gitGraphDiagram-PVQCEYII-MIdnRo_g.js} +1 -1
  34. package/dist/client/assets/index-4RQBXPuO.css +2 -0
  35. package/dist/client/assets/index-CrQjqMT3.js +276 -0
  36. package/dist/client/assets/{infoDiagram-5YYISTIA-Lwc-agyc.js → infoDiagram-5YYISTIA-DKPfwmc0.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-B95U1Xcl.js → ishikawaDiagram-YF4QCWOH-CdmhhgPS.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-JHISSGLW-xFpigy5D.js → journeyDiagram-JHISSGLW-D2mhNy3L.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-UN3LZRKU-D3mudQnR.js → kanban-definition-UN3LZRKU-DsVrWK-z.js} +1 -1
  40. package/dist/client/assets/{line-DtTL3ZI2.js → line-BKe4i2u2.js} +1 -1
  41. package/dist/client/assets/{mermaid-parser.core-Dau7K6zV.js → mermaid-parser.core-DQRegCB9.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-B8qtvATu.js → mermaid.core-CenZ1WhE.js} +3 -3
  43. package/dist/client/assets/{mindmap-definition-RKZ34NQL-Cus04Yq3.js → mindmap-definition-RKZ34NQL-DdkIKvhb.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-4H26LBE5-BCMGpBnn.js → pieDiagram-4H26LBE5-BCsiQ3pM.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CAmOr-PJ.js → quadrantDiagram-W4KKPZXB-BeT9n7sm.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-4Y6WPE33-kisD3jc4.js → requirementDiagram-4Y6WPE33-xFQAg4UF.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CsCedmM0.js → sankeyDiagram-5OEKKPKP-hWDlc8IK.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B-dAi954.js → sequenceDiagram-3UESZ5HK-iQK6MXGA.js} +1 -1
  49. package/dist/client/assets/{stateDiagram-AJRCARHV-X0BnedIW.js → stateDiagram-AJRCARHV-B-kfxKPW.js} +1 -1
  50. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CIIrJESX.js +1 -0
  51. package/dist/client/assets/{timeline-definition-PNZ67QCA-DfFoSjhr.js → timeline-definition-PNZ67QCA-DP7LFdUl.js} +1 -1
  52. package/dist/client/assets/{vennDiagram-CIIHVFJN-CKSCBwhG.js → vennDiagram-CIIHVFJN-Cn6VC0xx.js} +1 -1
  53. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DNIBQmHO.js → wardleyDiagram-YWT4CUSO-CAwD-Loy.js} +1 -1
  54. package/dist/client/assets/{xychartDiagram-2RQKCTM6-FvB55TQN.js → xychartDiagram-2RQKCTM6-NSixm9L3.js} +1 -1
  55. package/dist/client/index.html +2 -2
  56. package/dist/server/db.js +153 -26
  57. package/dist/server/db.js.map +1 -1
  58. package/dist/server/index.js +93 -36
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/lib/activeTool.js +23 -0
  61. package/dist/server/lib/activeTool.js.map +1 -0
  62. package/dist/server/lib/agent.js +291 -67
  63. package/dist/server/lib/agent.js.map +1 -1
  64. package/dist/server/lib/agentCatalog.js +92 -4
  65. package/dist/server/lib/agentCatalog.js.map +1 -1
  66. package/dist/server/lib/agentProvider.js +8 -11
  67. package/dist/server/lib/agentProvider.js.map +1 -1
  68. package/dist/server/lib/agentRuns.js +26 -2
  69. package/dist/server/lib/agentRuns.js.map +1 -1
  70. package/dist/server/lib/automationCheckGate.js +6 -0
  71. package/dist/server/lib/automationCheckGate.js.map +1 -1
  72. package/dist/server/lib/buildExecutionEnvironment.js +83 -29
  73. package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
  74. package/dist/server/lib/buildFlow.js +5 -4
  75. package/dist/server/lib/buildFlow.js.map +1 -1
  76. package/dist/server/lib/buildSequences.js +7 -29
  77. package/dist/server/lib/buildSequences.js.map +1 -1
  78. package/dist/server/lib/buildTicketCommit.js +82 -11
  79. package/dist/server/lib/buildTicketCommit.js.map +1 -1
  80. package/dist/server/lib/bundleReviewOperations.js +6 -0
  81. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  82. package/dist/server/lib/chatOperations.js +109 -93
  83. package/dist/server/lib/chatOperations.js.map +1 -1
  84. package/dist/server/lib/chatSessionTitle.js +66 -0
  85. package/dist/server/lib/chatSessionTitle.js.map +1 -0
  86. package/dist/server/lib/chats.js +20 -4
  87. package/dist/server/lib/chats.js.map +1 -1
  88. package/dist/server/lib/checkAutoFix.js +65 -61
  89. package/dist/server/lib/checkAutoFix.js.map +1 -1
  90. package/dist/server/lib/codexAgentProvider.js +320 -0
  91. package/dist/server/lib/codexAgentProvider.js.map +1 -0
  92. package/dist/server/lib/codexAppServer.js +678 -0
  93. package/dist/server/lib/codexAppServer.js.map +1 -0
  94. package/dist/server/lib/codexCatalogDiscoveryExecutionEnvironment.js +302 -0
  95. package/dist/server/lib/codexCatalogDiscoveryExecutionEnvironment.js.map +1 -0
  96. package/dist/server/lib/codexDiscovery.js +137 -0
  97. package/dist/server/lib/codexDiscovery.js.map +1 -0
  98. package/dist/server/lib/config.js.map +1 -1
  99. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +2 -2
  100. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
  101. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +39 -10
  102. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  103. package/dist/server/lib/dockerSandboxExecutionProvider.js +3 -1
  104. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
  105. package/dist/server/lib/dockerSandboxReleaseProfiles.js +8 -0
  106. package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -1
  107. package/dist/server/lib/executionCredentialCapabilities.js +39 -14
  108. package/dist/server/lib/executionCredentialCapabilities.js.map +1 -1
  109. package/dist/server/lib/executionEnvironment.js.map +1 -1
  110. package/dist/server/lib/executionEnvironmentContexts.js +1 -1
  111. package/dist/server/lib/executionEnvironmentContexts.js.map +1 -1
  112. package/dist/server/lib/executionEnvironmentLifecycle.js +1 -1
  113. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
  114. package/dist/server/lib/executionEnvironmentReconciliation.js +7 -7
  115. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
  116. package/dist/server/lib/executionEnvironmentRoutes.js +29 -25
  117. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
  118. package/dist/server/lib/executionEnvironmentValidation.js +205 -20
  119. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
  120. package/dist/server/lib/executionEnvironmentValidationOperations.js +34 -29
  121. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
  122. package/dist/server/lib/executionEnvironments.js +59 -1
  123. package/dist/server/lib/executionEnvironments.js.map +1 -1
  124. package/dist/server/lib/executionProfiles.js +6 -3
  125. package/dist/server/lib/executionProfiles.js.map +1 -1
  126. package/dist/server/lib/executionRetryPredicates.js +2 -1
  127. package/dist/server/lib/executionRetryPredicates.js.map +1 -1
  128. package/dist/server/lib/executionRunAuthority.js +6 -1
  129. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  130. package/dist/server/lib/genericToolCall.js +131 -9
  131. package/dist/server/lib/genericToolCall.js.map +1 -1
  132. package/dist/server/lib/hostExecutionEnvironment.js +5 -0
  133. package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
  134. package/dist/server/lib/lightweightPrompt.js +258 -18
  135. package/dist/server/lib/lightweightPrompt.js.map +1 -1
  136. package/dist/server/lib/mobileAccess.js +21 -13
  137. package/dist/server/lib/mobileAccess.js.map +1 -1
  138. package/dist/server/lib/modelResolutionErrors.js +4 -2
  139. package/dist/server/lib/modelResolutionErrors.js.map +1 -1
  140. package/dist/server/lib/models.js +15 -2
  141. package/dist/server/lib/models.js.map +1 -1
  142. package/dist/server/lib/oneShotAgent.js +11 -0
  143. package/dist/server/lib/oneShotAgent.js.map +1 -0
  144. package/dist/server/lib/projectAutoConfig.js +11 -5
  145. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  146. package/dist/server/lib/projectMemory.js +3 -1
  147. package/dist/server/lib/projectMemory.js.map +1 -1
  148. package/dist/server/lib/projectMemoryScan.js +13 -3
  149. package/dist/server/lib/projectMemoryScan.js.map +1 -1
  150. package/dist/server/lib/projectMemoryTransfer.js +5 -2
  151. package/dist/server/lib/projectMemoryTransfer.js.map +1 -1
  152. package/dist/server/lib/promptSkills.js +1 -1
  153. package/dist/server/lib/promptSkills.js.map +1 -1
  154. package/dist/server/lib/run.js +2 -0
  155. package/dist/server/lib/run.js.map +1 -1
  156. package/dist/server/lib/skills.js +21 -4
  157. package/dist/server/lib/skills.js.map +1 -1
  158. package/dist/server/lib/specOperations.js +14 -10
  159. package/dist/server/lib/specOperations.js.map +1 -1
  160. package/dist/server/lib/specReviewOperations.js +20 -11
  161. package/dist/server/lib/specReviewOperations.js.map +1 -1
  162. package/dist/server/lib/specs.js +14 -4
  163. package/dist/server/lib/specs.js.map +1 -1
  164. package/dist/server/lib/ticketAutomationCommit.js +23 -7
  165. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  166. package/dist/server/lib/ticketFollowUpOperations.js +7 -0
  167. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  168. package/dist/server/lib/ticketRunOperations.js +11 -1
  169. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  170. package/dist/server/lib/ticketSettingsOperations.js +4 -1
  171. package/dist/server/lib/ticketSettingsOperations.js.map +1 -1
  172. package/dist/server/lib/toolUpgrade.js +1 -0
  173. package/dist/server/lib/toolUpgrade.js.map +1 -1
  174. package/dist/server/lib/transcriptRuns.js +4 -1
  175. package/dist/server/lib/transcriptRuns.js.map +1 -1
  176. package/dist/server/lib/types.js.map +1 -1
  177. package/dist/server/workers/build.js +9 -4
  178. package/dist/server/workers/build.js.map +1 -1
  179. package/dist/server/workers/chat.js +55 -11
  180. package/dist/server/workers/chat.js.map +1 -1
  181. package/dist/server/workers/followUp.js +6 -1
  182. package/dist/server/workers/followUp.js.map +1 -1
  183. package/dist/server/workers/plan.js +235 -68
  184. package/dist/server/workers/plan.js.map +1 -1
  185. package/dist/server/workers/planFollowUp.js +11 -4
  186. package/dist/server/workers/planFollowUp.js.map +1 -1
  187. package/dist/server/workers/review.js +15 -18
  188. package/dist/server/workers/review.js.map +1 -1
  189. package/dist/server/workers/specReview.js +7 -7
  190. package/dist/server/workers/specReview.js.map +1 -1
  191. package/dist/server/workers/specSession.js +8 -12
  192. package/dist/server/workers/specSession.js.map +1 -1
  193. package/package.json +1 -1
  194. package/dist/client/assets/channel-DXKn3TOv.js +0 -1
  195. package/dist/client/assets/classDiagram-4FO5ZUOK-U0fU20hT.js +0 -1
  196. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C6bQNSxL.js +0 -1
  197. package/dist/client/assets/index-B6BltHSr.css +0 -2
  198. package/dist/client/assets/index-CFOYm3j9.js +0 -276
  199. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-6B8YH8Y2.js +0 -1
@@ -4,10 +4,12 @@ import db from '../db.js';
4
4
  import config from '../lib/config.js';
5
5
  import { JiraApiError } from '../lib/integrations/jira.js';
6
6
  import { GitHubApiError } from '../lib/integrations/github.js';
7
- import { formatGenericToolCall } from '../lib/genericToolCall.js';
7
+ import { formatCodexFileChange, formatCodexWebSearch, formatGenericToolCall } from '../lib/genericToolCall.js';
8
8
  import { PlanningReferenceError, resolveExternalPlanningDescription } from '../lib/integrations/planning.js';
9
9
  import { resolveExistingWorktreePath } from '../lib/git.js';
10
10
  import { getModelProvider, normalizeModelId } from '../lib/models.js';
11
+ import { getAgentModelProvider, normalizeAgentModelId, requireExecutableAgent } from '../lib/agentCatalog.js';
12
+ import { codexAgentProvider } from '../lib/codexAgentProvider.js';
11
13
  import { createOpencodeTaskSessionReader, parseOpencodeTaskEnvelope } from '../lib/opencodeTaskSessions.js';
12
14
  import { hostOpencodeSessionOperations } from '../lib/opencodeSessionOperations.js';
13
15
  import { markRetryableOpenCodeDatabaseLock, RetryableOpenCodeDatabaseLockError, retryOpenCodeDatabaseLocks } from '../lib/opencodeExecutionReliability.js';
@@ -81,9 +83,11 @@ const finalizePlanningInvocation = (invocation, status, evidence) => {
81
83
  };
82
84
  const activePlanProcesses = new Map();
83
85
  const activePlannerProcesses = new Set();
86
+ const plannerProtocolInterrupts = new Map();
84
87
  const createPlannerRecoveryTracker = () => {
85
88
  let sawError = false;
86
89
  let latestEvent = null;
90
+ let latestError = '';
87
91
  let finalAnswerFragments = [];
88
92
  return {
89
93
  consume: (events) => {
@@ -92,6 +96,7 @@ const createPlannerRecoveryTracker = () => {
92
96
  latestEvent = event.type;
93
97
  if (event.type === 'error') {
94
98
  sawError = true;
99
+ latestError = event.message?.trim() ?? '';
95
100
  finalAnswerFragments = [];
96
101
  shouldResetResponse = true;
97
102
  continue;
@@ -106,6 +111,7 @@ const createPlannerRecoveryTracker = () => {
106
111
  && latestEvent === 'final_answer'
107
112
  && finalAnswerFragments.length > 0),
108
113
  hasUnrecoveredError: () => latestEvent === 'error',
114
+ getUnrecoveredError: () => latestEvent === 'error' ? latestError : '',
109
115
  getRecoveredResponse: () => finalAnswerFragments.join('\n\n'),
110
116
  };
111
117
  };
@@ -254,8 +260,18 @@ const formatPlannerWriteToolCall = (input) => {
254
260
  lines.push('[[/archon-apply-patch]]');
255
261
  return lines.join('\n');
256
262
  };
257
- const formatPlannerToolCall = (name, input) => {
263
+ const formatPlannerToolCall = (name, input, cwd) => {
258
264
  const normalized = normalizeToolName(name);
265
+ if (normalized === 'filechange') {
266
+ const formatted = formatCodexFileChange(input, cwd);
267
+ if (formatted !== null) {
268
+ return formatted;
269
+ }
270
+ return formatGenericToolCall(name, input);
271
+ }
272
+ if (normalized === 'websearch') {
273
+ return formatCodexWebSearch(input);
274
+ }
259
275
  if (normalized === 'edit') {
260
276
  const formatted = formatPlannerEditToolCall(input);
261
277
  if (formatted !== null) {
@@ -367,25 +383,39 @@ const parseClaudePlannerStreamJsonLine = (line) => {
367
383
  }
368
384
  return emptyParsedPlannerStreamLine();
369
385
  };
370
- const formatPlannerOpencodeToolUse = (event) => {
386
+ const formatPlannerOpencodeToolUse = (event, cwd) => {
371
387
  const toolName = event.part?.tool;
372
388
  if (!toolName) {
373
389
  return null;
374
390
  }
375
- return formatPlannerToolCall(toolName, event.part?.state?.input);
391
+ return formatPlannerToolCall(toolName, event.part?.state?.input, cwd);
376
392
  };
377
393
  const formatPlannerStepTitle = (event) => {
378
394
  const title = event.part?.title?.trim();
379
395
  return title || null;
380
396
  };
381
- const parseOpencodePlannerJsonEvent = (event) => {
397
+ const parseOpencodePlannerJsonEvent = (event, cwd) => {
382
398
  const providerSessionId = event.sessionID?.trim()
383
399
  || event.sessionId?.trim()
384
400
  || event.part?.sessionID?.trim()
385
401
  || event.part?.sessionId?.trim()
386
402
  || null;
403
+ if (event.type === 'plan' && event.part?.type === 'text' && event.part.text) {
404
+ return {
405
+ logFragment: event.part.text,
406
+ responseFragment: null,
407
+ subagentEvents: [],
408
+ providerSessionId,
409
+ concatenateFragments: event.part.metadata?.codex?.delta === true,
410
+ replaceFragment: event.part.metadata?.codex?.snapshot === true,
411
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
412
+ ?? event.part.metadata?.codex?.itemId
413
+ ?? null,
414
+ };
415
+ }
387
416
  if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
388
- const text = event.part.text.trim();
417
+ const concatenateFragments = event.part.metadata?.codex?.delta === true;
418
+ const text = concatenateFragments ? event.part.text : event.part.text.trim();
389
419
  const phase = event.part.metadata?.openai?.phase;
390
420
  return {
391
421
  logFragment: text || null,
@@ -396,15 +426,24 @@ const parseOpencodePlannerJsonEvent = (event) => {
396
426
  recoveryEvents: text && phase === 'final_answer'
397
427
  ? [{ type: 'final_answer', text }]
398
428
  : [],
429
+ concatenateFragments,
430
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
431
+ ?? event.part.metadata?.codex?.itemId
432
+ ?? null,
399
433
  };
400
434
  }
401
435
  if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
402
- const text = event.part.text?.trim() ?? '';
436
+ const concatenateFragments = event.part.metadata?.codex?.delta === true;
437
+ const text = concatenateFragments ? event.part.text ?? '' : event.part.text?.trim() ?? '';
403
438
  return {
404
439
  logFragment: text || null,
405
440
  responseFragment: null,
406
441
  subagentEvents: [],
407
442
  providerSessionId,
443
+ concatenateFragments,
444
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
445
+ ?? event.part.metadata?.codex?.itemId
446
+ ?? null,
408
447
  };
409
448
  }
410
449
  if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
@@ -416,6 +455,7 @@ const parseOpencodePlannerJsonEvent = (event) => {
416
455
  const envelope = parseOpencodeTaskEnvelope(rawOutput);
417
456
  const output = envelope?.result ?? rawOutput;
418
457
  const taskStatus = event.part?.state?.status?.toLowerCase() ?? envelope?.state?.toLowerCase();
458
+ const isTerminal = Boolean(taskStatus && taskStatus !== 'running' && taskStatus !== 'pending');
419
459
  const childSessionId = envelope?.childSessionId
420
460
  ?? (typeof event.part?.state?.metadata?.sessionID === 'string' ? event.part.state.metadata.sessionID : null)
421
461
  ?? (typeof event.part?.state?.metadata?.sessionId === 'string' ? event.part.state.metadata.sessionId : null);
@@ -431,13 +471,15 @@ const parseOpencodePlannerJsonEvent = (event) => {
431
471
  sessionId: childSessionId,
432
472
  }]
433
473
  : []),
434
- ...(output
474
+ ...(output || isTerminal
435
475
  ? [{
436
- kind: taskStatus === 'running' || taskStatus === 'pending' ? 'fragment' : 'complete',
476
+ kind: isTerminal ? 'complete' : 'fragment',
437
477
  callId: event.part?.callID ?? null,
438
- text: output,
478
+ ...(output ? { text: output } : {}),
439
479
  sessionId: childSessionId,
440
- status: taskStatus === 'error' || taskStatus === 'failed' ? 'error' : 'done',
480
+ status: taskStatus === 'error' || taskStatus === 'failed'
481
+ ? 'error'
482
+ : taskStatus === 'stopped' ? 'stopped' : 'done',
441
483
  }]
442
484
  : []),
443
485
  ],
@@ -448,7 +490,7 @@ const parseOpencodePlannerJsonEvent = (event) => {
448
490
  return { ...emptyParsedPlannerStreamLine(), providerSessionId };
449
491
  }
450
492
  return {
451
- logFragment: formatPlannerOpencodeToolUse(event),
493
+ logFragment: formatPlannerOpencodeToolUse(event, cwd),
452
494
  responseFragment: null,
453
495
  subagentEvents: [],
454
496
  providerSessionId,
@@ -474,12 +516,12 @@ const parseOpencodePlannerJsonEvent = (event) => {
474
516
  responseFragment: null,
475
517
  subagentEvents: [],
476
518
  providerSessionId,
477
- recoveryEvents: [{ type: 'error' }],
519
+ recoveryEvents: [{ type: 'error', message: text }],
478
520
  };
479
521
  }
480
522
  return { ...emptyParsedPlannerStreamLine(), providerSessionId };
481
523
  };
482
- const parseOpencodePlannerJsonLine = (line) => {
524
+ const parseOpencodePlannerJsonLine = (line, cwd) => {
483
525
  const trimmedLine = line.trim();
484
526
  if (!trimmedLine) {
485
527
  return emptyParsedPlannerStreamLine();
@@ -510,6 +552,9 @@ const parseOpencodePlannerJsonLine = (line) => {
510
552
  const recoveryEvents = [];
511
553
  let providerSessionId = null;
512
554
  let hasFinalAnswer = false;
555
+ let concatenateFragments;
556
+ let replaceFragment;
557
+ let fragmentGroup;
513
558
  for (const jsonObject of jsonObjects) {
514
559
  let event;
515
560
  try {
@@ -518,7 +563,7 @@ const parseOpencodePlannerJsonLine = (line) => {
518
563
  catch {
519
564
  continue;
520
565
  }
521
- const parsedEvent = parseOpencodePlannerJsonEvent(event);
566
+ const parsedEvent = parseOpencodePlannerJsonEvent(event, cwd);
522
567
  const eventRecoveryEvents = parsedEvent.recoveryEvents ?? [];
523
568
  if (eventRecoveryEvents.some(recoveryEvent => recoveryEvent.type === 'error')) {
524
569
  responseFragments.length = 0;
@@ -533,6 +578,11 @@ const parseOpencodePlannerJsonLine = (line) => {
533
578
  recoveryEvents.push(...eventRecoveryEvents);
534
579
  providerSessionId = providerSessionId ?? parsedEvent.providerSessionId;
535
580
  hasFinalAnswer ||= parsedEvent.hasFinalAnswer === true;
581
+ if (jsonObjects.length === 1) {
582
+ concatenateFragments = parsedEvent.concatenateFragments;
583
+ replaceFragment = parsedEvent.replaceFragment;
584
+ fragmentGroup = parsedEvent.fragmentGroup;
585
+ }
536
586
  }
537
587
  if (remainder) {
538
588
  const envelope = parseOpencodeTaskEnvelope(remainder);
@@ -557,6 +607,9 @@ const parseOpencodePlannerJsonLine = (line) => {
557
607
  providerSessionId,
558
608
  recoveryEvents,
559
609
  hasFinalAnswer,
610
+ concatenateFragments,
611
+ replaceFragment,
612
+ fragmentGroup,
560
613
  };
561
614
  };
562
615
  const attachPlannerTranscriptRuns = (error, transcriptRuns) => {
@@ -950,12 +1003,10 @@ const resolvePlannerCwd = (project, branch) => {
950
1003
  }
951
1004
  return resolveExistingWorktreePath(branch, project) ?? project.repo_path;
952
1005
  };
953
- export const updatePlanningSessionId = (ticketId, sessionId, io, sessionOperations) => {
954
- const existing = getTicket(ticketId);
955
- const resolvedTool = resolveTicketTool(existing, config.tool ?? 'opencode');
956
- db.prepare('UPDATE tickets SET planning_session_id = ?, tool = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(sessionId, resolvedTool, ticketId);
1006
+ export const updatePlanningSessionId = (ticketId, sessionId, tool, io, sessionOperations) => {
1007
+ db.prepare('UPDATE tickets SET planning_session_id = ?, tool = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(sessionId, tool, ticketId);
957
1008
  const ticket = getTicket(ticketId);
958
- if (ticket && resolveTicketTool(ticket, config.tool ?? 'opencode') === 'opencode') {
1009
+ if (ticket && tool === 'opencode') {
959
1010
  if (sessionOperations) {
960
1011
  void storeTicketUsageSnapshot(ticket.id, sessionId, sessionOperations);
961
1012
  }
@@ -1001,9 +1052,15 @@ const killPlannerProcess = (proc, signal) => {
1001
1052
  }
1002
1053
  };
1003
1054
  export const stopPlannerProcesses = async (children) => {
1004
- for (const child of children) {
1005
- killPlannerProcess(child, 'SIGTERM');
1006
- }
1055
+ await Promise.all(children.map(async (child) => {
1056
+ const protocolInterrupt = plannerProtocolInterrupts.get(child);
1057
+ if (protocolInterrupt) {
1058
+ await protocolInterrupt().catch(() => killPlannerProcess(child, 'SIGTERM'));
1059
+ }
1060
+ else {
1061
+ killPlannerProcess(child, 'SIGTERM');
1062
+ }
1063
+ }));
1007
1064
  const deadline = Date.now() + PLANNER_KILL_GRACE_MS;
1008
1065
  while (children.some(isPlannerProcessAlive) && Date.now() < deadline) {
1009
1066
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -1013,7 +1070,7 @@ export const stopPlannerProcesses = async (children) => {
1013
1070
  }
1014
1071
  };
1015
1072
  const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) => {
1016
- const resolvedModel = normalizeModelId(model);
1073
+ const resolvedModel = normalizeAgentModelId(tool, model);
1017
1074
  const finalPrompt = tool === 'claude'
1018
1075
  ? appendClaudeHeadlessPlanPrompt(prompt)
1019
1076
  : prompt;
@@ -1025,10 +1082,10 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1025
1082
  let spawnTimestamp = null;
1026
1083
  let sessionLookupTimer = null;
1027
1084
  let hasResolvedSessionId = Boolean(sessionTracking?.resumeSessionId);
1028
- const [cmd, args] = tool === 'claude'
1085
+ const [cmd, args, stdio] = tool === 'claude'
1029
1086
  ? (() => {
1030
1087
  if (!sessionTracking) {
1031
- return ['claude', [...buildClaudeHeadlessPlanArgs(), '--print', '--verbose', '--output-format', 'stream-json', finalPrompt]];
1088
+ return ['claude', [...buildClaudeHeadlessPlanArgs(), '--print', '--verbose', '--output-format', 'stream-json', finalPrompt], ['ignore', 'pipe', 'pipe']];
1032
1089
  }
1033
1090
  sessionId = sessionTracking.resumeSessionId || randomUUID();
1034
1091
  return [
@@ -1043,22 +1100,38 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1043
1100
  sessionId,
1044
1101
  finalPrompt,
1045
1102
  ],
1103
+ ['ignore', 'pipe', 'pipe'],
1046
1104
  ];
1047
1105
  })()
1048
- : [
1049
- 'opencode',
1050
- [
1051
- 'run',
1052
- '--agent',
1053
- 'plan',
1054
- '--format',
1055
- 'json',
1056
- ...(sessionTracking?.resumeSessionId ? ['--session', sessionTracking.resumeSessionId] : []),
1057
- ...(resolvedModel ? ['--model', resolvedModel] : []),
1058
- ...(variant ? ['--variant', variant] : []),
1059
- finalPrompt,
1060
- ],
1061
- ];
1106
+ : tool === 'codex'
1107
+ ? (() => {
1108
+ const invocation = codexAgentProvider.buildStreamingInvocation({
1109
+ cwd: cwd ?? process.cwd(),
1110
+ prompt: finalPrompt,
1111
+ model: resolvedModel || null,
1112
+ variant,
1113
+ skills: options.skills,
1114
+ mode: 'plan',
1115
+ resumeSessionId: sessionTracking?.resumeSessionId ?? null,
1116
+ sessionId: randomUUID(),
1117
+ });
1118
+ return [invocation.command, [...invocation.args], invocation.stdio];
1119
+ })()
1120
+ : [
1121
+ 'opencode',
1122
+ [
1123
+ 'run',
1124
+ '--agent',
1125
+ 'plan',
1126
+ '--format',
1127
+ 'json',
1128
+ ...(sessionTracking?.resumeSessionId ? ['--session', sessionTracking.resumeSessionId] : []),
1129
+ ...(resolvedModel ? ['--model', resolvedModel] : []),
1130
+ ...(variant ? ['--variant', variant] : []),
1131
+ finalPrompt,
1132
+ ],
1133
+ ['ignore', 'pipe', 'pipe'],
1134
+ ];
1062
1135
  return new Promise((resolve, reject) => {
1063
1136
  let settled = false;
1064
1137
  let completionForced = false;
@@ -1118,7 +1191,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1118
1191
  }
1119
1192
  if (sessionTracking && !hasResolvedSessionId) {
1120
1193
  hasResolvedSessionId = true;
1121
- updatePlanningSessionId(sessionTracking.ticketId, normalized, sessionTracking.io, opencodeSessionOperations ?? undefined);
1194
+ updatePlanningSessionId(sessionTracking.ticketId, normalized, tool, sessionTracking.io, opencodeSessionOperations ?? undefined);
1122
1195
  }
1123
1196
  };
1124
1197
  const attemptOpencodeSessionLookup = async () => {
@@ -1140,6 +1213,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1140
1213
  spawnTimestamp = Date.now();
1141
1214
  }
1142
1215
  let child;
1216
+ let providerStdout = null;
1143
1217
  let placementFailure = Promise.resolve(null);
1144
1218
  try {
1145
1219
  if (options.executionProvider) {
@@ -1148,7 +1222,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1148
1222
  target: options.executionTarget ?? null,
1149
1223
  command: cmd,
1150
1224
  args,
1151
- stdio: ['ignore', 'pipe', 'pipe'],
1225
+ stdio,
1152
1226
  launcherEnvironment: options.executionLauncherEnvironment ?? createHostLauncherEnvironment(),
1153
1227
  runtimeEnvironment: options.executionRuntimeEnvironment ?? createLegacyHostRuntimeEnvironment(cwd ?? process.cwd(), cwd ? { PWD: cwd } : {}),
1154
1228
  detached: process.platform !== 'win32',
@@ -1164,7 +1238,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1164
1238
  }
1165
1239
  else {
1166
1240
  child = spawn(cmd, args, {
1167
- stdio: ['ignore', 'pipe', 'pipe'],
1241
+ stdio,
1168
1242
  env: cwd ? createWorkspaceProcessEnvironment(cwd, { PWD: cwd }) : process.env,
1169
1243
  detached: process.platform !== 'win32',
1170
1244
  ...(cwd ? { cwd } : {}),
@@ -1178,6 +1252,28 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1178
1252
  return;
1179
1253
  }
1180
1254
  activePlannerProcesses.add(child);
1255
+ if (tool === 'codex') {
1256
+ const connection = codexAgentProvider.connectStreamingInvocation?.(child, {
1257
+ cwd: cwd ?? process.cwd(),
1258
+ prompt: finalPrompt,
1259
+ model: resolvedModel || null,
1260
+ variant,
1261
+ skills: options.skills,
1262
+ mode: 'plan',
1263
+ resumeSessionId: sessionTracking?.resumeSessionId ?? null,
1264
+ sessionId: randomUUID(),
1265
+ }, {
1266
+ runtimeEnvironmentKind: options.executionRuntimeEnvironment?.kind ?? 'host',
1267
+ });
1268
+ if (connection) {
1269
+ providerStdout = connection.stdout;
1270
+ plannerProtocolInterrupts.set(child, connection.interrupt);
1271
+ void connection.sessionReady.then(bindResolvedProviderSession).catch(() => { });
1272
+ if (options.cancellationState?.stopRequested) {
1273
+ void connection.interrupt().catch(() => killPlannerProcess(child, 'SIGTERM'));
1274
+ }
1275
+ }
1276
+ }
1181
1277
  if (options.cancellationState) {
1182
1278
  options.cancellationState.researchChildren.add(child);
1183
1279
  if (options.cancellationState.stopRequested) {
@@ -1224,6 +1320,9 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1224
1320
  let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
1225
1321
  let remainder = '';
1226
1322
  let insideHiddenBlock = false;
1323
+ let concatenatingLogGroup;
1324
+ let concatenatingResponseGroup;
1325
+ const logIndexesByGroup = new Map();
1227
1326
  const startTime = Date.now();
1228
1327
  let firstFinalAnswerTime = null;
1229
1328
  const emitProgress = () => {
@@ -1236,25 +1335,27 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1236
1335
  };
1237
1336
  const isHiddenOpenTag = /^\s*<archon-[a-z0-9-]+>\s*$/i;
1238
1337
  const isHiddenCloseTag = /^\s*<\/archon-[a-z0-9-]+>\s*$/i;
1239
- child.stdout?.on('data', (chunk) => {
1338
+ const outputStream = providerStdout ?? child.stdout;
1339
+ outputStream?.on('data', (chunk) => {
1240
1340
  stdoutChunks.push(chunk);
1241
1341
  lastStdoutAt = Date.now();
1242
1342
  const text = remainder + chunk.toString();
1243
1343
  const lines = text.split('\n');
1244
1344
  remainder = lines.pop() ?? '';
1245
1345
  for (const line of lines) {
1246
- if (tool === 'claude') {
1346
+ if (tool === 'claude' || tool === 'codex') {
1247
1347
  usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
1248
1348
  }
1249
1349
  const parsedLine = tool === 'claude'
1250
1350
  ? parseClaudePlannerStreamJsonLine(line)
1251
- : parseOpencodePlannerJsonLine(line);
1351
+ : parseOpencodePlannerJsonLine(line, cwd);
1252
1352
  const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
1253
- if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
1353
+ if ((tool === 'opencode' || tool === 'codex') && recoveryTracker.consume(parsedLine.recoveryEvents)) {
1254
1354
  responseLines.length = 0;
1355
+ concatenatingResponseGroup = undefined;
1255
1356
  firstFinalAnswerTime = null;
1256
1357
  }
1257
- if (tool === 'opencode' && firstFinalAnswerTime === null && parsedLine.hasFinalAnswer) {
1358
+ if ((tool === 'opencode' || tool === 'codex') && firstFinalAnswerTime === null && parsedLine.hasFinalAnswer) {
1258
1359
  firstFinalAnswerTime = Date.now();
1259
1360
  }
1260
1361
  subagentTracker?.handle(subagentEvents);
@@ -1268,11 +1369,35 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1268
1369
  insideHiddenBlock = false;
1269
1370
  }
1270
1371
  if (logFragment !== null) {
1271
- logLines.push(logFragment);
1372
+ if (parsedLine.replaceFragment && parsedLine.fragmentGroup) {
1373
+ const index = logIndexesByGroup.get(parsedLine.fragmentGroup);
1374
+ if (index === undefined) {
1375
+ logIndexesByGroup.set(parsedLine.fragmentGroup, logLines.push(logFragment) - 1);
1376
+ }
1377
+ else {
1378
+ logLines[index] = logFragment;
1379
+ }
1380
+ }
1381
+ else if (parsedLine.concatenateFragments && concatenatingLogGroup === parsedLine.fragmentGroup && logLines.length > 0) {
1382
+ logLines[logLines.length - 1] += logFragment;
1383
+ }
1384
+ else {
1385
+ const index = logLines.push(logFragment) - 1;
1386
+ if (parsedLine.fragmentGroup) {
1387
+ logIndexesByGroup.set(parsedLine.fragmentGroup, index);
1388
+ }
1389
+ }
1390
+ concatenatingLogGroup = parsedLine.concatenateFragments ? parsedLine.fragmentGroup ?? null : undefined;
1272
1391
  }
1273
1392
  const isHiddenTagLine = isHiddenOpenTag.test(line) || isHiddenCloseTag.test(line);
1274
1393
  if (responseFragment !== null && !isHiddenTagLine && !insideHiddenBlock) {
1275
- responseLines.push(responseFragment);
1394
+ if (parsedLine.concatenateFragments && concatenatingResponseGroup === parsedLine.fragmentGroup && responseLines.length > 0) {
1395
+ responseLines[responseLines.length - 1] += responseFragment;
1396
+ }
1397
+ else {
1398
+ responseLines.push(responseFragment);
1399
+ }
1400
+ concatenatingResponseGroup = parsedLine.concatenateFragments ? parsedLine.fragmentGroup ?? null : undefined;
1276
1401
  }
1277
1402
  if (logFragment !== null || responseFragment !== null) {
1278
1403
  emitProgress();
@@ -1282,6 +1407,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1282
1407
  child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
1283
1408
  child.on('close', async (code) => {
1284
1409
  activePlannerProcesses.delete(child);
1410
+ plannerProtocolInterrupts.delete(child);
1285
1411
  options.cancellationState?.researchChildren.delete(child);
1286
1412
  clearWatchdogTimer();
1287
1413
  const placementError = await placementFailure;
@@ -1290,18 +1416,19 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1290
1416
  return;
1291
1417
  }
1292
1418
  if (remainder.trim()) {
1293
- if (tool === 'claude') {
1419
+ if (tool === 'claude' || tool === 'codex') {
1294
1420
  usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
1295
1421
  }
1296
1422
  const parsedLine = tool === 'claude'
1297
1423
  ? parseClaudePlannerStreamJsonLine(remainder)
1298
- : parseOpencodePlannerJsonLine(remainder);
1424
+ : parseOpencodePlannerJsonLine(remainder, cwd);
1299
1425
  const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
1300
- if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
1426
+ if ((tool === 'opencode' || tool === 'codex') && recoveryTracker.consume(parsedLine.recoveryEvents)) {
1301
1427
  responseLines.length = 0;
1428
+ concatenatingResponseGroup = undefined;
1302
1429
  firstFinalAnswerTime = null;
1303
1430
  }
1304
- if (tool === 'opencode' && firstFinalAnswerTime === null && parsedLine.hasFinalAnswer) {
1431
+ if ((tool === 'opencode' || tool === 'codex') && firstFinalAnswerTime === null && parsedLine.hasFinalAnswer) {
1305
1432
  firstFinalAnswerTime = Date.now();
1306
1433
  }
1307
1434
  subagentTracker?.handle(subagentEvents);
@@ -1309,11 +1436,35 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1309
1436
  bindResolvedProviderSession(providerSessionId);
1310
1437
  }
1311
1438
  if (logFragment !== null) {
1312
- logLines.push(logFragment);
1439
+ if (parsedLine.replaceFragment && parsedLine.fragmentGroup) {
1440
+ const index = logIndexesByGroup.get(parsedLine.fragmentGroup);
1441
+ if (index === undefined) {
1442
+ logIndexesByGroup.set(parsedLine.fragmentGroup, logLines.push(logFragment) - 1);
1443
+ }
1444
+ else {
1445
+ logLines[index] = logFragment;
1446
+ }
1447
+ }
1448
+ else if (parsedLine.concatenateFragments && concatenatingLogGroup === parsedLine.fragmentGroup && logLines.length > 0) {
1449
+ logLines[logLines.length - 1] += logFragment;
1450
+ }
1451
+ else {
1452
+ const index = logLines.push(logFragment) - 1;
1453
+ if (parsedLine.fragmentGroup) {
1454
+ logIndexesByGroup.set(parsedLine.fragmentGroup, index);
1455
+ }
1456
+ }
1457
+ concatenatingLogGroup = parsedLine.concatenateFragments ? parsedLine.fragmentGroup ?? null : undefined;
1313
1458
  }
1314
1459
  const remainderIsHiddenTagLine = isHiddenOpenTag.test(remainder) || isHiddenCloseTag.test(remainder);
1315
1460
  if (responseFragment !== null && !remainderIsHiddenTagLine && !insideHiddenBlock) {
1316
- responseLines.push(responseFragment);
1461
+ if (parsedLine.concatenateFragments && concatenatingResponseGroup === parsedLine.fragmentGroup && responseLines.length > 0) {
1462
+ responseLines[responseLines.length - 1] += responseFragment;
1463
+ }
1464
+ else {
1465
+ responseLines.push(responseFragment);
1466
+ }
1467
+ concatenatingResponseGroup = parsedLine.concatenateFragments ? parsedLine.fragmentGroup ?? null : undefined;
1317
1468
  }
1318
1469
  }
1319
1470
  opencodeSubagentMonitor?.refresh();
@@ -1321,9 +1472,10 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1321
1472
  const errorOutput = Buffer.concat(stderrChunks).toString().trim();
1322
1473
  const recoveredResponse = recoveryTracker.getRecoveredResponse();
1323
1474
  const parsedResponse = responseLines.join('\n\n').trim();
1324
- const streamedOutput = tool === 'opencode' && recoveryTracker.hasRecoveredFinalAnswer()
1475
+ const isStructuredStream = tool === 'opencode' || tool === 'codex';
1476
+ const streamedOutput = isStructuredStream && recoveryTracker.hasRecoveredFinalAnswer()
1325
1477
  ? recoveredResponse
1326
- : tool === 'opencode'
1478
+ : isStructuredStream
1327
1479
  ? parsedResponse || (rawOutput.startsWith('{') ? '' : rawOutput)
1328
1480
  : !rawOutput.startsWith('{')
1329
1481
  ? rawOutput
@@ -1379,7 +1531,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1379
1531
  const fallbackError = code === 0
1380
1532
  ? 'Planner did not return a usable plan.'
1381
1533
  : `Process exited with code ${code}`;
1382
- const error = new Error(output || errorOutput || fallbackError);
1534
+ const error = new Error(output || errorOutput || recoveryTracker.getUnrecoveredError() || fallbackError);
1383
1535
  const retryableError = tool === 'opencode' && stdoutChunks.length === 0
1384
1536
  ? markRetryableOpenCodeDatabaseLock(error)
1385
1537
  : error;
@@ -1393,6 +1545,7 @@ const runPlannerPromptOnce = (tool, model, variant, prompt, cwd, options = {}) =
1393
1545
  });
1394
1546
  child.on('error', err => {
1395
1547
  activePlannerProcesses.delete(child);
1548
+ plannerProtocolInterrupts.delete(child);
1396
1549
  options.cancellationState?.researchChildren.delete(child);
1397
1550
  clearWatchdogTimer();
1398
1551
  if (sessionLookupTimer && hasResolvedSessionId) {
@@ -1702,11 +1855,12 @@ export const prepareLinkedPlanningResearch = async (taskDescription, options = {
1702
1855
  if (linkedProjects.length === 0) {
1703
1856
  return [];
1704
1857
  }
1705
- const tool = options.tool ?? config.tool ?? 'opencode';
1858
+ const tool = requireExecutableAgent(options.tool ?? config.tool ?? 'opencode');
1706
1859
  let decision;
1707
1860
  const selectionInvocation = createPlanningInvocation(options, 'Linked Research Selection', 'research-selection');
1708
1861
  try {
1709
1862
  const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, buildLinkedProjectSelectionPrompt(taskDescription, project, linkedProjects), cwd, {
1863
+ skills: options.skills,
1710
1864
  agentRunParent: resolvePlannerAgentRunParent(options),
1711
1865
  executionContext: options.executionContext,
1712
1866
  cancellationState: options.cancellationState,
@@ -1738,6 +1892,7 @@ export const prepareLinkedPlanningResearch = async (taskDescription, options = {
1738
1892
  const legacyInvocation = createPlanningInvocation(options, `Linked Research: ${linkedProject.name}`, 'research');
1739
1893
  try {
1740
1894
  const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildLinkedProjectResearchPrompt(project, linkedProject, request), options.skills ?? []), linkedProject.repo_path, {
1895
+ skills: options.skills,
1741
1896
  agentRunParent: resolvePlannerAgentRunParent(options),
1742
1897
  cancellationState: options.cancellationState,
1743
1898
  ...(legacyInvocation ? { mainRun: legacyInvocation } : {}),
@@ -1800,6 +1955,7 @@ export const prepareLinkedPlanningResearch = async (taskDescription, options = {
1800
1955
  skills: options.skills ?? [],
1801
1956
  target: executionTarget,
1802
1957
  workload: 'plan',
1958
+ signal: options.cancellationState?.abortController?.signal,
1803
1959
  });
1804
1960
  const invocationId = createAgentRun({
1805
1961
  executionRunId: childContext.runId,
@@ -1821,6 +1977,7 @@ export const prepareLinkedPlanningResearch = async (taskDescription, options = {
1821
1977
  role: invocationRun.role,
1822
1978
  };
1823
1979
  const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildLinkedProjectResearchPrompt(project, linkedProject, request), options.skills ?? []), resolvedTarget.workspacePath, {
1980
+ skills: options.skills,
1824
1981
  agentRunParent: options.agentRunParent
1825
1982
  ? { parentType: 'planning', parentId: linkedProject.id, io: options.agentRunParent.io }
1826
1983
  : undefined,
@@ -1867,7 +2024,7 @@ export const prepareLinkedPlanningResearch = async (taskDescription, options = {
1867
2024
  return linkedResearch.filter((result) => result !== null);
1868
2025
  };
1869
2026
  export const generatePlanText = (title, description, options = {}) => {
1870
- const tool = options.tool ?? config.tool ?? 'opencode';
2027
+ const tool = requireExecutableAgent(options.tool ?? config.tool ?? 'opencode');
1871
2028
  const project = options.projectId ? getProjectById(options.projectId) : null;
1872
2029
  return (async () => {
1873
2030
  const invocation = createPlanningInvocation(options, 'Ticket Planning', 'planning');
@@ -1882,6 +2039,7 @@ export const generatePlanText = (title, description, options = {}) => {
1882
2039
  });
1883
2040
  const { output: plannerOutput, responseTiming: plannerTiming, usageSnapshot, transcriptRuns } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildSingleTicketPrompt(title, description, linkedResearchContext, projectMemoryBlock, Boolean(project?.memory_enabled), Boolean(options.ticketId)), options.skills ?? []), options.cwd ?? resolvePlannerCwd(project, options.branch), {
1884
2041
  onProgress: options.onProgress,
2042
+ skills: options.skills,
1885
2043
  ...(options.ticketId && options.io
1886
2044
  ? {
1887
2045
  sessionTracking: {
@@ -1949,7 +2107,7 @@ export const generatePlanText = (title, description, options = {}) => {
1949
2107
  })();
1950
2108
  };
1951
2109
  export const generatePlanningTicketOutline = async (description, options = {}) => {
1952
- const tool = options.tool ?? config.tool ?? 'opencode';
2110
+ const tool = requireExecutableAgent(options.tool ?? config.tool ?? 'opencode');
1953
2111
  const project = options.projectId ? getProjectById(options.projectId) : null;
1954
2112
  const invocation = createPlanningInvocation(options, 'Planning Outline', 'planning-outline');
1955
2113
  try {
@@ -1963,6 +2121,7 @@ export const generatePlanningTicketOutline = async (description, options = {}) =
1963
2121
  ...(invocation ? { invocation: { parentType: invocation.parentType, parentId: invocation.parentId, agentRunId: invocation.id } } : {}),
1964
2122
  });
1965
2123
  const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildPlanningAgentOutlinePrompt(description, linkedResearchContext, projectMemoryBlock), options.skills ?? []), options.cwd ?? resolvePlannerCwd(project, options.branch), {
2124
+ skills: options.skills,
1966
2125
  agentRunParent: resolvePlannerAgentRunParent(options),
1967
2126
  executionContext: options.executionContext,
1968
2127
  ...plannerExecutionOptions(options),
@@ -1978,7 +2137,7 @@ export const generatePlanningTicketOutline = async (description, options = {}) =
1978
2137
  }
1979
2138
  };
1980
2139
  export const expandPlanningTicketOutline = async (description, outline, options = {}) => {
1981
- const tool = options.tool ?? config.tool ?? 'opencode';
2140
+ const tool = requireExecutableAgent(options.tool ?? config.tool ?? 'opencode');
1982
2141
  const project = options.projectId ? getProjectById(options.projectId) : null;
1983
2142
  const invocation = createPlanningInvocation(options, 'Planning Expansion', 'planning-expansion');
1984
2143
  try {
@@ -1992,6 +2151,7 @@ export const expandPlanningTicketOutline = async (description, outline, options
1992
2151
  ...(invocation ? { invocation: { parentType: invocation.parentType, parentId: invocation.parentId, agentRunId: invocation.id } } : {}),
1993
2152
  });
1994
2153
  const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildPlanningAgentExpansionPrompt(description, outline, linkedResearchContext, projectMemoryBlock), options.skills ?? []), options.cwd ?? resolvePlannerCwd(project, options.branch), {
2154
+ skills: options.skills,
1995
2155
  agentRunParent: resolvePlannerAgentRunParent(options),
1996
2156
  executionContext: options.executionContext,
1997
2157
  ...plannerExecutionOptions(options),
@@ -2056,7 +2216,7 @@ export const startPlan = (ticket, io, options = {}) => {
2056
2216
  };
2057
2217
  const targetBundleId = options.targetBundleId === undefined ? ticket.worktree_bundle_id : options.targetBundleId;
2058
2218
  const targetBranch = options.branch === undefined ? resolveTicketBranch(ticket) : options.branch;
2059
- const runTool = resolveTicketTool(ticket, config.tool ?? 'opencode');
2219
+ const runTool = requireExecutableAgent(resolveTicketTool(ticket, config.tool ?? 'opencode'));
2060
2220
  const runSkills = options.skills ?? ticket.skills ?? [];
2061
2221
  const executionRun = createExecutionRun({
2062
2222
  projectId: project.id,
@@ -2087,7 +2247,11 @@ export const startPlan = (ticket, io, options = {}) => {
2087
2247
  projectId: executionRun.project_id,
2088
2248
  kind: executionRun.kind,
2089
2249
  };
2090
- const cancellationState = { researchChildren: new Set(), stopRequested: false };
2250
+ const cancellationState = {
2251
+ researchChildren: new Set(),
2252
+ stopRequested: false,
2253
+ abortController: new AbortController(),
2254
+ };
2091
2255
  activePlanProcesses.set(ticket.id, cancellationState);
2092
2256
  let queuedJob;
2093
2257
  try {
@@ -2153,6 +2317,7 @@ export const startPlan = (ticket, io, options = {}) => {
2153
2317
  skills: runSkills,
2154
2318
  target: executionTarget,
2155
2319
  workload: 'plan',
2320
+ signal: cancellationState.abortController?.signal,
2156
2321
  });
2157
2322
  const planningSessionId = options.providerContinuation === 'fresh' ? null : ticket.planning_session_id;
2158
2323
  if (planningSessionId) {
@@ -2163,7 +2328,7 @@ export const startPlan = (ticket, io, options = {}) => {
2163
2328
  parentId: ticket.id,
2164
2329
  sessionId: planningSessionId,
2165
2330
  agent: runTool,
2166
- modelProvider: runTool === 'claude' ? 'anthropic' : getModelProvider(ticket.model),
2331
+ modelProvider: getAgentModelProvider(runTool, ticket.model),
2167
2332
  authority: workloadEnvironment.runAuthority,
2168
2333
  });
2169
2334
  }
@@ -2202,7 +2367,7 @@ export const startPlan = (ticket, io, options = {}) => {
2202
2367
  }, io);
2203
2368
  return;
2204
2369
  }
2205
- const resolvedTool = resolveTicketTool(ticket, config.tool ?? 'opencode');
2370
+ const resolvedTool = requireExecutableAgent(resolveTicketTool(ticket, config.tool ?? 'opencode'));
2206
2371
  if (resolvedTool === 'opencode') {
2207
2372
  const freshTicket = getTicket(ticket.id);
2208
2373
  if (freshTicket?.planning_session_id) {
@@ -2215,7 +2380,7 @@ export const startPlan = (ticket, io, options = {}) => {
2215
2380
  }
2216
2381
  }
2217
2382
  }
2218
- else if (resolvedTool === 'claude' && planned.usageSnapshot) {
2383
+ else if ((resolvedTool === 'claude' || resolvedTool === 'codex') && planned.usageSnapshot) {
2219
2384
  const freshTicket = getTicket(ticket.id);
2220
2385
  if (freshTicket) {
2221
2386
  storeTicketUsageSnapshotDirect(freshTicket.id, planned.usageSnapshot);
@@ -2409,6 +2574,7 @@ export const stopPlan = async (ticketId) => {
2409
2574
  return false;
2410
2575
  }
2411
2576
  state.stopRequested = true;
2577
+ state.abortController?.abort();
2412
2578
  const children = [...state.researchChildren];
2413
2579
  await stopPlannerProcesses(children);
2414
2580
  return true;
@@ -2416,6 +2582,7 @@ export const stopPlan = async (ticketId) => {
2416
2582
  export const stopAllPlanAgents = async () => {
2417
2583
  for (const state of activePlanProcesses.values()) {
2418
2584
  state.stopRequested = true;
2585
+ state.abortController?.abort();
2419
2586
  }
2420
2587
  await stopPlannerProcesses([...activePlannerProcesses]);
2421
2588
  };