@straiffi/archon 1.10.8 → 1.10.10

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 (169) hide show
  1. package/dist/client/assets/ActivityView-CRkM5TeN.js +1 -0
  2. package/dist/client/assets/GitHubActionsDialog-KFUiR00H.js +1 -0
  3. package/dist/client/assets/VerificationDialog-BIDttgiW.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CzUKSWJZ.js → architectureDiagram-3BPJPVTR-u0PSZlNP.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-DoNm6BnS.js → blockDiagram-GPEHLZMM-DFXLHRIk.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-C7ocs3wQ.js → c4Diagram-AAUBKEIU-Dvic8tCt.js} +1 -1
  7. package/dist/client/assets/channel-Dhv_uSKx.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-DUmaxCq8.js → chunk-2J33WTMH-t5CxhRXJ.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CX2MPsMQ.js → chunk-3OPIFGDE-YHT1dP8Q.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-B103kU_y.js → chunk-5ZQYHXKU-DcxuI7lb.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-BmLJ4h5X.js → chunk-727SXJPM-D1F6DmrT.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-Cnv1b4Vd.js → chunk-AQP2D5EJ-BIx6Ohbk.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CkIUhpY9.js → chunk-CSCIHK7Q-B355h6HA.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-wmkCuZ5U.js → chunk-KSCS5N6A-CLHGYOLQ.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-D9AdL5mv.js → chunk-L5ZTLDWV-DFLYcMx8.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-rMyTvR3J.js → chunk-LZXEDZCA-DtljFujX.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-19HTozu8.js → chunk-ND2GUHAM-CIuUQ86K.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-DBHgwKcS.js → chunk-NZK2D7GU-CIwqlxek.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-4WpSG06s.js → chunk-O5CBEL6O-W42BEPu4.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-loqCl7ki.js → chunk-WU5MYG2G-C_zuaNxV.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-9XKvSSLq.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Dbr5_49Y.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-FVpIr0qZ.js → dagre-BM42HDAG-UKfIUJTP.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-Dgj8FaDO.js → diagram-2AECGRRQ-Xn6yojqq.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-DNpYKz_a.js → diagram-5GNKFQAL-BzpY1ZEl.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-N7R7efvN.js → diagram-KO2AKTUF-Dyrvzvsg.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-hoOvd_tG.js → diagram-LMA3HP47-AqImKwAr.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-ze25ZIy6.js → diagram-OG6HWLK6-CaQQ46mi.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-780zvNNZ.js → erDiagram-TEJ5UH35-Dkw4Aa5u.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-x4UB_M_4.js → flowDiagram-I6XJVG4X-CJTuGX9h.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-B5Wl0y91.js → ganttDiagram-6RSMTGT7-CidgOdS-.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CZtVHpBH.js → gitGraphDiagram-PVQCEYII-Bj02mIQj.js} +1 -1
  33. package/dist/client/assets/index-ClRB9YWJ.css +2 -0
  34. package/dist/client/assets/index-DBMdsTvy.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-BOzLwsnA.js → infoDiagram-5YYISTIA-GwJWfo_r.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-ButBhNg6.js → ishikawaDiagram-YF4QCWOH-rbrlWbUn.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-CO0Db0Gn.js → journeyDiagram-JHISSGLW-BLi_uzQM.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-B6KT8yln.js → kanban-definition-UN3LZRKU-ynlywm-_.js} +1 -1
  39. package/dist/client/assets/{line-Cv-WaOjt.js → line-BNyTZBB4.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-DgJy4Qlx.js → mermaid-parser.core-CzUOsZAD.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-YpvipwZo.js → mermaid.core-K0QR6SzN.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DGgtT98Y.js → mindmap-definition-RKZ34NQL-BKcgqXm0.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CgqnLewO.js → pieDiagram-4H26LBE5-Bjx0lvDI.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Ci_0SzdR.js → quadrantDiagram-W4KKPZXB-CmUlgyUv.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-wJl0DP74.js → requirementDiagram-4Y6WPE33-Dj2mzg1y.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CE7aDnzF.js → sankeyDiagram-5OEKKPKP-DC--H6qf.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CW0bF1i6.js → sequenceDiagram-3UESZ5HK-BkNrt6Ra.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-Ccl0RXsJ.js → stateDiagram-AJRCARHV-BuxxRu1Y.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cyo85GPN.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-TuF6SUnb.js → timeline-definition-PNZ67QCA-Db3pCVSp.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-D8OvSROI.js → vennDiagram-CIIHVFJN-BgBSGThQ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DXW3R5NJ.js → wardleyDiagram-YWT4CUSO-BJhPdjYS.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-qdiUQDW9.js → xychartDiagram-2RQKCTM6-BY4Ra0kX.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +341 -4
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +197 -270
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agentRuns.js +14 -1
  60. package/dist/server/lib/agentRuns.js.map +1 -1
  61. package/dist/server/lib/automationLoopProjection.js +55 -12
  62. package/dist/server/lib/automationLoopProjection.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +2 -5
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationResumeDecision.js +19 -1
  66. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  67. package/dist/server/lib/boundedProcess.js +129 -0
  68. package/dist/server/lib/boundedProcess.js.map +1 -0
  69. package/dist/server/lib/buildControlEligibility.js +4 -0
  70. package/dist/server/lib/buildControlEligibility.js.map +1 -0
  71. package/dist/server/lib/buildExecutionAdmission.js +76 -0
  72. package/dist/server/lib/buildExecutionAdmission.js.map +1 -0
  73. package/dist/server/lib/buildSequenceResume.js +79 -0
  74. package/dist/server/lib/buildSequenceResume.js.map +1 -0
  75. package/dist/server/lib/buildWorkload.js +11 -0
  76. package/dist/server/lib/buildWorkload.js.map +1 -0
  77. package/dist/server/lib/bundleReviewOperations.js +7 -0
  78. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  79. package/dist/server/lib/chats.js +12 -0
  80. package/dist/server/lib/chats.js.map +1 -1
  81. package/dist/server/lib/checkAutoFixDetails.js +36 -1
  82. package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
  83. package/dist/server/lib/executionRetry.js +13 -1
  84. package/dist/server/lib/executionRetry.js.map +1 -1
  85. package/dist/server/lib/executionRunAuthority.js +2 -2
  86. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  87. package/dist/server/lib/executionTargets.js +7 -4
  88. package/dist/server/lib/executionTargets.js.map +1 -1
  89. package/dist/server/lib/executionTimeline.js +8 -13
  90. package/dist/server/lib/executionTimeline.js.map +1 -1
  91. package/dist/server/lib/executionTimelineHierarchy.js +83 -0
  92. package/dist/server/lib/executionTimelineHierarchy.js.map +1 -0
  93. package/dist/server/lib/failedBuildSequenceRecovery.js +133 -0
  94. package/dist/server/lib/failedBuildSequenceRecovery.js.map +1 -0
  95. package/dist/server/lib/git.js +89 -48
  96. package/dist/server/lib/git.js.map +1 -1
  97. package/dist/server/lib/hostRunSession.js +62 -0
  98. package/dist/server/lib/hostRunSession.js.map +1 -0
  99. package/dist/server/lib/hostServiceRecovery.js +159 -0
  100. package/dist/server/lib/hostServiceRecovery.js.map +1 -0
  101. package/dist/server/lib/hostServiceRunProvider.js +32 -0
  102. package/dist/server/lib/hostServiceRunProvider.js.map +1 -0
  103. package/dist/server/lib/integrations/github.js +49 -105
  104. package/dist/server/lib/integrations/github.js.map +1 -1
  105. package/dist/server/lib/mobileAccess.js +6 -2
  106. package/dist/server/lib/mobileAccess.js.map +1 -1
  107. package/dist/server/lib/projectAutoConfig.js +5 -2
  108. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  109. package/dist/server/lib/projectTargets.js +8 -2
  110. package/dist/server/lib/projectTargets.js.map +1 -1
  111. package/dist/server/lib/projects.js +25 -1
  112. package/dist/server/lib/projects.js.map +1 -1
  113. package/dist/server/lib/reviewFindingReconciliation.js +9 -2
  114. package/dist/server/lib/reviewFindingReconciliation.js.map +1 -1
  115. package/dist/server/lib/reviewFindings.js +113 -23
  116. package/dist/server/lib/reviewFindings.js.map +1 -1
  117. package/dist/server/lib/reviewRepairAssessmentRecords.js +41 -0
  118. package/dist/server/lib/reviewRepairAssessmentRecords.js.map +1 -0
  119. package/dist/server/lib/reviewRepairCompletion.js +78 -18
  120. package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
  121. package/dist/server/lib/reviewRepairIterationRecords.js +4 -2
  122. package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -1
  123. package/dist/server/lib/reviewRepairIterations.js +8 -2
  124. package/dist/server/lib/reviewRepairIterations.js.map +1 -1
  125. package/dist/server/lib/reviewRepairProgress.js +177 -0
  126. package/dist/server/lib/reviewRepairProgress.js.map +1 -0
  127. package/dist/server/lib/reviewRepairReconciliation.js +119 -140
  128. package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
  129. package/dist/server/lib/reviewRepairResumeOverride.js +55 -0
  130. package/dist/server/lib/reviewRepairResumeOverride.js.map +1 -0
  131. package/dist/server/lib/run.js +523 -504
  132. package/dist/server/lib/run.js.map +1 -1
  133. package/dist/server/lib/runProcessIdentity.js +71 -0
  134. package/dist/server/lib/runProcessIdentity.js.map +1 -0
  135. package/dist/server/lib/runProcesses.js +261 -0
  136. package/dist/server/lib/runProcesses.js.map +1 -0
  137. package/dist/server/lib/runServices.js +221 -0
  138. package/dist/server/lib/runServices.js.map +1 -0
  139. package/dist/server/lib/runSessionLifecycle.js +68 -0
  140. package/dist/server/lib/runSessionLifecycle.js.map +1 -0
  141. package/dist/server/lib/runStatus.js +7 -0
  142. package/dist/server/lib/runStatus.js.map +1 -0
  143. package/dist/server/lib/serviceRuns.js +273 -16
  144. package/dist/server/lib/serviceRuns.js.map +1 -1
  145. package/dist/server/lib/ticketAutomation.js +4 -27
  146. package/dist/server/lib/ticketAutomation.js.map +1 -1
  147. package/dist/server/lib/ticketRunOperations.js +103 -34
  148. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  149. package/dist/server/lib/tickets.js +46 -21
  150. package/dist/server/lib/tickets.js.map +1 -1
  151. package/dist/server/lib/types.js.map +1 -1
  152. package/dist/server/workers/build.js +44 -69
  153. package/dist/server/workers/build.js.map +1 -1
  154. package/dist/server/workers/plan.js +0 -7
  155. package/dist/server/workers/plan.js.map +1 -1
  156. package/dist/server/workers/review.js +95 -146
  157. package/dist/server/workers/review.js.map +1 -1
  158. package/package.json +1 -1
  159. package/dist/client/assets/ActivityView-PGEex8sS.js +0 -1
  160. package/dist/client/assets/GitHubActionsDialog-CwsZRevc.js +0 -1
  161. package/dist/client/assets/VerificationDialog-BYE1M9T2.js +0 -4
  162. package/dist/client/assets/channel-b-KoJfeL.js +0 -1
  163. package/dist/client/assets/classDiagram-4FO5ZUOK-2c5Acffy.js +0 -1
  164. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ClrW9LEB.js +0 -1
  165. package/dist/client/assets/index-BXsDja_y.css +0 -2
  166. package/dist/client/assets/index-Cy3ZoenG.js +0 -276
  167. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-k65NSsdn.js +0 -1
  168. package/dist/server/lib/automationDeliveryDecision.js +0 -34
  169. package/dist/server/lib/automationDeliveryDecision.js.map +0 -1
@@ -18,7 +18,7 @@ import { registerApprovalRoutes } from './lib/approvalRoutes.js';
18
18
  import { admitForcePush, finalizeGitPushApproval, resumeGitPushApproval } from './lib/gitPushApprovals.js';
19
19
  import { reconcileApprovalRequests } from './lib/approvalReconciliation.js';
20
20
  import { dockerSandboxAdapter, dockerSandboxRunner, dockerSandboxRuntimeController, } from './lib/dockerSandboxEnvironmentOperations.js';
21
- import { deleteChatSession, getChatSession, listChatSessions } from './lib/chats.js';
21
+ import { deleteChatSession, getChatSession, hasActiveBundleChatSession, listChatSessions } from './lib/chats.js';
22
22
  import { deleteSpecSession, getCreatedTicketIdsForSnapshot, getSpecSession, getSpecSnapshot, listSpecSessions } from './lib/specs.js';
23
23
  import { getSpecSnapshotRepositoryEvidenceFreshness } from './lib/specSnapshotEvidence.js';
24
24
  import { projectSpecSnapshotForActionPrompt } from './lib/specRepositoryEvidenceFormatting.js';
@@ -32,7 +32,7 @@ import { resolveSessionTargetForExecution } from './lib/sessionTargets.js';
32
32
  import { createChatMessage, getChatMessage, updateChatMessageContent } from './lib/chatMessages.js';
33
33
  import { attachChatTicketProposalsToMessageContent } from './lib/chatTicketProposals.js';
34
34
  import { createCorsOriginResolver, parseCorsOrigins } from './lib/cors.js';
35
- import { abortGitOperation, branchExistsLocally, commitGitChanges, createWorktreeAsync, deleteLocalBranch, deleteWorktree, getBundleReviewDiffForCwd, getGitDiffDataAsync, getGitDiffFiles, getGitStatus, getGitStatusAsync, getGitUnifiedDiff, getGitUnifiedDiffAsync, getLocalProjectBranch, getProjectBranch, getProjectBranchSuggestions, getReviewDiffDataAsync, getSelectedGitUnifiedDiff, getUnpushedCommitDiffForCwd, getUnpushedCommitSummaryForCwd, getUnpushedCommitSummaryForCwdAsync, hasKnownBranchRef, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, rebaseGitBranchOntoBase, resolveExistingWorktreePath, resolveWorktreePath, squashUnpushedCommitsForCwd, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd, UnpushedCommitDiffSelectionError } from './lib/git.js';
35
+ import { abortGitOperation, branchExistsLocally, commitGitChanges, createWorktreeAsync, deleteLocalBranch, deleteWorktree, getBundleReviewDiffForCwd, getGitDiffDataAsync, getGitDiffFiles, getGitDiffFilesAsync, getGitStatus, getGitStatusAsync, getGitUnifiedDiff, getGitUnifiedDiffAsync, getLocalProjectBranch, getProjectBranch, getProjectBranchAsync, getProjectBranchSuggestions, getReviewDiffDataAsync, getSelectedGitUnifiedDiff, getUnpushedCommitDiffForCwd, getUnpushedCommitSummaryForCwd, getUnpushedCommitSummaryForCwdAsync, hasKnownBranchRef, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, rebaseGitBranchOntoBase, resolveExistingWorktreePath, resolveWorktreePath, squashUnpushedCommitsForCwd, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd, UnpushedCommitDiffSelectionError } from './lib/git.js';
36
36
  import config from './lib/config.js';
37
37
  import { autoConfigProject, ProjectAutoConfigError } from './lib/projectAutoConfig.js';
38
38
  import { analyzeRepoPath, createInitialCommit, ensureInitialCommitForWorktrees, initializeGitRepo, isInitialCommitRequiredError, } from './lib/projectRepos.js';
@@ -70,8 +70,8 @@ import { abandonAutomationPause, completeAutomationProgression, completeAutomati
70
70
  import { reconcileAutomationStage } from './lib/automationReconciliation.js';
71
71
  import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
72
72
  import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
73
- import { resolveAutomationDeliveryDecision } from './lib/automationDeliveryDecision.js';
74
- import { isLegacyReviewRepairMigrationPause } from './lib/reviewRepairReconciliation.js';
73
+ import { isLegacyReviewRepairMigrationPause, recoverCompletedReviewRepairVerification } from './lib/reviewRepairReconciliation.js';
74
+ import { overrideNoProgressReviewRepairInTransaction } from './lib/reviewRepairResumeOverride.js';
75
75
  import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
76
76
  import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationActive, isAutomationDepth, recordAutomationPause, resetBundleAutomationLoop, resolveAutomationConfig, } from './lib/ticketAutomation.js';
77
77
  import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
@@ -80,7 +80,7 @@ import { continueAfterBuildCheckpoint } from './lib/automationBuildContinuation.
80
80
  import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
81
81
  import { clearAllPreviewProxies } from './lib/previewProxy.js';
82
82
  import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, getLifecycleEvent, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
83
- import { appendReviewRepairIterationDecisionInTransaction, attachReviewRepairResultInTransaction, attachReviewRepairActionEnvelopeInTransaction, attachReviewRepairActionInTransaction, claimReviewRepairIterationInTransaction, assertReviewRepairIterationDeletionEligible, deleteTerminalReviewRepairIterationsForTicketInTransaction, getActiveReviewRepairIteration, getReviewRepairIteration, pauseReviewRepairIteration, rewindStaleReviewRepairVerification, replaceReviewRepairActionEnvelopeRetryInTransaction, replaceReviewRepairActionRetryInTransaction, replaceReviewRepairVerificationRetryInTransaction, resetReviewRepairGateForRetry, resumePausedReviewRepairIteration, terminalizeReviewRepairIteration, terminalizeReviewRepairIterationInTransaction, } from './lib/reviewRepairIterations.js';
83
+ import { appendReviewRepairIterationDecisionInTransaction, attachReviewRepairActionEnvelopeInTransaction, attachReviewRepairActionInTransaction, claimReviewRepairIterationInTransaction, assertReviewRepairIterationDeletionEligible, deleteTerminalReviewRepairIterationsForTicketInTransaction, getActiveReviewRepairIteration, getReviewRepairIteration, pauseReviewRepairIteration, rewindStaleReviewRepairVerification, replaceReviewRepairActionEnvelopeRetryInTransaction, replaceReviewRepairActionRetryInTransaction, replaceReviewRepairVerificationRetryInTransaction, resetReviewRepairGateForRetry, resumePausedReviewRepairIteration, terminalizeReviewRepairIteration, terminalizeReviewRepairIterationInTransaction, } from './lib/reviewRepairIterations.js';
84
84
  import { dispatchExecutionRetry, getExecutionRetryEligibility, resolveExecutionRetry } from './lib/executionRetry.js';
85
85
  import { listExecutionTimeline } from './lib/executionTimeline.js';
86
86
  import { loadReviewAutomationLoopDetails } from './lib/automationLoopProjection.js';
@@ -105,7 +105,9 @@ import { refreshIfNeeded as refreshAssignedIssues, clearCachedAssignedIssues } f
105
105
  import { buildCommitMessagePrompt, normalizeGeneratedCommitMessage } from './lib/commitMessage.js';
106
106
  import { BUNDLE_PULL_REQUEST_SYNC_TTL_MS, getBundlePullRequestRecord, hasOpenBundlePullRequest, serializeBundlePullRequest, shouldSyncBundlePullRequest, syncTrackedBundlePullRequestIfNeeded, upsertBundlePullRequest, upsertBundlePullRequestFromGitHub, } from './lib/bundlePullRequests.js';
107
107
  import { countInboundProjectLinks, getEffectiveProject, getProjectCheckDefinitions, getProjectById, getProjectTestCommands, getProjectTicketStats, hasProjectRunIde, listInterruptedProjectDeletions, listProjects, releaseInterruptedProjectDeletionClaims, replaceProjectLinks, serializeProject, updateProjectActiveTarget, validateProjectRequiredCheckPolicyReferences, validateProjectPayload, } from './lib/projects.js';
108
- import { getActiveRunStatus, getActiveRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
108
+ import { getActiveRunStatus, getActiveRunStatusForProject, getLastRunStatusForProject, getPreviewStatusForProject, hasRunConfig, isRunning, openIde, openTicketIdeInWorkspace, resolveProjectRunContextKey, runTicketInWorkspace, stopAllTickets, stopTicket } from './lib/run.js';
109
+ import { hasActiveProjectServiceRun } from './lib/serviceRuns.js';
110
+ import { projectRunStatus } from './lib/runStatus.js';
109
111
  import { clearAllTestSessions, createTestSession, deleteTestSession, discoverTestsForSession, doesTestFileExistForSession, getTestSession, isTestSelectionSupported, startTestSessionRun, stopTestSessionRun } from './lib/testSessions.js';
110
112
  import { closeCheckRunnerAdmissions, finalizeProductionCheckApproval, getCheckBatchAttempts, openCheckRunnerAdmissions, rerunCheckBatchAndSchedule, resumeProductionCheckApproval, shutdownCheckRunner, startCheckBatch, stopCheckBatch } from './lib/checkRunner.js';
111
113
  import { CheckRepairError, getCheckRepairCandidateState, getCheckRepairDetails, getLatestCheckRepairForBatch, retryCheckRepair, startCheckRepair, stopCheckRepair } from './lib/checkRepair.js';
@@ -421,7 +423,7 @@ const getCheckBatchRerunEligibility = (project, batch, selection) => {
421
423
  workspaceAvailable: !('error' in workspace),
422
424
  });
423
425
  };
424
- const buildExecutionRunDetailResponse = async (project, run) => {
426
+ const buildExecutionRunDetailResponse = async (project, run, scope) => {
425
427
  const checkBatch = run.kind === 'check_batch' ? getCheckBatchDetails(project.id, run.id) : null;
426
428
  const retryEligibility = checkBatch
427
429
  ? getCheckBatchRerunEligibility(project, checkBatch)
@@ -463,7 +465,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
463
465
  ? {
464
466
  kind: 'check_auto_fix',
465
467
  available: true,
466
- check_auto_fix: getCheckAutoFixDetails(project.id, run.id),
468
+ check_auto_fix: getCheckAutoFixDetails(project.id, run.id, scope),
467
469
  retry_eligibility: retryEligibility,
468
470
  action_kind: null,
469
471
  }
@@ -490,7 +492,7 @@ const buildExecutionRunDetailResponse = async (project, run) => {
490
492
  ...artifacts,
491
493
  environment_context: getExecutionEnvironmentContextForRun(project.id, run.id),
492
494
  automation_loops: run.kind === 'automation_progression'
493
- ? loadReviewAutomationLoopDetails(project.id, run.id)
495
+ ? loadReviewAutomationLoopDetails(project.id, run.id, scope)
494
496
  : [],
495
497
  };
496
498
  };
@@ -576,7 +578,26 @@ const cleanupWorktreeExecutionTarget = async ({ project, targetKind, targetKey,
576
578
  }
577
579
  return { branchWarning: buildBranchCleanupWarning(branch, deleteLocalBranch(branch, project)) };
578
580
  };
581
+ const assertTicketServiceTargetMutable = (ticketId) => {
582
+ db.transaction(() => {
583
+ const authority = db.prepare(`SELECT 1 FROM active_project_service_authorities active
584
+ JOIN execution_runs run ON run.id = active.execution_run_id
585
+ WHERE run.source_kind = 'ticket' AND run.source_id = ? LIMIT 1`).get(ticketId);
586
+ if (authority) {
587
+ throw new HttpError(409, 'Ticket has active service authority. Stop the service session and verify cleanup before changing or deleting its target.');
588
+ }
589
+ }).immediate();
590
+ };
579
591
  const deleteTicketRecord = async (ticket) => {
592
+ try {
593
+ assertTicketServiceTargetMutable(ticket.id);
594
+ }
595
+ catch (error) {
596
+ if (error instanceof HttpError) {
597
+ return { ok: false, status: error.status, error: error.message };
598
+ }
599
+ throw error;
600
+ }
580
601
  if (ticket.project_id) {
581
602
  try {
582
603
  assertReviewRepairIterationDeletionEligible(ticket.project_id, ticket.id);
@@ -667,6 +688,7 @@ const deleteTicketRecord = async (ticket) => {
667
688
  }
668
689
  try {
669
690
  withExecutionRunTransaction(transaction => {
691
+ assertTicketServiceTargetMutable(ticket.id);
670
692
  if (ticket.project_id) {
671
693
  deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
672
694
  }
@@ -1467,8 +1489,8 @@ const getWorkspaceFileTargetUnavailableMessage = (targetKind) => {
1467
1489
  const targetName = targetKind === 'repo_root' ? 'Repository' : targetKind === 'bundle' ? 'Bundle' : 'Ticket';
1468
1490
  return `${targetName} target is unavailable`;
1469
1491
  };
1470
- const getProjectBranchStateForFooter = (project, options = {}) => {
1471
- const branchState = getProjectBranch(project, {
1492
+ const getProjectBranchStateForFooter = async (project, options = {}) => {
1493
+ const branchState = await getProjectBranchAsync(project, {
1472
1494
  refreshRemote: options.refreshRemote,
1473
1495
  includeDiffStats: project.active_target_kind !== 'bundle',
1474
1496
  });
@@ -1484,7 +1506,7 @@ const getProjectBranchStateForFooter = (project, options = {}) => {
1484
1506
  }
1485
1507
  return {
1486
1508
  ...branchState,
1487
- diff_stats: summarizeTicketDiffFiles(getGitDiffFiles(workspace.cwd)),
1509
+ diff_stats: summarizeTicketDiffFiles(await getGitDiffFilesAsync(workspace.cwd)),
1488
1510
  };
1489
1511
  };
1490
1512
  const listBundleTickets = (projectId, bundleId) => {
@@ -1613,22 +1635,20 @@ const getBundleReviewState = (projectId, tickets) => {
1613
1635
  has_completed_review: false,
1614
1636
  has_follow_up_since_last_review: false,
1615
1637
  can_re_review: false,
1638
+ re_review_eligibility_reason: 'no_completed_review',
1616
1639
  };
1617
1640
  }
1618
- const reviewState = buildReviewFindingsResponse({
1619
- context: {
1620
- ticketId: representativeTicket.id,
1621
- projectId,
1622
- worktreeBundleId: representativeTicket.worktree_bundle_id,
1623
- },
1624
- isContextRunning: tickets.some(ticket => ticket.agent_status === 'running'),
1625
- currentWorkspaceSignature: null,
1626
- currentDiffSignature: null,
1627
- });
1641
+ const activityRunning = tickets.some(ticket => ticket.agent_status === 'running') || Boolean(representativeTicket.worktree_bundle_id
1642
+ && hasActiveBundleChatSession(projectId, representativeTicket.worktree_bundle_id));
1643
+ const eligibleTicket = tickets.find(ticket => ticket.can_re_review === true);
1644
+ const fallbackReason = tickets.find(ticket => ticket.re_review_eligibility_reason)?.re_review_eligibility_reason;
1628
1645
  return {
1629
- has_completed_review: reviewState.has_completed_review,
1630
- has_follow_up_since_last_review: reviewState.has_follow_up_since_last_review,
1631
- can_re_review: reviewState.can_re_review,
1646
+ has_completed_review: tickets.some(ticket => ticket.has_completed_review === true),
1647
+ has_follow_up_since_last_review: tickets.some(ticket => ticket.has_follow_up_since_last_review === true),
1648
+ can_re_review: !activityRunning && Boolean(eligibleTicket),
1649
+ re_review_eligibility_reason: activityRunning
1650
+ ? 'activity_running'
1651
+ : eligibleTicket?.re_review_eligibility_reason ?? fallbackReason ?? 'no_completed_review',
1632
1652
  };
1633
1653
  };
1634
1654
  const BUNDLE_PULL_REQUEST_DISCOVERY_MISS_COOLDOWN_MS = 10 * 60 * 1000;
@@ -1977,6 +1997,7 @@ const serializeBundleDetails = (project, bundle, extras = {}) => {
1977
1997
  has_completed_review: bundleReviewState.has_completed_review,
1978
1998
  has_follow_up_since_last_review: bundleReviewState.has_follow_up_since_last_review,
1979
1999
  can_re_review: bundleReviewState.can_re_review,
2000
+ re_review_eligibility_reason: bundleReviewState.re_review_eligibility_reason,
1980
2001
  review_recovery_available: getBundleAutomationState(project.id, bundle.id)?.automation_paused_reason
1981
2002
  ? false
1982
2003
  : getBundleReviewRecovery(project.id, bundle.id) !== null,
@@ -2071,7 +2092,7 @@ const rerunTicketReview = (ticketId) => {
2071
2092
  return { ok: false, status: 409, error: 'Ticket is already running' };
2072
2093
  }
2073
2094
  if (!ticket.can_re_review) {
2074
- return { ok: false, status: 409, error: 'Review can only be re-run after a follow-up finishes' };
2095
+ return { ok: false, status: 409, error: 'Review can only be re-run after the workspace changes' };
2075
2096
  }
2076
2097
  const scheduling = rerunTicketReviewOperation(ticket, io);
2077
2098
  if (!scheduling.ok) {
@@ -2296,6 +2317,7 @@ const getBundleReviewFindings = async (projectId, bundleId) => {
2296
2317
  isContextRunning: false,
2297
2318
  currentWorkspaceSignature: null,
2298
2319
  currentDiffSignature: null,
2320
+ isBundleChatRunning: hasActiveBundleChatSession(projectId, bundleId),
2299
2321
  });
2300
2322
  }
2301
2323
  const context = {
@@ -2319,6 +2341,7 @@ const getBundleReviewFindings = async (projectId, bundleId) => {
2319
2341
  : null,
2320
2342
  currentDiffText: currentDiffState?.diffText ?? null,
2321
2343
  currentChangeScope: currentDiffState?.changeScope ?? null,
2344
+ isBundleChatRunning: hasActiveBundleChatSession(projectId, bundleId),
2322
2345
  });
2323
2346
  };
2324
2347
  const getTicketReviewFindings = async (ticketId) => {
@@ -2347,6 +2370,9 @@ const getTicketReviewFindings = async (ticketId) => {
2347
2370
  : null,
2348
2371
  currentDiffText: currentDiffState?.diffText ?? null,
2349
2372
  currentChangeScope: currentDiffState?.changeScope ?? null,
2373
+ isBundleChatRunning: Boolean(ticket.project_id
2374
+ && ticket.worktree_bundle_id
2375
+ && hasActiveBundleChatSession(ticket.project_id, ticket.worktree_bundle_id)),
2350
2376
  });
2351
2377
  };
2352
2378
  const undoBundleReviewTickets = (projectId, bundleId) => {
@@ -2795,11 +2821,9 @@ app.get('/run-config', (req, res) => {
2795
2821
  });
2796
2822
  app.get('/run-status', (req, res) => {
2797
2823
  const requestedProjectId = getRequestedProjectId(req);
2798
- return res.json({
2799
- active_run: requestedProjectId
2800
- ? getActiveRunStatusForProject(requestedProjectId)
2801
- : getActiveRunStatus(),
2802
- });
2824
+ const activeRun = requestedProjectId ? getActiveRunStatusForProject(requestedProjectId) : getActiveRunStatus();
2825
+ const durableAuthority = hasActiveProjectServiceRun(requestedProjectId);
2826
+ return res.json(projectRunStatus(activeRun, requestedProjectId ? getLastRunStatusForProject(requestedProjectId) : null, durableAuthority));
2803
2827
  });
2804
2828
  app.get('/preview-status', (req, res) => {
2805
2829
  const projectId = getRequestedProjectId(req);
@@ -4588,7 +4612,25 @@ app.post('/bundles/:id/undo-review', (req, res) => {
4588
4612
  }
4589
4613
  return res.json(undoResult.details);
4590
4614
  });
4591
- const parseServiceExecutionProviderChoice = (body) => {
4615
+ const parseServiceRunControl = (body) => {
4616
+ if (body === undefined) {
4617
+ return {};
4618
+ }
4619
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
4620
+ return { error: 'Run control body must be an object' };
4621
+ }
4622
+ const input = body;
4623
+ for (const field of ['service_id', 'session_id']) {
4624
+ if (input[field] !== undefined && (typeof input[field] !== 'string' || !input[field].trim())) {
4625
+ return { error: `${field} must be a non-empty string` };
4626
+ }
4627
+ }
4628
+ return {
4629
+ serviceId: typeof input.service_id === 'string' ? input.service_id.trim() : undefined,
4630
+ sessionId: typeof input.session_id === 'string' ? input.session_id.trim() : undefined,
4631
+ };
4632
+ };
4633
+ const parseServiceExecutionProviderChoice = (body, allowServiceControl = false) => {
4592
4634
  if (body === undefined || body === null) {
4593
4635
  return 'project_default';
4594
4636
  }
@@ -4596,7 +4638,8 @@ const parseServiceExecutionProviderChoice = (body) => {
4596
4638
  return null;
4597
4639
  }
4598
4640
  const request = body;
4599
- if (Object.keys(request).some(key => key !== 'execution_provider')) {
4641
+ if (Object.keys(request).some(key => key !== 'execution_provider'
4642
+ && !(allowServiceControl && (key === 'service_id' || key === 'session_id')))) {
4600
4643
  return null;
4601
4644
  }
4602
4645
  if (request.execution_provider === undefined || request.execution_provider === 'project_default') {
@@ -4655,8 +4698,17 @@ app.post('/bundles/:id/run', (req, res) => {
4655
4698
  return res.status(409).json(activeRunConflict);
4656
4699
  }
4657
4700
  const profileSelection = createServiceExecutionProfileSelection(project.id, executionProvider);
4701
+ try {
4702
+ void runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection)
4703
+ .catch(error => console.error('[run] admitted bundle service startup failed:', error));
4704
+ }
4705
+ catch (error) {
4706
+ return res.status(409).json({
4707
+ error: error instanceof Error ? error.message : String(error),
4708
+ code: error instanceof Error && 'code' in error ? error.code : 'service_start_rejected',
4709
+ });
4710
+ }
4658
4711
  emitRunStateForContext(targetTicket, true);
4659
- runTicketInWorkspace(targetTicket, io, project, workspace.cwd, profileSelection);
4660
4712
  return res.json(serializeBundleDetails(result.project, bundle));
4661
4713
  });
4662
4714
  app.post('/bundles/:id/stop', async (req, res) => {
@@ -4696,52 +4748,6 @@ app.post('/bundles/:id/follow-up', (req, res) => {
4696
4748
  });
4697
4749
  const AUTOMATION_SIDE_EFFECT_KINDS = new Set(['automation_commit', 'automation_push', 'automation_pull_request']);
4698
4750
  const AUTOMATION_WORKER_PAUSE_KINDS = new Set(['build', 'review', 'build_follow_up', 'automation_review_fix', 'check_auto_fix', 'check_repair', 'check_batch']);
4699
- app.post('/bundles/:id/continue-delivery', async (req, res) => {
4700
- const result = getRequiredProject(req);
4701
- if ('error' in result) {
4702
- return res.status(400).json({ error: result.error });
4703
- }
4704
- const bundle = getBundle(req.params.id, result.project.id);
4705
- if (!bundle) {
4706
- return res.status(404).json({ error: 'Bundle not found' });
4707
- }
4708
- const state = getBundleAutomationState(result.project.id, bundle.id);
4709
- const tickets = listDetailedBundleTickets(result.project.id, bundle.id);
4710
- if (!state) {
4711
- return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
4712
- }
4713
- const automationConfig = resolveAutomationConfig(bundle, result.project);
4714
- const decision = resolveAutomationDeliveryDecision({
4715
- projectId: result.project.id,
4716
- bundleId: bundle.id,
4717
- state,
4718
- config: automationConfig,
4719
- tickets,
4720
- });
4721
- if (!decision) {
4722
- return res.status(409).json({ error: 'Bundle delivery is not ready to continue' });
4723
- }
4724
- const owner = tickets.find(ticket => ticket.id === decision.owner_ticket_id);
4725
- const reviewStageRunId = state.automation_delivery_ready_stage_run_id;
4726
- if (!owner || !reviewStageRunId) {
4727
- return res.status(409).json({ error: 'Bundle delivery owner is no longer available' });
4728
- }
4729
- try {
4730
- const progression = ensureAutomationProgression({
4731
- projectId: result.project.id,
4732
- bundleId: bundle.id,
4733
- ticketId: owner.id,
4734
- ticketTitle: owner.title,
4735
- parentRunId: reviewStageRunId,
4736
- broadcaster: io,
4737
- });
4738
- await runAutomationCommitChain(owner, result.project, owner.bundle ?? bundle, automationConfig, io, progression.runId, undefined, reviewStageRunId);
4739
- }
4740
- catch (error) {
4741
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
4742
- }
4743
- return res.json(serializeBundleDetails(result.project, bundle));
4744
- });
4745
4751
  app.post('/bundles/:id/resume-automation', async (req, res) => {
4746
4752
  const result = getRequiredProject(req);
4747
4753
  if ('error' in result) {
@@ -4781,7 +4787,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4781
4787
  : 'Bundle automation cannot be resumed in its current state';
4782
4788
  return res.status(409).json({ error, automation_resume_decision: initialDecision });
4783
4789
  }
4784
- let decision = initialDecision;
4790
+ const decision = initialDecision;
4785
4791
  const revalidateDecision = () => {
4786
4792
  const current = resolveCurrentDecision();
4787
4793
  return current?.status === 'enabled'
@@ -4997,9 +5003,21 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
4997
5003
  }
4998
5004
  const automationConfig = resolveAutomationConfig(owner.bundle ?? null, project);
4999
5005
  const legacyReviewRepairPause = isLegacyReviewRepairMigrationPause(result.project.id, bundle.id, progressionRunId);
5000
- let completedVerificationIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
5001
- let reconciledSourceReviewRunId = null;
5002
- if (completedVerificationIteration?.phase === 'paused') {
5006
+ const completedVerificationIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
5007
+ if (completedVerificationIteration
5008
+ && ['paused', 'verification_active'].includes(completedVerificationIteration.phase)
5009
+ && completedVerificationIteration.stop_reason !== 'no_progress'
5010
+ && completedVerificationIteration.resulting_review_execution_run_id
5011
+ && getExecutionRun(result.project.id, completedVerificationIteration.resulting_review_execution_run_id)?.status === 'completed') {
5012
+ try {
5013
+ await recoverCompletedReviewRepairVerification(result.project.id, completedVerificationIteration.id, io, { resume: true });
5014
+ return res.json(serializeBundleDetails(result.project, bundle));
5015
+ }
5016
+ catch (error) {
5017
+ return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
5018
+ }
5019
+ }
5020
+ if (completedVerificationIteration?.phase === 'paused' && completedVerificationIteration.stop_reason !== 'no_progress') {
5003
5021
  withExecutionRunTransaction(transaction => {
5004
5022
  appendReviewRepairIterationDecisionInTransaction(transaction, {
5005
5023
  projectId: result.project.id,
@@ -5010,165 +5028,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
5010
5028
  });
5011
5029
  }, io, 'immediate');
5012
5030
  }
5013
- if (completedVerificationIteration?.phase === 'paused') {
5014
- const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
5015
- const verificationRun = verificationRunId ? getExecutionRun(result.project.id, verificationRunId) : null;
5016
- if (verificationRun?.status === 'completed') {
5017
- try {
5018
- completedVerificationIteration = resumePausedReviewRepairIteration({
5019
- projectId: result.project.id,
5020
- iterationId: completedVerificationIteration.id,
5021
- expectedCurrentRunId: verificationRunId,
5022
- broadcaster: io,
5023
- });
5024
- }
5025
- catch (error) {
5026
- const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
5027
- if (current?.resulting_review_execution_run_id === verificationRunId
5028
- && ['verification_active', 'terminal'].includes(current.phase)) {
5029
- return res.json(serializeBundleDetails(result.project, bundle));
5030
- }
5031
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
5032
- }
5033
- }
5034
- }
5035
- if (completedVerificationIteration?.phase === 'verification_active'
5036
- && completedVerificationIteration.resulting_review_execution_run_id) {
5037
- const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
5038
- const verificationRun = getExecutionRun(result.project.id, verificationRunId);
5039
- const resultingReview = verificationRun?.status === 'completed'
5040
- ? db.prepare(`
5041
- SELECT review.id
5042
- FROM review_runs review
5043
- JOIN execution_run_links result_link
5044
- ON result_link.execution_run_id = ?
5045
- AND result_link.project_id = review.project_id
5046
- AND result_link.entity_type = 'review_run'
5047
- AND result_link.entity_id = review.id
5048
- AND result_link.relationship = 'result'
5049
- WHERE review.project_id = ? AND review.ticket_id = ?
5050
- AND review.worktree_bundle_id = ? AND review.status = 'completed'
5051
- LIMIT 1
5052
- `).get(verificationRunId, result.project.id, owner.id, bundle.id)
5053
- : undefined;
5054
- if (resultingReview) {
5055
- reconciledSourceReviewRunId = resultingReview.id;
5056
- const blockingCount = countBlockingFindings(listReviewFindings(resultingReview.id), automationConfig);
5057
- const terminal = !isAutomationActive(automationConfig)
5058
- ? { outcome: 'interrupted', stopReason: 'configuration_changed' }
5059
- : blockingCount === 0
5060
- ? { outcome: 'repaired', stopReason: 'no_blocking_findings' }
5061
- : automationConfig.reviewFixEnabled
5062
- ? completedVerificationIteration.ordinal < automationConfig.reviewMaxIterations
5063
- ? { outcome: 'continuing', stopReason: null }
5064
- : { outcome: 'exhausted', stopReason: 'max_iterations' }
5065
- : { outcome: 'interrupted', stopReason: 'automation_disabled' };
5066
- try {
5067
- withExecutionRunTransaction(transaction => {
5068
- if (!completedVerificationIteration.resulting_review_run_id) {
5069
- attachReviewRepairResultInTransaction(transaction, {
5070
- projectId: result.project.id,
5071
- iterationId: completedVerificationIteration.id,
5072
- expectedPhase: 'verification_active',
5073
- expectedVerificationRunId: verificationRunId,
5074
- resultingReviewRunId: resultingReview.id,
5075
- });
5076
- }
5077
- appendReviewRepairIterationDecisionInTransaction(transaction, {
5078
- projectId: result.project.id,
5079
- iterationId: completedVerificationIteration.id,
5080
- decisionPoint: 'verification_completion',
5081
- effectiveAutomationConfig: automationConfig,
5082
- decisionCode: 'verification_reconciled_on_resume',
5083
- operationRunId: verificationRunId,
5084
- });
5085
- terminalizeReviewRepairIterationInTransaction(transaction, {
5086
- projectId: result.project.id,
5087
- iterationId: completedVerificationIteration.id,
5088
- expectedPhase: 'verification_active',
5089
- expectedCurrentRunId: verificationRunId,
5090
- outcome: terminal.outcome,
5091
- stopReason: terminal.stopReason,
5092
- resultingBlockingCount: blockingCount,
5093
- });
5094
- if (automationConfig.depth === 'manual') {
5095
- completeAutomationProgressionInTransaction(transaction, {
5096
- projectId: result.project.id,
5097
- bundleId: bundle.id,
5098
- progressionRunId,
5099
- domainStatus: 'manual_boundary_reached',
5100
- });
5101
- }
5102
- if (terminal.outcome === 'continuing') {
5103
- claimReviewRepairIterationInTransaction(transaction, {
5104
- projectId: result.project.id,
5105
- worktreeBundleId: bundle.id,
5106
- ownerTicketId: owner.id,
5107
- automationProgressionRunId: progressionRunId,
5108
- triggeringReviewExecutionRunId: verificationRunId,
5109
- triggeringReviewRunId: resultingReview.id,
5110
- triggeringBlockingCount: blockingCount,
5111
- effectiveAutomationConfig: automationConfig,
5112
- decisionCode: 'resume_verification_repair_admitted',
5113
- });
5114
- }
5115
- }, io, 'immediate');
5116
- }
5117
- catch (error) {
5118
- const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
5119
- if (current?.phase === 'terminal'
5120
- && current.resulting_review_execution_run_id === verificationRunId
5121
- && current.resulting_review_run_id === resultingReview.id) {
5122
- return res.json(serializeBundleDetails(result.project, bundle));
5123
- }
5124
- return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
5125
- }
5126
- if (terminal.outcome === 'continuing') {
5127
- const continuationDecision = resolveCurrentDecision();
5128
- if (!continuationDecision || continuationDecision.status !== 'enabled') {
5129
- return res.status(409).json({ error: 'Bundle automation state changed before repair continuation could start' });
5130
- }
5131
- decision = continuationDecision;
5132
- }
5133
- else if (terminal.outcome === 'repaired') {
5134
- resetBundleAutomationLoop(result.project.id, bundle.id);
5135
- runAutomationProgression({
5136
- projectId: result.project.id,
5137
- bundleId: bundle.id,
5138
- progressionRunId,
5139
- broadcaster: io,
5140
- });
5141
- if (depthAtLeast(automationConfig.depth, 'auto_commit')) {
5142
- await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, undefined, verificationRunId);
5143
- }
5144
- else {
5145
- completeAutomationProgression({
5146
- projectId: result.project.id,
5147
- bundleId: bundle.id,
5148
- progressionRunId,
5149
- broadcaster: io,
5150
- });
5151
- }
5152
- return res.json(serializeBundleDetails(result.project, bundle));
5153
- }
5154
- else if (automationConfig.depth !== 'manual') {
5155
- waitAutomationProgression({
5156
- projectId: result.project.id,
5157
- bundleId: bundle.id,
5158
- progressionRunId,
5159
- pauseCode: 'review',
5160
- pauseReason: 'Blocking Review findings remain after repair verification.',
5161
- reasonCode: 'precondition_failed',
5162
- pausedStageRunId: verificationRunId,
5163
- broadcaster: io,
5164
- });
5165
- return res.json(serializeBundleDetails(result.project, bundle));
5166
- }
5167
- else {
5168
- return res.json(serializeBundleDetails(result.project, bundle));
5169
- }
5170
- }
5171
- }
5172
5031
  const restoreWaitingProgression = () => {
5173
5032
  waitAutomationProgression({
5174
5033
  projectId: result.project.id,
@@ -5361,7 +5220,16 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
5361
5220
  }
5362
5221
  const workflowContext = validateWorkflowContext(progression.metadata);
5363
5222
  let existingIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
5364
- const sourceReviewRunId = reconciledSourceReviewRunId ?? existingIteration?.triggering_review_run_id ?? null;
5223
+ const overridingNoProgress = existingIteration?.phase === 'paused' && existingIteration.stop_reason === 'no_progress';
5224
+ if (overridingNoProgress) {
5225
+ existingIteration = withExecutionRunTransaction(transaction => {
5226
+ if (!revalidateDecision()) {
5227
+ throw new Error('Bundle automation state changed before Resume override could start');
5228
+ }
5229
+ return overrideNoProgressReviewRepairInTransaction(transaction, result.project.id, existingIteration.id, io);
5230
+ }, io, 'immediate');
5231
+ }
5232
+ const sourceReviewRunId = existingIteration?.triggering_review_run_id ?? null;
5365
5233
  if (!sourceReviewRunId) {
5366
5234
  throw new Error('The completed review findings to resume are unavailable');
5367
5235
  }
@@ -5370,12 +5238,14 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
5370
5238
  review_finding_run_id: sourceReviewRunId,
5371
5239
  review_severity: automationConfig.reviewSeverity,
5372
5240
  });
5373
- runAutomationProgression({
5374
- projectId: result.project.id,
5375
- bundleId: bundle.id,
5376
- progressionRunId,
5377
- broadcaster: io,
5378
- });
5241
+ if (!overridingNoProgress) {
5242
+ runAutomationProgression({
5243
+ projectId: result.project.id,
5244
+ bundleId: bundle.id,
5245
+ progressionRunId,
5246
+ broadcaster: io,
5247
+ });
5248
+ }
5379
5249
  if (existingIteration?.phase === 'paused') {
5380
5250
  existingIteration = resumePausedReviewRepairIteration({
5381
5251
  projectId: result.project.id,
@@ -5646,9 +5516,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
5646
5516
  });
5647
5517
  }, io, 'immediate');
5648
5518
  }
5649
- if (bundleState.fix_loop_active === 1) {
5650
- resetBundleAutomationLoop(result.project.id, bundle.id);
5651
- }
5519
+ resetBundleAutomationLoop(result.project.id, bundle.id);
5652
5520
  }
5653
5521
  runAutomationProgression({
5654
5522
  projectId: result.project.id,
@@ -7677,7 +7545,7 @@ app.post('/projects/:id/memory/suggestions/:suggestionId/supersede', (req, res)
7677
7545
  }
7678
7546
  return res.json(superseded);
7679
7547
  });
7680
- app.get('/projects/:id/branch', (req, res) => {
7548
+ app.get('/projects/:id/branch', async (req, res) => {
7681
7549
  const project = getProjectById(req.params.id);
7682
7550
  if (!project) {
7683
7551
  return res.status(404).json({ error: 'Not found' });
@@ -7685,7 +7553,7 @@ app.get('/projects/:id/branch', (req, res) => {
7685
7553
  ensureProjectRootBundle(project.id);
7686
7554
  try {
7687
7555
  const refreshRemote = req.query.refresh_remote === 'true';
7688
- return res.json(getProjectBranchStateForFooter(project, { refreshRemote }));
7556
+ return res.json(await getProjectBranchStateForFooter(project, { refreshRemote }));
7689
7557
  }
7690
7558
  catch (error) {
7691
7559
  console.error(`Failed to read branch for project ${project.id}:`, error);
@@ -8197,14 +8065,6 @@ app.patch('/projects/:id', async (req, res) => {
8197
8065
  if ('error' in roleModelValidation) {
8198
8066
  return res.status(400).json({ error: roleModelValidation.error });
8199
8067
  }
8200
- if (hasOwn(validation.values, 'repo_path') && validation.values.repo_path !== project.repo_path) {
8201
- const stats = getProjectTicketStats(project.id);
8202
- if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
8203
- return res.status(409).json({
8204
- error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project',
8205
- });
8206
- }
8207
- }
8208
8068
  const { name, repo_path, linked_project_ids, run_setup, run_services, check_definitions, required_check_definition_ids, pre_review_check_definition_ids, post_build_check_definition_ids, auto_fix_check_definition_ids, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, helper_models_json, reviewer_model, reviewer_variant, reviewer_models_json, commit_message_rules, commit_after_each_ticket, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_pause_on_open_questions, automation_review_severity, automation_review_max_iterations, } = validation.values;
8209
8069
  const updateProject = db.prepare(`
8210
8070
  UPDATE projects SET
@@ -8262,7 +8122,7 @@ app.patch('/projects/:id', async (req, res) => {
8262
8122
  const nextReviewerVariant = nextReviewerModel
8263
8123
  ? (hasOwn(validation.values, 'reviewer_variant') ? reviewer_variant ?? null : project.reviewer_variant)
8264
8124
  : null;
8265
- const updateResult = db.transaction(() => {
8125
+ const applyUpdate = db.transaction(() => {
8266
8126
  const currentProject = getProjectById(project.id);
8267
8127
  if (!currentProject || JSON.stringify(currentProject) !== JSON.stringify(project)) {
8268
8128
  return {
@@ -8270,6 +8130,15 @@ app.patch('/projects/:id', async (req, res) => {
8270
8130
  status: 409,
8271
8131
  };
8272
8132
  }
8133
+ if (hasOwn(validation.values, 'repo_path') && repo_path !== currentProject.repo_path) {
8134
+ if (currentProject.deletion_started_at || hasActiveProjectServiceRun(project.id) || getActiveRunStatusForProject(project.id)) {
8135
+ return { error: 'Cannot change project repository while service authority or deletion is active', status: 409 };
8136
+ }
8137
+ const stats = getProjectTicketStats(project.id);
8138
+ if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
8139
+ return { error: 'repo_path cannot be changed after tickets, bundles, or worktrees exist for this project', status: 409 };
8140
+ }
8141
+ }
8273
8142
  const policyReferences = validateProjectRequiredCheckPolicyReferences({
8274
8143
  projectId: project.id,
8275
8144
  checkDefinitionsJson: nextCheckDefinitions,
@@ -8301,7 +8170,17 @@ app.patch('/projects/:id', async (req, res) => {
8301
8170
  replaceProjectLinks(project.id, linked_project_ids ?? []);
8302
8171
  }
8303
8172
  return { ok: true };
8304
- }).immediate();
8173
+ });
8174
+ let updateResult;
8175
+ try {
8176
+ updateResult = applyUpdate.immediate();
8177
+ }
8178
+ catch (error) {
8179
+ if (error instanceof Error && 'code' in error && error.code === 'SQLITE_CONSTRAINT_TRIGGER') {
8180
+ return res.status(409).json({ error: error.message });
8181
+ }
8182
+ throw error;
8183
+ }
8305
8184
  if ('error' in updateResult) {
8306
8185
  const status = 'status' in updateResult && typeof updateResult.status === 'number'
8307
8186
  ? updateResult.status
@@ -8312,6 +8191,9 @@ app.patch('/projects/:id', async (req, res) => {
8312
8191
  res.json(serializeProject(updated ?? null));
8313
8192
  });
8314
8193
  const assertProjectDeletionEligible = (projectId, claimId) => {
8194
+ if (hasActiveProjectServiceRun(projectId) || getActiveRunStatusForProject(projectId)) {
8195
+ throw new ProjectDeletionError(409, 'Cannot delete a project while services are active');
8196
+ }
8315
8197
  const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
8316
8198
  if (!project) {
8317
8199
  throw new ProjectDeletionError(404, 'Not found');
@@ -8359,6 +8241,7 @@ const assertProjectDeletionEligible = (projectId, claimId) => {
8359
8241
  }
8360
8242
  };
8361
8243
  const completeClaimedProjectDeletion = async (projectId, claimId) => {
8244
+ assertProjectDeletionEligible(projectId, claimId);
8362
8245
  const marked = db.prepare(`
8363
8246
  UPDATE projects
8364
8247
  SET deletion_cleanup_started_at = COALESCE(deletion_cleanup_started_at, CURRENT_TIMESTAMP)
@@ -8595,6 +8478,17 @@ app.patch('/tickets/:id', async (req, res) => {
8595
8478
  const nextProjectId = hasProjectField ? project_id : ticket.project_id;
8596
8479
  const isProjectChanging = nextProjectId !== ticket.project_id;
8597
8480
  const hasStandaloneWorktree = typeof ticket.branch === 'string' && ticket.branch.trim() !== '';
8481
+ if (isProjectChanging || (hasBundleField && req.body.worktree_bundle_id !== ticket.worktree_bundle_id)) {
8482
+ try {
8483
+ assertTicketServiceTargetMutable(ticket.id);
8484
+ }
8485
+ catch (error) {
8486
+ if (error instanceof HttpError) {
8487
+ return res.status(error.status).json({ error: error.message });
8488
+ }
8489
+ throw error;
8490
+ }
8491
+ }
8598
8492
  if (isProjectChanging && hasStandaloneWorktree) {
8599
8493
  return res.status(409).json({ error: 'project_id cannot be changed after a worktree has been created' });
8600
8494
  }
@@ -8704,10 +8598,23 @@ app.patch('/tickets/:id', async (req, res) => {
8704
8598
  }
8705
8599
  }
8706
8600
  }
8707
- if (isProjectChanging) {
8708
- deleteTicketDependenciesForTicket(ticket.id);
8601
+ try {
8602
+ db.transaction(() => {
8603
+ if (bundleChanged) {
8604
+ assertTicketServiceTargetMutable(ticket.id);
8605
+ }
8606
+ if (isProjectChanging) {
8607
+ deleteTicketDependenciesForTicket(ticket.id);
8608
+ }
8609
+ db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
8610
+ }).immediate();
8611
+ }
8612
+ catch (error) {
8613
+ if (error instanceof HttpError) {
8614
+ return res.status(error.status).json({ error: error.message });
8615
+ }
8616
+ throw error;
8709
8617
  }
8710
- db.prepare('UPDATE tickets SET title = ?, description = ?, model = ?, variant = ?, skills_json = ?, worktree_bundle_id = ?, project_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(title?.trim() ?? ticket.title, description ?? ticket.description, resolvedModel, resolvedVariant, JSON.stringify(resolvedSkills), resolvedBundleId, nextProjectId, req.params.id);
8711
8618
  upsertTicketDescriptionMessage(req.params.id, description ?? ticket.description);
8712
8619
  const updated = getTicket(req.params.id);
8713
8620
  if (bundleChanged && previousBundleId && previousProject) {
@@ -9129,7 +9036,12 @@ app.post('/tickets/:id/undo', (req, res) => {
9129
9036
  return res.json(result.ticket);
9130
9037
  });
9131
9038
  app.post('/tickets/:id/run', (req, res) => {
9039
+ const control = parseServiceRunControl(req.body);
9040
+ if ('error' in control) {
9041
+ return res.status(400).json({ error: control.error });
9042
+ }
9132
9043
  const result = runTicketRun({
9044
+ ...control,
9133
9045
  ticketId: req.params.id,
9134
9046
  requestedProjectId: getRequestedProjectId(req),
9135
9047
  broadcaster: io,
@@ -9390,11 +9302,16 @@ app.post('/projects/:id/open-ide', (req, res) => {
9390
9302
  return res.json({ ok: true });
9391
9303
  });
9392
9304
  app.post('/projects/:id/run', (req, res) => {
9393
- const executionProvider = parseServiceExecutionProviderChoice(req.body);
9305
+ const control = parseServiceRunControl(req.body);
9306
+ if ('error' in control) {
9307
+ return res.status(400).json({ error: control.error });
9308
+ }
9309
+ const executionProvider = parseServiceExecutionProviderChoice(req.body, true);
9394
9310
  if (!executionProvider) {
9395
9311
  return res.status(400).json({ error: 'execution_provider must be project_default or host' });
9396
9312
  }
9397
9313
  const result = runProjectTarget({
9314
+ ...control,
9398
9315
  projectId: req.params.id,
9399
9316
  executionProvider,
9400
9317
  });
@@ -9879,7 +9796,7 @@ app.get('/projects/:id/execution-timeline/:runId', async (req, res) => {
9879
9796
  if (scope.scopeEntity && !links.some(link => link.entity_type === scope.scopeEntity?.type && link.entity_id === scope.scopeEntity.id)) {
9880
9797
  return res.status(404).json({ error: 'Execution run not found' });
9881
9798
  }
9882
- return res.json(await buildExecutionRunDetailResponse(project, run));
9799
+ return res.json(await buildExecutionRunDetailResponse(project, run, scope.scopeEntity));
9883
9800
  });
9884
9801
  app.get('/projects/:id/execution-runs/:runId', async (req, res) => {
9885
9802
  const project = getProjectById(req.params.id);
@@ -10127,7 +10044,12 @@ app.post('/terminals/:sessionId/destroy', (req, res) => {
10127
10044
  return res.status(204).send();
10128
10045
  });
10129
10046
  app.post('/tickets/:id/stop', async (req, res) => {
10047
+ const control = parseServiceRunControl(req.body);
10048
+ if ('error' in control) {
10049
+ return res.status(400).json({ error: control.error });
10050
+ }
10130
10051
  const result = await stopTicketRun({
10052
+ ...control,
10131
10053
  ticketId: req.params.id,
10132
10054
  requestedProjectId: getRequestedProjectId(req),
10133
10055
  broadcaster: io,
@@ -10138,7 +10060,12 @@ app.post('/tickets/:id/stop', async (req, res) => {
10138
10060
  return res.status(result.status).json(result.value);
10139
10061
  });
10140
10062
  app.post('/projects/:id/stop-run', async (req, res) => {
10063
+ const control = parseServiceRunControl(req.body);
10064
+ if ('error' in control) {
10065
+ return res.status(400).json({ error: control.error });
10066
+ }
10141
10067
  const result = await stopProjectTargetRun({
10068
+ ...control,
10142
10069
  projectId: req.params.id,
10143
10070
  });
10144
10071
  if (!result.ok) {