@straiffi/archon 1.2.34 → 1.3.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 (158) hide show
  1. package/dist/client/assets/TestsDialog-DelI4dOJ.js +5 -0
  2. package/dist/client/assets/arc-4YfDy8uK.js +1 -0
  3. package/dist/client/assets/architecture-7EHR7CIX-B0ldQ5d_.js +1 -0
  4. package/dist/client/assets/architectureDiagram-3BPJPVTR-Cn7GlwQg.js +36 -0
  5. package/dist/client/assets/array-DOVTz2Mq.js +1 -0
  6. package/dist/client/assets/blockDiagram-GPEHLZMM-Dbt3i35a.js +132 -0
  7. package/dist/client/assets/c4Diagram-AAUBKEIU-BCNsqu0u.js +10 -0
  8. package/dist/client/assets/channel-BmkYY3c1.js +1 -0
  9. package/dist/client/assets/chunk-2J33WTMH-B9c8AzpT.js +1 -0
  10. package/dist/client/assets/chunk-3OPIFGDE-D-qpTyaE.js +62 -0
  11. package/dist/client/assets/chunk-4BX2VUAB-B3mVWXS4.js +1 -0
  12. package/dist/client/assets/chunk-4EGX6M5U-DTB6Um9X.js +1 -0
  13. package/dist/client/assets/chunk-55IACEB6-BB-0ajVZ.js +1 -0
  14. package/dist/client/assets/chunk-5DO6E6H7-DtX6GYFY.js +1 -0
  15. package/dist/client/assets/chunk-5ZQYHXKU-bBV3HPCk.js +2 -0
  16. package/dist/client/assets/chunk-727SXJPM-InkxsEhY.js +206 -0
  17. package/dist/client/assets/chunk-AQP2D5EJ-DZZL694W.js +231 -0
  18. package/dist/client/assets/chunk-BR22UD5L-CkJbcXZG.js +1 -0
  19. package/dist/client/assets/chunk-BSJP7CBP-X4eyoSqE.js +1 -0
  20. package/dist/client/assets/chunk-CSCIHK7Q-BuAgybUd.js +124 -0
  21. package/dist/client/assets/chunk-FHYWG6QK-Cb5GzaCb.js +1 -0
  22. package/dist/client/assets/chunk-FMBD7UC4-mGY2D2YB.js +15 -0
  23. package/dist/client/assets/chunk-KSCS5N6A-B9fgatSq.js +10 -0
  24. package/dist/client/assets/chunk-L5ZTLDWV-L-7z2xOe.js +1 -0
  25. package/dist/client/assets/chunk-LZXEDZCA-hrScZnrx.js +2 -0
  26. package/dist/client/assets/chunk-MPE355IW-WT7jTCZh.js +1 -0
  27. package/dist/client/assets/chunk-MZUSXYTE-CClXfOSj.js +1 -0
  28. package/dist/client/assets/chunk-N66VUXT2-Bpe4yprw.js +1 -0
  29. package/dist/client/assets/chunk-ND2GUHAM-d2HjiRzD.js +1 -0
  30. package/dist/client/assets/chunk-NNHCCRGN-C4ci-Gd6.js +159 -0
  31. package/dist/client/assets/chunk-NZK2D7GU-C6cn05ZM.js +1 -0
  32. package/dist/client/assets/chunk-O5CBEL6O-BAjbtmB_.js +70 -0
  33. package/dist/client/assets/chunk-PUPMXCY4-Dh45WL5s.js +1 -0
  34. package/dist/client/assets/chunk-QZHKN3VN-U_l6-b5-.js +1 -0
  35. package/dist/client/assets/chunk-UIBZB4QT-Cp2xSKlH.js +1 -0
  36. package/dist/client/assets/chunk-WCWK7LTN-BOt5FFIu.js +1 -0
  37. package/dist/client/assets/chunk-WU5MYG2G-By4sIjXN.js +1 -0
  38. package/dist/client/assets/chunk-XPW4576I-YiT9fJFY.js +32 -0
  39. package/dist/client/assets/classDiagram-4FO5ZUOK-B9B9lIAo.js +1 -0
  40. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DXR72HYe.js +1 -0
  41. package/dist/client/assets/cose-bilkent-S5V4N54A-CtC2zeMr.js +1 -0
  42. package/dist/client/assets/cytoscape.esm-DnmRtSj9.js +321 -0
  43. package/dist/client/assets/dagre-5AiD43Uu.js +1 -0
  44. package/dist/client/assets/dagre-BM42HDAG-_iu0KXos.js +4 -0
  45. package/dist/client/assets/defaultLocale-Ck2Xxk-C.js +1 -0
  46. package/dist/client/assets/diagram-2AECGRRQ-DyBAakP6.js +43 -0
  47. package/dist/client/assets/diagram-5GNKFQAL-xD8YOWp-.js +10 -0
  48. package/dist/client/assets/diagram-KO2AKTUF-BQDiusQc.js +3 -0
  49. package/dist/client/assets/diagram-LMA3HP47-BRu1DXcK.js +24 -0
  50. package/dist/client/assets/diagram-OG6HWLK6-C3A1wt8S.js +24 -0
  51. package/dist/client/assets/dist-BQjeO--f.js +1 -0
  52. package/dist/client/assets/erDiagram-TEJ5UH35-C_fWil4q.js +85 -0
  53. package/dist/client/assets/eventmodeling-FCH6USID-BczyucFk.js +1 -0
  54. package/dist/client/assets/flowDiagram-I6XJVG4X-CKsgXWad.js +162 -0
  55. package/dist/client/assets/ganttDiagram-6RSMTGT7-DRhmLmdi.js +292 -0
  56. package/dist/client/assets/gitGraph-WXDBUCRP-x5mlGyzr.js +1 -0
  57. package/dist/client/assets/gitGraphDiagram-PVQCEYII-C34Vr5C1.js +106 -0
  58. package/dist/client/assets/graphlib-CMVU3iJd.js +1 -0
  59. package/dist/client/assets/index-DENrWubs.js +260 -0
  60. package/dist/client/assets/index-iElL-crz.css +2 -0
  61. package/dist/client/assets/info-J43DQDTF-BsM_Qb70.js +1 -0
  62. package/dist/client/assets/infoDiagram-5YYISTIA-Bkbr_hQH.js +2 -0
  63. package/dist/client/assets/init-Bft5Ffpj.js +1 -0
  64. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-CEXsy2hU.js +70 -0
  65. package/dist/client/assets/journeyDiagram-JHISSGLW-RuKnL83f.js +139 -0
  66. package/dist/client/assets/kanban-definition-UN3LZRKU-DrlhCmkA.js +89 -0
  67. package/dist/client/assets/katex-Bpz5kWH9.js +257 -0
  68. package/dist/client/assets/line-CGdxyPh9.js +1 -0
  69. package/dist/client/assets/linear-BYVhmsFj.js +1 -0
  70. package/dist/client/assets/mermaid-parser.core-CbzywbCW.js +4 -0
  71. package/dist/client/assets/mermaid.core-09FBpi2R.js +9 -0
  72. package/dist/client/assets/mindmap-definition-RKZ34NQL-CXrsI5b1.js +96 -0
  73. package/dist/client/assets/ordinal-DIg8h6NI.js +1 -0
  74. package/dist/client/assets/packet-YPE3B663-DeVIMHV5.js +1 -0
  75. package/dist/client/assets/path-DfRbCp9y.js +1 -0
  76. package/dist/client/assets/pie-LRSECV5Y-ByRF4_cj.js +1 -0
  77. package/dist/client/assets/pieDiagram-4H26LBE5-HbOpep9N.js +30 -0
  78. package/dist/client/assets/quadrantDiagram-W4KKPZXB-BWw-ZWMn.js +7 -0
  79. package/dist/client/assets/radar-GUYGQ44K-XEreg8aR.js +1 -0
  80. package/dist/client/assets/requirementDiagram-4Y6WPE33-CpscsQdI.js +84 -0
  81. package/dist/client/assets/rough.esm-DCvqWubr.js +1 -0
  82. package/dist/client/assets/sankeyDiagram-5OEKKPKP-BoV5tyAf.js +40 -0
  83. package/dist/client/assets/sequenceDiagram-3UESZ5HK-CBcDRtjR.js +162 -0
  84. package/dist/client/assets/src-BcaL9nCM.js +1 -0
  85. package/dist/client/assets/stateDiagram-AJRCARHV-Df9lcs70.js +1 -0
  86. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-FIWIctC4.js +1 -0
  87. package/dist/client/assets/timeline-definition-PNZ67QCA-BFv1VICg.js +120 -0
  88. package/dist/client/assets/treeView-BLDUP644-D85P2Ji3.js +1 -0
  89. package/dist/client/assets/treemap-LRROVOQU-BnXG5utQ.js +1 -0
  90. package/dist/client/assets/vennDiagram-CIIHVFJN-DPRtn-XH.js +34 -0
  91. package/dist/client/assets/wardley-L42UT6IY-Ct9qHtJP.js +1 -0
  92. package/dist/client/assets/wardleyDiagram-YWT4CUSO-CDiint0P.js +78 -0
  93. package/dist/client/assets/xychartDiagram-2RQKCTM6-ELyFdgmG.js +7 -0
  94. package/dist/client/index.html +3 -4
  95. package/dist/server/db.js +92 -0
  96. package/dist/server/db.js.map +1 -1
  97. package/dist/server/index.js +396 -123
  98. package/dist/server/index.js.map +1 -1
  99. package/dist/server/lib/agent.js +462 -0
  100. package/dist/server/lib/agent.js.map +1 -1
  101. package/dist/server/lib/attachments.js +146 -0
  102. package/dist/server/lib/attachments.js.map +1 -1
  103. package/dist/server/lib/buildFlow.js +2 -1
  104. package/dist/server/lib/buildFlow.js.map +1 -1
  105. package/dist/server/lib/chatOperations.js +2 -0
  106. package/dist/server/lib/chatOperations.js.map +1 -1
  107. package/dist/server/lib/chats.js +9 -4
  108. package/dist/server/lib/chats.js.map +1 -1
  109. package/dist/server/lib/externalSource.js +32 -0
  110. package/dist/server/lib/externalSource.js.map +1 -0
  111. package/dist/server/lib/httpError.js +9 -0
  112. package/dist/server/lib/httpError.js.map +1 -0
  113. package/dist/server/lib/mobileAccess.js +3 -0
  114. package/dist/server/lib/mobileAccess.js.map +1 -1
  115. package/dist/server/lib/specArtifact.js +353 -0
  116. package/dist/server/lib/specArtifact.js.map +1 -0
  117. package/dist/server/lib/specDraft.js +33 -0
  118. package/dist/server/lib/specDraft.js.map +1 -0
  119. package/dist/server/lib/specExport.js +70 -0
  120. package/dist/server/lib/specExport.js.map +1 -0
  121. package/dist/server/lib/specFormatting.js +26 -0
  122. package/dist/server/lib/specFormatting.js.map +1 -0
  123. package/dist/server/lib/specMessages.js +79 -0
  124. package/dist/server/lib/specMessages.js.map +1 -0
  125. package/dist/server/lib/specOperations.js +171 -0
  126. package/dist/server/lib/specOperations.js.map +1 -0
  127. package/dist/server/lib/specSections.js +82 -0
  128. package/dist/server/lib/specSections.js.map +1 -0
  129. package/dist/server/lib/specs.js +347 -0
  130. package/dist/server/lib/specs.js.map +1 -0
  131. package/dist/server/lib/thinkingState.js +26 -0
  132. package/dist/server/lib/thinkingState.js.map +1 -1
  133. package/dist/server/lib/ticketBatch.js +55 -0
  134. package/dist/server/lib/ticketBatch.js.map +1 -0
  135. package/dist/server/lib/ticketDescriptionContract.js +52 -0
  136. package/dist/server/lib/ticketDescriptionContract.js.map +1 -0
  137. package/dist/server/lib/tickets.js +7 -3
  138. package/dist/server/lib/tickets.js.map +1 -1
  139. package/dist/server/workers/build.js +10 -1
  140. package/dist/server/workers/build.js.map +1 -1
  141. package/dist/server/workers/chat.js +10 -1
  142. package/dist/server/workers/chat.js.map +1 -1
  143. package/dist/server/workers/followUp.js +10 -1
  144. package/dist/server/workers/followUp.js.map +1 -1
  145. package/dist/server/workers/plan.js +10 -32
  146. package/dist/server/workers/plan.js.map +1 -1
  147. package/dist/server/workers/planFollowUp.js +10 -0
  148. package/dist/server/workers/planFollowUp.js.map +1 -1
  149. package/dist/server/workers/review.js +10 -1
  150. package/dist/server/workers/review.js.map +1 -1
  151. package/dist/server/workers/specSession.js +520 -0
  152. package/dist/server/workers/specSession.js.map +1 -0
  153. package/package.json +1 -1
  154. package/dist/client/assets/TestsDialog-1JPVtcz7.js +0 -5
  155. package/dist/client/assets/dropdown-menu-BULYW006.js +0 -43
  156. package/dist/client/assets/index-DGoXllNX.js +0 -150
  157. package/dist/client/assets/index-DoNUd8CK.css +0 -2
  158. /package/dist/client/assets/{rolldown-runtime-BYbx6iT9.js → chunk-BNv3lrIs.js} +0 -0
@@ -6,6 +6,7 @@ import db from '../db.js';
6
6
  import { getChatSession, updateChatSessionUsageSnapshot } from './chats.js';
7
7
  import { normalizeModelId } from './models.js';
8
8
  import { PLAN_MODE_QUESTION_TOOL_NAME } from './planModeQuestions.js';
9
+ import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
9
10
  import { resolveOpencodeDbPath } from './runtimePaths.js';
10
11
  import { markTicketDescriptionVisibleInBundle } from './ticketMessages.js';
11
12
  import { getTicket } from './tickets.js';
@@ -20,6 +21,7 @@ const OPENCODE_STEP_FINISH_USAGE_QUERY = "SELECT data FROM part WHERE session_id
20
21
  const OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY = "SELECT data FROM part WHERE session_id = ? AND time_created >= ? AND json_extract(data, '$.type') = 'step-finish' ORDER BY time_created DESC LIMIT 1";
21
22
  const OPENCODE_SESSION_COST_QUERY = 'SELECT cost FROM session WHERE id = ?';
22
23
  const GET_PERSISTED_CHAT_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM chat_sessions WHERE id = ?';
24
+ const GET_PERSISTED_SPEC_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM spec_sessions WHERE id = ?';
23
25
  const EMPTY_CHAT_USAGE_SNAPSHOT = {
24
26
  usage_snapshot_context_window: null,
25
27
  usage_snapshot_max_output_tokens: null,
@@ -174,9 +176,15 @@ const hasMatchingUsageSnapshot = (current, next) => {
174
176
  };
175
177
  const activeBuildProcesses = new Map();
176
178
  const activeChatProcesses = new Map();
179
+ const activeSpecProcesses = new Map();
177
180
  const getErrorMessage = (error) => {
178
181
  return error instanceof Error ? error.message : String(error);
179
182
  };
183
+ export const formatAgentErrorForTranscript = (error) => {
184
+ const rawMessage = getErrorMessage(error);
185
+ const cleaned = rawMessage.replace(/^Agent exited with code \S+\n/, '').trim();
186
+ return cleaned || rawMessage;
187
+ };
180
188
  const getPersistedChatState = (chatSessionId) => {
181
189
  const row = db.prepare(GET_PERSISTED_CHAT_STATE_QUERY).get(chatSessionId);
182
190
  if (!row) {
@@ -188,6 +196,17 @@ const getPersistedChatState = (chatSessionId) => {
188
196
  opencodeSessionId: row.opencode_session_id ?? null,
189
197
  };
190
198
  };
199
+ const getPersistedSpecState = (specSessionId) => {
200
+ const row = db.prepare(GET_PERSISTED_SPEC_STATE_QUERY).get(specSessionId);
201
+ if (!row) {
202
+ return null;
203
+ }
204
+ return {
205
+ agentLog: row.agent_log ?? null,
206
+ streamingResponse: row.streaming_response ?? null,
207
+ opencodeSessionId: row.opencode_session_id ?? null,
208
+ };
209
+ };
191
210
  const preferNonEmptyChatValue = (value, fallback) => {
192
211
  if (typeof value === 'string' && value.trim()) {
193
212
  return value;
@@ -205,6 +224,14 @@ const getPreservedChatOutput = ({ chatSessionId, log, response, }) => {
205
224
  opencodeSessionId: persistedState?.opencodeSessionId ?? null,
206
225
  };
207
226
  };
227
+ const getPreservedSpecOutput = ({ specSessionId, log, response, }) => {
228
+ const persistedState = getPersistedSpecState(specSessionId);
229
+ return {
230
+ log: preferNonEmptyChatValue(log, persistedState?.agentLog ?? null),
231
+ response: preferNonEmptyChatValue(response, persistedState?.streamingResponse ?? null),
232
+ opencodeSessionId: persistedState?.opencodeSessionId ?? null,
233
+ };
234
+ };
208
235
  const updateStatus = (ticketId, status, log, io, streamingResponse = null, options = {}) => {
209
236
  db.prepare('UPDATE tickets SET agent_status = ?, agent_log = ?, streaming_response = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(status, log ?? null, streamingResponse ?? null, ticketId);
210
237
  const updatedTicket = getTicket(ticketId);
@@ -245,6 +272,30 @@ const updateChatSessionId = (chatSessionId, tool, sessionId, io) => {
245
272
  io.emit('chat-session:updated', chatSession);
246
273
  }
247
274
  };
275
+ const updateSpecSessionStatusLocal = (specSessionId, status, log, io, streamingResponse = null) => {
276
+ updateSpecSessionStatus(specSessionId, status, log, streamingResponse);
277
+ const specSession = getSpecSession(specSessionId, { includeMessages: true });
278
+ if (specSession) {
279
+ io.emit('spec-session:updated', specSession);
280
+ }
281
+ };
282
+ const updateSpecSessionIdLocal = (specSessionId, tool, sessionId, io) => {
283
+ updateSpecSessionResumeId(specSessionId, tool, sessionId);
284
+ const specSession = getSpecSession(specSessionId, { includeMessages: true });
285
+ if (specSession) {
286
+ io.emit('spec-session:updated', specSession);
287
+ }
288
+ };
289
+ const updateSpecSessionOpencodeMetadata = (specSessionId, sessionId, title, io) => {
290
+ updateSpecSessionResumeId(specSessionId, 'opencode', sessionId);
291
+ if (title) {
292
+ updateSpecSessionTitle(specSessionId, title);
293
+ }
294
+ const specSession = getSpecSession(specSessionId, { includeMessages: true });
295
+ if (specSession) {
296
+ io.emit('spec-session:updated', specSession);
297
+ }
298
+ };
248
299
  const createThrottledProgressUpdater = (flushUpdate) => {
249
300
  let timer = null;
250
301
  let hasPendingUpdate = false;
@@ -367,6 +418,26 @@ export const refreshChatSessionUsageSnapshot = (chatSessionId, options = {}) =>
367
418
  updateChatSessionUsageSnapshot(chatSessionId, nextUsageSnapshot);
368
419
  return getChatSession(chatSessionId, { includeMessages: options.includeMessages });
369
420
  };
421
+ export const refreshSpecSessionUsageSnapshot = (specSessionId, options = {}) => {
422
+ const specSession = getSpecSession(specSessionId, { includeMessages: options.includeMessages });
423
+ if (!specSession || specSession.tool !== 'opencode' || !specSession.opencode_session_id) {
424
+ return specSession;
425
+ }
426
+ let nextUsageSnapshot;
427
+ try {
428
+ nextUsageSnapshot = readOpencodeUsageSnapshot({
429
+ sessionId: specSession.opencode_session_id,
430
+ });
431
+ }
432
+ catch {
433
+ return specSession;
434
+ }
435
+ if (hasMatchingUsageSnapshot(specSession, nextUsageSnapshot)) {
436
+ return specSession;
437
+ }
438
+ updateSpecSessionUsageSnapshot(specSessionId, nextUsageSnapshot);
439
+ return getSpecSession(specSessionId, { includeMessages: options.includeMessages });
440
+ };
370
441
  export const storeTicketUsageSnapshot = (ticketId, opencodeSessionId) => {
371
442
  try {
372
443
  const snapshot = readOpencodeUsageSnapshot({ sessionId: opencodeSessionId });
@@ -454,6 +525,74 @@ const createOpencodeMetadataRefresher = ({ chatSessionId, io, spawnTimestamp, in
454
525
  },
455
526
  };
456
527
  };
528
+ const createSpecOpencodeMetadataRefresher = ({ specSessionId, io, spawnTimestamp, initialSessionId = null, }) => {
529
+ let lookupTimer = null;
530
+ let stopped = false;
531
+ let knownSessionId = initialSessionId;
532
+ let lastAppliedSessionId = null;
533
+ let lastAppliedTitle = null;
534
+ const clearLookupTimer = () => {
535
+ if (!lookupTimer) {
536
+ return;
537
+ }
538
+ clearTimeout(lookupTimer);
539
+ lookupTimer = null;
540
+ };
541
+ const readCurrentSession = () => {
542
+ if (knownSessionId) {
543
+ return readOpencodeSession(OPENCODE_SESSION_BY_ID_QUERY, knownSessionId);
544
+ }
545
+ if (spawnTimestamp === null) {
546
+ return null;
547
+ }
548
+ return readOpencodeSession(OPENCODE_SESSION_BY_CREATED_AT_QUERY, spawnTimestamp);
549
+ };
550
+ const refresh = () => {
551
+ if (stopped) {
552
+ return { hasSession: false, hasResolvedTitle: false, sessionId: knownSessionId };
553
+ }
554
+ try {
555
+ const session = readCurrentSession();
556
+ if (!session) {
557
+ return { hasSession: false, hasResolvedTitle: false, sessionId: knownSessionId };
558
+ }
559
+ knownSessionId = session.id;
560
+ if (session.id !== lastAppliedSessionId || session.title !== lastAppliedTitle) {
561
+ updateSpecSessionOpencodeMetadata(specSessionId, session.id, session.title, io);
562
+ lastAppliedSessionId = session.id;
563
+ lastAppliedTitle = session.title;
564
+ }
565
+ return {
566
+ hasSession: true,
567
+ hasResolvedTitle: session.title !== null,
568
+ sessionId: session.id,
569
+ };
570
+ }
571
+ catch {
572
+ return { hasSession: false, hasResolvedTitle: false, sessionId: knownSessionId };
573
+ }
574
+ };
575
+ const scheduleRetry = () => {
576
+ if (stopped) {
577
+ return;
578
+ }
579
+ clearLookupTimer();
580
+ lookupTimer = setTimeout(() => {
581
+ const result = refresh();
582
+ if (!result.hasResolvedTitle) {
583
+ scheduleRetry();
584
+ }
585
+ }, OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS);
586
+ };
587
+ return {
588
+ refresh,
589
+ scheduleRetry,
590
+ stop: () => {
591
+ stopped = true;
592
+ clearLookupTimer();
593
+ },
594
+ };
595
+ };
457
596
  const TOOL_CALL_PREVIEW_MAX_LENGTH = 100;
458
597
  const APPLY_PATCH_START_MARKER = '[[archon-apply-patch]]';
459
598
  const APPLY_PATCH_END_MARKER = '[[/archon-apply-patch]]';
@@ -775,6 +914,17 @@ const parseOpencodeJsonEvent = (event, cwd) => {
775
914
  responseFragment: null,
776
915
  };
777
916
  }
917
+ if (event.type === 'error') {
918
+ const text = event.part?.text?.trim()
919
+ || (typeof event.error === 'string' ? event.error.trim() : '')
920
+ || '';
921
+ if (text) {
922
+ return {
923
+ logFragment: `**Error:** ${text}`,
924
+ responseFragment: text,
925
+ };
926
+ }
927
+ }
778
928
  return { logFragment: null, responseFragment: null };
779
929
  };
780
930
  const parseOpencodeJsonLine = (line, cwd) => {
@@ -881,6 +1031,13 @@ const untrackChatProcess = (chatSessionId, state) => {
881
1031
  state.metadataRefresher?.stop();
882
1032
  state.metadataRefresher = null;
883
1033
  };
1034
+ const untrackSpecProcess = (specSessionId, state) => {
1035
+ if (activeSpecProcesses.get(specSessionId) === state) {
1036
+ activeSpecProcesses.delete(specSessionId);
1037
+ }
1038
+ state.metadataRefresher?.stop();
1039
+ state.metadataRefresher = null;
1040
+ };
884
1041
  export const isBuildAgentRunning = (ticketId) => activeBuildProcesses.has(ticketId);
885
1042
  export const stopAllBuildAgents = async () => {
886
1043
  const ticketIds = [...activeBuildProcesses.keys()];
@@ -915,6 +1072,21 @@ export const stopChatAgent = async (chatSessionId) => {
915
1072
  }
916
1073
  return true;
917
1074
  };
1075
+ export const stopSpecAgent = async (specSessionId) => {
1076
+ const state = activeSpecProcesses.get(specSessionId);
1077
+ if (!state?.child) {
1078
+ return false;
1079
+ }
1080
+ state.stopRequested = true;
1081
+ state.metadataRefresher?.stop();
1082
+ killProcess(state.child, 'SIGTERM');
1083
+ await waitForProcessExit(state.child, 5000);
1084
+ if (isProcessAlive(state.child)) {
1085
+ killProcess(state.child, 'SIGKILL');
1086
+ await waitForProcessExit(state.child, 1000);
1087
+ }
1088
+ return true;
1089
+ };
918
1090
  export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, emitSuccessUpdate = true, opencodeAgentMode, }) => {
919
1091
  const resolvedModel = normalizeModelId(model);
920
1092
  return new Promise((resolve, reject) => {
@@ -1457,4 +1629,294 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
1457
1629
  });
1458
1630
  });
1459
1631
  };
1632
+ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, }) => {
1633
+ const resolvedModel = normalizeModelId(model);
1634
+ return new Promise((resolve, reject) => {
1635
+ let sessionId = null;
1636
+ let spawnTimestamp = null;
1637
+ let opencodeMetadataRefresher = null;
1638
+ let settled = false;
1639
+ let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
1640
+ const specState = {
1641
+ child: null,
1642
+ stopRequested: false,
1643
+ metadataRefresher: null,
1644
+ };
1645
+ const settle = (handler) => (value) => {
1646
+ if (settled) {
1647
+ return;
1648
+ }
1649
+ settled = true;
1650
+ opencodeMetadataRefresher?.stop();
1651
+ untrackSpecProcess(specSessionId, specState);
1652
+ handler(value);
1653
+ };
1654
+ const resolveOnce = settle(resolve);
1655
+ const rejectOnce = settle(reject);
1656
+ const [cmd, args] = tool === 'claude'
1657
+ ? (() => {
1658
+ sessionId = resumeSessionId || randomUUID();
1659
+ return [
1660
+ 'claude',
1661
+ [
1662
+ '--permission-mode',
1663
+ 'plan',
1664
+ '--disallowedTools',
1665
+ PLAN_MODE_QUESTION_TOOL_NAME,
1666
+ '--print',
1667
+ '--verbose',
1668
+ '--output-format',
1669
+ 'stream-json',
1670
+ resumeSessionId ? '--resume' : '--session-id',
1671
+ sessionId,
1672
+ prompt,
1673
+ ],
1674
+ ];
1675
+ })()
1676
+ : ['opencode', ['run', '--agent', 'plan', '--format', 'json', ...(resumeSessionId ? ['--session', resumeSessionId] : []), ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
1677
+ updateSpecSessionStatusLocal(specSessionId, 'running', null, io);
1678
+ if (tool === 'claude' && sessionId) {
1679
+ updateSpecSessionIdLocal(specSessionId, tool, sessionId, io);
1680
+ }
1681
+ if (tool === 'opencode') {
1682
+ spawnTimestamp = Date.now();
1683
+ opencodeMetadataRefresher = createSpecOpencodeMetadataRefresher({
1684
+ specSessionId,
1685
+ io,
1686
+ spawnTimestamp,
1687
+ initialSessionId: resumeSessionId,
1688
+ });
1689
+ specState.metadataRefresher = opencodeMetadataRefresher;
1690
+ if (resumeSessionId) {
1691
+ const result = opencodeMetadataRefresher.refresh();
1692
+ if (!result.hasResolvedTitle) {
1693
+ opencodeMetadataRefresher.scheduleRetry();
1694
+ }
1695
+ }
1696
+ }
1697
+ const child = spawn(cmd, args, {
1698
+ cwd,
1699
+ stdio: ['ignore', 'pipe', 'pipe'],
1700
+ env: { ...process.env, PWD: cwd },
1701
+ detached: process.platform !== 'win32',
1702
+ });
1703
+ specState.child = child;
1704
+ activeSpecProcesses.set(specSessionId, specState);
1705
+ if (tool === 'claude') {
1706
+ const logLines = [];
1707
+ const responseLines = [];
1708
+ let remainder = '';
1709
+ const stderrChunks = [];
1710
+ const progressUpdater = createThrottledProgressUpdater(() => {
1711
+ updateSpecSessionStatusLocal(specSessionId, 'running', logLines.join('\n\n'), io, responseLines.join('\n\n'));
1712
+ });
1713
+ child.stdout?.on('data', (chunk) => {
1714
+ const text = remainder + chunk.toString();
1715
+ const lines = text.split('\n');
1716
+ remainder = lines.pop() ?? '';
1717
+ for (const line of lines) {
1718
+ const { logFragment, responseFragment } = parseClaudeStreamJsonLine(line);
1719
+ usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
1720
+ if (logFragment !== null) {
1721
+ logLines.push(logFragment);
1722
+ }
1723
+ if (responseFragment !== null) {
1724
+ responseLines.push(responseFragment);
1725
+ }
1726
+ if (logFragment !== null || responseFragment !== null) {
1727
+ progressUpdater.schedule();
1728
+ }
1729
+ }
1730
+ });
1731
+ child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
1732
+ child.on('close', code => {
1733
+ progressUpdater.cancel();
1734
+ if (remainder.trim()) {
1735
+ usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
1736
+ const { logFragment, responseFragment } = parseClaudeStreamJsonLine(remainder);
1737
+ if (logFragment !== null) {
1738
+ logLines.push(logFragment);
1739
+ }
1740
+ if (responseFragment !== null) {
1741
+ responseLines.push(responseFragment);
1742
+ }
1743
+ }
1744
+ const errOutput = Buffer.concat(stderrChunks).toString();
1745
+ const finalLog = logLines.join('\n\n') || errOutput;
1746
+ const finalResponse = responseLines.join('\n\n') || errOutput;
1747
+ if (specState.stopRequested) {
1748
+ const preservedOutput = getPreservedSpecOutput({
1749
+ specSessionId,
1750
+ log: finalLog,
1751
+ response: finalResponse,
1752
+ });
1753
+ updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
1754
+ resolveOnce({
1755
+ completionStatus: 'stopped',
1756
+ finalResponse: preservedOutput.response,
1757
+ usageSnapshot,
1758
+ });
1759
+ return;
1760
+ }
1761
+ const modelError = isModelResolutionError(finalLog);
1762
+ if (code === 0 && !modelError) {
1763
+ const finalOutput = getPreservedSpecOutput({
1764
+ specSessionId,
1765
+ log: finalLog,
1766
+ response: finalResponse,
1767
+ });
1768
+ updateSpecSessionStatusLocal(specSessionId, 'running', finalOutput.log, io, finalOutput.response);
1769
+ resolveOnce({
1770
+ completionStatus: 'done',
1771
+ finalResponse: finalOutput.response,
1772
+ usageSnapshot,
1773
+ });
1774
+ return;
1775
+ }
1776
+ updateSpecSessionStatusLocal(specSessionId, 'error', finalLog + (errOutput ? '\n\n' + errOutput : ''), io, finalResponse);
1777
+ rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}`));
1778
+ });
1779
+ child.on('error', err => {
1780
+ progressUpdater.cancel();
1781
+ if (specState.stopRequested) {
1782
+ const log = logLines.join('\n\n') || null;
1783
+ const response = responseLines.join('\n\n') || null;
1784
+ const preservedOutput = getPreservedSpecOutput({
1785
+ specSessionId,
1786
+ log,
1787
+ response,
1788
+ });
1789
+ updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
1790
+ resolveOnce({
1791
+ completionStatus: 'stopped',
1792
+ finalResponse: preservedOutput.response,
1793
+ usageSnapshot,
1794
+ });
1795
+ return;
1796
+ }
1797
+ updateSpecSessionStatusLocal(specSessionId, 'error', getErrorMessage(err), io);
1798
+ rejectOnce(err instanceof Error ? err : new Error(getErrorMessage(err)));
1799
+ });
1800
+ return;
1801
+ }
1802
+ const logLines = [];
1803
+ const responseLines = [];
1804
+ let remainder = '';
1805
+ const stderrChunks = [];
1806
+ const progressUpdater = createThrottledProgressUpdater(() => {
1807
+ updateSpecSessionStatusLocal(specSessionId, 'running', logLines.join('\n\n'), io, responseLines.join('\n\n'));
1808
+ });
1809
+ child.stdout?.on('data', (chunk) => {
1810
+ const text = remainder + chunk.toString();
1811
+ const lines = text.split('\n');
1812
+ remainder = lines.pop() ?? '';
1813
+ for (const line of lines) {
1814
+ usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
1815
+ const { logFragment, responseFragment } = parseOpencodeJsonLine(line, cwd);
1816
+ if (logFragment !== null) {
1817
+ logLines.push(logFragment);
1818
+ }
1819
+ if (responseFragment !== null) {
1820
+ responseLines.push(responseFragment);
1821
+ }
1822
+ if (logFragment !== null || responseFragment !== null) {
1823
+ progressUpdater.schedule();
1824
+ }
1825
+ }
1826
+ });
1827
+ child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
1828
+ if (!resumeSessionId) {
1829
+ opencodeMetadataRefresher?.scheduleRetry();
1830
+ }
1831
+ child.on('close', code => {
1832
+ progressUpdater.cancel();
1833
+ if (remainder.trim()) {
1834
+ usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
1835
+ const { logFragment, responseFragment } = parseOpencodeJsonLine(remainder, cwd);
1836
+ if (logFragment !== null) {
1837
+ logLines.push(logFragment);
1838
+ }
1839
+ if (responseFragment !== null) {
1840
+ responseLines.push(responseFragment);
1841
+ }
1842
+ }
1843
+ const stderrOutput = Buffer.concat(stderrChunks).toString();
1844
+ const log = logLines.join('\n\n') || stderrOutput;
1845
+ const finalResponse = responseLines.join('\n\n') || stderrOutput;
1846
+ const preservedOutput = specState.stopRequested
1847
+ ? getPreservedSpecOutput({
1848
+ specSessionId,
1849
+ log,
1850
+ response: finalResponse,
1851
+ })
1852
+ : null;
1853
+ const metadataResult = opencodeMetadataRefresher?.refresh();
1854
+ const resolvedOpencodeSessionId = metadataResult?.sessionId
1855
+ ?? preservedOutput?.opencodeSessionId
1856
+ ?? getSpecSession(specSessionId)?.opencode_session_id
1857
+ ?? resumeSessionId;
1858
+ if (specState.stopRequested) {
1859
+ if (resolvedOpencodeSessionId) {
1860
+ usageSnapshot = resolveOpencodeUsageSnapshot({
1861
+ current: usageSnapshot,
1862
+ sessionId: resolvedOpencodeSessionId,
1863
+ spawnTimestamp,
1864
+ });
1865
+ }
1866
+ updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput?.log ?? null, io, preservedOutput?.response ?? null);
1867
+ resolveOnce({
1868
+ completionStatus: 'stopped',
1869
+ finalResponse: preservedOutput?.response ?? null,
1870
+ usageSnapshot,
1871
+ });
1872
+ return;
1873
+ }
1874
+ const modelError = isModelResolutionError(log);
1875
+ if (code === 0 && !modelError) {
1876
+ const finalOutput = getPreservedSpecOutput({
1877
+ specSessionId,
1878
+ log,
1879
+ response: finalResponse,
1880
+ });
1881
+ if (resolvedOpencodeSessionId && spawnTimestamp !== null) {
1882
+ usageSnapshot = resolveOpencodeUsageSnapshot({
1883
+ current: usageSnapshot,
1884
+ sessionId: resolvedOpencodeSessionId,
1885
+ spawnTimestamp,
1886
+ });
1887
+ }
1888
+ updateSpecSessionStatusLocal(specSessionId, 'running', finalOutput.log, io, finalOutput.response);
1889
+ resolveOnce({
1890
+ completionStatus: 'done',
1891
+ finalResponse: finalOutput.response,
1892
+ usageSnapshot,
1893
+ });
1894
+ return;
1895
+ }
1896
+ updateSpecSessionStatusLocal(specSessionId, 'error', log, io, finalResponse);
1897
+ rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${log}`));
1898
+ });
1899
+ child.on('error', err => {
1900
+ progressUpdater.cancel();
1901
+ if (specState.stopRequested) {
1902
+ const log = logLines.join('\n\n') || null;
1903
+ const response = responseLines.join('\n\n') || null;
1904
+ const preservedOutput = getPreservedSpecOutput({
1905
+ specSessionId,
1906
+ log,
1907
+ response,
1908
+ });
1909
+ updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
1910
+ resolveOnce({
1911
+ completionStatus: 'stopped',
1912
+ finalResponse: preservedOutput.response,
1913
+ usageSnapshot,
1914
+ });
1915
+ return;
1916
+ }
1917
+ updateSpecSessionStatusLocal(specSessionId, 'error', getErrorMessage(err), io);
1918
+ rejectOnce(err instanceof Error ? err : new Error(getErrorMessage(err)));
1919
+ });
1920
+ });
1921
+ };
1460
1922
  //# sourceMappingURL=agent.js.map