@straiffi/archon 1.8.8 → 1.8.9

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 (176) hide show
  1. package/dist/client/assets/{ActivityView--4X_m6mR.js → ActivityView-gBeoYQdD.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-DZ1lHM01.js → GitHubActionsDialog-fc1Vwafm.js} +1 -1
  3. package/dist/client/assets/RepositoryGraphDialog-DCOjwUR3.js +1 -0
  4. package/dist/client/assets/VerificationDialog-CIObj1Rw.js +4 -0
  5. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CY5xTOZJ.js → architectureDiagram-3BPJPVTR-B1tJZe1S.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-GPEHLZMM-DkMi7gMh.js → blockDiagram-GPEHLZMM-D_90AQ2Z.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-AAUBKEIU-ZZxXBBU4.js → c4Diagram-AAUBKEIU-DSyJfqly.js} +1 -1
  8. package/dist/client/assets/channel-DFw1f2Rh.js +1 -0
  9. package/dist/client/assets/{chunk-2J33WTMH-C9PiKMs-.js → chunk-2J33WTMH-B9RStQGj.js} +1 -1
  10. package/dist/client/assets/{chunk-3OPIFGDE-B81aUmvi.js → chunk-3OPIFGDE-CHKuJYq0.js} +1 -1
  11. package/dist/client/assets/{chunk-5ZQYHXKU-Bk2Owm90.js → chunk-5ZQYHXKU-Cznw7LTJ.js} +1 -1
  12. package/dist/client/assets/{chunk-727SXJPM-Bx-79aJk.js → chunk-727SXJPM-ix6OeZwJ.js} +1 -1
  13. package/dist/client/assets/{chunk-AQP2D5EJ-GQNXYOf0.js → chunk-AQP2D5EJ-DrPJIcE7.js} +1 -1
  14. package/dist/client/assets/{chunk-CSCIHK7Q-BV22j6vn.js → chunk-CSCIHK7Q-BSEHTnBR.js} +1 -1
  15. package/dist/client/assets/{chunk-KSCS5N6A-C9LlcbWv.js → chunk-KSCS5N6A-MdDdaLn8.js} +1 -1
  16. package/dist/client/assets/{chunk-L5ZTLDWV-D8PJqJnh.js → chunk-L5ZTLDWV-D_5xeDG1.js} +1 -1
  17. package/dist/client/assets/{chunk-LZXEDZCA-DcbPPozR.js → chunk-LZXEDZCA-Bz8_Fyt2.js} +2 -2
  18. package/dist/client/assets/{chunk-ND2GUHAM-NV-_2KqZ.js → chunk-ND2GUHAM-DCINKlk8.js} +1 -1
  19. package/dist/client/assets/{chunk-NZK2D7GU-CbqA26gb.js → chunk-NZK2D7GU-rGPgryKx.js} +1 -1
  20. package/dist/client/assets/{chunk-O5CBEL6O-B0DMN44B.js → chunk-O5CBEL6O-BHB8UIYA.js} +1 -1
  21. package/dist/client/assets/{chunk-WU5MYG2G-CYpcnwf9.js → chunk-WU5MYG2G-Bz5BJWjF.js} +1 -1
  22. package/dist/client/assets/classDiagram-4FO5ZUOK-Dt0kmMzQ.js +1 -0
  23. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BuxhA4Gw.js +1 -0
  24. package/dist/client/assets/{dagre-BM42HDAG-B5HD9fyK.js → dagre-BM42HDAG-B4TXLkOG.js} +1 -1
  25. package/dist/client/assets/{diagram-2AECGRRQ-BCXvWFpM.js → diagram-2AECGRRQ-CxMup1wq.js} +1 -1
  26. package/dist/client/assets/{diagram-5GNKFQAL-CSpWGfAO.js → diagram-5GNKFQAL-CTfP_wnS.js} +1 -1
  27. package/dist/client/assets/{diagram-KO2AKTUF-CFECGppT.js → diagram-KO2AKTUF-31NKNt4r.js} +1 -1
  28. package/dist/client/assets/{diagram-LMA3HP47-Dpb99QbZ.js → diagram-LMA3HP47-Cxo7yvjc.js} +1 -1
  29. package/dist/client/assets/{diagram-OG6HWLK6-DJcTGnx0.js → diagram-OG6HWLK6-ChgSAMLv.js} +1 -1
  30. package/dist/client/assets/{erDiagram-TEJ5UH35-jWArJxNU.js → erDiagram-TEJ5UH35-u6msywL_.js} +1 -1
  31. package/dist/client/assets/{flowDiagram-I6XJVG4X-DDF3P1hY.js → flowDiagram-I6XJVG4X-CimeXkDN.js} +1 -1
  32. package/dist/client/assets/{ganttDiagram-6RSMTGT7-Corwr_18.js → ganttDiagram-6RSMTGT7-DOu5sDUI.js} +1 -1
  33. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-WzlpszWn.js → gitGraphDiagram-PVQCEYII-zIToibUf.js} +1 -1
  34. package/dist/client/assets/index-C-VHd0Pr.js +274 -0
  35. package/dist/client/assets/index-D3xlEyeM.css +2 -0
  36. package/dist/client/assets/{infoDiagram-5YYISTIA-C0VwTwNC.js → infoDiagram-5YYISTIA-CAtWGGJI.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-m9l3k2qX.js → ishikawaDiagram-YF4QCWOH-7N-C7DEt.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-JHISSGLW-DMhT8HTW.js → journeyDiagram-JHISSGLW-D7VUypFG.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-UN3LZRKU-C2ZkcOi0.js → kanban-definition-UN3LZRKU-AmbDmzSD.js} +1 -1
  40. package/dist/client/assets/{line-gKkUdUWR.js → line-qf4UZ8vh.js} +1 -1
  41. package/dist/client/assets/{mermaid-parser.core-DdBfJWbP.js → mermaid-parser.core-BssCd1kF.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-DXnhq-eu.js → mermaid.core-DzeMqg9Y.js} +3 -3
  43. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B0gX4aJy.js → mindmap-definition-RKZ34NQL-dEnNQcDm.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-4H26LBE5-BHYdLmpx.js → pieDiagram-4H26LBE5-VjMue0a-.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-C-H-yQwU.js → quadrantDiagram-W4KKPZXB-DQFdm-0f.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DY_pSCFy.js → requirementDiagram-4Y6WPE33-CHIaXlsy.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CC61HRGf.js → sankeyDiagram-5OEKKPKP-D-kzGaKb.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B8fXWNxw.js → sequenceDiagram-3UESZ5HK-CiDmlD8k.js} +1 -1
  49. package/dist/client/assets/{stateDiagram-AJRCARHV-C_VYKsXW.js → stateDiagram-AJRCARHV-BKlnnFdT.js} +1 -1
  50. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DhJxc4em.js +1 -0
  51. package/dist/client/assets/{timeline-definition-PNZ67QCA-0fB4PIIX.js → timeline-definition-PNZ67QCA-D-BK07Un.js} +1 -1
  52. package/dist/client/assets/{vennDiagram-CIIHVFJN-CfpVF7Aa.js → vennDiagram-CIIHVFJN-CB9_GqSx.js} +1 -1
  53. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-CgdK4FcC.js → wardleyDiagram-YWT4CUSO-BEuNfyLT.js} +1 -1
  54. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BEfatQYR.js → xychartDiagram-2RQKCTM6-CSS5WMrZ.js} +1 -1
  55. package/dist/client/index.html +2 -2
  56. package/dist/server/db.js +481 -27
  57. package/dist/server/db.js.map +1 -1
  58. package/dist/server/index.js +72 -18
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/lib/activeAutomationChild.js +1 -0
  61. package/dist/server/lib/activeAutomationChild.js.map +1 -1
  62. package/dist/server/lib/agent.js +127 -15
  63. package/dist/server/lib/agent.js.map +1 -1
  64. package/dist/server/lib/agentRuns.js +12 -0
  65. package/dist/server/lib/agentRuns.js.map +1 -1
  66. package/dist/server/lib/agentSpawnPermits.js +37 -0
  67. package/dist/server/lib/agentSpawnPermits.js.map +1 -0
  68. package/dist/server/lib/automationCheckGate.js +9 -1
  69. package/dist/server/lib/automationCheckGate.js.map +1 -1
  70. package/dist/server/lib/automationProgression.js +19 -5
  71. package/dist/server/lib/automationProgression.js.map +1 -1
  72. package/dist/server/lib/automationReviewGate.js +1 -0
  73. package/dist/server/lib/automationReviewGate.js.map +1 -1
  74. package/dist/server/lib/buildCompletionContinuation.js +144 -4
  75. package/dist/server/lib/buildCompletionContinuation.js.map +1 -1
  76. package/dist/server/lib/buildSequences.js +13 -1
  77. package/dist/server/lib/buildSequences.js.map +1 -1
  78. package/dist/server/lib/bundleActivity.js +19 -0
  79. package/dist/server/lib/bundleActivity.js.map +1 -1
  80. package/dist/server/lib/chatOperations.js +81 -2
  81. package/dist/server/lib/chatOperations.js.map +1 -1
  82. package/dist/server/lib/checkAutoFix.js +604 -0
  83. package/dist/server/lib/checkAutoFix.js.map +1 -0
  84. package/dist/server/lib/checkAutoFixDetails.js +83 -0
  85. package/dist/server/lib/checkAutoFixDetails.js.map +1 -0
  86. package/dist/server/lib/checkAutoFixPolicy.js +59 -0
  87. package/dist/server/lib/checkAutoFixPolicy.js.map +1 -0
  88. package/dist/server/lib/checkRepair.js +149 -54
  89. package/dist/server/lib/checkRepair.js.map +1 -1
  90. package/dist/server/lib/checkRepairDispatch.js +91 -0
  91. package/dist/server/lib/checkRepairDispatch.js.map +1 -0
  92. package/dist/server/lib/checkRepairEvidence.js +84 -0
  93. package/dist/server/lib/checkRepairEvidence.js.map +1 -0
  94. package/dist/server/lib/checkRepairParent.js +21 -0
  95. package/dist/server/lib/checkRepairParent.js.map +1 -0
  96. package/dist/server/lib/checkRepairReconciliation.js +14 -5
  97. package/dist/server/lib/checkRepairReconciliation.js.map +1 -1
  98. package/dist/server/lib/checkRunner.js +27 -8
  99. package/dist/server/lib/checkRunner.js.map +1 -1
  100. package/dist/server/lib/checkRuns.js +63 -4
  101. package/dist/server/lib/checkRuns.js.map +1 -1
  102. package/dist/server/lib/executionReconciliation.js +387 -8
  103. package/dist/server/lib/executionReconciliation.js.map +1 -1
  104. package/dist/server/lib/executionRuns.js +24 -1
  105. package/dist/server/lib/executionRuns.js.map +1 -1
  106. package/dist/server/lib/integrations/github.js +10 -7
  107. package/dist/server/lib/integrations/github.js.map +1 -1
  108. package/dist/server/lib/postBuildCheckRuns.js +200 -5
  109. package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
  110. package/dist/server/lib/postBuildChecks.js +83 -13
  111. package/dist/server/lib/postBuildChecks.js.map +1 -1
  112. package/dist/server/lib/projects.js +25 -1
  113. package/dist/server/lib/projects.js.map +1 -1
  114. package/dist/server/lib/queue.js +9 -3
  115. package/dist/server/lib/queue.js.map +1 -1
  116. package/dist/server/lib/repositoryGraphQueries.js +5 -3
  117. package/dist/server/lib/repositoryGraphQueries.js.map +1 -1
  118. package/dist/server/lib/repositoryGraphScan.js +13 -2
  119. package/dist/server/lib/repositoryGraphScan.js.map +1 -1
  120. package/dist/server/lib/repositoryGraphStore.js +61 -1
  121. package/dist/server/lib/repositoryGraphStore.js.map +1 -1
  122. package/dist/server/lib/specArtifact.js +10 -1
  123. package/dist/server/lib/specArtifact.js.map +1 -1
  124. package/dist/server/lib/specDraft.js +10 -2
  125. package/dist/server/lib/specDraft.js.map +1 -1
  126. package/dist/server/lib/specExport.js +70 -6
  127. package/dist/server/lib/specExport.js.map +1 -1
  128. package/dist/server/lib/specFormatting.js +3 -2
  129. package/dist/server/lib/specFormatting.js.map +1 -1
  130. package/dist/server/lib/specRepositoryEvidenceFormatting.js +75 -0
  131. package/dist/server/lib/specRepositoryEvidenceFormatting.js.map +1 -0
  132. package/dist/server/lib/specRepositoryImpact.js +447 -0
  133. package/dist/server/lib/specRepositoryImpact.js.map +1 -0
  134. package/dist/server/lib/specRepositoryImpactLimits.js +6 -0
  135. package/dist/server/lib/specRepositoryImpactLimits.js.map +1 -0
  136. package/dist/server/lib/specReviewContext.js +15 -5
  137. package/dist/server/lib/specReviewContext.js.map +1 -1
  138. package/dist/server/lib/specReviewValidation.js +1 -1
  139. package/dist/server/lib/specReviewValidation.js.map +1 -1
  140. package/dist/server/lib/specSections.js +28 -3
  141. package/dist/server/lib/specSections.js.map +1 -1
  142. package/dist/server/lib/specSnapshotEvidence.js +162 -0
  143. package/dist/server/lib/specSnapshotEvidence.js.map +1 -0
  144. package/dist/server/lib/specSnapshotEvidenceSerialization.js +202 -0
  145. package/dist/server/lib/specSnapshotEvidenceSerialization.js.map +1 -0
  146. package/dist/server/lib/specs.js +31 -5
  147. package/dist/server/lib/specs.js.map +1 -1
  148. package/dist/server/lib/ticketAutomationCommit.js +3 -1
  149. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  150. package/dist/server/lib/ticketFollowUpOperations.js +16 -12
  151. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  152. package/dist/server/lib/ticketRunOperations.js +13 -1
  153. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  154. package/dist/server/lib/ticketWorkflowOperations.js +5 -2
  155. package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
  156. package/dist/server/lib/types.js +1 -0
  157. package/dist/server/lib/types.js.map +1 -1
  158. package/dist/server/workers/build.js +46 -148
  159. package/dist/server/workers/build.js.map +1 -1
  160. package/dist/server/workers/chat.js +141 -31
  161. package/dist/server/workers/chat.js.map +1 -1
  162. package/dist/server/workers/followUp.js +210 -48
  163. package/dist/server/workers/followUp.js.map +1 -1
  164. package/dist/server/workers/specReview.js +17 -4
  165. package/dist/server/workers/specReview.js.map +1 -1
  166. package/dist/server/workers/specSession.js +127 -22
  167. package/dist/server/workers/specSession.js.map +1 -1
  168. package/package.json +1 -1
  169. package/dist/client/assets/RepositoryGraphDialog-B--YlQcV.js +0 -1
  170. package/dist/client/assets/VerificationDialog-BIWPtBg3.js +0 -4
  171. package/dist/client/assets/channel-b2AN-U3x.js +0 -1
  172. package/dist/client/assets/classDiagram-4FO5ZUOK-C0exS5oM.js +0 -1
  173. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkB9dQZ1.js +0 -1
  174. package/dist/client/assets/index-Bs5SQZhz.css +0 -2
  175. package/dist/client/assets/index-Dy-7h3lN.js +0 -274
  176. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ctk1r914.js +0 -1
@@ -1,7 +1,8 @@
1
1
  import db from '../db.js';
2
- import { formatAgentErrorForTranscript, getAgentErrorTranscriptRuns, runAgent } from '../lib/agent.js';
2
+ import { formatAgentErrorForTranscript, getAgentErrorTranscriptRuns, isAgentContinuationRejectedError, registerBuildProcessPlaceholder, runAgent, untrackBuildProcessById } from '../lib/agent.js';
3
+ import { acquireAgentSpawnPermit } from '../lib/agentSpawnPermits.js';
3
4
  import { createAgentRun, emitAgentRunsUpdated, getAgentRun, terminalizeAgentRunsForExecutionRun } from '../lib/agentRuns.js';
4
- import { getExecutionRun, withExecutionRunTransaction } from '../lib/executionRuns.js';
5
+ import { appendLifecycleEvent, getExecutionRun, withExecutionRunTransaction } from '../lib/executionRuns.js';
5
6
  import config from '../lib/config.js';
6
7
  import { failBuildOpenQuestionBatch, filterResolvedBuildOpenQuestionsForBatch, listBuildOpenQuestionsForBundle, reconcileBuildOpenQuestions, setBuildOpenQuestionBatchStatus, } from '../lib/buildOpenQuestions.js';
7
8
  import { advanceBuildChain } from '../lib/buildChains.js';
@@ -24,6 +25,7 @@ import { getRequiredCheckPolicyPromptBlock } from '../lib/requiredCheckPolicyPro
24
25
  import { clearTicketStreamingResponse, createTicketMessage, markTicketDescriptionVisibleInBundle, } from '../lib/ticketMessages.js';
25
26
  import { captureStageSuccess } from '../lib/ticketUndo.js';
26
27
  import { runPostBuildChecks } from '../lib/postBuildChecks.js';
28
+ import { finalizePostBuildCheckTicket, markPostBuildCheckFinalizationComplete, persistPendingPostBuildFinalization } from '../lib/postBuildCheckRuns.js';
27
29
  import { beginTicketThinkingRun, bindTicketThinkingMessage } from '../lib/thinkingState.js';
28
30
  import { autoParkTicketIfStale, getTicket, listTickets, resolveTicketBranch, resolveTicketContextKey, resolveTicketTool, resolveTicketWorkerKey } from '../lib/tickets.js';
29
31
  import { decideReviewFollowUpAutomation, clearAutomationPause, depthAtLeast, getBundleAutomationState, resolveAutomationConfig, } from '../lib/ticketAutomation.js';
@@ -67,9 +69,6 @@ const updateStatus = (ticketId, status, log, io, operation) => {
67
69
  const clearBuildCompletion = (ticketId) => {
68
70
  db.prepare('UPDATE tickets SET build_completed_at = NULL, auto_park_dismissed_at = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(ticketId);
69
71
  };
70
- const markBuildCompleted = (ticketId) => {
71
- db.prepare('UPDATE tickets SET build_completed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(ticketId);
72
- };
73
72
  const emitBundleTickets = (projectId, bundleId, io) => {
74
73
  for (const bundleTicket of listTickets(projectId).filter(ticket => ticket.worktree_bundle_id === bundleId)) {
75
74
  const refreshed = getTicket(bundleTicket.id);
@@ -195,7 +194,7 @@ Use the review findings as the source of truth unless the follow-up instruction
195
194
 
196
195
  ${getProjectMemorySuggestionPromptRules(memoryEnabled, 'follow_up')}` : ''}`;
197
196
  };
198
- const runReviewFollowUpCompletionAutomation = async (ticket, project, io, executionContext, initiation) => {
197
+ const runReviewFollowUpCompletionAutomation = async (ticket, project, io, executionContext, initiation, releaseOwnerQueue) => {
199
198
  if (!ticket.worktree_bundle_id || !ticket.project_id) {
200
199
  return false;
201
200
  }
@@ -263,6 +262,7 @@ const runReviewFollowUpCompletionAutomation = async (ticket, project, io, execut
263
262
  broadcaster: io,
264
263
  initiation,
265
264
  pausedStageRunId: executionContext.runId,
265
+ releaseOwnerQueue,
266
266
  });
267
267
  return true;
268
268
  }
@@ -280,6 +280,7 @@ const runReviewFollowUpCompletionAutomation = async (ticket, project, io, execut
280
280
  broadcaster: io,
281
281
  initiation,
282
282
  pausedStageRunId: executionContext.runId,
283
+ releaseOwnerQueue,
283
284
  });
284
285
  return true;
285
286
  }
@@ -287,6 +288,20 @@ const runReviewFollowUpCompletionAutomation = async (ticket, project, io, execut
287
288
  return false;
288
289
  }
289
290
  };
291
+ export const completeSuccessfulReviewPostFinalization = async ({ ticket, project, executionContext, initiation, io, releaseOwnerQueue, memorySuggestions, }) => {
292
+ if (project.memory_enabled && memorySuggestions.length > 0) {
293
+ persistProjectMemorySuggestions(project.id, ticket.id, 'follow_up', memorySuggestions);
294
+ }
295
+ const completedTicket = getTicket(ticket.id) ?? ticket;
296
+ captureStageSuccess(ticket.id, 'review');
297
+ const handled = await runReviewFollowUpCompletionAutomation(completedTicket, project, io, executionContext, initiation, releaseOwnerQueue);
298
+ if (!handled) {
299
+ const completed = autoParkTicketIfStale(ticket.id);
300
+ if (completed) {
301
+ io.emit('ticket:updated', completed);
302
+ }
303
+ }
304
+ };
290
305
  export const isFollowUpRunning = (ticket) => {
291
306
  const jobKey = resolveTicketWorkerKey(ticket, 'follow_up');
292
307
  return !jobKey || isRunning(jobKey);
@@ -399,11 +414,18 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
399
414
  updateStatus(ticket.id, 'running', null, io, operation);
400
415
  }
401
416
  const activityKind = ticket.state === 'build' ? 'build_follow_up' : 'review_follow_up';
417
+ const hasBuildCancellationHandle = mode !== 'plan' && (ticket.state === 'build' || ticket.state === 'review');
418
+ if (hasBuildCancellationHandle) {
419
+ registerBuildProcessPlaceholder(ticket.id);
420
+ }
402
421
  let queuedJob;
403
422
  try {
404
- queuedJob = enqueue(jobKey, async () => {
423
+ queuedJob = enqueue(jobKey, async (releaseQueueOwnership) => {
405
424
  const scheduledRun = getExecutionRun(executionContext.projectId, executionContext.runId);
406
425
  if (!scheduledRun || !['queued', 'running', 'waiting'].includes(scheduledRun.status)) {
426
+ if (hasBuildCancellationHandle) {
427
+ untrackBuildProcessById(ticket.id);
428
+ }
407
429
  if (options.buildOpenQuestionBatchId
408
430
  && scheduledRun
409
431
  && ['failed', 'cancelled', 'interrupted'].includes(scheduledRun.status)) {
@@ -414,6 +436,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
414
436
  registerBundleActivity(ticket, activityKind);
415
437
  let buildOpenQuestionBatchCompleted = false;
416
438
  let sequenceHasNextBuild = false;
439
+ let postBuildContinuationHasNextBuild = false;
417
440
  try {
418
441
  withExecutionRunTransaction(transaction => {
419
442
  const operation = ticket.state === 'review' && mode !== 'plan' ? 'follow_up' : null;
@@ -438,6 +461,9 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
438
461
  }
439
462
  const cwd = ticket.bundle?.kind === 'project_root' ? project.repo_path : resolveWorktreePath(branch, project);
440
463
  await options.trustedOrchestration?.beforeStart(executionContext);
464
+ const spawnPermitId = options.trustedOrchestration
465
+ ? acquireAgentSpawnPermit(executionContext.runId)
466
+ : undefined;
441
467
  const runTicket = { ...ticket, ...runConfig };
442
468
  const tool = resolveTicketTool(runTicket, config.tool ?? 'opencode');
443
469
  const isBuildTicket = ticket.state === 'build';
@@ -454,17 +480,21 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
454
480
  clearBuildCompletion(ticket.id);
455
481
  }
456
482
  const isReviewResume = isReviewTicket && !isPlanMode && Boolean(ticket.session_id) && hasPreviousReviewFollowUp(ticket);
457
- const selectedPrompt = options.trustedOrchestration?.prompt ?? (isPlanMode
458
- ? questionFollowUpPrompt(runTicket, instruction, Boolean(project.memory_enabled))
459
- : isBuildTicket
460
- ? followUpPrompt(runTicket, project, instruction, Boolean(project.memory_enabled), !options.buildOpenQuestionBatchId)
461
- : prependSkillPromptBlock(reviewFindingsFollowUpPrompt(runTicket, project, latestReviewFindings, instruction, Boolean(project.memory_enabled)), runTicket.skills));
483
+ const selectedPrompt = options.trustedOrchestration
484
+ ? prependSkillPromptBlock(options.trustedOrchestration.prompt, runTicket.skills)
485
+ : (isPlanMode
486
+ ? questionFollowUpPrompt(runTicket, instruction, Boolean(project.memory_enabled))
487
+ : isBuildTicket
488
+ ? followUpPrompt(runTicket, project, instruction, Boolean(project.memory_enabled), !options.buildOpenQuestionBatchId)
489
+ : prependSkillPromptBlock(reviewFindingsFollowUpPrompt(runTicket, project, latestReviewFindings, instruction, Boolean(project.memory_enabled)), runTicket.skills));
462
490
  const prompt = isBuildTicket && !isPlanMode
463
491
  ? appendFigmaInspectionGuidance(selectedPrompt, runTicket.description || runTicket.title, 'implementation')
464
492
  : selectedPrompt;
465
- const resumeSessionId = options.providerContinuation === 'fresh' || isPlanMode
466
- ? null
467
- : (isBuildTicket || isReviewResume ? ticket.session_id : null);
493
+ const resumeSessionId = options.resumeSessionId !== undefined
494
+ ? options.resumeSessionId
495
+ : options.providerContinuation === 'fresh' || isPlanMode
496
+ ? null
497
+ : (isBuildTicket || isReviewResume ? ticket.session_id : null);
468
498
  const mainRunId = createAgentRun({
469
499
  executionRunId: executionContext.runId,
470
500
  parentType: 'ticket',
@@ -477,29 +507,88 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
477
507
  throw new Error('Unable to create the follow-up agent run');
478
508
  }
479
509
  emitAgentRunsUpdated(asGenericEmitter(io), 'ticket', ticket.id);
480
- const { response: finalResponse, responseTiming, transcriptRuns } = await runAgent({
510
+ const launch = (run, sessionId, permitId) => runAgent({
481
511
  ticketId: ticket.id,
482
512
  tool,
483
513
  prompt,
484
514
  cwd,
485
515
  model: runTicket.model,
486
516
  variant: runTicket.variant,
487
- resumeSessionId,
517
+ resumeSessionId: sessionId,
488
518
  trackBuildProcess: !isPlanMode && (isBuildTicket || isReviewTicket),
489
519
  emitSuccessUpdate: false,
490
520
  persistSessionIdToTicket: !isPlanMode,
491
521
  executionContext,
492
522
  runStream: {
493
- runId: mainRun.id,
494
- label: mainRun.label,
495
- role: mainRun.role,
496
- runIndex: mainRun.run_index,
523
+ runId: run.id,
524
+ label: run.label,
525
+ role: run.role,
526
+ runIndex: run.run_index,
497
527
  },
498
528
  io,
499
529
  opencodeAgentMode: isPlanMode ? mode : undefined,
530
+ spawnPermitId: permitId,
500
531
  });
501
- const completedMainRun = getAgentRun(mainRun.id);
502
- const completionStatus = completedMainRun?.agent_status === 'stopped' ? 'stopped' : 'done';
532
+ let agentResult;
533
+ let completedMainRun = mainRun;
534
+ if (options.trustedOrchestration && !resumeSessionId) {
535
+ appendLifecycleEvent({
536
+ projectId: executionContext.projectId,
537
+ runId: executionContext.runId,
538
+ domainStatus: 'running',
539
+ eventType: 'provider_fresh_without_historical_session',
540
+ }, asGenericEmitter(io));
541
+ }
542
+ try {
543
+ agentResult = await launch(mainRun, resumeSessionId, spawnPermitId);
544
+ if (options.trustedOrchestration && resumeSessionId) {
545
+ appendLifecycleEvent({
546
+ projectId: executionContext.projectId,
547
+ runId: executionContext.runId,
548
+ domainStatus: 'running',
549
+ eventType: 'provider_continuation_resumed',
550
+ }, asGenericEmitter(io));
551
+ }
552
+ }
553
+ catch (error) {
554
+ if (!options.trustedOrchestration || !resumeSessionId || !isAgentContinuationRejectedError(error)) {
555
+ if (options.trustedOrchestration && resumeSessionId) {
556
+ appendLifecycleEvent({
557
+ projectId: executionContext.projectId,
558
+ runId: executionContext.runId,
559
+ domainStatus: 'continuation_unsafe_failure',
560
+ eventType: 'provider_continuation_unsafe_failure',
561
+ }, asGenericEmitter(io));
562
+ }
563
+ throw error;
564
+ }
565
+ withExecutionRunTransaction(transaction => transaction.appendLifecycleEvent({
566
+ projectId: executionContext.projectId,
567
+ runId: executionContext.runId,
568
+ domainStatus: 'continuation_rejected_before_execution',
569
+ eventType: 'provider_continuation_rejected_before_execution',
570
+ }), asGenericEmitter(io));
571
+ await options.trustedOrchestration.onContinuationFallback?.(executionContext);
572
+ await options.trustedOrchestration.beforeStart(executionContext);
573
+ const fallbackPermitId = acquireAgentSpawnPermit(executionContext.runId);
574
+ const fallbackRunId = createAgentRun({
575
+ executionRunId: executionContext.runId,
576
+ parentType: 'ticket',
577
+ parentId: ticket.id,
578
+ label: `${mainRun.label} (fresh fallback)`,
579
+ role: 'main',
580
+ });
581
+ const fallbackRun = getAgentRun(fallbackRunId);
582
+ if (!fallbackRun) {
583
+ throw new Error('Unable to create the fresh fallback AgentRun');
584
+ }
585
+ emitAgentRunsUpdated(asGenericEmitter(io), 'ticket', ticket.id);
586
+ completedMainRun = fallbackRun;
587
+ agentResult = await launch(fallbackRun, null, fallbackPermitId);
588
+ }
589
+ const { response: finalResponse, responseTiming, transcriptRuns } = agentResult;
590
+ const persistedCompletedMainRun = getAgentRun(completedMainRun.id);
591
+ const completionStatus = persistedCompletedMainRun?.agent_status === 'stopped' ? 'stopped' : 'done';
503
592
  const askQuestion = isPlanMode && completionStatus === 'done' && typeof finalResponse === 'string'
504
593
  ? extractAskQuestionFromOutput(finalResponse)
505
594
  : { questionSet: null, cleanedOutput: finalResponse ?? '', error: null };
@@ -559,6 +648,9 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
559
648
  openQuestions,
560
649
  })
561
650
  : parsedResponse.response.trim();
651
+ const shouldDeferPostBuildCompletion = !isPlanMode
652
+ && completionStatus === 'done'
653
+ && !options.trustedOrchestration;
562
654
  const authoritativeResult = withExecutionRunTransaction(transaction => {
563
655
  const assistantMessageId = assistantMessage
564
656
  ? createTicketMessage({
@@ -570,11 +662,21 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
570
662
  transcriptRuns,
571
663
  })
572
664
  : null;
573
- db.prepare('UPDATE tickets SET agent_status = ?, agent_log = NULL, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND project_id = ?').run(completionStatus, ticket.id, executionContext.projectId);
665
+ db.prepare('UPDATE tickets SET agent_status = ?, agent_log = NULL, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND project_id = ?').run((shouldDeferPostBuildCompletion || (isBuildTicket
666
+ && !isPlanMode
667
+ && completionStatus === 'done'
668
+ && options.trustedOrchestration?.preserveOwnerRunningOnCompletion))
669
+ ? 'running'
670
+ : completionStatus, ticket.id, executionContext.projectId);
574
671
  const completedTicket = getTicket(ticket.id) ?? ticket;
575
- if (isBuildTicket && !isPlanMode && completionStatus === 'done' && !options.trustedOrchestration) {
576
- markBuildCompleted(ticket.id);
577
- if (parsedResponse.hasOpenQuestionsMetadata) {
672
+ if (shouldDeferPostBuildCompletion) {
673
+ if (isBuildTicket) {
674
+ persistPendingPostBuildFinalization(executionContext.projectId, executionContext.runId, parsedResponse.suggestions);
675
+ }
676
+ else {
677
+ persistPendingPostBuildFinalization(executionContext.projectId, executionContext.runId, parsedResponse.suggestions, 'review');
678
+ }
679
+ if (isBuildTicket && parsedResponse.hasOpenQuestionsMetadata) {
578
680
  db.prepare('UPDATE tickets SET planning_context_json = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(JSON.stringify(createTicketOpenQuestionsContext(completedTicket, openQuestions)), ticket.id);
579
681
  if (ticket.project_id && ticket.worktree_bundle_id) {
580
682
  reconcileBuildOpenQuestions({
@@ -628,22 +730,36 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
628
730
  bindTicketThinkingMessage(ticket.id, assistantMessageId);
629
731
  }
630
732
  emitAgentRunsUpdated(asGenericEmitter(io), 'ticket', ticket.id);
631
- const completedTicket = getTicket(ticket.id) ?? ticket;
632
- if (!isPlanMode && completedTicket.agent_status === 'done' && !options.trustedOrchestration) {
633
- await runPostBuildChecks({
733
+ let finalizedBuildTicket = null;
734
+ let buildPostBuildFinalized = false;
735
+ const completedBeforePostBuild = getTicket(ticket.id) ?? ticket;
736
+ if (shouldDeferPostBuildCompletion) {
737
+ const postBuildResult = await runPostBuildChecks({
634
738
  project,
635
739
  sourceRunId: executionContext.runId,
636
- owner: { kind: 'ticket', ticket: completedTicket },
740
+ owner: { kind: 'ticket', ticket: completedBeforePostBuild },
637
741
  onStatusChange: () => emitUpdatedTickets([ticket.id], io),
638
742
  emitter: asGenericEmitter(io),
743
+ releaseOwnerQueue: releaseQueueOwnership,
744
+ });
745
+ const finalization = finalizePostBuildCheckTicket({
746
+ projectId: executionContext.projectId,
747
+ sourceRunId: executionContext.runId,
748
+ batch: postBuildResult?.batch ?? null,
749
+ error: postBuildResult?.error ?? null,
750
+ getTicket,
751
+ emitter: asGenericEmitter(io),
639
752
  });
753
+ finalizedBuildTicket = finalization.ticket;
754
+ buildPostBuildFinalized = finalization.finalized;
640
755
  }
756
+ const completedTicket = finalizedBuildTicket ?? getTicket(ticket.id) ?? ticket;
641
757
  const bundleReadyForAutomaticReview = Boolean(ticket.project_id
642
758
  && ticket.worktree_bundle_id
643
759
  && areBundleTicketsReadyForAutomaticReview(listTickets(ticket.project_id), ticket.worktree_bundle_id));
644
760
  const preserveAutomaticReviewOwner = bundleReadyForAutomaticReview
645
761
  && depthAtLeast(resolveAutomationConfig(completedTicket.bundle ?? null, project).depth, 'auto_review');
646
- if (isBuildTicket && !isPlanMode && completedTicket.agent_status === 'done' && !options.trustedOrchestration) {
762
+ if (isBuildTicket && !isPlanMode && buildPostBuildFinalized && completedTicket.agent_status === 'done' && !options.trustedOrchestration) {
647
763
  captureStageSuccess(ticket.id, 'build');
648
764
  if (!preserveAutomaticReviewOwner) {
649
765
  const completed = autoParkTicketIfStale(ticket.id);
@@ -659,10 +775,12 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
659
775
  workflowContext: options.initiation?.workflowContext ?? null,
660
776
  io,
661
777
  emitUpdatedTickets: ticketIds => emitUpdatedTickets(ticketIds, io),
778
+ releaseOwnerQueue: releaseQueueOwnership,
662
779
  });
663
780
  if (continuation.nextTicketId) {
664
781
  sequenceHasNextBuild = true;
665
782
  }
783
+ postBuildContinuationHasNextBuild = Boolean(continuation.nextBuild);
666
784
  if (continuation.nextBuild) {
667
785
  const nextBuild = continuation.nextBuild;
668
786
  const nextSequenceId = continuation.nextSequenceId;
@@ -704,6 +822,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
704
822
  const stoppedSequence = stopBuildSequence(nextBuild.id);
705
823
  emitUpdatedTickets(stoppedSequence.affectedTicketIds, io);
706
824
  }
825
+ markPostBuildCheckFinalizationComplete(executionContext.projectId, executionContext.runId);
707
826
  };
708
827
  if (resolveTicketContextKey(nextBuild) === resolveTicketContextKey(ticket)) {
709
828
  queuedJob.finally(scheduleNextBuild).catch(() => { });
@@ -764,6 +883,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
764
883
  }
765
884
  if (isBuildTicket
766
885
  && !isPlanMode
886
+ && buildPostBuildFinalized
767
887
  && completedTicket.agent_status === 'done'
768
888
  && parsedResponse.hasOpenQuestionsMetadata
769
889
  && !sequenceHasNextBuild) {
@@ -810,7 +930,9 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
810
930
  emitBundleTickets(ticket.project_id, ticket.worktree_bundle_id, io);
811
931
  }
812
932
  }
813
- if (project.memory_enabled && parsedResponse.suggestions.length > 0) {
933
+ if (project.memory_enabled
934
+ && parsedResponse.suggestions.length > 0
935
+ && (isPlanMode || isBuildTicket)) {
814
936
  persistProjectMemorySuggestions(project.id, ticket.id, 'follow_up', parsedResponse.suggestions);
815
937
  }
816
938
  clearTicketStreamingResponse(ticket.id);
@@ -819,7 +941,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
819
941
  io.emit('ticket:updated', completedWithMessage);
820
942
  }
821
943
  if (options.trustedOrchestration) {
822
- if (completedTicket.agent_status === 'done') {
944
+ if (completionStatus === 'done') {
823
945
  await options.trustedOrchestration.onCompletion(executionContext);
824
946
  }
825
947
  else if (completedTicket.agent_status === 'stopped') {
@@ -828,7 +950,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
828
950
  }
829
951
  else if (!isPlanMode) {
830
952
  const updated = getTicket(ticket.id);
831
- if (isBuildTicket && updated?.agent_status === 'done') {
953
+ if (isBuildTicket && buildPostBuildFinalized && updated?.agent_status === 'done') {
832
954
  if (ticket.project_id && ticket.worktree_bundle_id && !sequenceHasNextBuild) {
833
955
  if (options.buildOpenQuestionBatchId) {
834
956
  if (!parsedResponse.hasOpenQuestionsMetadata) {
@@ -875,6 +997,7 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
875
997
  broadcaster: io,
876
998
  initiation: options.initiation,
877
999
  pausedStageRunId: executionContext.runId,
1000
+ releaseOwnerQueue: releaseQueueOwnership,
878
1001
  });
879
1002
  }
880
1003
  else if (ownedProgressionRunId) {
@@ -924,27 +1047,37 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
924
1047
  workflowContext: options.initiation?.workflowContext ?? null,
925
1048
  },
926
1049
  pausedStageRunId: executionContext.runId,
1050
+ releaseOwnerQueue: releaseQueueOwnership,
927
1051
  });
928
1052
  }
929
1053
  emitBundleTickets(ticket.project_id, ticket.worktree_bundle_id, io);
930
1054
  }
931
1055
  }
932
- else if (!isBuildTicket && updated?.agent_status === 'done') {
933
- captureStageSuccess(ticket.id, 'review');
934
- const handled = await runReviewFollowUpCompletionAutomation(updated, project, io, executionContext, options.initiation);
935
- if (!handled) {
936
- const completed = autoParkTicketIfStale(ticket.id);
937
- if (completed) {
938
- io.emit('ticket:updated', completed);
939
- }
940
- }
1056
+ else if (!isBuildTicket
1057
+ && updated?.agent_status === 'done'
1058
+ && (!shouldDeferPostBuildCompletion || buildPostBuildFinalized)) {
1059
+ await completeSuccessfulReviewPostFinalization({
1060
+ ticket: updated,
1061
+ project,
1062
+ executionContext,
1063
+ initiation: options.initiation,
1064
+ io,
1065
+ releaseOwnerQueue: releaseQueueOwnership,
1066
+ memorySuggestions: parsedResponse.suggestions,
1067
+ });
941
1068
  }
942
1069
  }
1070
+ if (!isPlanMode
1071
+ && buildPostBuildFinalized
1072
+ && (!isBuildTicket || !postBuildContinuationHasNextBuild)) {
1073
+ markPostBuildCheckFinalizationComplete(executionContext.projectId, executionContext.runId);
1074
+ }
943
1075
  }
944
1076
  catch (error) {
945
1077
  const executionRun = getExecutionRun(executionContext.projectId, executionContext.runId);
946
1078
  if (executionRun && !['queued', 'running', 'waiting'].includes(executionRun.status)) {
947
1079
  if (options.trustedOrchestration) {
1080
+ releaseQueueOwnership();
948
1081
  await options.trustedOrchestration.onCancellation(executionContext);
949
1082
  return;
950
1083
  }
@@ -1049,8 +1182,20 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
1049
1182
  },
1050
1183
  });
1051
1184
  }
1052
- db.prepare('UPDATE tickets SET agent_status = ?, agent_log = ?, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND project_id = ?')
1053
- .run('error', message, ticket.id, executionContext.projectId);
1185
+ const ownerProjection = options.trustedOrchestration?.preserveOwnerOnFailure
1186
+ ? options.trustedOrchestration.ownerProjectionBeforeRun
1187
+ : null;
1188
+ if (ownerProjection) {
1189
+ db.prepare(`
1190
+ UPDATE tickets SET agent_status = ?, agent_operation = ?, agent_log = ?, streaming_response = ?,
1191
+ updated_at = CURRENT_TIMESTAMP
1192
+ WHERE id = ? AND project_id = ? AND agent_status = 'running'
1193
+ `).run(ownerProjection.agentStatus, ownerProjection.agentOperation, ownerProjection.agentLog, ownerProjection.streamingResponse, ticket.id, executionContext.projectId);
1194
+ }
1195
+ else {
1196
+ db.prepare('UPDATE tickets SET agent_status = ?, agent_log = ?, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND project_id = ?')
1197
+ .run('error', message, ticket.id, executionContext.projectId);
1198
+ }
1054
1199
  if (ticket.state === 'build' && mode !== 'plan' && !options.trustedOrchestration) {
1055
1200
  affectedTicketIds = advanceBuildChain(ticket.id, 'error').affectedTicketIds;
1056
1201
  }
@@ -1064,7 +1209,10 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
1064
1209
  eventType: 'build_follow_up_failed',
1065
1210
  });
1066
1211
  }, asGenericEmitter(io));
1067
- await options.trustedOrchestration?.onFailure(executionContext, message);
1212
+ if (options.trustedOrchestration) {
1213
+ releaseQueueOwnership();
1214
+ await options.trustedOrchestration.onFailure(executionContext, message);
1215
+ }
1068
1216
  emitUpdatedTickets(affectedTicketIds, io);
1069
1217
  if (ownedReviewFixRunId && ownedReviewProgressionRunId && ticket.project_id && ticket.worktree_bundle_id) {
1070
1218
  finishAutomationReviewFix({
@@ -1085,14 +1233,28 @@ export function startFollowUp(ticket, io, instruction, mode = 'build', options =
1085
1233
  });
1086
1234
  }
1087
1235
  emitAgentRunsUpdated(asGenericEmitter(io), 'ticket', ticket.id);
1088
- updateStatus(ticket.id, 'error', message, io);
1236
+ if (options.trustedOrchestration?.preserveOwnerOnFailure) {
1237
+ const restoredTicket = getTicket(ticket.id);
1238
+ if (restoredTicket) {
1239
+ io.emit('ticket:updated', restoredTicket);
1240
+ }
1241
+ }
1242
+ else {
1243
+ updateStatus(ticket.id, 'error', message, io);
1244
+ }
1089
1245
  }
1090
1246
  finally {
1091
1247
  unregisterBundleActivity(ticket, activityKind);
1248
+ if (hasBuildCancellationHandle) {
1249
+ untrackBuildProcessById(ticket.id);
1250
+ }
1092
1251
  }
1093
1252
  });
1094
1253
  }
1095
1254
  catch (error) {
1255
+ if (hasBuildCancellationHandle) {
1256
+ untrackBuildProcessById(ticket.id);
1257
+ }
1096
1258
  const message = getErrorMessage(error);
1097
1259
  withExecutionRunTransaction(transaction => {
1098
1260
  options.compensate?.();