@straiffi/archon 1.2.33 → 1.3.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 (158) hide show
  1. package/dist/client/assets/TestsDialog-j0fYZ2AP.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-CnivPz9K.js +36 -0
  5. package/dist/client/assets/array-DOVTz2Mq.js +1 -0
  6. package/dist/client/assets/blockDiagram-GPEHLZMM-Rd0YcyFR.js +132 -0
  7. package/dist/client/assets/c4Diagram-AAUBKEIU-kFDNpO2v.js +10 -0
  8. package/dist/client/assets/channel-C2eURzdr.js +1 -0
  9. package/dist/client/assets/chunk-2J33WTMH-CaqozdP8.js +1 -0
  10. package/dist/client/assets/chunk-3OPIFGDE-B8iFdxrR.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-BrIMEXNE.js +2 -0
  16. package/dist/client/assets/chunk-727SXJPM-BtIA7PWh.js +206 -0
  17. package/dist/client/assets/chunk-AQP2D5EJ-fZoHENDl.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-DWO1NEfx.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-CF2IF83X.js +10 -0
  24. package/dist/client/assets/chunk-L5ZTLDWV-BUItv2Ib.js +1 -0
  25. package/dist/client/assets/chunk-LZXEDZCA-BXfHhDLH.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-C2qj1a3s.js +1 -0
  30. package/dist/client/assets/chunk-NNHCCRGN-C4ci-Gd6.js +159 -0
  31. package/dist/client/assets/chunk-NZK2D7GU-Chir8aJ-.js +1 -0
  32. package/dist/client/assets/chunk-O5CBEL6O-DEfSochA.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-BszlpZ1y.js +1 -0
  38. package/dist/client/assets/chunk-XPW4576I-YiT9fJFY.js +32 -0
  39. package/dist/client/assets/classDiagram-4FO5ZUOK-D8cVFEdr.js +1 -0
  40. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BJah23qn.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-D-czLNXn.js +4 -0
  45. package/dist/client/assets/defaultLocale-Ck2Xxk-C.js +1 -0
  46. package/dist/client/assets/diagram-2AECGRRQ-DswYVROV.js +43 -0
  47. package/dist/client/assets/diagram-5GNKFQAL-C2LB4-Kp.js +10 -0
  48. package/dist/client/assets/diagram-KO2AKTUF-4pdiiwxL.js +3 -0
  49. package/dist/client/assets/diagram-LMA3HP47-DgGvwfLE.js +24 -0
  50. package/dist/client/assets/diagram-OG6HWLK6-ODytJwCx.js +24 -0
  51. package/dist/client/assets/dist-BQjeO--f.js +1 -0
  52. package/dist/client/assets/erDiagram-TEJ5UH35-CE_PuRb6.js +85 -0
  53. package/dist/client/assets/eventmodeling-FCH6USID-BczyucFk.js +1 -0
  54. package/dist/client/assets/flowDiagram-I6XJVG4X-COhcCfz4.js +162 -0
  55. package/dist/client/assets/ganttDiagram-6RSMTGT7-Dj2Sy2Gh.js +292 -0
  56. package/dist/client/assets/gitGraph-WXDBUCRP-x5mlGyzr.js +1 -0
  57. package/dist/client/assets/gitGraphDiagram-PVQCEYII-D_3GyFuB.js +106 -0
  58. package/dist/client/assets/graphlib-CMVU3iJd.js +1 -0
  59. package/dist/client/assets/index-C4OZfq84.js +260 -0
  60. package/dist/client/assets/index-I0a6lbgt.css +2 -0
  61. package/dist/client/assets/info-J43DQDTF-BsM_Qb70.js +1 -0
  62. package/dist/client/assets/infoDiagram-5YYISTIA-CBWV9QV_.js +2 -0
  63. package/dist/client/assets/init-Bft5Ffpj.js +1 -0
  64. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-Ddnuid12.js +70 -0
  65. package/dist/client/assets/journeyDiagram-JHISSGLW-BpXuCD5H.js +139 -0
  66. package/dist/client/assets/kanban-definition-UN3LZRKU-BuZe6GDK.js +89 -0
  67. package/dist/client/assets/katex-Bpz5kWH9.js +257 -0
  68. package/dist/client/assets/line-BE4yoTuW.js +1 -0
  69. package/dist/client/assets/linear-BYVhmsFj.js +1 -0
  70. package/dist/client/assets/mermaid-parser.core-B2H_eqyg.js +4 -0
  71. package/dist/client/assets/mermaid.core-CU1B0VWF.js +9 -0
  72. package/dist/client/assets/mindmap-definition-RKZ34NQL-CG4XJeRx.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-CN5uHxOy.js +30 -0
  78. package/dist/client/assets/quadrantDiagram-W4KKPZXB-Bxov0SvH.js +7 -0
  79. package/dist/client/assets/radar-GUYGQ44K-XEreg8aR.js +1 -0
  80. package/dist/client/assets/requirementDiagram-4Y6WPE33-DWw4nY3R.js +84 -0
  81. package/dist/client/assets/rough.esm-DCvqWubr.js +1 -0
  82. package/dist/client/assets/sankeyDiagram-5OEKKPKP-CzRuy9FU.js +40 -0
  83. package/dist/client/assets/sequenceDiagram-3UESZ5HK-BLAdf9cH.js +162 -0
  84. package/dist/client/assets/src-BcaL9nCM.js +1 -0
  85. package/dist/client/assets/stateDiagram-AJRCARHV-Dbwp6kCy.js +1 -0
  86. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BWC0_Q2v.js +1 -0
  87. package/dist/client/assets/timeline-definition-PNZ67QCA-HajxAhbq.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-DWaZcpRa.js +34 -0
  91. package/dist/client/assets/wardley-L42UT6IY-Ct9qHtJP.js +1 -0
  92. package/dist/client/assets/wardleyDiagram-YWT4CUSO-BPhQM7CW.js +78 -0
  93. package/dist/client/assets/xychartDiagram-2RQKCTM6-CITmODsS.js +7 -0
  94. package/dist/client/index.html +3 -4
  95. package/dist/server/db.js +91 -0
  96. package/dist/server/db.js.map +1 -1
  97. package/dist/server/index.js +384 -123
  98. package/dist/server/index.js.map +1 -1
  99. package/dist/server/lib/agent.js +475 -1
  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 +256 -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 +47 -0
  124. package/dist/server/lib/specMessages.js.map +1 -0
  125. package/dist/server/lib/specOperations.js +150 -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 +318 -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 +29 -3
  138. package/dist/server/lib/tickets.js.map +1 -1
  139. package/dist/server/workers/build.js +14 -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 +23 -33
  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 +14 -1
  150. package/dist/server/workers/review.js.map +1 -1
  151. package/dist/server/workers/specSession.js +418 -0
  152. package/dist/server/workers/specSession.js.map +1 -0
  153. package/package.json +1 -1
  154. package/dist/client/assets/TestsDialog-B7r9ZQQo.js +0 -5
  155. package/dist/client/assets/dropdown-menu-BULYW006.js +0 -43
  156. package/dist/client/assets/index-B62IAkKy.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;
@@ -300,7 +351,7 @@ const readOpencodeSession = (statement, value) => {
300
351
  opencodeDb.close();
301
352
  }
302
353
  };
303
- const readOpencodeUsageSnapshot = ({ sessionId, spawnTimestamp = null, }) => {
354
+ export const readOpencodeUsageSnapshot = ({ sessionId, spawnTimestamp = null, }) => {
304
355
  const opencodeDb = openOpencodeDatabase();
305
356
  try {
306
357
  const costRow = opencodeDb.prepare(OPENCODE_SESSION_COST_QUERY).get(sessionId);
@@ -367,6 +418,38 @@ 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
+ };
441
+ export const storeTicketUsageSnapshot = (ticketId, opencodeSessionId) => {
442
+ try {
443
+ const snapshot = readOpencodeUsageSnapshot({ sessionId: opencodeSessionId });
444
+ if (snapshot.usage_snapshot_total_tokens === null) {
445
+ return;
446
+ }
447
+ db.prepare('UPDATE tickets SET usage_snapshot_json = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(JSON.stringify(snapshot), ticketId);
448
+ }
449
+ catch {
450
+ // Best-effort usage snapshot storage.
451
+ }
452
+ };
370
453
  const updateChatSessionOpencodeMetadata = (chatSessionId, sessionId, title, io) => {
371
454
  db.prepare('UPDATE chat_sessions SET opencode_session_id = ?, title = COALESCE(?, title), updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(sessionId, title, chatSessionId);
372
455
  const chatSession = getChatSession(chatSessionId, { includeMessages: true });
@@ -442,6 +525,74 @@ const createOpencodeMetadataRefresher = ({ chatSessionId, io, spawnTimestamp, in
442
525
  },
443
526
  };
444
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
+ };
445
596
  const TOOL_CALL_PREVIEW_MAX_LENGTH = 100;
446
597
  const APPLY_PATCH_START_MARKER = '[[archon-apply-patch]]';
447
598
  const APPLY_PATCH_END_MARKER = '[[/archon-apply-patch]]';
@@ -763,6 +914,17 @@ const parseOpencodeJsonEvent = (event, cwd) => {
763
914
  responseFragment: null,
764
915
  };
765
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
+ }
766
928
  return { logFragment: null, responseFragment: null };
767
929
  };
768
930
  const parseOpencodeJsonLine = (line, cwd) => {
@@ -869,6 +1031,13 @@ const untrackChatProcess = (chatSessionId, state) => {
869
1031
  state.metadataRefresher?.stop();
870
1032
  state.metadataRefresher = null;
871
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
+ };
872
1041
  export const isBuildAgentRunning = (ticketId) => activeBuildProcesses.has(ticketId);
873
1042
  export const stopAllBuildAgents = async () => {
874
1043
  const ticketIds = [...activeBuildProcesses.keys()];
@@ -903,6 +1072,21 @@ export const stopChatAgent = async (chatSessionId) => {
903
1072
  }
904
1073
  return true;
905
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
+ };
906
1090
  export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, emitSuccessUpdate = true, opencodeAgentMode, }) => {
907
1091
  const resolvedModel = normalizeModelId(model);
908
1092
  return new Promise((resolve, reject) => {
@@ -1445,4 +1629,294 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
1445
1629
  });
1446
1630
  });
1447
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
+ };
1448
1922
  //# sourceMappingURL=agent.js.map