@straiffi/archon 1.10.10 → 1.11.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 (215) hide show
  1. package/README.md +9 -2
  2. package/dist/client/assets/{ActivityView-CRkM5TeN.js → ActivityView-BvXSn9Jw.js} +1 -1
  3. package/dist/client/assets/GitHubActionsDialog-BPZg_Wy3.js +1 -0
  4. package/dist/client/assets/VerificationDialog-D3mOY3z9.js +4 -0
  5. package/dist/client/assets/{architectureDiagram-3BPJPVTR-u0PSZlNP.js → architectureDiagram-3BPJPVTR-Bj7m5nzx.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-GPEHLZMM-DFXLHRIk.js → blockDiagram-GPEHLZMM-Dv4SrhZg.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-AAUBKEIU-Dvic8tCt.js → c4Diagram-AAUBKEIU-BKGsMo_n.js} +1 -1
  8. package/dist/client/assets/channel-DmiHuOD2.js +1 -0
  9. package/dist/client/assets/{chunk-2J33WTMH-t5CxhRXJ.js → chunk-2J33WTMH-oppoEflV.js} +1 -1
  10. package/dist/client/assets/{chunk-3OPIFGDE-YHT1dP8Q.js → chunk-3OPIFGDE-FoeHxEDL.js} +1 -1
  11. package/dist/client/assets/{chunk-5ZQYHXKU-DcxuI7lb.js → chunk-5ZQYHXKU-DFo7Hn8f.js} +1 -1
  12. package/dist/client/assets/{chunk-727SXJPM-D1F6DmrT.js → chunk-727SXJPM-BbM0ijwZ.js} +1 -1
  13. package/dist/client/assets/{chunk-AQP2D5EJ-BIx6Ohbk.js → chunk-AQP2D5EJ-hGswj21x.js} +1 -1
  14. package/dist/client/assets/{chunk-CSCIHK7Q-B355h6HA.js → chunk-CSCIHK7Q-k3Y3HBPl.js} +1 -1
  15. package/dist/client/assets/{chunk-KSCS5N6A-CLHGYOLQ.js → chunk-KSCS5N6A-D-fMWFHX.js} +1 -1
  16. package/dist/client/assets/{chunk-L5ZTLDWV-DFLYcMx8.js → chunk-L5ZTLDWV-Cp8034Xo.js} +1 -1
  17. package/dist/client/assets/{chunk-LZXEDZCA-DtljFujX.js → chunk-LZXEDZCA-DCmWwuZY.js} +2 -2
  18. package/dist/client/assets/{chunk-ND2GUHAM-CIuUQ86K.js → chunk-ND2GUHAM-CPxoYLrb.js} +1 -1
  19. package/dist/client/assets/{chunk-NZK2D7GU-CIwqlxek.js → chunk-NZK2D7GU-B1xsQ-Ge.js} +1 -1
  20. package/dist/client/assets/{chunk-O5CBEL6O-W42BEPu4.js → chunk-O5CBEL6O-CP_QLulv.js} +1 -1
  21. package/dist/client/assets/{chunk-WU5MYG2G-C_zuaNxV.js → chunk-WU5MYG2G-CqDmmASz.js} +1 -1
  22. package/dist/client/assets/classDiagram-4FO5ZUOK-GuChK9KD.js +1 -0
  23. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ezsQe4jc.js +1 -0
  24. package/dist/client/assets/{dagre-BM42HDAG-UKfIUJTP.js → dagre-BM42HDAG-I60HgRr5.js} +1 -1
  25. package/dist/client/assets/{diagram-2AECGRRQ-Xn6yojqq.js → diagram-2AECGRRQ-DH7489lu.js} +1 -1
  26. package/dist/client/assets/{diagram-5GNKFQAL-BzpY1ZEl.js → diagram-5GNKFQAL-UG5kJveS.js} +1 -1
  27. package/dist/client/assets/{diagram-KO2AKTUF-Dyrvzvsg.js → diagram-KO2AKTUF-DLPd9cl-.js} +1 -1
  28. package/dist/client/assets/{diagram-LMA3HP47-AqImKwAr.js → diagram-LMA3HP47-gfoMHIE-.js} +1 -1
  29. package/dist/client/assets/{diagram-OG6HWLK6-CaQQ46mi.js → diagram-OG6HWLK6-D7zejHzo.js} +1 -1
  30. package/dist/client/assets/{erDiagram-TEJ5UH35-Dkw4Aa5u.js → erDiagram-TEJ5UH35-DB1R9rUd.js} +1 -1
  31. package/dist/client/assets/{flowDiagram-I6XJVG4X-CJTuGX9h.js → flowDiagram-I6XJVG4X-rSasRun9.js} +1 -1
  32. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CidgOdS-.js → ganttDiagram-6RSMTGT7-ickt48MT.js} +1 -1
  33. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Bj02mIQj.js → gitGraphDiagram-PVQCEYII-B2MWuej-.js} +1 -1
  34. package/dist/client/assets/index-4RQBXPuO.css +2 -0
  35. package/dist/client/assets/index-BN2UMUYo.js +276 -0
  36. package/dist/client/assets/{infoDiagram-5YYISTIA-GwJWfo_r.js → infoDiagram-5YYISTIA-ClB9-yBC.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-rbrlWbUn.js → ishikawaDiagram-YF4QCWOH-CyUX0IyE.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-JHISSGLW-BLi_uzQM.js → journeyDiagram-JHISSGLW-CvZv1SZ7.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-UN3LZRKU-ynlywm-_.js → kanban-definition-UN3LZRKU-C3MgzQMY.js} +1 -1
  40. package/dist/client/assets/{line-BNyTZBB4.js → line-Dpj7mkeR.js} +1 -1
  41. package/dist/client/assets/{mermaid-parser.core-CzUOsZAD.js → mermaid-parser.core-Ezn4UHHp.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-K0QR6SzN.js → mermaid.core-GVI7XZqR.js} +3 -3
  43. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BKcgqXm0.js → mindmap-definition-RKZ34NQL-Bu2cgOo4.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-4H26LBE5-Bjx0lvDI.js → pieDiagram-4H26LBE5-_WS41t2_.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CmUlgyUv.js → quadrantDiagram-W4KKPZXB-Urvq-XqI.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-4Y6WPE33-Dj2mzg1y.js → requirementDiagram-4Y6WPE33-bkmWG0rM.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DC--H6qf.js → sankeyDiagram-5OEKKPKP-DI0SArVz.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BkNrt6Ra.js → sequenceDiagram-3UESZ5HK-n7PKrHd3.js} +1 -1
  49. package/dist/client/assets/{stateDiagram-AJRCARHV-BuxxRu1Y.js → stateDiagram-AJRCARHV-CxnEzrQA.js} +1 -1
  50. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DXTMu4iJ.js +1 -0
  51. package/dist/client/assets/{timeline-definition-PNZ67QCA-Db3pCVSp.js → timeline-definition-PNZ67QCA-BFr88_A9.js} +1 -1
  52. package/dist/client/assets/{vennDiagram-CIIHVFJN-BgBSGThQ.js → vennDiagram-CIIHVFJN-BNYs6tfr.js} +1 -1
  53. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BJhPdjYS.js → wardleyDiagram-YWT4CUSO-CaGZd3fe.js} +1 -1
  54. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BY4Ra0kX.js → xychartDiagram-2RQKCTM6-CMyDrx2a.js} +1 -1
  55. package/dist/client/index.html +2 -2
  56. package/dist/server/db.js +181 -28
  57. package/dist/server/db.js.map +1 -1
  58. package/dist/server/index.js +118 -40
  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 +82 -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 +22 -9
  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 +74 -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/projectTargets.js +10 -2
  153. package/dist/server/lib/projectTargets.js.map +1 -1
  154. package/dist/server/lib/promptSkills.js +1 -1
  155. package/dist/server/lib/promptSkills.js.map +1 -1
  156. package/dist/server/lib/run.js +32 -12
  157. package/dist/server/lib/run.js.map +1 -1
  158. package/dist/server/lib/runProcesses.js +13 -1
  159. package/dist/server/lib/runProcesses.js.map +1 -1
  160. package/dist/server/lib/runServices.js +5 -3
  161. package/dist/server/lib/runServices.js.map +1 -1
  162. package/dist/server/lib/runSessionLifecycle.js +10 -1
  163. package/dist/server/lib/runSessionLifecycle.js.map +1 -1
  164. package/dist/server/lib/runStatus.js +2 -1
  165. package/dist/server/lib/runStatus.js.map +1 -1
  166. package/dist/server/lib/serviceRunCleanup.js +70 -0
  167. package/dist/server/lib/serviceRunCleanup.js.map +1 -0
  168. package/dist/server/lib/serviceRuns.js +194 -12
  169. package/dist/server/lib/serviceRuns.js.map +1 -1
  170. package/dist/server/lib/skills.js +21 -4
  171. package/dist/server/lib/skills.js.map +1 -1
  172. package/dist/server/lib/specOperations.js +14 -10
  173. package/dist/server/lib/specOperations.js.map +1 -1
  174. package/dist/server/lib/specReviewOperations.js +20 -11
  175. package/dist/server/lib/specReviewOperations.js.map +1 -1
  176. package/dist/server/lib/specs.js +14 -4
  177. package/dist/server/lib/specs.js.map +1 -1
  178. package/dist/server/lib/ticketAutomationCommit.js +23 -7
  179. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  180. package/dist/server/lib/ticketFollowUpOperations.js +7 -0
  181. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  182. package/dist/server/lib/ticketRunOperations.js +11 -1
  183. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  184. package/dist/server/lib/ticketSettingsOperations.js +4 -1
  185. package/dist/server/lib/ticketSettingsOperations.js.map +1 -1
  186. package/dist/server/lib/toolUpgrade.js +1 -0
  187. package/dist/server/lib/toolUpgrade.js.map +1 -1
  188. package/dist/server/lib/transcriptRuns.js +4 -1
  189. package/dist/server/lib/transcriptRuns.js.map +1 -1
  190. package/dist/server/lib/types.js.map +1 -1
  191. package/dist/server/workers/build.js +9 -4
  192. package/dist/server/workers/build.js.map +1 -1
  193. package/dist/server/workers/chat.js +55 -11
  194. package/dist/server/workers/chat.js.map +1 -1
  195. package/dist/server/workers/followUp.js +6 -1
  196. package/dist/server/workers/followUp.js.map +1 -1
  197. package/dist/server/workers/plan.js +235 -68
  198. package/dist/server/workers/plan.js.map +1 -1
  199. package/dist/server/workers/planFollowUp.js +11 -4
  200. package/dist/server/workers/planFollowUp.js.map +1 -1
  201. package/dist/server/workers/review.js +15 -18
  202. package/dist/server/workers/review.js.map +1 -1
  203. package/dist/server/workers/specReview.js +7 -7
  204. package/dist/server/workers/specReview.js.map +1 -1
  205. package/dist/server/workers/specSession.js +8 -12
  206. package/dist/server/workers/specSession.js.map +1 -1
  207. package/package.json +1 -1
  208. package/dist/client/assets/GitHubActionsDialog-KFUiR00H.js +0 -1
  209. package/dist/client/assets/VerificationDialog-BIDttgiW.js +0 -4
  210. package/dist/client/assets/channel-Dhv_uSKx.js +0 -1
  211. package/dist/client/assets/classDiagram-4FO5ZUOK-9XKvSSLq.js +0 -1
  212. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Dbr5_49Y.js +0 -1
  213. package/dist/client/assets/index-ClRB9YWJ.css +0 -2
  214. package/dist/client/assets/index-DBMdsTvy.js +0 -276
  215. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cyo85GPN.js +0 -1
@@ -3,15 +3,17 @@ import { randomUUID } from 'crypto';
3
3
  import { isAbsolute, relative, resolve, sep } from 'path';
4
4
  import db from '../db.js';
5
5
  import { getChatSession, updateChatSessionUsageSnapshot } from './chats.js';
6
- import { normalizeClaudeModelId, normalizeModelId } from './models.js';
6
+ import { normalizeAgentModelId, requireExecutableAgent } from './agentCatalog.js';
7
7
  import { createOpencodeTaskSessionReader, parseOpencodeTaskEnvelope } from './opencodeTaskSessions.js';
8
8
  import { hostOpencodeSessionOperations } from './opencodeSessionOperations.js';
9
9
  import { hostExecutionEnvironment } from './hostExecutionEnvironment.js';
10
10
  import { markRetryableOpenCodeDatabaseLock, RetryableOpenCodeDatabaseLockError, retryOpenCodeDatabaseLocks } from './opencodeExecutionReliability.js';
11
11
  import { claudeAgentProvider } from './claudeAgentProvider.js';
12
+ import { codexAgentProvider } from './codexAgentProvider.js';
13
+ import { redactCodexDiagnostic } from './codexAppServer.js';
12
14
  import { buildOpencodeSpecReviewInvocation, createOpencodeExitError, createOpencodeRecoveryTracker, interpretOpencodeRecoveryEvent, isSuccessfulOpencodeExit, opencodeAgentProvider, } from './opencodeAgentProvider.js';
13
15
  import { appendClaudeHeadlessPlanPrompt, buildClaudeHeadlessPlanArgs } from './planModeQuestions.js';
14
- import { formatGenericToolCall } from './genericToolCall.js';
16
+ import { formatCodexFileChange, formatCodexWebSearch, formatGenericToolCall } from './genericToolCall.js';
15
17
  import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
16
18
  import { updateSpecReviewRunStatus, updateSpecReviewRunStreaming, updateSpecReviewRunUsage } from './specReviews.js';
17
19
  import { createAgentRun, emitAgentRunsUpdated, emitExecutionAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForExecutionRun, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
@@ -34,7 +36,7 @@ export class AgentContinuationRejectedError extends Error {
34
36
  }
35
37
  }
36
38
  export const isAgentContinuationRejectedError = (error) => (error instanceof AgentContinuationRejectedError);
37
- const isTypedPreExecutionContinuationRejection = (message) => (/(?:session|conversation).*(?:not found|does not exist|invalid|expired)|(?:invalid|unknown).*(?:session|conversation)/i.test(message));
39
+ export const isTypedPreExecutionContinuationRejection = (message) => (/(?:session|conversation|thread).*(?:not found|does not exist|invalid|expired)|(?:invalid|unknown).*(?:session|conversation|thread)|no rollout found for thread id/i.test(message));
38
40
  const attachTranscriptRunsToError = (error, transcriptRuns) => {
39
41
  const resolvedError = (error instanceof Error ? error : new Error(String(error)));
40
42
  if (transcriptRuns) {
@@ -53,10 +55,20 @@ const OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS = 1500;
53
55
  const AGENT_PROGRESS_UPDATE_INTERVAL_MS = 100;
54
56
  const OPENCODE_SUBAGENT_POLL_INTERVAL_MS = 750;
55
57
  const resolveAgentModel = (tool, model) => {
58
+ return normalizeAgentModelId(tool, model) || null;
59
+ };
60
+ const getStreamingAgentProvider = (tool) => {
56
61
  if (tool === 'claude') {
57
- return normalizeClaudeModelId(model) || null;
62
+ return claudeAgentProvider;
63
+ }
64
+ if (tool === 'codex') {
65
+ return codexAgentProvider;
58
66
  }
59
- return normalizeModelId(model) || null;
67
+ return opencodeAgentProvider;
68
+ };
69
+ const getAgentStderr = (tool, chunks) => {
70
+ const stderr = Buffer.concat(chunks).toString();
71
+ return tool === 'codex' ? redactCodexDiagnostic(stderr) : stderr;
60
72
  };
61
73
  const writeClaudeStdin = (child, message) => {
62
74
  const stdin = child.stdin;
@@ -358,7 +370,11 @@ const updateChatSessionStatus = (chatSessionId, status, log, io, streamingRespon
358
370
  }
359
371
  };
360
372
  const updateChatSessionId = (chatSessionId, tool, sessionId, io) => {
361
- const sessionField = tool === 'claude' ? 'claude_session_id' : 'opencode_session_id';
373
+ const sessionField = tool === 'claude'
374
+ ? 'claude_session_id'
375
+ : tool === 'codex'
376
+ ? 'codex_session_id'
377
+ : 'opencode_session_id';
362
378
  db.prepare(`UPDATE chat_sessions SET ${sessionField} = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(sessionId, chatSessionId);
363
379
  const chatSession = getChatSession(chatSessionId, { includeMessages: true });
364
380
  if (chatSession) {
@@ -787,6 +803,16 @@ const formatToolCall = (name, input, cwd) => {
787
803
  return formatApplyPatchToolCall(input, cwd);
788
804
  }
789
805
  const normalized = normalizeToolName(name);
806
+ if (normalized === 'filechange') {
807
+ const formatted = formatCodexFileChange(input, cwd);
808
+ if (formatted !== null) {
809
+ return formatted;
810
+ }
811
+ return formatGenericToolCall(name, input);
812
+ }
813
+ if (normalized === 'websearch') {
814
+ return formatCodexWebSearch(input);
815
+ }
790
816
  if (normalized === 'edit') {
791
817
  const formatted = formatEditToolCall(input, cwd);
792
818
  if (formatted !== null) {
@@ -836,7 +862,10 @@ const stringifyToolResult = (content) => {
836
862
  };
837
863
  const getSubagentCompletionStatus = (state, isError = false) => {
838
864
  const normalizedState = state?.toLowerCase();
839
- return isError || normalizedState === 'failed' || normalizedState === 'error' ? 'error' : 'done';
865
+ if (isError || normalizedState === 'failed' || normalizedState === 'error') {
866
+ return 'error';
867
+ }
868
+ return normalizedState === 'stopped' ? 'stopped' : 'done';
840
869
  };
841
870
  const emptyParsedStreamLine = () => ({
842
871
  logFragment: null,
@@ -961,8 +990,23 @@ const parseOpencodeJsonEvent = (event, cwd) => {
961
990
  || event.part?.sessionID?.trim()
962
991
  || event.part?.sessionId?.trim()
963
992
  || null;
993
+ if (event.type === 'plan' && event.part?.type === 'text' && event.part.text) {
994
+ return {
995
+ logFragment: event.part.text,
996
+ responseFragment: null,
997
+ subagentEvents: [],
998
+ providerSessionId,
999
+ modelResolutionError: false,
1000
+ concatenateFragments: event.part.metadata?.codex?.delta === true,
1001
+ replaceFragment: event.part.metadata?.codex?.snapshot === true,
1002
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
1003
+ ?? event.part.metadata?.codex?.itemId
1004
+ ?? null,
1005
+ };
1006
+ }
964
1007
  if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
965
- const text = event.part.text.trim();
1008
+ const concatenateFragments = event.part.metadata?.codex?.delta === true;
1009
+ const text = concatenateFragments ? event.part.text : event.part.text.trim();
966
1010
  const phase = event.part.metadata?.openai?.phase;
967
1011
  const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, text, phase);
968
1012
  return {
@@ -972,16 +1016,25 @@ const parseOpencodeJsonEvent = (event, cwd) => {
972
1016
  providerSessionId,
973
1017
  modelResolutionError: false,
974
1018
  recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
1019
+ concatenateFragments,
1020
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
1021
+ ?? event.part.metadata?.codex?.itemId
1022
+ ?? null,
975
1023
  };
976
1024
  }
977
1025
  if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
978
- const text = event.part.text?.trim() ?? '';
1026
+ const concatenateFragments = event.part.metadata?.codex?.delta === true;
1027
+ const text = concatenateFragments ? event.part.text ?? '' : event.part.text?.trim() ?? '';
979
1028
  return {
980
1029
  logFragment: text || null,
981
1030
  responseFragment: null,
982
1031
  subagentEvents: [],
983
1032
  providerSessionId,
984
1033
  modelResolutionError: false,
1034
+ concatenateFragments,
1035
+ fragmentGroup: event.part.metadata?.codex?.fragmentGroup
1036
+ ?? event.part.metadata?.codex?.itemId
1037
+ ?? null,
985
1038
  };
986
1039
  }
987
1040
  if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
@@ -993,8 +1046,9 @@ const parseOpencodeJsonEvent = (event, cwd) => {
993
1046
  const envelope = parseOpencodeTaskEnvelope(rawOutput);
994
1047
  const output = envelope ? envelope.result : rawOutput;
995
1048
  const taskStatus = event.part?.state?.status?.toLowerCase() ?? envelope?.state?.toLowerCase();
996
- const outputKind = taskStatus === 'running' || taskStatus === 'pending' ? 'fragment' : 'complete';
997
- const completionStatus = taskStatus === 'error' || taskStatus === 'failed' ? 'error' : 'done';
1049
+ const isTerminal = Boolean(taskStatus && taskStatus !== 'running' && taskStatus !== 'pending');
1050
+ const outputKind = isTerminal ? 'complete' : 'fragment';
1051
+ const completionStatus = getSubagentCompletionStatus(taskStatus);
998
1052
  const childSessionId = envelope?.childSessionId
999
1053
  ?? (typeof event.part?.state?.metadata?.sessionID === 'string' ? event.part.state.metadata.sessionID : null)
1000
1054
  ?? (typeof event.part?.state?.metadata?.sessionId === 'string' ? event.part.state.metadata.sessionId : null);
@@ -1010,11 +1064,11 @@ const parseOpencodeJsonEvent = (event, cwd) => {
1010
1064
  sessionId: childSessionId,
1011
1065
  }]
1012
1066
  : []),
1013
- ...(output
1067
+ ...(output || isTerminal
1014
1068
  ? [{
1015
1069
  kind: outputKind,
1016
1070
  callId: event.part?.callID ?? null,
1017
- text: output,
1071
+ ...(output ? { text: output } : {}),
1018
1072
  sessionId: childSessionId,
1019
1073
  status: completionStatus,
1020
1074
  }]
@@ -1099,6 +1153,9 @@ const parseOpencodeJsonLine = (line, cwd) => {
1099
1153
  const recoveryEvents = [];
1100
1154
  let providerSessionId = null;
1101
1155
  let modelResolutionError = false;
1156
+ let concatenateFragments;
1157
+ let replaceFragment;
1158
+ let fragmentGroup;
1102
1159
  for (const jsonObject of jsonObjects) {
1103
1160
  let event;
1104
1161
  try {
@@ -1113,6 +1170,11 @@ const parseOpencodeJsonLine = (line, cwd) => {
1113
1170
  recoveryEvents.push(...(parsedEvent.recoveryEvents ?? []));
1114
1171
  modelResolutionError ||= Boolean(parsedEvent.modelResolutionError);
1115
1172
  providerSessionId = providerSessionId ?? parsedEvent.providerSessionId;
1173
+ if (jsonObjects.length === 1) {
1174
+ concatenateFragments = parsedEvent.concatenateFragments;
1175
+ replaceFragment = parsedEvent.replaceFragment;
1176
+ fragmentGroup = parsedEvent.fragmentGroup;
1177
+ }
1116
1178
  if (logFragment !== null) {
1117
1179
  logFragments.push(logFragment);
1118
1180
  }
@@ -1144,6 +1206,9 @@ const parseOpencodeJsonLine = (line, cwd) => {
1144
1206
  providerSessionId,
1145
1207
  modelResolutionError,
1146
1208
  recoveryEvents,
1209
+ concatenateFragments,
1210
+ replaceFragment,
1211
+ fragmentGroup,
1147
1212
  };
1148
1213
  }
1149
1214
  return emptyParsedStreamLine();
@@ -1155,16 +1220,49 @@ const appendTranscriptText = (current, fragment) => {
1155
1220
  }
1156
1221
  return current ? `${current}\n\n${trimmed}` : trimmed;
1157
1222
  };
1223
+ const concatenatingLogLines = new WeakMap();
1224
+ const concatenatingResponseLines = new WeakMap();
1225
+ const groupedLogLineIndexes = new WeakMap();
1226
+ const groupedResponseLineIndexes = new WeakMap();
1227
+ const appendGroupedFragment = (lines, fragment, group, concatenate, replace, activeGroups, indexesByGroup) => {
1228
+ const groupKey = group ?? null;
1229
+ const indexes = indexesByGroup.get(lines) ?? new Map();
1230
+ if (replace && group) {
1231
+ const index = indexes.get(group);
1232
+ if (index !== undefined) {
1233
+ lines[index] = fragment;
1234
+ }
1235
+ else {
1236
+ indexes.set(group, lines.push(fragment) - 1);
1237
+ }
1238
+ }
1239
+ else if (concatenate && activeGroups.get(lines) === groupKey && lines.length > 0) {
1240
+ lines[lines.length - 1] += fragment;
1241
+ }
1242
+ else {
1243
+ const index = lines.push(fragment) - 1;
1244
+ if (group) {
1245
+ indexes.set(group, index);
1246
+ }
1247
+ }
1248
+ indexesByGroup.set(lines, indexes);
1249
+ if (concatenate) {
1250
+ activeGroups.set(lines, groupKey);
1251
+ }
1252
+ else {
1253
+ activeGroups.delete(lines);
1254
+ }
1255
+ };
1158
1256
  const appendOpencodeTranscriptLine = ({ parsed, logLines, responseLines, recoveryTracker, }) => {
1159
1257
  const shouldResetResponse = recoveryTracker.consume(parsed.recoveryEvents);
1160
1258
  if (shouldResetResponse) {
1161
1259
  responseLines.length = 0;
1162
1260
  }
1163
1261
  if (parsed.logFragment !== null) {
1164
- logLines.push(parsed.logFragment);
1262
+ appendGroupedFragment(logLines, parsed.logFragment, parsed.fragmentGroup, parsed.concatenateFragments, parsed.replaceFragment, concatenatingLogLines, groupedLogLineIndexes);
1165
1263
  }
1166
1264
  if (parsed.responseFragment !== null) {
1167
- responseLines.push(parsed.responseFragment);
1265
+ appendGroupedFragment(responseLines, parsed.responseFragment, parsed.fragmentGroup, parsed.concatenateFragments, parsed.replaceFragment, concatenatingResponseLines, groupedResponseLineIndexes);
1168
1266
  }
1169
1267
  return shouldResetResponse;
1170
1268
  };
@@ -1360,6 +1458,9 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, ex
1360
1458
  run_index: run.runIndex,
1361
1459
  label: run.label,
1362
1460
  role: 'subagent',
1461
+ tool: persistedRun?.tool,
1462
+ model: persistedRun?.model,
1463
+ variant: persistedRun?.variant,
1363
1464
  agent_status: run.status,
1364
1465
  agent_log: run.log || null,
1365
1466
  streaming_response: null,
@@ -1388,6 +1489,9 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, ex
1388
1489
  run_index: mainRun.runIndex,
1389
1490
  label: mainRun.label,
1390
1491
  role: mainRun.role,
1492
+ tool: persistedMainRun?.tool,
1493
+ model: persistedMainRun?.model,
1494
+ variant: persistedMainRun?.variant,
1391
1495
  agent_status: status,
1392
1496
  agent_log: parentLog,
1393
1497
  streaming_response: null,
@@ -1935,7 +2039,7 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
1935
2039
  }
1936
2040
  return true;
1937
2041
  };
1938
- const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, reviewProcessState, emitSuccessUpdate = true, opencodeAgentMode, runStream, executionContext, persistSessionIdToTicket = false, spawnPermitId, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
2042
+ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null, skills = [], resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, reviewProcessState, emitSuccessUpdate = true, opencodeAgentMode, runStream, executionContext, persistSessionIdToTicket = false, spawnPermitId, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
1939
2043
  const resolvedModel = resolveAgentModel(tool, model);
1940
2044
  const opencodeSessionOperations = executionProvider.sessionOperations?.opencode ?? null;
1941
2045
  const subagentTracker = createSubagentRunTracker({
@@ -2067,18 +2171,18 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2067
2171
  }
2068
2172
  return hasResolvedOpencodeSessionId;
2069
2173
  };
2070
- const agentProvider = tool === 'claude'
2071
- ? claudeAgentProvider
2072
- : opencodeAgentProvider;
2073
- const invocation = agentProvider.buildStreamingInvocation({
2174
+ const agentProvider = getStreamingAgentProvider(tool);
2175
+ const streamingRequest = {
2074
2176
  cwd,
2177
+ skills,
2075
2178
  prompt,
2076
2179
  model: resolvedModel,
2077
2180
  variant,
2078
2181
  mode: opencodeAgentMode,
2079
2182
  resumeSessionId,
2080
2183
  sessionId: randomUUID(),
2081
- });
2184
+ };
2185
+ const invocation = agentProvider.buildStreamingInvocation(streamingRequest);
2082
2186
  sessionId = invocation.sessionId;
2083
2187
  writeRunStatus('running', null);
2084
2188
  if (tool === 'claude' && sessionId) {
@@ -2088,6 +2192,7 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2088
2192
  spawnTimestamp = Date.now();
2089
2193
  }
2090
2194
  let child;
2195
+ let providerStdout = null;
2091
2196
  let interrupt;
2092
2197
  let placementFailure;
2093
2198
  let placementCompletion = Promise.resolve(null);
@@ -2115,6 +2220,31 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2115
2220
  if (handle.placementIdentity) {
2116
2221
  placementCompletion = placementFailure;
2117
2222
  }
2223
+ const connection = agentProvider.connectStreamingInvocation?.(child, streamingRequest, {
2224
+ runtimeEnvironmentKind: executionRuntimeEnvironment.kind,
2225
+ });
2226
+ if (connection) {
2227
+ providerStdout = connection.stdout;
2228
+ const placementInterrupt = interrupt;
2229
+ interrupt = async (signal) => {
2230
+ if (signal === 'SIGKILL') {
2231
+ await placementInterrupt(signal);
2232
+ return;
2233
+ }
2234
+ try {
2235
+ await connection.interrupt();
2236
+ }
2237
+ catch {
2238
+ await placementInterrupt(signal);
2239
+ }
2240
+ };
2241
+ void connection.sessionReady.then(providerThreadId => {
2242
+ if (!settled && providerThreadId) {
2243
+ sessionId = providerThreadId;
2244
+ writeRunSessionId(providerThreadId);
2245
+ }
2246
+ }).catch(() => { });
2247
+ }
2118
2248
  }
2119
2249
  catch (error) {
2120
2250
  const resolvedError = agentProvider.classifyExecutionFailure(error);
@@ -2219,7 +2349,7 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2219
2349
  responseLines.push(responseFragment);
2220
2350
  }
2221
2351
  }
2222
- const errOutput = Buffer.concat(stderrChunks).toString();
2352
+ const errOutput = getAgentStderr(tool, stderrChunks);
2223
2353
  const finalLog = logLines.join('\n\n') || errOutput;
2224
2354
  const finalResponse = responseLines.join('\n\n') || errOutput;
2225
2355
  const modelError = isLegacyModelResolutionError(errOutput);
@@ -2285,7 +2415,8 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2285
2415
  },
2286
2416
  });
2287
2417
  }
2288
- child.stdout?.on('data', (chunk) => {
2418
+ const output = providerStdout ?? child.stdout;
2419
+ output?.on('data', (chunk) => {
2289
2420
  lastOutputAt = Date.now();
2290
2421
  const text = remainder + chunk.toString();
2291
2422
  const lines = text.split('\n');
@@ -2395,7 +2526,7 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2395
2526
  clearTimeout(sessionLookupTimer);
2396
2527
  sessionLookupTimer = null;
2397
2528
  }
2398
- const stderrOutput = Buffer.concat(stderrChunks).toString();
2529
+ const stderrOutput = getAgentStderr(tool, stderrChunks);
2399
2530
  const log = logLines.join('\n\n') || persistedTranscript?.log || stderrOutput;
2400
2531
  const recoveredResponse = recoveryTracker.getRecoveredResponse();
2401
2532
  const streamedResponse = recoveryTracker.hasRecoveredFinalAnswer()
@@ -2463,7 +2594,7 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2463
2594
  subagentTracker.handle(parsed.subagentEvents);
2464
2595
  appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker });
2465
2596
  }
2466
- const stderrOutput = Buffer.concat(stderrChunks).toString();
2597
+ const stderrOutput = getAgentStderr(tool, stderrChunks);
2467
2598
  const log = logLines.join('\n\n') || stderrOutput;
2468
2599
  const response = recoveryTracker.hasRecoveredFinalAnswer()
2469
2600
  ? recoveryTracker.getRecoveredResponse()
@@ -2490,10 +2621,11 @@ const runAgentOnce = ({ ticketId, tool, prompt, cwd, io, model, variant = null,
2490
2621
  });
2491
2622
  };
2492
2623
  export const runAgent = (options) => {
2624
+ requireExecutableAgent(options.tool);
2493
2625
  const run = () => runAgentOnce(options);
2494
2626
  return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
2495
2627
  };
2496
- const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, opencodeAgentMode = 'build', executionContext, mainRun, spawnPermitId, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
2628
+ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, skills = [], resumeSessionId = null, opencodeAgentMode = 'build', executionContext, mainRun, spawnPermitId, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
2497
2629
  const resolvedModel = resolveAgentModel(tool, model);
2498
2630
  const opencodeSessionOperations = executionProvider.sessionOperations?.opencode ?? null;
2499
2631
  const subagentTracker = createSubagentRunTracker({
@@ -2537,19 +2669,19 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2537
2669
  }, transcriptRuns));
2538
2670
  return;
2539
2671
  }
2540
- const agentProvider = tool === 'claude'
2541
- ? claudeAgentProvider
2542
- : opencodeAgentProvider;
2543
- const invocation = agentProvider.buildStreamingInvocation({
2672
+ const agentProvider = getStreamingAgentProvider(tool);
2673
+ const streamingRequest = {
2544
2674
  cwd,
2545
2675
  files,
2676
+ skills,
2546
2677
  prompt,
2547
2678
  model: resolvedModel,
2548
2679
  variant,
2549
2680
  mode: opencodeAgentMode,
2550
2681
  resumeSessionId,
2551
2682
  sessionId: randomUUID(),
2552
- });
2683
+ };
2684
+ const invocation = agentProvider.buildStreamingInvocation(streamingRequest);
2553
2685
  sessionId = invocation.sessionId;
2554
2686
  updateChatSessionStatus(chatSessionId, 'running', null, io);
2555
2687
  subagentTracker.updateMain(null, null);
@@ -2580,6 +2712,7 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2580
2712
  }
2581
2713
  }
2582
2714
  let child;
2715
+ let providerStdout = null;
2583
2716
  let placementCompletion;
2584
2717
  try {
2585
2718
  if (chatState.stopRequested) {
@@ -2602,6 +2735,32 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2602
2735
  child = handle.child;
2603
2736
  chatState.interrupt = handle.interrupt;
2604
2737
  placementCompletion = handle.placementFailure;
2738
+ const connection = agentProvider.connectStreamingInvocation?.(child, streamingRequest, {
2739
+ runtimeEnvironmentKind: executionRuntimeEnvironment.kind,
2740
+ });
2741
+ if (connection) {
2742
+ providerStdout = connection.stdout;
2743
+ const placementInterrupt = handle.interrupt;
2744
+ chatState.interrupt = async (signal) => {
2745
+ if (signal === 'SIGKILL') {
2746
+ await placementInterrupt(signal);
2747
+ return;
2748
+ }
2749
+ try {
2750
+ await connection.interrupt();
2751
+ }
2752
+ catch {
2753
+ await placementInterrupt(signal);
2754
+ }
2755
+ };
2756
+ void connection.sessionReady.then(providerThreadId => {
2757
+ if (!settled && providerThreadId) {
2758
+ sessionId = providerThreadId;
2759
+ updateChatSessionId(chatSessionId, 'codex', providerThreadId, io);
2760
+ subagentTracker.setMainSessionId(providerThreadId);
2761
+ }
2762
+ }).catch(() => { });
2763
+ }
2605
2764
  }
2606
2765
  catch (error) {
2607
2766
  const resolvedError = agentProvider.classifyExecutionFailure(error);
@@ -2696,7 +2855,7 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2696
2855
  responseLines.push(responseFragment);
2697
2856
  }
2698
2857
  }
2699
- const errOutput = Buffer.concat(stderrChunks).toString();
2858
+ const errOutput = getAgentStderr(tool, stderrChunks);
2700
2859
  const finalLog = logLines.join('\n\n') || errOutput;
2701
2860
  const finalResponse = responseLines.join('\n\n') || errOutput;
2702
2861
  if (chatState.stopRequested) {
@@ -2777,7 +2936,8 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2777
2936
  updateChatSessionStatus(chatSessionId, 'running', log, io, response);
2778
2937
  subagentTracker.updateMain(log, response);
2779
2938
  });
2780
- child.stdout?.on('data', (chunk) => {
2939
+ const output = providerStdout ?? child.stdout;
2940
+ output?.on('data', (chunk) => {
2781
2941
  const text = remainder + chunk.toString();
2782
2942
  const lines = text.split('\n');
2783
2943
  remainder = lines.pop() ?? '';
@@ -2836,7 +2996,7 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2836
2996
  firstFinalAnswerTime = null;
2837
2997
  }
2838
2998
  }
2839
- const stderrOutput = Buffer.concat(stderrChunks).toString();
2999
+ const stderrOutput = getAgentStderr(tool, stderrChunks);
2840
3000
  const log = logLines.join('\n\n') || stderrOutput;
2841
3001
  const recoveredResponse = recoveryTracker.getRecoveredResponse();
2842
3002
  const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
@@ -2941,10 +3101,11 @@ const runChatAgentOnce = ({ chatSessionId, tool, prompt, files = [], cwd, io, mo
2941
3101
  });
2942
3102
  };
2943
3103
  export const runChatAgent = (options) => {
3104
+ requireExecutableAgent(options.tool);
2944
3105
  const run = () => runChatAgentOnce(options);
2945
3106
  return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
2946
3107
  };
2947
- const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, executionContext, mainRun, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
3108
+ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, skills = [], resumeSessionId = null, executionContext, mainRun, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
2948
3109
  const resolvedModel = resolveAgentModel(tool, model);
2949
3110
  const subagentTracker = createSubagentRunTracker({
2950
3111
  parentType: 'spec',
@@ -2987,15 +3148,20 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
2987
3148
  }, transcriptRuns));
2988
3149
  return;
2989
3150
  }
2990
- const [cmd, args] = tool === 'claude'
2991
- ? (() => {
2992
- sessionId = resumeSessionId || randomUUID();
2993
- return [
2994
- 'claude',
2995
- buildClaudeArgs('plan', resolvedModel, resumeSessionId, sessionId),
2996
- ];
2997
- })()
2998
- : ['opencode', ['run', '--agent', 'plan', '--format', 'json', '--thinking', ...(resumeSessionId ? ['--session', resumeSessionId] : []), ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
3151
+ const agentProvider = getStreamingAgentProvider(tool);
3152
+ const streamingRequest = {
3153
+ cwd,
3154
+ files,
3155
+ skills,
3156
+ prompt,
3157
+ model: resolvedModel,
3158
+ variant,
3159
+ mode: 'plan',
3160
+ resumeSessionId,
3161
+ sessionId: randomUUID(),
3162
+ };
3163
+ const invocation = agentProvider.buildStreamingInvocation(streamingRequest);
3164
+ sessionId = invocation.sessionId;
2999
3165
  updateSpecSessionStatusLocal(specSessionId, 'running', null, io);
3000
3166
  subagentTracker.updateMain(null, null);
3001
3167
  if (tool === 'claude' && sessionId) {
@@ -3024,15 +3190,16 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3024
3190
  }
3025
3191
  }
3026
3192
  let child;
3193
+ let providerStdout = null;
3027
3194
  let placementFailure = Promise.resolve(null);
3028
3195
  try {
3029
3196
  const handle = executionProvider.execute({
3030
3197
  phase: 'agent',
3031
3198
  target: executionTarget ?? null,
3032
- command: cmd,
3033
- args,
3199
+ command: invocation.command,
3200
+ args: invocation.args,
3034
3201
  cwd,
3035
- stdio: tool === 'claude' ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
3202
+ stdio: invocation.stdio,
3036
3203
  launcherEnvironment: executionLauncherEnvironment,
3037
3204
  runtimeEnvironment: executionRuntimeEnvironment,
3038
3205
  detached: process.platform !== 'win32',
@@ -3040,6 +3207,32 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3040
3207
  child = handle.child;
3041
3208
  specState.interrupt = handle.interrupt;
3042
3209
  placementFailure = handle.placementFailure;
3210
+ const connection = agentProvider.connectStreamingInvocation?.(child, streamingRequest, {
3211
+ runtimeEnvironmentKind: executionRuntimeEnvironment.kind,
3212
+ });
3213
+ if (connection) {
3214
+ providerStdout = connection.stdout;
3215
+ const placementInterrupt = handle.interrupt;
3216
+ specState.interrupt = async (signal) => {
3217
+ if (signal === 'SIGKILL') {
3218
+ await placementInterrupt(signal);
3219
+ return;
3220
+ }
3221
+ try {
3222
+ await connection.interrupt();
3223
+ }
3224
+ catch {
3225
+ await placementInterrupt(signal);
3226
+ }
3227
+ };
3228
+ void connection.sessionReady.then(providerThreadId => {
3229
+ if (!settled && providerThreadId) {
3230
+ sessionId = providerThreadId;
3231
+ updateSpecSessionIdLocal(specSessionId, 'codex', providerThreadId, io);
3232
+ subagentTracker.setMainSessionId(providerThreadId);
3233
+ }
3234
+ }).catch(() => { });
3235
+ }
3043
3236
  void placementFailure.then(failure => {
3044
3237
  if (failure) {
3045
3238
  rejectOnce(failure);
@@ -3129,7 +3322,7 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3129
3322
  responseLines.push(responseFragment);
3130
3323
  }
3131
3324
  }
3132
- const errOutput = Buffer.concat(stderrChunks).toString();
3325
+ const errOutput = getAgentStderr(tool, stderrChunks);
3133
3326
  const finalLog = logLines.join('\n\n') || errOutput;
3134
3327
  const finalResponse = responseLines.join('\n\n') || errOutput;
3135
3328
  if (specState.stopRequested) {
@@ -3210,7 +3403,8 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3210
3403
  updateSpecSessionStatusLocal(specSessionId, 'running', log, io, response);
3211
3404
  subagentTracker.updateMain(log, response);
3212
3405
  });
3213
- child.stdout?.on('data', (chunk) => {
3406
+ const output = providerStdout ?? child.stdout;
3407
+ output?.on('data', (chunk) => {
3214
3408
  const text = remainder + chunk.toString();
3215
3409
  const lines = text.split('\n');
3216
3410
  remainder = lines.pop() ?? '';
@@ -3271,7 +3465,7 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3271
3465
  firstFinalAnswerTime = null;
3272
3466
  }
3273
3467
  }
3274
- const stderrOutput = Buffer.concat(stderrChunks).toString();
3468
+ const stderrOutput = getAgentStderr(tool, stderrChunks);
3275
3469
  const log = logLines.join('\n\n') || stderrOutput;
3276
3470
  const recoveredResponse = recoveryTracker.getRecoveredResponse();
3277
3471
  const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
@@ -3374,6 +3568,7 @@ const runSpecAgentOnce = ({ specSessionId, tool, prompt, files = [], cwd, io, mo
3374
3568
  });
3375
3569
  };
3376
3570
  export const runSpecAgent = (options) => {
3571
+ requireExecutableAgent(options.tool);
3377
3572
  const run = () => runSpecAgentOnce(options);
3378
3573
  return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
3379
3574
  };
@@ -3387,7 +3582,7 @@ export const runSpecAgent = (options) => {
3387
3582
  * updateSpecReviewRunStatus / updateSpecReviewRunStreaming / updateSpecReviewRunUsage —
3388
3583
  * the spec_sessions row is never touched by this path.
3389
3584
  */
3390
- const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, executionContext, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
3585
+ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, skills = [], executionContext, executionProvider = hostExecutionEnvironment, executionTarget, executionLauncherEnvironment = createHostLauncherEnvironment(), executionRuntimeEnvironment = createLegacyHostRuntimeEnvironment(cwd, { PWD: cwd }), }) => {
3391
3586
  const registeredState = activeSpecReviewProcesses.get(specReviewRunId);
3392
3587
  if (registeredState?.stopRequested) {
3393
3588
  return Promise.resolve({
@@ -3431,20 +3626,25 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3431
3626
  };
3432
3627
  const resolveOnce = settle(resolve);
3433
3628
  const rejectOnce = settle(reject);
3434
- const [cmd, args] = [
3435
- 'claude',
3436
- buildClaudeArgs('plan', resolvedModel, null, randomUUID()),
3437
- ];
3438
3629
  updateSpecReviewRunStreaming(specReviewRunId, null);
3439
3630
  let child;
3631
+ let providerStdout = null;
3440
3632
  let placementFailure = Promise.resolve(null);
3633
+ const agentProvider = getStreamingAgentProvider(tool);
3634
+ const streamingRequest = {
3635
+ cwd,
3636
+ files,
3637
+ skills,
3638
+ prompt,
3639
+ model: resolvedModel,
3640
+ variant,
3641
+ mode: 'plan',
3642
+ resumeSessionId: null,
3643
+ sessionId: randomUUID(),
3644
+ };
3441
3645
  const invocation = tool === 'opencode'
3442
3646
  ? buildOpencodeSpecReviewInvocation({ prompt, files, model: resolvedModel, variant })
3443
- : {
3444
- command: cmd,
3445
- args,
3446
- stdio: ['pipe', 'pipe', 'pipe'],
3447
- };
3647
+ : agentProvider.buildStreamingInvocation(streamingRequest);
3448
3648
  try {
3449
3649
  const handle = executionProvider.execute({
3450
3650
  phase: 'agent',
@@ -3460,14 +3660,36 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3460
3660
  child = handle.child;
3461
3661
  reviewState.interrupt = handle.interrupt;
3462
3662
  placementFailure = handle.placementFailure;
3663
+ const connection = agentProvider.connectStreamingInvocation?.(child, streamingRequest, {
3664
+ runtimeEnvironmentKind: executionRuntimeEnvironment.kind,
3665
+ });
3666
+ if (connection) {
3667
+ providerStdout = connection.stdout;
3668
+ const placementInterrupt = handle.interrupt;
3669
+ reviewState.interrupt = async (signal) => {
3670
+ if (signal === 'SIGKILL') {
3671
+ await placementInterrupt(signal);
3672
+ return;
3673
+ }
3674
+ try {
3675
+ await connection.interrupt();
3676
+ }
3677
+ catch {
3678
+ await placementInterrupt(signal);
3679
+ }
3680
+ };
3681
+ void connection.ready.catch(error => {
3682
+ rejectOnce(agentProvider.classifyExecutionFailure(error));
3683
+ });
3684
+ }
3463
3685
  void placementFailure.then(failure => {
3464
3686
  if (failure) {
3465
- rejectOnce(tool === 'opencode' ? opencodeAgentProvider.classifyExecutionFailure(failure) : failure);
3687
+ rejectOnce(agentProvider.classifyExecutionFailure(failure));
3466
3688
  }
3467
3689
  });
3468
3690
  }
3469
3691
  catch (error) {
3470
- rejectOnce(tool === 'opencode' ? opencodeAgentProvider.classifyExecutionFailure(error) : error);
3692
+ rejectOnce(agentProvider.classifyExecutionFailure(error));
3471
3693
  return;
3472
3694
  }
3473
3695
  reviewState.child = child;
@@ -3536,7 +3758,7 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3536
3758
  responseLines.push(responseFragment);
3537
3759
  }
3538
3760
  }
3539
- const errOutput = Buffer.concat(stderrChunks).toString();
3761
+ const errOutput = getAgentStderr(tool, stderrChunks);
3540
3762
  const finalLog = logLines.join('\n\n') || errOutput;
3541
3763
  const finalResponse = responseLines.join('\n\n') || errOutput;
3542
3764
  if (reviewState.stopRequested) {
@@ -3580,7 +3802,8 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3580
3802
  const progressUpdater = createThrottledProgressUpdater(() => {
3581
3803
  updateSpecReviewRunStreaming(specReviewRunId, responseLines.join('\n\n') || null);
3582
3804
  });
3583
- child.stdout?.on('data', (chunk) => {
3805
+ const output = providerStdout ?? child.stdout;
3806
+ output?.on('data', (chunk) => {
3584
3807
  const text = remainder + chunk.toString();
3585
3808
  const lines = text.split('\n');
3586
3809
  remainder = lines.pop() ?? '';
@@ -3619,7 +3842,7 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3619
3842
  progressUpdater.cancel();
3620
3843
  const placementError = await placementFailure;
3621
3844
  if (placementError) {
3622
- rejectOnce(tool === 'opencode' ? opencodeAgentProvider.classifyExecutionFailure(placementError) : placementError);
3845
+ rejectOnce(agentProvider.classifyExecutionFailure(placementError));
3623
3846
  return;
3624
3847
  }
3625
3848
  await opencodeSubagentMonitor?.refresh();
@@ -3633,7 +3856,7 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3633
3856
  firstFinalAnswerTime = null;
3634
3857
  }
3635
3858
  }
3636
- const stderrOutput = Buffer.concat(stderrChunks).toString();
3859
+ const stderrOutput = getAgentStderr(tool, stderrChunks);
3637
3860
  const log = logLines.join('\n\n') || stderrOutput;
3638
3861
  const recoveredResponse = recoveryTracker.getRecoveredResponse();
3639
3862
  const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
@@ -3699,13 +3922,14 @@ const runSpecReviewAgentOnce = ({ specReviewRunId, specSessionId, tool, prompt,
3699
3922
  });
3700
3923
  return;
3701
3924
  }
3702
- const failure = opencodeAgentProvider.classifyExecutionFailure(err);
3925
+ const failure = agentProvider.classifyExecutionFailure(err);
3703
3926
  subagentTracker.finalize('error', failure.message, null);
3704
3927
  rejectOnce(failure);
3705
3928
  });
3706
3929
  });
3707
3930
  };
3708
3931
  export const runSpecReviewAgent = (options) => {
3932
+ requireExecutableAgent(options.tool);
3709
3933
  const run = () => runSpecReviewAgentOnce(options);
3710
3934
  return options.tool === 'opencode' ? retryOpenCodeDatabaseLocks(run) : run();
3711
3935
  };