@straiffi/archon 1.10.0 → 1.10.2
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.
- package/dist/client/assets/ActivityView-CdDT5tgr.js +1 -0
- package/dist/client/assets/{GitHubActionsDialog-GWVxWGPK.js → GitHubActionsDialog-NdYtgMhg.js} +1 -1
- package/dist/client/assets/{VerificationDialog-C4RMuI4y.js → VerificationDialog-BfIKtVjt.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CeM6gbLN.js → architectureDiagram-3BPJPVTR-C4zoWnXM.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BP4hta96.js → blockDiagram-GPEHLZMM-ZhRwV8ma.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-5aJScvhn.js → c4Diagram-AAUBKEIU-CA2USxCJ.js} +1 -1
- package/dist/client/assets/channel-C4I-VlJu.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-BKc2IelV.js → chunk-2J33WTMH-CY32Brnt.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DCc15la5.js → chunk-3OPIFGDE-H70UH7rH.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-DSD9HbTp.js → chunk-5ZQYHXKU-DC1dllSt.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DrAjnKiA.js → chunk-727SXJPM-DVIgI7hM.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DxKnpHfQ.js → chunk-AQP2D5EJ-gwSYEPou.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DLejrAB_.js → chunk-CSCIHK7Q-Z1cfABPc.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-BWRNmF8S.js → chunk-KSCS5N6A-DFoW6dtq.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-D48i966f.js → chunk-L5ZTLDWV-CmQbbP8b.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-DLhg6Bok.js → chunk-LZXEDZCA-Ba16nX-R.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-B4-7cOEq.js → chunk-ND2GUHAM-Bc7u1Zqm.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-D2ttXA_d.js → chunk-NZK2D7GU-DNXYx7JX.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-BrWrOVCq.js → chunk-O5CBEL6O-Ba00cuZ5.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CLcPwxPe.js → chunk-WU5MYG2G-SJ_ny5dU.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-Ca3KxBnW.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CSpMessw.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DIXN8_VX.js → dagre-BM42HDAG-CL2dKbj9.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-D2wjdCij.js → diagram-2AECGRRQ-DQq1qZ0l.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-E1Og_PHE.js → diagram-5GNKFQAL-Zf0YQuRe.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-B6P1Ud8a.js → diagram-KO2AKTUF-DcUxL9SG.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-C3Jxq56v.js → diagram-LMA3HP47-BAIixEu7.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-Do9LVhdL.js → diagram-OG6HWLK6-Dhhjm84p.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-DLb5E4-f.js → erDiagram-TEJ5UH35-BcQOacBP.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-CcTs_wU9.js → flowDiagram-I6XJVG4X-BZIJqLp3.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-q9ez5fri.js → ganttDiagram-6RSMTGT7-DFC6qgrY.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BcaKiz4c.js → gitGraphDiagram-PVQCEYII-Ds0nwSU0.js} +1 -1
- package/dist/client/assets/index-Bg3l17L3.css +2 -0
- package/dist/client/assets/index-D_KmViSu.js +276 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-Cu0w4frE.js → infoDiagram-5YYISTIA-DfDrR5Mz.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CJ47Z-ak.js → ishikawaDiagram-YF4QCWOH-B2v4Byji.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-fAL_Zcvs.js → journeyDiagram-JHISSGLW-CO6laklO.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BTNIybMC.js → kanban-definition-UN3LZRKU-BziFO199.js} +1 -1
- package/dist/client/assets/{line-BvsadeZR.js → line-Vb0A8-Oe.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-C0EkH2y_.js → mermaid-parser.core-Bm3Xv0Zt.js} +1 -1
- package/dist/client/assets/{mermaid.core-BU5QIIbY.js → mermaid.core-DXBRp0XG.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-CWbSYK8f.js → mindmap-definition-RKZ34NQL-4GueJvPT.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-t4w2cB-y.js → pieDiagram-4H26LBE5-Djy0sVJR.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Cqasvrpa.js → quadrantDiagram-W4KKPZXB-3rWAi4K5.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-_l-mY-tb.js → requirementDiagram-4Y6WPE33-D7-WCnjH.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Cr9YrZ-Y.js → sankeyDiagram-5OEKKPKP-B4-3BUQ2.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DvzZhQpb.js → sequenceDiagram-3UESZ5HK-DCWomuSc.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-cFjPpAO3.js → stateDiagram-AJRCARHV-DpsWWFaO.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BE_IAuYt.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BjRNrEJY.js → timeline-definition-PNZ67QCA-B0Mh4MHF.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-xs9afjrw.js → vennDiagram-CIIHVFJN-Dpxr8LTG.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DH68ZYti.js → wardleyDiagram-YWT4CUSO-BdAz5ERd.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DLW5ktTU.js → xychartDiagram-2RQKCTM6-BV9Ah-jk.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +640 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +795 -125
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agentCatalog.js +35 -0
- package/dist/server/lib/agentCatalog.js.map +1 -0
- package/dist/server/lib/agentModelConfig.js +151 -0
- package/dist/server/lib/agentModelConfig.js.map +1 -0
- package/dist/server/lib/agentRuns.js +1 -0
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationCheckGate.js +98 -9
- package/dist/server/lib/automationCheckGate.js.map +1 -1
- package/dist/server/lib/automationLoopAgentEvidence.js +42 -0
- package/dist/server/lib/automationLoopAgentEvidence.js.map +1 -0
- package/dist/server/lib/automationLoopProjection.js +339 -0
- package/dist/server/lib/automationLoopProjection.js.map +1 -0
- package/dist/server/lib/automationProgression.js +115 -6
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationReconciliation.js +27 -11
- package/dist/server/lib/automationReconciliation.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +55 -15
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/automationReviewGate.js +352 -26
- package/dist/server/lib/automationReviewGate.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +7 -4
- package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/buildFlow.js +12 -0
- package/dist/server/lib/buildFlow.js.map +1 -1
- package/dist/server/lib/bundleReviewOperations.js +40 -23
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +26 -4
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/checkAutoFix.js +5 -4
- package/dist/server/lib/checkAutoFix.js.map +1 -1
- package/dist/server/lib/checkAutoFixDetails.js +54 -9
- package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
- package/dist/server/lib/checkGateEvaluations.js +6 -1
- package/dist/server/lib/checkGateEvaluations.js.map +1 -1
- package/dist/server/lib/checkRepair.js +30 -0
- package/dist/server/lib/checkRepair.js.map +1 -1
- package/dist/server/lib/claudeAgentProvider.js +19 -0
- package/dist/server/lib/claudeAgentProvider.js.map +1 -1
- package/dist/server/lib/claudeValidationOutput.js +4 -4
- package/dist/server/lib/claudeValidationOutput.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +48 -7
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/executionEnvironmentValidation.js +19 -10
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +30 -0
- package/dist/server/lib/executionRunAuthority.js.map +1 -1
- package/dist/server/lib/executionRuns.js +17 -3
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +3 -0
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/lightweightPrompt.js +13 -40
- package/dist/server/lib/lightweightPrompt.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +47 -3
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/modelResolutionErrors.js +24 -0
- package/dist/server/lib/modelResolutionErrors.js.map +1 -0
- package/dist/server/lib/models.js +9 -9
- package/dist/server/lib/models.js.map +1 -1
- package/dist/server/lib/postBuildCheckRuns.js +79 -2
- package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +15 -52
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projectMemoryScan.js +15 -9
- package/dist/server/lib/projectMemoryScan.js.map +1 -1
- package/dist/server/lib/projects.js +59 -4
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/reviewRepairCompletion.js +126 -0
- package/dist/server/lib/reviewRepairCompletion.js.map +1 -0
- package/dist/server/lib/reviewRepairIterationRecords.js +149 -0
- package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -0
- package/dist/server/lib/reviewRepairIterations.js +590 -0
- package/dist/server/lib/reviewRepairIterations.js.map +1 -0
- package/dist/server/lib/reviewRepairReconciliation.js +588 -0
- package/dist/server/lib/reviewRepairReconciliation.js.map +1 -0
- package/dist/server/lib/reviewerConfig.js +18 -7
- package/dist/server/lib/reviewerConfig.js.map +1 -1
- package/dist/server/lib/run.js +7 -2
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/specArtifact.js +1 -1
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specGraphFeature.js +2 -2
- package/dist/server/lib/specGraphFeature.js.map +1 -1
- package/dist/server/lib/specOperations.js +26 -4
- package/dist/server/lib/specOperations.js.map +1 -1
- package/dist/server/lib/specRepositoryEvidenceFormatting.js +2 -2
- package/dist/server/lib/specRepositoryEvidenceFormatting.js.map +1 -1
- package/dist/server/lib/specRepositoryImpact.js +3 -3
- package/dist/server/lib/specRepositoryImpact.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +7 -1
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specReviewOperations.js +20 -7
- package/dist/server/lib/specReviewOperations.js.map +1 -1
- package/dist/server/lib/specSnapshotEvidence.js +3 -3
- package/dist/server/lib/specSnapshotEvidence.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +9 -11
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketAutomationCommit.js +21 -11
- package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +25 -7
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketSettingsOperations.js +9 -0
- package/dist/server/lib/ticketSettingsOperations.js.map +1 -1
- package/dist/server/lib/ticketWorkflowOperations.js +2 -6
- package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +16 -6
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js +45 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +111 -1
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +109 -22
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/review.js +292 -60
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +17 -8
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +33 -23
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-Y5IaJiim.js +0 -1
- package/dist/client/assets/channel-B3DjZXbm.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DproSkn2.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-JVMMzJxJ.js +0 -1
- package/dist/client/assets/index-1CokwSFm.css +0 -2
- package/dist/client/assets/index-CSOKbu3w.js +0 -276
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-btHMH5Cg.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -48,6 +48,8 @@ import { createExecutionProfileSelection, executionProfileSelectionMetadata, val
|
|
|
48
48
|
import { prepareTicketForBuild } from './lib/buildFlow.js';
|
|
49
49
|
import { getActiveBundleRepairBlocker, getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
|
|
50
50
|
import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
|
|
51
|
+
import { getSupportedAgentCatalog } from './lib/agentCatalog.js';
|
|
52
|
+
import { parseStoredAgentModelConfig, resolveHelperModelConfig, serializeAgentModelConfig, validateAgentModelConfigUpdate, validateDiscoveredAgentModelConfig, } from './lib/agentModelConfig.js';
|
|
51
53
|
import { upgradeTool, UpgradeInProgressError } from './lib/toolUpgrade.js';
|
|
52
54
|
import { acceptProjectMemorySuggestion, applyProjectMemorySuggestionToDecision, applyProjectMemorySuggestionToConvention, archiveProjectConvention, archiveProjectDecision, createProjectConvention, createProjectDecision, dismissProjectMemorySuggestion, getProjectMemoryById, getProjectMemorySuggestionById, isProjectMemoryEnabled, listProjectConventions, listLatestSuccessfulProjectContextArtifacts, listProjectDecisions, listProjectMemories, listProjectMemorySuggestions, restoreProjectConvention, restoreProjectDecision, serializeProjectMemoryFromConventionValue, serializeProjectMemoryFromDecisionValue, serializeProjectMemoryFromSuggestionValue, supersedeProjectDecision, supersedeProjectDecisionFromSuggestion, updateProjectConvention, updateProjectDecision, validateProjectConventionPayload, validateProjectDecisionPayload, } from './lib/projectMemory.js';
|
|
53
55
|
import { selectRelevantProjectMemory } from './lib/projectMemoryPrompt.js';
|
|
@@ -64,20 +66,23 @@ import { buildReviewFindingsResponse, computeDiffSignature, getLatestCompletedRe
|
|
|
64
66
|
import { formatBuildOpenQuestionAnswersPrompt, getLatestBuildOpenQuestionBatchStatus, hasActiveBuildOpenQuestionBatch, listBuildOpenQuestionsForBundle, loadBuildOpenQuestionsByBundleIds, } from './lib/buildOpenQuestions.js';
|
|
65
67
|
import { acceptBuildOpenQuestionResolution } from './lib/buildOpenQuestionOperations.js';
|
|
66
68
|
import { areBundleTicketsReadyForAutomaticReview } from './lib/bundleAutomationReadiness.js';
|
|
67
|
-
import { abandonAutomationPause, completeAutomationProgression,
|
|
69
|
+
import { abandonAutomationPause, completeAutomationProgression, completeAutomationProgressionInTransaction, completeAutomationReviewFixActionInTransaction, createAutomationReviewFixInTransaction, ensureAutomationProgression, finishAutomationReviewFix, getAutomationProgression, recoverLegacyAutomationProgression, recoverStoppedSideEffectProgression, runAutomationProgression, stopAutomationProgression, waitAutomationProgression } from './lib/automationProgression.js';
|
|
68
70
|
import { reconcileAutomationStage } from './lib/automationReconciliation.js';
|
|
69
71
|
import { runAutomationReviewGateOrPause } from './lib/automationReviewGate.js';
|
|
70
72
|
import { pausedRunBelongsToProgression, resolveAutomationResumeDecision } from './lib/automationResumeDecision.js';
|
|
73
|
+
import { isLegacyReviewRepairMigrationPause } from './lib/reviewRepairReconciliation.js';
|
|
71
74
|
import { getAutomationStageDetails, } from './lib/automationStageDetails.js';
|
|
72
|
-
import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationDepth, recordAutomationPause,
|
|
75
|
+
import { buildAutomationFixInstruction, clearAutomationPause, countBlockingFindings, depthAtLeast, getBundleAutomationState, isAutomationActive, isAutomationDepth, recordAutomationPause, resetBundleAutomationLoop, resolveAutomationConfig, } from './lib/ticketAutomation.js';
|
|
73
76
|
import { resolveAutomationCheckGateResume, runAutomationCommitChain, runAutomationCommitOperation } from './lib/ticketAutomationCommit.js';
|
|
74
77
|
import { resolveBuildTicketCommitCheckpointLineage, canMutateBuildTicketCommitCheckpoint } from './lib/buildTicketCommit.js';
|
|
75
78
|
import { continueAfterBuildCheckpoint } from './lib/automationBuildContinuation.js';
|
|
76
79
|
import { getDiscoveredSkills, normalizeSkillNames } from './lib/skills.js';
|
|
77
80
|
import { clearAllPreviewProxies } from './lib/previewProxy.js';
|
|
78
81
|
import { createExecutionRun, addExecutionRunLink, addExecutionRunLinks, getExecutionRun, getLifecycleEvent, hasActiveBuildExecutionRunForTicket, getGitHubActionsExecutionDetails, getTestExecutionDetails, GitHubActionsDispatchConflictError, listExecutionRunLinks, listExecutionRuns, listLifecycleEvents, listProjectLifecycleEvents, toCanonicalLifecycleEvent, transitionExecutionRun, withExecutionRunTransaction, } from './lib/executionRuns.js';
|
|
82
|
+
import { appendReviewRepairIterationDecisionInTransaction, attachReviewRepairResultInTransaction, attachReviewRepairActionEnvelopeInTransaction, attachReviewRepairActionInTransaction, claimReviewRepairIterationInTransaction, assertReviewRepairIterationDeletionEligible, deleteTerminalReviewRepairIterationsForTicketInTransaction, getActiveReviewRepairIteration, getReviewRepairIteration, pauseReviewRepairIteration, rewindStaleReviewRepairVerification, replaceReviewRepairActionEnvelopeRetryInTransaction, replaceReviewRepairActionRetryInTransaction, replaceReviewRepairVerificationRetryInTransaction, resetReviewRepairGateForRetry, resumePausedReviewRepairIteration, terminalizeReviewRepairIteration, terminalizeReviewRepairIterationInTransaction, } from './lib/reviewRepairIterations.js';
|
|
79
83
|
import { dispatchExecutionRetry, getExecutionRetryEligibility, resolveExecutionRetry } from './lib/executionRetry.js';
|
|
80
84
|
import { listExecutionTimeline } from './lib/executionTimeline.js';
|
|
85
|
+
import { loadReviewAutomationLoopDetails } from './lib/automationLoopProjection.js';
|
|
81
86
|
import { loadExecutionArtifactSummariesForRuns, openExecutionArtifactForRetrieval, } from './lib/executionArtifacts.js';
|
|
82
87
|
import { cleanupAllExecutionEvidence, EXECUTION_EVIDENCE_CLEANUP_INTERVAL_MS, resolveExecutionEvidenceRetentionDays, } from './lib/executionRunRetention.js';
|
|
83
88
|
import { ExecutionArtifactCleanupError, reconcileExecutionArtifactRegistrationIntents, reconcileFinalizedExecutionArtifacts, reconcileProjectExecutionArtifacts, resolveArtifactCleanupIntervalMs, } from './lib/executionArtifactRetention.js';
|
|
@@ -134,7 +139,7 @@ import { buildSpecImplementationDraft } from './lib/specDraft.js';
|
|
|
134
139
|
import { materializeSpecSnapshotTickets } from './lib/specTicketMaterialization.js';
|
|
135
140
|
import { exportSpecToDocs, checkSpecExportCollision, SpecExportCollisionError } from './lib/specExport.js';
|
|
136
141
|
import { createTicketDependency, deleteTicketDependenciesForTicket, deleteTicketDependency, getTicketDependency, isTicketBuildComplete, isTicketRunSetupReady, listTicketDependencies, validateCreateTicketDependency, } from './lib/ticketDependencies.js';
|
|
137
|
-
import { startBuild } from './workers/build.js';
|
|
142
|
+
import { startBuild, startPostBuildCheckRecovery } from './workers/build.js';
|
|
138
143
|
import { expandPlanningTicketOutline, generatePlanningTicketOutline, generatePlanText, prepareLinkedPlanningResearch, startPlan, stopAllPlanAgents, } from './workers/plan.js';
|
|
139
144
|
import { EXECUTION_RUN_KINDS, MAX_ARTIFACTS_PER_RUN, validateWorkflowContext } from './lib/types.js';
|
|
140
145
|
class ProjectDeletionError extends Error {
|
|
@@ -483,6 +488,9 @@ const buildExecutionRunDetailResponse = async (project, run) => {
|
|
|
483
488
|
agent_runs: listAgentRunsForExecutionRun(run.id),
|
|
484
489
|
...artifacts,
|
|
485
490
|
environment_context: getExecutionEnvironmentContextForRun(project.id, run.id),
|
|
491
|
+
automation_loops: run.kind === 'automation_progression'
|
|
492
|
+
? loadReviewAutomationLoopDetails(project.id, run.id)
|
|
493
|
+
: [],
|
|
486
494
|
};
|
|
487
495
|
};
|
|
488
496
|
const formatWorktreeDeleteError = (target, branch, error) => {
|
|
@@ -568,6 +576,18 @@ const cleanupWorktreeExecutionTarget = async ({ project, targetKind, targetKey,
|
|
|
568
576
|
return { branchWarning: buildBranchCleanupWarning(branch, deleteLocalBranch(branch, project)) };
|
|
569
577
|
};
|
|
570
578
|
const deleteTicketRecord = async (ticket) => {
|
|
579
|
+
if (ticket.project_id) {
|
|
580
|
+
try {
|
|
581
|
+
assertReviewRepairIterationDeletionEligible(ticket.project_id, ticket.id);
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
return {
|
|
585
|
+
ok: false,
|
|
586
|
+
status: 409,
|
|
587
|
+
error: error instanceof Error ? error.message : 'Unable to validate Review repair history',
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
571
591
|
if (ticket.project_id
|
|
572
592
|
&& ticket.worktree_bundle_id
|
|
573
593
|
&& hasActiveBundleCheckWork(ticket.project_id, ticket.worktree_bundle_id)) {
|
|
@@ -644,7 +664,21 @@ const deleteTicketRecord = async (ticket) => {
|
|
|
644
664
|
affectedTicketIds = sequenceRemoval.affectedTicketIds;
|
|
645
665
|
}
|
|
646
666
|
}
|
|
647
|
-
|
|
667
|
+
try {
|
|
668
|
+
withExecutionRunTransaction(transaction => {
|
|
669
|
+
if (ticket.project_id) {
|
|
670
|
+
deleteTerminalReviewRepairIterationsForTicketInTransaction(transaction, ticket.project_id, ticket.id);
|
|
671
|
+
}
|
|
672
|
+
db.prepare('DELETE FROM tickets WHERE id = ?').run(ticket.id);
|
|
673
|
+
}, io, 'immediate');
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
status: 409,
|
|
679
|
+
error: error instanceof Error ? error.message : 'Unable to delete Review repair history',
|
|
680
|
+
};
|
|
681
|
+
}
|
|
648
682
|
try {
|
|
649
683
|
clearTicketStageSnapshots(ticket.id);
|
|
650
684
|
}
|
|
@@ -1002,6 +1036,34 @@ const asProjectPayload = (body) => {
|
|
|
1002
1036
|
}
|
|
1003
1037
|
return body;
|
|
1004
1038
|
};
|
|
1039
|
+
const validateProjectRoleModelDiscovery = async (body, values, currentProject) => {
|
|
1040
|
+
for (const role of ['helper', 'reviewer']) {
|
|
1041
|
+
const mapField = `${role}_models`;
|
|
1042
|
+
const jsonField = `${role}_models_json`;
|
|
1043
|
+
const hasMapUpdate = Object.hasOwn(body, mapField);
|
|
1044
|
+
const hasLegacyUpdate = Object.hasOwn(body, `${role}_model`) || Object.hasOwn(body, `${role}_variant`);
|
|
1045
|
+
if ((!hasMapUpdate && !hasLegacyUpdate) || !values[jsonField]) {
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
const submittedAgents = hasMapUpdate
|
|
1049
|
+
? Object.keys(body[mapField])
|
|
1050
|
+
: ['opencode'];
|
|
1051
|
+
const nextConfig = parseStoredAgentModelConfig(values[jsonField]);
|
|
1052
|
+
const currentConfig = parseStoredAgentModelConfig(currentProject?.[jsonField]);
|
|
1053
|
+
const agents = currentProject
|
|
1054
|
+
? submittedAgents.filter(agent => (nextConfig[agent]?.model !== currentConfig[agent]?.model
|
|
1055
|
+
|| nextConfig[agent]?.variant !== currentConfig[agent]?.variant))
|
|
1056
|
+
: submittedAgents;
|
|
1057
|
+
if (agents.length === 0) {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const validation = await validateDiscoveredAgentModelConfig(nextConfig, mapField, agents);
|
|
1061
|
+
if ('error' in validation) {
|
|
1062
|
+
return validation;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return { ok: true };
|
|
1066
|
+
};
|
|
1005
1067
|
const isWorktreeReady = (branch, project) => {
|
|
1006
1068
|
return existsSync(resolveWorktreePath(branch, project));
|
|
1007
1069
|
};
|
|
@@ -1651,7 +1713,7 @@ const startBundlePullRequestDiscoveryIfNeeded = (project, bundle) => {
|
|
|
1651
1713
|
})();
|
|
1652
1714
|
bundlePullRequestDiscoveryInFlight.set(key, task);
|
|
1653
1715
|
};
|
|
1654
|
-
const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuildOpenQuestionsByBundleIds(projectId, [bundle.id]).get(bundle.id) ?? [] : [], resolvedProject) => {
|
|
1716
|
+
const serializeBundleRow = (projectId, bundle, agent, openQuestions = bundle ? loadBuildOpenQuestionsByBundleIds(projectId, [bundle.id]).get(bundle.id) ?? [] : [], resolvedProject) => {
|
|
1655
1717
|
if (!bundle) {
|
|
1656
1718
|
return null;
|
|
1657
1719
|
}
|
|
@@ -1660,11 +1722,20 @@ const serializeBundleRow = (projectId, bundle, openQuestions = bundle ? loadBuil
|
|
|
1660
1722
|
? true
|
|
1661
1723
|
: Boolean(project && resolveExistingWorktreePath(bundle.branch, project));
|
|
1662
1724
|
const effectivePolicy = resolveEffectiveRequiredCheckPolicyMembership(project);
|
|
1725
|
+
const reviewerModels = bundle.kind === 'project_root'
|
|
1726
|
+
? {}
|
|
1727
|
+
: parseStoredAgentModelConfig(bundle.reviewer_models_json);
|
|
1728
|
+
const reviewerOpenCode = reviewerModels.opencode;
|
|
1729
|
+
const { reviewer_models_json: _reviewerModelsJson, ...serializedRow } = bundle;
|
|
1730
|
+
void _reviewerModelsJson;
|
|
1663
1731
|
return {
|
|
1664
|
-
...
|
|
1665
|
-
reviewer_model:
|
|
1666
|
-
reviewer_variant:
|
|
1667
|
-
|
|
1732
|
+
...serializedRow,
|
|
1733
|
+
reviewer_model: reviewerOpenCode?.model ?? null,
|
|
1734
|
+
reviewer_variant: reviewerOpenCode?.model ? reviewerOpenCode.variant : null,
|
|
1735
|
+
reviewer_models: reviewerModels,
|
|
1736
|
+
reviewer_config: resolveReviewerConfig(bundle.kind === 'project_root' ? null : {
|
|
1737
|
+
reviewer_models: reviewerModels,
|
|
1738
|
+
}, project, undefined, agent),
|
|
1668
1739
|
has_prepared_worktree: hasPreparedWorktree,
|
|
1669
1740
|
pull_request: getGitHubConnectionConfig(projectId)
|
|
1670
1741
|
? serializeBundlePullRequest(getBundlePullRequestRecord(bundle.id, projectId))
|
|
@@ -1879,8 +1950,9 @@ const resolveBundleRenameEligibility = (project, bundle, tickets = listBundleTic
|
|
|
1879
1950
|
return { eligible: true, reason: null };
|
|
1880
1951
|
};
|
|
1881
1952
|
const serializeBundleDetails = (project, bundle, extras = {}) => {
|
|
1953
|
+
const agent = config.tool ?? 'opencode';
|
|
1882
1954
|
const tickets = listDetailedBundleTickets(project.id, bundle.id);
|
|
1883
|
-
const serializedBundle = serializeBundleRow(project.id, bundle, tickets.flatMap(ticket => ticket.build_open_questions ?? []), project);
|
|
1955
|
+
const serializedBundle = serializeBundleRow(project.id, bundle, agent, tickets.flatMap(ticket => ticket.build_open_questions ?? []), project);
|
|
1884
1956
|
if (!serializedBundle) {
|
|
1885
1957
|
throw new Error(`Failed to serialize bundle ${bundle.id}`);
|
|
1886
1958
|
}
|
|
@@ -1907,7 +1979,10 @@ const serializeBundleDetails = (project, bundle, extras = {}) => {
|
|
|
1907
1979
|
review_recovery_available: getBundleAutomationState(project.id, bundle.id)?.automation_paused_reason
|
|
1908
1980
|
? false
|
|
1909
1981
|
: getBundleReviewRecovery(project.id, bundle.id) !== null,
|
|
1910
|
-
has_helper_model: Boolean(
|
|
1982
|
+
has_helper_model: Boolean(resolveHelperModelConfig({
|
|
1983
|
+
helperModelsJson: project.helper_models_json,
|
|
1984
|
+
agent,
|
|
1985
|
+
}).model),
|
|
1911
1986
|
recent_commit_subjects: getBundleRecentCommitSubjects(project, bundle),
|
|
1912
1987
|
};
|
|
1913
1988
|
};
|
|
@@ -3631,7 +3706,7 @@ app.get('/bundles', async (req, res) => {
|
|
|
3631
3706
|
const bundles = listBundles(result.project.id);
|
|
3632
3707
|
timing.mark('list_bundles');
|
|
3633
3708
|
const openQuestionsByBundleId = loadBuildOpenQuestionsByBundleIds(result.project.id, bundles.map(bundle => bundle.id));
|
|
3634
|
-
const serializedBundles = bundles.map(bundle => serializeBundleRow(result.project.id, bundle, openQuestionsByBundleId.get(bundle.id) ?? [], result.project));
|
|
3709
|
+
const serializedBundles = bundles.map(bundle => serializeBundleRow(result.project.id, bundle, config.tool ?? 'opencode', openQuestionsByBundleId.get(bundle.id) ?? [], result.project));
|
|
3635
3710
|
timing.mark('serialize_bundles');
|
|
3636
3711
|
timing.end({ count: serializedBundles.length });
|
|
3637
3712
|
res.json(serializedBundles);
|
|
@@ -3755,7 +3830,12 @@ app.post('/bundles/:id/pull-request-description', async (req, res) => {
|
|
|
3755
3830
|
if (isProjectRootBundle(bundle)) {
|
|
3756
3831
|
return res.status(409).json({ error: 'Pull request descriptions are not available for the project root bundle.' });
|
|
3757
3832
|
}
|
|
3758
|
-
|
|
3833
|
+
const helperTool = config.tool ?? 'opencode';
|
|
3834
|
+
const helperConfig = resolveHelperModelConfig({
|
|
3835
|
+
helperModelsJson: result.project.helper_models_json,
|
|
3836
|
+
agent: helperTool,
|
|
3837
|
+
});
|
|
3838
|
+
if (!helperConfig.model) {
|
|
3759
3839
|
return res.status(409).json({ error: 'Set a helper model in project settings before generating a PR description.' });
|
|
3760
3840
|
}
|
|
3761
3841
|
const title = asTrimmedString(req.body?.title);
|
|
@@ -3772,9 +3852,9 @@ app.post('/bundles/:id/pull-request-description', async (req, res) => {
|
|
|
3772
3852
|
try {
|
|
3773
3853
|
const worktreePath = resolveWorktreePath(bundle.branch, result.project);
|
|
3774
3854
|
const rawDescription = await runLightweightPrompt({
|
|
3775
|
-
tool:
|
|
3776
|
-
model:
|
|
3777
|
-
variant:
|
|
3855
|
+
tool: helperTool,
|
|
3856
|
+
model: helperConfig.model,
|
|
3857
|
+
variant: helperConfig.variant,
|
|
3778
3858
|
}, buildPullRequestDescriptionPrompt({
|
|
3779
3859
|
title,
|
|
3780
3860
|
ticketTitles,
|
|
@@ -4002,7 +4082,7 @@ app.post('/bundles', async (req, res) => {
|
|
|
4002
4082
|
});
|
|
4003
4083
|
}
|
|
4004
4084
|
}
|
|
4005
|
-
res.status(201).json(serializeBundleRow(result.project.id, bundle, [], result.project));
|
|
4085
|
+
res.status(201).json(serializeBundleRow(result.project.id, bundle, config.tool ?? 'opencode', [], result.project));
|
|
4006
4086
|
});
|
|
4007
4087
|
app.delete('/bundles/:id', async (req, res) => {
|
|
4008
4088
|
const result = getRequiredProject(req);
|
|
@@ -4075,7 +4155,7 @@ app.delete('/bundles/:id', async (req, res) => {
|
|
|
4075
4155
|
}
|
|
4076
4156
|
res.status(204).end();
|
|
4077
4157
|
});
|
|
4078
|
-
app.patch('/bundles/:id', (req, res) => {
|
|
4158
|
+
app.patch('/bundles/:id', async (req, res) => {
|
|
4079
4159
|
const result = getRequiredProject(req);
|
|
4080
4160
|
if ('error' in result) {
|
|
4081
4161
|
return res.status(400).json({ error: result.error });
|
|
@@ -4085,7 +4165,7 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
4085
4165
|
return res.status(404).json({ error: 'Bundle not found' });
|
|
4086
4166
|
}
|
|
4087
4167
|
const hasNameUpdate = hasOwn(req.body, 'name');
|
|
4088
|
-
const hasReviewerUpdate = hasOwn(req.body, 'reviewer_model') || hasOwn(req.body, 'reviewer_variant');
|
|
4168
|
+
const hasReviewerUpdate = hasOwn(req.body, 'reviewer_models') || hasOwn(req.body, 'reviewer_model') || hasOwn(req.body, 'reviewer_variant');
|
|
4089
4169
|
if (hasOwn(req.body, 'required_check_policy_override')) {
|
|
4090
4170
|
return res.status(400).json({ error: 'Bundle required-check policy overrides are no longer supported; configure required checks on the project' });
|
|
4091
4171
|
}
|
|
@@ -4181,8 +4261,32 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
4181
4261
|
return res.status(400).json({ error: 'automation_review_max_iterations must be an integer between 1 and 10' });
|
|
4182
4262
|
}
|
|
4183
4263
|
}
|
|
4184
|
-
let
|
|
4185
|
-
let
|
|
4264
|
+
let reviewerModels = parseStoredAgentModelConfig(bundle.reviewer_models_json);
|
|
4265
|
+
let reviewerModel = reviewerModels.opencode?.model ?? null;
|
|
4266
|
+
let reviewerVariant = reviewerModel ? reviewerModels.opencode?.variant ?? null : null;
|
|
4267
|
+
let reviewerModelsPatch = null;
|
|
4268
|
+
if (hasOwn(req.body, 'reviewer_models')) {
|
|
4269
|
+
if (hasOwn(req.body, 'reviewer_model') || hasOwn(req.body, 'reviewer_variant')) {
|
|
4270
|
+
return res.status(400).json({
|
|
4271
|
+
error: 'reviewer_models cannot be provided with legacy reviewer_model or reviewer_variant fields',
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
const result = validateAgentModelConfigUpdate(req.body.reviewer_models, 'reviewer_models');
|
|
4275
|
+
if ('error' in result) {
|
|
4276
|
+
return res.status(400).json({ error: result.error });
|
|
4277
|
+
}
|
|
4278
|
+
const submittedAgents = Object.keys(req.body.reviewer_models);
|
|
4279
|
+
const changedAgents = submittedAgents.filter(agent => (result.value[agent]?.model !== reviewerModels[agent]?.model
|
|
4280
|
+
|| result.value[agent]?.variant !== reviewerModels[agent]?.variant));
|
|
4281
|
+
const discoveryValidation = await validateDiscoveredAgentModelConfig(result.value, 'reviewer_models', changedAgents);
|
|
4282
|
+
if ('error' in discoveryValidation) {
|
|
4283
|
+
return res.status(400).json({ error: discoveryValidation.error });
|
|
4284
|
+
}
|
|
4285
|
+
reviewerModelsPatch = result.value;
|
|
4286
|
+
reviewerModels = { ...reviewerModels, ...result.value };
|
|
4287
|
+
reviewerModel = reviewerModels.opencode?.model ?? null;
|
|
4288
|
+
reviewerVariant = reviewerModel ? reviewerModels.opencode?.variant ?? null : null;
|
|
4289
|
+
}
|
|
4186
4290
|
if (hasOwn(req.body, 'reviewer_model')) {
|
|
4187
4291
|
const value = req.body.reviewer_model;
|
|
4188
4292
|
if (value == null) {
|
|
@@ -4206,16 +4310,34 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
4206
4310
|
reviewerVariant = null;
|
|
4207
4311
|
}
|
|
4208
4312
|
else if (typeof value === 'string' && value.trim()) {
|
|
4209
|
-
reviewerVariant =
|
|
4313
|
+
reviewerVariant = value.trim();
|
|
4210
4314
|
}
|
|
4211
4315
|
else {
|
|
4212
4316
|
return res.status(400).json({ error: 'reviewer_variant must be a non-empty string' });
|
|
4213
4317
|
}
|
|
4214
4318
|
}
|
|
4319
|
+
if (!hasOwn(req.body, 'reviewer_models') && (hasOwn(req.body, 'reviewer_model') || hasOwn(req.body, 'reviewer_variant'))) {
|
|
4320
|
+
const result = validateAgentModelConfigUpdate({
|
|
4321
|
+
opencode: { model: reviewerModel, variant: reviewerVariant },
|
|
4322
|
+
}, 'reviewer_models');
|
|
4323
|
+
if ('error' in result) {
|
|
4324
|
+
return res.status(400).json({ error: result.error });
|
|
4325
|
+
}
|
|
4326
|
+
const discoveryValidation = await validateDiscoveredAgentModelConfig(result.value, 'reviewer_models', ['opencode']);
|
|
4327
|
+
if ('error' in discoveryValidation) {
|
|
4328
|
+
return res.status(400).json({ error: discoveryValidation.error });
|
|
4329
|
+
}
|
|
4330
|
+
reviewerModelsPatch = result.value;
|
|
4331
|
+
reviewerModels = { ...reviewerModels, ...result.value };
|
|
4332
|
+
}
|
|
4215
4333
|
let updated = null;
|
|
4216
4334
|
try {
|
|
4217
4335
|
updated = db.transaction(() => {
|
|
4218
|
-
|
|
4336
|
+
const currentBundle = getBundle(bundle.id, result.project.id);
|
|
4337
|
+
if (!currentBundle) {
|
|
4338
|
+
return null;
|
|
4339
|
+
}
|
|
4340
|
+
if (hasNameUpdate && nextName !== currentBundle.name) {
|
|
4219
4341
|
updateBundleIdentity({
|
|
4220
4342
|
bundleId: bundle.id,
|
|
4221
4343
|
projectId: result.project.id,
|
|
@@ -4223,21 +4345,36 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
4223
4345
|
branch: nextBranch,
|
|
4224
4346
|
});
|
|
4225
4347
|
}
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4348
|
+
if (hasOwn(req.body, 'automation_depth')
|
|
4349
|
+
|| hasOwn(req.body, 'automation_review_fix_enabled')
|
|
4350
|
+
|| hasOwn(req.body, 'automation_pause_on_open_questions')
|
|
4351
|
+
|| hasOwn(req.body, 'automation_review_max_iterations')) {
|
|
4352
|
+
updateBundleAutomationConfig({
|
|
4353
|
+
bundleId: bundle.id,
|
|
4354
|
+
projectId: result.project.id,
|
|
4355
|
+
automationDepth: hasOwn(req.body, 'automation_depth') ? automationDepth : currentBundle.automation_depth ?? null,
|
|
4356
|
+
automationReviewFixEnabled: hasOwn(req.body, 'automation_review_fix_enabled') ? automationReviewFixEnabled : currentBundle.automation_review_fix_enabled ?? null,
|
|
4357
|
+
automationPauseOnOpenQuestions: hasOwn(req.body, 'automation_pause_on_open_questions') ? automationPauseOnOpenQuestions : currentBundle.automation_pause_on_open_questions ?? null,
|
|
4358
|
+
automationReviewMaxIterations: hasOwn(req.body, 'automation_review_max_iterations') ? automationReviewMaxIterations : currentBundle.automation_review_max_iterations ?? null,
|
|
4359
|
+
});
|
|
4360
|
+
}
|
|
4361
|
+
if (!reviewerModelsPatch) {
|
|
4362
|
+
return getBundle(bundle.id, result.project.id);
|
|
4363
|
+
}
|
|
4364
|
+
const currentReviewerModels = parseStoredAgentModelConfig(currentBundle.reviewer_models_json);
|
|
4365
|
+
const mergedReviewerModels = validateAgentModelConfigUpdate(reviewerModelsPatch, 'reviewer_models', currentReviewerModels);
|
|
4366
|
+
if ('error' in mergedReviewerModels) {
|
|
4367
|
+
throw new Error(mergedReviewerModels.error);
|
|
4368
|
+
}
|
|
4369
|
+
const currentOpenCode = mergedReviewerModels.value.opencode;
|
|
4234
4370
|
return updateBundleReviewerConfig({
|
|
4235
4371
|
bundleId: bundle.id,
|
|
4236
4372
|
projectId: result.project.id,
|
|
4237
|
-
reviewerModel,
|
|
4238
|
-
reviewerVariant,
|
|
4373
|
+
reviewerModel: currentOpenCode?.model ?? null,
|
|
4374
|
+
reviewerVariant: currentOpenCode?.model ? currentOpenCode.variant : null,
|
|
4375
|
+
reviewerModelsJson: serializeAgentModelConfig(mergedReviewerModels.value),
|
|
4239
4376
|
});
|
|
4240
|
-
})();
|
|
4377
|
+
}).immediate();
|
|
4241
4378
|
}
|
|
4242
4379
|
catch (error) {
|
|
4243
4380
|
if (error instanceof Error && 'code' in error && String(error.code).startsWith('SQLITE_CONSTRAINT')) {
|
|
@@ -4248,7 +4385,7 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
4248
4385
|
listBundleTickets(result.project.id, bundle.id).forEach(ticket => {
|
|
4249
4386
|
io.emit('ticket:updated', ticket);
|
|
4250
4387
|
});
|
|
4251
|
-
res.json(serializeBundleRow(result.project.id, updated));
|
|
4388
|
+
res.json(serializeBundleRow(result.project.id, updated, config.tool ?? 'opencode'));
|
|
4252
4389
|
});
|
|
4253
4390
|
app.post('/bundles/:id/build', (req, res) => {
|
|
4254
4391
|
const result = getRequiredProject(req);
|
|
@@ -4545,17 +4682,18 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4545
4682
|
questionResolutionActive: hasActiveBuildOpenQuestionBatch(result.project.id, bundle.id),
|
|
4546
4683
|
});
|
|
4547
4684
|
};
|
|
4548
|
-
const
|
|
4549
|
-
if (!
|
|
4550
|
-
const error =
|
|
4685
|
+
const initialDecision = resolveCurrentDecision();
|
|
4686
|
+
if (!initialDecision || initialDecision.status !== 'enabled') {
|
|
4687
|
+
const error = initialDecision?.status === 'hidden' && initialDecision.reason === 'question_resolution_active'
|
|
4551
4688
|
? 'Build open question resolution is still in progress'
|
|
4552
|
-
:
|
|
4689
|
+
: initialDecision?.status === 'hidden' && initialDecision.reason === 'resolve_open_questions'
|
|
4553
4690
|
? 'Resolve or dismiss all Build open questions before resuming automation'
|
|
4554
|
-
:
|
|
4691
|
+
: initialDecision?.status === 'disabled' && initialDecision.reason === 'invalid_stage_lineage'
|
|
4555
4692
|
? 'The paused automation stage does not belong to the active progression'
|
|
4556
4693
|
: 'Bundle automation cannot be resumed in its current state';
|
|
4557
|
-
return res.status(409).json({ error, automation_resume_decision:
|
|
4694
|
+
return res.status(409).json({ error, automation_resume_decision: initialDecision });
|
|
4558
4695
|
}
|
|
4696
|
+
let decision = initialDecision;
|
|
4559
4697
|
const revalidateDecision = () => {
|
|
4560
4698
|
const current = resolveCurrentDecision();
|
|
4561
4699
|
return current?.status === 'enabled'
|
|
@@ -4770,6 +4908,179 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4770
4908
|
return res.status(400).json({ error: 'Ticket project no longer exists' });
|
|
4771
4909
|
}
|
|
4772
4910
|
const automationConfig = resolveAutomationConfig(owner.bundle ?? null, project);
|
|
4911
|
+
const legacyReviewRepairPause = isLegacyReviewRepairMigrationPause(result.project.id, bundle.id, progressionRunId);
|
|
4912
|
+
let completedVerificationIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
4913
|
+
let reconciledSourceReviewRunId = null;
|
|
4914
|
+
if (completedVerificationIteration?.phase === 'paused') {
|
|
4915
|
+
withExecutionRunTransaction(transaction => {
|
|
4916
|
+
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
4917
|
+
projectId: result.project.id,
|
|
4918
|
+
iterationId: completedVerificationIteration.id,
|
|
4919
|
+
decisionPoint: 'resume',
|
|
4920
|
+
effectiveAutomationConfig: automationConfig,
|
|
4921
|
+
decisionCode: 'resume_iteration_requested',
|
|
4922
|
+
});
|
|
4923
|
+
}, io, 'immediate');
|
|
4924
|
+
}
|
|
4925
|
+
if (completedVerificationIteration?.phase === 'paused') {
|
|
4926
|
+
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
4927
|
+
const verificationRun = verificationRunId ? getExecutionRun(result.project.id, verificationRunId) : null;
|
|
4928
|
+
if (verificationRun?.status === 'completed') {
|
|
4929
|
+
try {
|
|
4930
|
+
completedVerificationIteration = resumePausedReviewRepairIteration({
|
|
4931
|
+
projectId: result.project.id,
|
|
4932
|
+
iterationId: completedVerificationIteration.id,
|
|
4933
|
+
expectedCurrentRunId: verificationRunId,
|
|
4934
|
+
broadcaster: io,
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4937
|
+
catch (error) {
|
|
4938
|
+
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
4939
|
+
if (current?.resulting_review_execution_run_id === verificationRunId
|
|
4940
|
+
&& ['verification_active', 'terminal'].includes(current.phase)) {
|
|
4941
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4942
|
+
}
|
|
4943
|
+
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
if (completedVerificationIteration?.phase === 'verification_active'
|
|
4948
|
+
&& completedVerificationIteration.resulting_review_execution_run_id) {
|
|
4949
|
+
const verificationRunId = completedVerificationIteration.resulting_review_execution_run_id;
|
|
4950
|
+
const verificationRun = getExecutionRun(result.project.id, verificationRunId);
|
|
4951
|
+
const resultingReview = verificationRun?.status === 'completed'
|
|
4952
|
+
? db.prepare(`
|
|
4953
|
+
SELECT review.id
|
|
4954
|
+
FROM review_runs review
|
|
4955
|
+
JOIN execution_run_links result_link
|
|
4956
|
+
ON result_link.execution_run_id = ?
|
|
4957
|
+
AND result_link.project_id = review.project_id
|
|
4958
|
+
AND result_link.entity_type = 'review_run'
|
|
4959
|
+
AND result_link.entity_id = review.id
|
|
4960
|
+
AND result_link.relationship = 'result'
|
|
4961
|
+
WHERE review.project_id = ? AND review.ticket_id = ?
|
|
4962
|
+
AND review.worktree_bundle_id = ? AND review.status = 'completed'
|
|
4963
|
+
LIMIT 1
|
|
4964
|
+
`).get(verificationRunId, result.project.id, owner.id, bundle.id)
|
|
4965
|
+
: undefined;
|
|
4966
|
+
if (resultingReview) {
|
|
4967
|
+
reconciledSourceReviewRunId = resultingReview.id;
|
|
4968
|
+
const blockingCount = countBlockingFindings(listReviewFindings(resultingReview.id), automationConfig);
|
|
4969
|
+
const terminal = !isAutomationActive(automationConfig)
|
|
4970
|
+
? { outcome: 'interrupted', stopReason: 'configuration_changed' }
|
|
4971
|
+
: blockingCount === 0
|
|
4972
|
+
? { outcome: 'repaired', stopReason: 'no_blocking_findings' }
|
|
4973
|
+
: automationConfig.reviewFixEnabled
|
|
4974
|
+
? completedVerificationIteration.ordinal < automationConfig.reviewMaxIterations
|
|
4975
|
+
? { outcome: 'continuing', stopReason: null }
|
|
4976
|
+
: { outcome: 'exhausted', stopReason: 'max_iterations' }
|
|
4977
|
+
: { outcome: 'interrupted', stopReason: 'automation_disabled' };
|
|
4978
|
+
try {
|
|
4979
|
+
withExecutionRunTransaction(transaction => {
|
|
4980
|
+
if (!completedVerificationIteration.resulting_review_run_id) {
|
|
4981
|
+
attachReviewRepairResultInTransaction(transaction, {
|
|
4982
|
+
projectId: result.project.id,
|
|
4983
|
+
iterationId: completedVerificationIteration.id,
|
|
4984
|
+
expectedPhase: 'verification_active',
|
|
4985
|
+
expectedVerificationRunId: verificationRunId,
|
|
4986
|
+
resultingReviewRunId: resultingReview.id,
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
4990
|
+
projectId: result.project.id,
|
|
4991
|
+
iterationId: completedVerificationIteration.id,
|
|
4992
|
+
decisionPoint: 'verification_completion',
|
|
4993
|
+
effectiveAutomationConfig: automationConfig,
|
|
4994
|
+
decisionCode: 'verification_reconciled_on_resume',
|
|
4995
|
+
operationRunId: verificationRunId,
|
|
4996
|
+
});
|
|
4997
|
+
terminalizeReviewRepairIterationInTransaction(transaction, {
|
|
4998
|
+
projectId: result.project.id,
|
|
4999
|
+
iterationId: completedVerificationIteration.id,
|
|
5000
|
+
expectedPhase: 'verification_active',
|
|
5001
|
+
expectedCurrentRunId: verificationRunId,
|
|
5002
|
+
outcome: terminal.outcome,
|
|
5003
|
+
stopReason: terminal.stopReason,
|
|
5004
|
+
resultingBlockingCount: blockingCount,
|
|
5005
|
+
});
|
|
5006
|
+
if (automationConfig.depth === 'manual') {
|
|
5007
|
+
completeAutomationProgressionInTransaction(transaction, {
|
|
5008
|
+
projectId: result.project.id,
|
|
5009
|
+
bundleId: bundle.id,
|
|
5010
|
+
progressionRunId,
|
|
5011
|
+
domainStatus: 'manual_boundary_reached',
|
|
5012
|
+
});
|
|
5013
|
+
}
|
|
5014
|
+
if (terminal.outcome === 'continuing') {
|
|
5015
|
+
claimReviewRepairIterationInTransaction(transaction, {
|
|
5016
|
+
projectId: result.project.id,
|
|
5017
|
+
worktreeBundleId: bundle.id,
|
|
5018
|
+
ownerTicketId: owner.id,
|
|
5019
|
+
automationProgressionRunId: progressionRunId,
|
|
5020
|
+
triggeringReviewExecutionRunId: verificationRunId,
|
|
5021
|
+
triggeringReviewRunId: resultingReview.id,
|
|
5022
|
+
triggeringBlockingCount: blockingCount,
|
|
5023
|
+
effectiveAutomationConfig: automationConfig,
|
|
5024
|
+
decisionCode: 'resume_verification_repair_admitted',
|
|
5025
|
+
});
|
|
5026
|
+
}
|
|
5027
|
+
}, io, 'immediate');
|
|
5028
|
+
}
|
|
5029
|
+
catch (error) {
|
|
5030
|
+
const current = getReviewRepairIteration(result.project.id, completedVerificationIteration.id);
|
|
5031
|
+
if (current?.phase === 'terminal'
|
|
5032
|
+
&& current.resulting_review_execution_run_id === verificationRunId
|
|
5033
|
+
&& current.resulting_review_run_id === resultingReview.id) {
|
|
5034
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5035
|
+
}
|
|
5036
|
+
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
5037
|
+
}
|
|
5038
|
+
if (terminal.outcome === 'continuing') {
|
|
5039
|
+
const continuationDecision = resolveCurrentDecision();
|
|
5040
|
+
if (!continuationDecision || continuationDecision.status !== 'enabled') {
|
|
5041
|
+
return res.status(409).json({ error: 'Bundle automation state changed before repair continuation could start' });
|
|
5042
|
+
}
|
|
5043
|
+
decision = continuationDecision;
|
|
5044
|
+
}
|
|
5045
|
+
else if (terminal.outcome === 'repaired') {
|
|
5046
|
+
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5047
|
+
runAutomationProgression({
|
|
5048
|
+
projectId: result.project.id,
|
|
5049
|
+
bundleId: bundle.id,
|
|
5050
|
+
progressionRunId,
|
|
5051
|
+
broadcaster: io,
|
|
5052
|
+
});
|
|
5053
|
+
if (depthAtLeast(automationConfig.depth, 'auto_commit')) {
|
|
5054
|
+
await runAutomationCommitChain(owner, project, owner.bundle ?? null, automationConfig, io, progressionRunId, undefined, verificationRunId);
|
|
5055
|
+
}
|
|
5056
|
+
else {
|
|
5057
|
+
completeAutomationProgression({
|
|
5058
|
+
projectId: result.project.id,
|
|
5059
|
+
bundleId: bundle.id,
|
|
5060
|
+
progressionRunId,
|
|
5061
|
+
broadcaster: io,
|
|
5062
|
+
});
|
|
5063
|
+
}
|
|
5064
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5065
|
+
}
|
|
5066
|
+
else if (automationConfig.depth !== 'manual') {
|
|
5067
|
+
waitAutomationProgression({
|
|
5068
|
+
projectId: result.project.id,
|
|
5069
|
+
bundleId: bundle.id,
|
|
5070
|
+
progressionRunId,
|
|
5071
|
+
pauseCode: 'review',
|
|
5072
|
+
pauseReason: 'Blocking Review findings remain after repair verification.',
|
|
5073
|
+
reasonCode: 'precondition_failed',
|
|
5074
|
+
pausedStageRunId: verificationRunId,
|
|
5075
|
+
broadcaster: io,
|
|
5076
|
+
});
|
|
5077
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5078
|
+
}
|
|
5079
|
+
else {
|
|
5080
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
4773
5084
|
const restoreWaitingProgression = () => {
|
|
4774
5085
|
waitAutomationProgression({
|
|
4775
5086
|
projectId: result.project.id,
|
|
@@ -4821,19 +5132,36 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4821
5132
|
if (!revalidateDecision()) {
|
|
4822
5133
|
return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
|
|
4823
5134
|
}
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
5135
|
+
withExecutionRunTransaction(transaction => {
|
|
5136
|
+
const activeIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5137
|
+
if (activeIteration && activeIteration.phase !== 'terminal') {
|
|
5138
|
+
terminalizeReviewRepairIterationInTransaction(transaction, {
|
|
5139
|
+
projectId: result.project.id,
|
|
5140
|
+
iterationId: activeIteration.id,
|
|
5141
|
+
expectedPhase: activeIteration.phase,
|
|
5142
|
+
expectedCurrentRunId: activeIteration.resulting_review_execution_run_id
|
|
5143
|
+
?? activeIteration.gate_batch_run_id
|
|
5144
|
+
?? activeIteration.action_run_id
|
|
5145
|
+
?? activeIteration.action_envelope_run_id,
|
|
5146
|
+
outcome: 'interrupted',
|
|
5147
|
+
stopReason: 'configuration_changed',
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
5150
|
+
completeAutomationProgressionInTransaction(transaction, {
|
|
5151
|
+
projectId: result.project.id,
|
|
5152
|
+
bundleId: bundle.id,
|
|
5153
|
+
progressionRunId,
|
|
5154
|
+
domainStatus: 'manual_boundary_reached',
|
|
5155
|
+
});
|
|
5156
|
+
}, io, 'immediate');
|
|
4831
5157
|
return res.json(serializeBundleDetails(result.project, bundle));
|
|
4832
5158
|
}
|
|
4833
5159
|
if ((decision.outcome === 'start_review' || decision.outcome === 'rerun_review_checks')
|
|
4834
5160
|
&& owner.parked_at === null
|
|
4835
5161
|
&& isTicketBuildComplete(owner)
|
|
4836
|
-
&& (owner.state === 'build'
|
|
5162
|
+
&& (owner.state === 'build'
|
|
5163
|
+
|| (decision.outcome === 'rerun_review_checks' && owner.state === 'review')
|
|
5164
|
+
|| (legacyReviewRepairPause && owner.state === 'review'))) {
|
|
4837
5165
|
if (!revalidateDecision()) {
|
|
4838
5166
|
return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
|
|
4839
5167
|
}
|
|
@@ -4842,6 +5170,36 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4842
5170
|
return res.status(409).json({ error: 'Automation progression ownership is invalid' });
|
|
4843
5171
|
}
|
|
4844
5172
|
const workflowContext = validateWorkflowContext(progression.metadata);
|
|
5173
|
+
let repairIteration = decision.outcome === 'rerun_review_checks'
|
|
5174
|
+
? getActiveReviewRepairIteration(result.project.id, progressionRunId)
|
|
5175
|
+
: null;
|
|
5176
|
+
if (repairIteration?.phase === 'paused') {
|
|
5177
|
+
repairIteration = repairIteration.stop_reason === 'stale_evidence'
|
|
5178
|
+
&& repairIteration.resulting_review_execution_run_id
|
|
5179
|
+
? rewindStaleReviewRepairVerification({
|
|
5180
|
+
projectId: result.project.id,
|
|
5181
|
+
iterationId: repairIteration.id,
|
|
5182
|
+
expectedVerificationRunId: repairIteration.resulting_review_execution_run_id,
|
|
5183
|
+
broadcaster: io,
|
|
5184
|
+
})
|
|
5185
|
+
: resumePausedReviewRepairIteration({
|
|
5186
|
+
projectId: result.project.id,
|
|
5187
|
+
iterationId: repairIteration.id,
|
|
5188
|
+
expectedCurrentRunId: repairIteration.resulting_review_execution_run_id
|
|
5189
|
+
?? repairIteration.gate_batch_run_id
|
|
5190
|
+
?? repairIteration.action_run_id
|
|
5191
|
+
?? repairIteration.action_envelope_run_id,
|
|
5192
|
+
broadcaster: io,
|
|
5193
|
+
});
|
|
5194
|
+
}
|
|
5195
|
+
if (decision.outcome === 'rerun_review_checks' && repairIteration?.phase === 'gate_active') {
|
|
5196
|
+
repairIteration = resetReviewRepairGateForRetry({
|
|
5197
|
+
projectId: result.project.id,
|
|
5198
|
+
iterationId: repairIteration.id,
|
|
5199
|
+
expectedGateBatchRunId: repairIteration.gate_batch_run_id,
|
|
5200
|
+
broadcaster: io,
|
|
5201
|
+
});
|
|
5202
|
+
}
|
|
4845
5203
|
let reviewStartFailureStatus = null;
|
|
4846
5204
|
let reviewStartFailureError = null;
|
|
4847
5205
|
try {
|
|
@@ -4858,6 +5216,7 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4858
5216
|
broadcaster: io,
|
|
4859
5217
|
pausedStageRunId: bundleState.automation_paused_stage_run_id,
|
|
4860
5218
|
initiation: { workflowContext },
|
|
5219
|
+
iterationId: repairIteration?.id,
|
|
4861
5220
|
onReviewStartFailure: failure => {
|
|
4862
5221
|
reviewStartFailureStatus = failure.status;
|
|
4863
5222
|
reviewStartFailureError = failure.error;
|
|
@@ -4866,6 +5225,31 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4866
5225
|
});
|
|
4867
5226
|
}
|
|
4868
5227
|
catch (error) {
|
|
5228
|
+
const currentIteration = repairIteration
|
|
5229
|
+
? getReviewRepairIteration(result.project.id, repairIteration.id)
|
|
5230
|
+
: null;
|
|
5231
|
+
const pausedStage = bundleState.automation_paused_stage_run_id
|
|
5232
|
+
? getExecutionRun(result.project.id, bundleState.automation_paused_stage_run_id)
|
|
5233
|
+
: null;
|
|
5234
|
+
const retrySource = pausedStage?.retry_of_run_id
|
|
5235
|
+
? getExecutionRun(result.project.id, pausedStage.retry_of_run_id)
|
|
5236
|
+
: null;
|
|
5237
|
+
console.error('Bundle automation Review resume failed', {
|
|
5238
|
+
project_id: result.project.id,
|
|
5239
|
+
bundle_id: bundle.id,
|
|
5240
|
+
progression_run_id: progressionRunId,
|
|
5241
|
+
decision_outcome: decision.outcome,
|
|
5242
|
+
iteration_id: currentIteration?.id ?? null,
|
|
5243
|
+
iteration_phase: currentIteration?.phase ?? null,
|
|
5244
|
+
iteration_stop_reason: currentIteration?.stop_reason ?? null,
|
|
5245
|
+
paused_stage_run_id: pausedStage?.id ?? null,
|
|
5246
|
+
paused_stage_parent_run_id: pausedStage?.parent_run_id ?? null,
|
|
5247
|
+
paused_stage_retry_of_run_id: pausedStage?.retry_of_run_id ?? null,
|
|
5248
|
+
retry_source_parent_run_id: retrySource?.parent_run_id ?? null,
|
|
5249
|
+
error_name: error instanceof Error ? error.name : null,
|
|
5250
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
5251
|
+
error_stack: error instanceof Error ? error.stack : null,
|
|
5252
|
+
});
|
|
4869
5253
|
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4870
5254
|
}
|
|
4871
5255
|
if (reviewStartFailureStatus !== null && reviewStartFailureError !== null) {
|
|
@@ -4873,12 +5257,12 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4873
5257
|
}
|
|
4874
5258
|
}
|
|
4875
5259
|
else if (owner.state === 'review' && owner.agent_status !== 'running') {
|
|
4876
|
-
const iteration = bundleState.review_iteration_count ?? 0;
|
|
4877
5260
|
if (decision.outcome === 'start_review_fix') {
|
|
4878
5261
|
let reviewFixRunId = null;
|
|
5262
|
+
let repairIterationId = null;
|
|
5263
|
+
let followUpRunId = null;
|
|
5264
|
+
let retryActionRunId = null;
|
|
4879
5265
|
let followUpResult;
|
|
4880
|
-
const previousIteration = bundleState.review_iteration_count ?? 0;
|
|
4881
|
-
const previousFixLoopActive = bundleState.fix_loop_active;
|
|
4882
5266
|
try {
|
|
4883
5267
|
if (!revalidateDecision()) {
|
|
4884
5268
|
return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
|
|
@@ -4888,17 +5272,14 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4888
5272
|
return res.status(409).json({ error: 'Automation progression ownership is invalid' });
|
|
4889
5273
|
}
|
|
4890
5274
|
const workflowContext = validateWorkflowContext(progression.metadata);
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
worktreeBundleId: owner.worktree_bundle_id,
|
|
4895
|
-
});
|
|
4896
|
-
if (!sourceReviewRun || sourceReviewRun.status !== 'completed') {
|
|
5275
|
+
let existingIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5276
|
+
const sourceReviewRunId = reconciledSourceReviewRunId ?? existingIteration?.triggering_review_run_id ?? null;
|
|
5277
|
+
if (!sourceReviewRunId) {
|
|
4897
5278
|
throw new Error('The completed review findings to resume are unavailable');
|
|
4898
5279
|
}
|
|
4899
5280
|
const reviewFixWorkflowContext = validateWorkflowContext({
|
|
4900
5281
|
...(workflowContext ?? {}),
|
|
4901
|
-
review_finding_run_id:
|
|
5282
|
+
review_finding_run_id: sourceReviewRunId,
|
|
4902
5283
|
review_severity: automationConfig.reviewSeverity,
|
|
4903
5284
|
});
|
|
4904
5285
|
runAutomationProgression({
|
|
@@ -4907,22 +5288,144 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4907
5288
|
progressionRunId,
|
|
4908
5289
|
broadcaster: io,
|
|
4909
5290
|
});
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5291
|
+
if (existingIteration?.phase === 'paused') {
|
|
5292
|
+
existingIteration = resumePausedReviewRepairIteration({
|
|
5293
|
+
projectId: result.project.id,
|
|
5294
|
+
iterationId: existingIteration.id,
|
|
5295
|
+
expectedCurrentRunId: existingIteration.resulting_review_execution_run_id
|
|
5296
|
+
?? existingIteration.gate_batch_run_id
|
|
5297
|
+
?? existingIteration.action_run_id
|
|
5298
|
+
?? existingIteration.action_envelope_run_id,
|
|
5299
|
+
broadcaster: io,
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
if (existingIteration && ['gate_pending', 'gate_active', 'verification_pending', 'verification_active'].includes(existingIteration.phase)) {
|
|
5303
|
+
await runAutomationReviewGateOrPause({
|
|
5304
|
+
projectId: result.project.id,
|
|
5305
|
+
bundleId: bundle.id,
|
|
5306
|
+
progressionRunId,
|
|
5307
|
+
broadcaster: io,
|
|
5308
|
+
pausedStageRunId: existingIteration.action_run_id,
|
|
5309
|
+
initiation: { workflowContext: reviewFixWorkflowContext },
|
|
5310
|
+
iterationId: existingIteration.id,
|
|
5311
|
+
});
|
|
5312
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5313
|
+
}
|
|
5314
|
+
if (existingIteration && existingIteration.triggering_review_run_id !== sourceReviewRunId) {
|
|
5315
|
+
throw new Error('The active Review repair iteration does not match the Resume source Review');
|
|
5316
|
+
}
|
|
5317
|
+
if (existingIteration?.phase === 'action_active' && existingIteration.action_run_id) {
|
|
5318
|
+
const actionRun = getExecutionRun(result.project.id, existingIteration.action_run_id);
|
|
5319
|
+
if (actionRun?.status === 'completed') {
|
|
5320
|
+
await runAutomationReviewGateOrPause({
|
|
5321
|
+
projectId: result.project.id,
|
|
5322
|
+
bundleId: bundle.id,
|
|
5323
|
+
progressionRunId,
|
|
5324
|
+
broadcaster: io,
|
|
5325
|
+
pausedStageRunId: actionRun.id,
|
|
5326
|
+
initiation: { workflowContext: reviewFixWorkflowContext },
|
|
5327
|
+
iterationId: existingIteration.id,
|
|
5328
|
+
});
|
|
5329
|
+
return res.json(serializeBundleDetails(result.project, bundle));
|
|
5330
|
+
}
|
|
5331
|
+
if (!actionRun || ['queued', 'running', 'waiting'].includes(actionRun.status)) {
|
|
5332
|
+
throw new Error('Review repair action is still active or unavailable');
|
|
5333
|
+
}
|
|
5334
|
+
retryActionRunId = actionRun.id;
|
|
5335
|
+
}
|
|
5336
|
+
const sourceReviewExecution = existingIteration ? null : db.prepare(`
|
|
5337
|
+
SELECT execution.id
|
|
5338
|
+
FROM execution_runs execution
|
|
5339
|
+
JOIN execution_run_links result_link
|
|
5340
|
+
ON result_link.execution_run_id = execution.id
|
|
5341
|
+
AND result_link.project_id = execution.project_id
|
|
5342
|
+
AND result_link.entity_type = 'review_run'
|
|
5343
|
+
AND result_link.entity_id = ?
|
|
5344
|
+
AND result_link.relationship = 'result'
|
|
5345
|
+
WHERE execution.project_id = ? AND execution.kind = 'review'
|
|
5346
|
+
AND execution.status = 'completed'
|
|
5347
|
+
LIMIT 1
|
|
5348
|
+
`).get(sourceReviewRunId, result.project.id);
|
|
5349
|
+
if (!existingIteration && !sourceReviewExecution) {
|
|
5350
|
+
throw new Error('The completed Review execution to resume is unavailable');
|
|
5351
|
+
}
|
|
5352
|
+
const claimed = withExecutionRunTransaction(transaction => {
|
|
5353
|
+
const repairIteration = existingIteration ?? claimReviewRepairIterationInTransaction(transaction, {
|
|
5354
|
+
projectId: result.project.id,
|
|
5355
|
+
worktreeBundleId: bundle.id,
|
|
5356
|
+
ownerTicketId: owner.id,
|
|
5357
|
+
automationProgressionRunId: progressionRunId,
|
|
5358
|
+
triggeringReviewExecutionRunId: sourceReviewExecution.id,
|
|
5359
|
+
triggeringReviewRunId: sourceReviewRunId,
|
|
5360
|
+
triggeringBlockingCount: countBlockingFindings(listReviewFindings(sourceReviewRunId), automationConfig),
|
|
5361
|
+
effectiveAutomationConfig: automationConfig,
|
|
5362
|
+
decisionCode: 'resume_repair_admitted',
|
|
5363
|
+
});
|
|
5364
|
+
if (!existingIteration) {
|
|
5365
|
+
appendReviewRepairIterationDecisionInTransaction(transaction, {
|
|
5366
|
+
projectId: result.project.id,
|
|
5367
|
+
iterationId: repairIteration.id,
|
|
5368
|
+
decisionPoint: 'resume',
|
|
5369
|
+
effectiveAutomationConfig: automationConfig,
|
|
5370
|
+
decisionCode: 'resume_repair_claimed',
|
|
5371
|
+
});
|
|
5372
|
+
}
|
|
5373
|
+
if (repairIteration.phase === 'action_claimed' && repairIteration.action_envelope_run_id) {
|
|
5374
|
+
const existingEnvelope = getExecutionRun(result.project.id, repairIteration.action_envelope_run_id);
|
|
5375
|
+
if (existingEnvelope && ['queued', 'running', 'waiting'].includes(existingEnvelope.status)) {
|
|
5376
|
+
return { iterationId: repairIteration.id, reviewFixRunId: repairIteration.action_envelope_run_id };
|
|
5377
|
+
}
|
|
5378
|
+
if (!existingEnvelope) {
|
|
5379
|
+
throw new Error('Review repair action envelope is unavailable');
|
|
5380
|
+
}
|
|
5381
|
+
const replacement = createAutomationReviewFixInTransaction(transaction, {
|
|
5382
|
+
projectId: result.project.id,
|
|
5383
|
+
bundleId: bundle.id,
|
|
5384
|
+
ticketId: owner.id,
|
|
5385
|
+
ticketTitle: owner.title,
|
|
5386
|
+
progressionRunId,
|
|
5387
|
+
workflowContext: reviewFixWorkflowContext,
|
|
5388
|
+
triggeringReviewRunId: sourceReviewExecution?.id,
|
|
5389
|
+
reviewFindingRunId: sourceReviewRunId,
|
|
5390
|
+
reviewSeverity: automationConfig.reviewSeverity,
|
|
5391
|
+
retryOfRunId: existingEnvelope.id,
|
|
5392
|
+
});
|
|
5393
|
+
replaceReviewRepairActionEnvelopeRetryInTransaction(transaction, {
|
|
5394
|
+
projectId: result.project.id,
|
|
5395
|
+
iterationId: repairIteration.id,
|
|
5396
|
+
expectedPhase: 'action_claimed',
|
|
5397
|
+
expectedCurrentRunId: existingEnvelope.id,
|
|
5398
|
+
runId: replacement.runId,
|
|
5399
|
+
});
|
|
5400
|
+
return { iterationId: repairIteration.id, reviewFixRunId: replacement.runId };
|
|
5401
|
+
}
|
|
5402
|
+
if (repairIteration.phase === 'action_active' && repairIteration.action_envelope_run_id && retryActionRunId) {
|
|
5403
|
+
return { iterationId: repairIteration.id, reviewFixRunId: repairIteration.action_envelope_run_id };
|
|
5404
|
+
}
|
|
5405
|
+
if (repairIteration.phase !== 'observed') {
|
|
5406
|
+
throw new Error('Review repair iteration is not resumable at action dispatch');
|
|
5407
|
+
}
|
|
5408
|
+
const reviewFix = createAutomationReviewFixInTransaction(transaction, {
|
|
5409
|
+
projectId: result.project.id,
|
|
5410
|
+
bundleId: bundle.id,
|
|
5411
|
+
ticketId: owner.id,
|
|
5412
|
+
ticketTitle: owner.title,
|
|
5413
|
+
progressionRunId,
|
|
5414
|
+
workflowContext: reviewFixWorkflowContext,
|
|
5415
|
+
triggeringReviewRunId: sourceReviewExecution?.id,
|
|
5416
|
+
reviewFindingRunId: sourceReviewRunId,
|
|
5417
|
+
reviewSeverity: automationConfig.reviewSeverity,
|
|
5418
|
+
});
|
|
5419
|
+
attachReviewRepairActionEnvelopeInTransaction(transaction, {
|
|
5420
|
+
projectId: result.project.id,
|
|
5421
|
+
iterationId: repairIteration.id,
|
|
5422
|
+
expectedPhase: 'observed',
|
|
5423
|
+
runId: reviewFix.runId,
|
|
5424
|
+
});
|
|
5425
|
+
return { iterationId: repairIteration.id, reviewFixRunId: reviewFix.runId };
|
|
5426
|
+
}, io, 'immediate');
|
|
5427
|
+
repairIterationId = claimed.iterationId;
|
|
5428
|
+
reviewFixRunId = claimed.reviewFixRunId;
|
|
4926
5429
|
followUpResult = submitTicketFollowUp({
|
|
4927
5430
|
ticketId: owner.id,
|
|
4928
5431
|
requestedProjectId: result.project.id,
|
|
@@ -4934,16 +5437,42 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4934
5437
|
persistTriggerMessage: false,
|
|
4935
5438
|
initiation: {
|
|
4936
5439
|
initiator: 'automation',
|
|
4937
|
-
causalParentRunId:
|
|
5440
|
+
causalParentRunId: reviewFixRunId,
|
|
5441
|
+
...(retryActionRunId ? { retrySourceRunId: retryActionRunId } : {}),
|
|
4938
5442
|
workflowContext: reviewFixWorkflowContext,
|
|
4939
5443
|
},
|
|
5444
|
+
onExecutionCreated: (executionContext, transaction) => {
|
|
5445
|
+
if (retryActionRunId) {
|
|
5446
|
+
replaceReviewRepairActionRetryInTransaction(transaction, {
|
|
5447
|
+
projectId: result.project.id,
|
|
5448
|
+
iterationId: repairIterationId,
|
|
5449
|
+
expectedPhase: 'action_active',
|
|
5450
|
+
expectedCurrentRunId: retryActionRunId,
|
|
5451
|
+
runId: executionContext.runId,
|
|
5452
|
+
});
|
|
5453
|
+
}
|
|
5454
|
+
else {
|
|
5455
|
+
attachReviewRepairActionInTransaction(transaction, {
|
|
5456
|
+
projectId: result.project.id,
|
|
5457
|
+
iterationId: repairIterationId,
|
|
5458
|
+
expectedPhase: 'action_claimed',
|
|
5459
|
+
runId: executionContext.runId,
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
followUpRunId = executionContext.runId;
|
|
5463
|
+
},
|
|
5464
|
+
onExecutionCompleted: (executionContext, transaction) => {
|
|
5465
|
+
const liveProject = getProjectById(result.project.id) ?? result.project;
|
|
5466
|
+
const liveBundle = getBundle(bundle.id, result.project.id) ?? bundle;
|
|
5467
|
+
completeAutomationReviewFixActionInTransaction(transaction, {
|
|
5468
|
+
projectId: result.project.id,
|
|
5469
|
+
actionRunId: executionContext.runId,
|
|
5470
|
+
effectiveAutomationConfig: resolveAutomationConfig(liveBundle, liveProject),
|
|
5471
|
+
});
|
|
5472
|
+
},
|
|
4940
5473
|
});
|
|
4941
5474
|
}
|
|
4942
5475
|
catch (error) {
|
|
4943
|
-
upsertBundleAutomationState(result.project.id, bundle.id, {
|
|
4944
|
-
review_iteration_count: previousIteration,
|
|
4945
|
-
fix_loop_active: previousFixLoopActive,
|
|
4946
|
-
});
|
|
4947
5476
|
try {
|
|
4948
5477
|
if (reviewFixRunId) {
|
|
4949
5478
|
finishAutomationReviewFix({
|
|
@@ -4952,6 +5481,22 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4952
5481
|
failedMessage: error instanceof Error ? error.message : String(error),
|
|
4953
5482
|
broadcaster: io,
|
|
4954
5483
|
});
|
|
5484
|
+
const iteration = repairIterationId
|
|
5485
|
+
? getActiveReviewRepairIteration(result.project.id, progressionRunId)
|
|
5486
|
+
: null;
|
|
5487
|
+
if (iteration && !['paused', 'terminal'].includes(iteration.phase)) {
|
|
5488
|
+
pauseReviewRepairIteration({
|
|
5489
|
+
projectId: result.project.id,
|
|
5490
|
+
iterationId: iteration.id,
|
|
5491
|
+
expectedPhase: iteration.phase,
|
|
5492
|
+
expectedCurrentRunId: iteration.resulting_review_execution_run_id
|
|
5493
|
+
?? iteration.gate_batch_run_id
|
|
5494
|
+
?? iteration.action_run_id
|
|
5495
|
+
?? iteration.action_envelope_run_id,
|
|
5496
|
+
stopReason: 'action_failed',
|
|
5497
|
+
broadcaster: io,
|
|
5498
|
+
});
|
|
5499
|
+
}
|
|
4955
5500
|
}
|
|
4956
5501
|
}
|
|
4957
5502
|
finally {
|
|
@@ -4960,10 +5505,6 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4960
5505
|
return res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
|
|
4961
5506
|
}
|
|
4962
5507
|
if (!followUpResult.ok) {
|
|
4963
|
-
upsertBundleAutomationState(result.project.id, bundle.id, {
|
|
4964
|
-
review_iteration_count: previousIteration,
|
|
4965
|
-
fix_loop_active: previousFixLoopActive,
|
|
4966
|
-
});
|
|
4967
5508
|
if (reviewFixRunId) {
|
|
4968
5509
|
finishAutomationReviewFix({
|
|
4969
5510
|
projectId: result.project.id,
|
|
@@ -4981,6 +5522,16 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4981
5522
|
pausedStageRunId: reviewFixRunId,
|
|
4982
5523
|
broadcaster: io,
|
|
4983
5524
|
});
|
|
5525
|
+
if (repairIterationId && followUpRunId) {
|
|
5526
|
+
pauseReviewRepairIteration({
|
|
5527
|
+
projectId: result.project.id,
|
|
5528
|
+
iterationId: repairIterationId,
|
|
5529
|
+
expectedPhase: 'action_active',
|
|
5530
|
+
expectedCurrentRunId: followUpRunId,
|
|
5531
|
+
stopReason: 'action_failed',
|
|
5532
|
+
broadcaster: io,
|
|
5533
|
+
});
|
|
5534
|
+
}
|
|
4984
5535
|
}
|
|
4985
5536
|
return res.status(followUpResult.status).json({ error: followUpResult.error });
|
|
4986
5537
|
}
|
|
@@ -4990,6 +5541,27 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
4990
5541
|
if (!revalidateDecision()) {
|
|
4991
5542
|
return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
|
|
4992
5543
|
}
|
|
5544
|
+
if (decision.outcome === 'continue_commit_chain') {
|
|
5545
|
+
const activeIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5546
|
+
if (activeIteration && activeIteration.phase !== 'terminal') {
|
|
5547
|
+
withExecutionRunTransaction(transaction => {
|
|
5548
|
+
terminalizeReviewRepairIterationInTransaction(transaction, {
|
|
5549
|
+
projectId: result.project.id,
|
|
5550
|
+
iterationId: activeIteration.id,
|
|
5551
|
+
expectedPhase: activeIteration.phase,
|
|
5552
|
+
expectedCurrentRunId: activeIteration.resulting_review_execution_run_id
|
|
5553
|
+
?? activeIteration.gate_batch_run_id
|
|
5554
|
+
?? activeIteration.action_run_id
|
|
5555
|
+
?? activeIteration.action_envelope_run_id,
|
|
5556
|
+
outcome: 'interrupted',
|
|
5557
|
+
stopReason: 'configuration_changed',
|
|
5558
|
+
});
|
|
5559
|
+
}, io, 'immediate');
|
|
5560
|
+
}
|
|
5561
|
+
if (bundleState.fix_loop_active === 1) {
|
|
5562
|
+
resetBundleAutomationLoop(result.project.id, bundle.id);
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
4993
5565
|
runAutomationProgression({
|
|
4994
5566
|
projectId: result.project.id,
|
|
4995
5567
|
bundleId: bundle.id,
|
|
@@ -5010,6 +5582,22 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5010
5582
|
return res.status(409).json({ error: 'Bundle automation state changed before Resume could start' });
|
|
5011
5583
|
}
|
|
5012
5584
|
const sourceRunId = bundleState.automation_paused_stage_run_id;
|
|
5585
|
+
let repairIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5586
|
+
if (repairIteration?.phase === 'paused') {
|
|
5587
|
+
repairIteration = resumePausedReviewRepairIteration({
|
|
5588
|
+
projectId: result.project.id,
|
|
5589
|
+
iterationId: repairIteration.id,
|
|
5590
|
+
expectedCurrentRunId: repairIteration.resulting_review_execution_run_id
|
|
5591
|
+
?? repairIteration.gate_batch_run_id
|
|
5592
|
+
?? repairIteration.action_run_id
|
|
5593
|
+
?? repairIteration.action_envelope_run_id,
|
|
5594
|
+
broadcaster: io,
|
|
5595
|
+
});
|
|
5596
|
+
}
|
|
5597
|
+
const verificationRetryRunId = repairIteration?.phase === 'verification_active'
|
|
5598
|
+
? repairIteration.resulting_review_execution_run_id
|
|
5599
|
+
: null;
|
|
5600
|
+
let replacementVerificationRunId = null;
|
|
5013
5601
|
const reviewResult = rerunTicketReviewOperation(owner, io, {
|
|
5014
5602
|
automatic: true,
|
|
5015
5603
|
initiation: {
|
|
@@ -5018,8 +5606,30 @@ app.post('/bundles/:id/resume-automation', async (req, res) => {
|
|
|
5018
5606
|
},
|
|
5019
5607
|
continuation: sourceRunId ? resolveReviewContinuation(sourceRunId) : undefined,
|
|
5020
5608
|
accept: acceptResumedReview,
|
|
5609
|
+
onExecutionCreated: verificationRetryRunId && repairIteration
|
|
5610
|
+
? (executionContext, transaction) => {
|
|
5611
|
+
replaceReviewRepairVerificationRetryInTransaction(transaction, {
|
|
5612
|
+
projectId: result.project.id,
|
|
5613
|
+
iterationId: repairIteration.id,
|
|
5614
|
+
expectedPhase: 'verification_active',
|
|
5615
|
+
expectedCurrentRunId: verificationRetryRunId,
|
|
5616
|
+
runId: executionContext.runId,
|
|
5617
|
+
});
|
|
5618
|
+
replacementVerificationRunId = executionContext.runId;
|
|
5619
|
+
}
|
|
5620
|
+
: undefined,
|
|
5021
5621
|
});
|
|
5022
5622
|
if (!reviewResult.ok) {
|
|
5623
|
+
if (repairIteration && replacementVerificationRunId) {
|
|
5624
|
+
pauseReviewRepairIteration({
|
|
5625
|
+
projectId: result.project.id,
|
|
5626
|
+
iterationId: repairIteration.id,
|
|
5627
|
+
expectedPhase: 'verification_active',
|
|
5628
|
+
expectedCurrentRunId: replacementVerificationRunId,
|
|
5629
|
+
stopReason: 'verification_failed',
|
|
5630
|
+
broadcaster: io,
|
|
5631
|
+
});
|
|
5632
|
+
}
|
|
5023
5633
|
return res.status(reviewResult.status).json({ error: reviewResult.error });
|
|
5024
5634
|
}
|
|
5025
5635
|
}
|
|
@@ -5090,6 +5700,21 @@ app.post('/bundles/:id/dismiss-automation-pause', async (req, res) => {
|
|
|
5090
5700
|
catch {
|
|
5091
5701
|
// Explicit abandonment also repairs a stale progression pointer.
|
|
5092
5702
|
}
|
|
5703
|
+
const repairIteration = getActiveReviewRepairIteration(result.project.id, progressionRunId);
|
|
5704
|
+
if (repairIteration) {
|
|
5705
|
+
terminalizeReviewRepairIteration({
|
|
5706
|
+
projectId: result.project.id,
|
|
5707
|
+
iterationId: repairIteration.id,
|
|
5708
|
+
expectedPhase: repairIteration.phase,
|
|
5709
|
+
expectedCurrentRunId: repairIteration.resulting_review_execution_run_id
|
|
5710
|
+
?? repairIteration.gate_batch_run_id
|
|
5711
|
+
?? repairIteration.action_run_id
|
|
5712
|
+
?? repairIteration.action_envelope_run_id,
|
|
5713
|
+
outcome: 'cancelled',
|
|
5714
|
+
stopReason: 'cancelled',
|
|
5715
|
+
broadcaster: io,
|
|
5716
|
+
});
|
|
5717
|
+
}
|
|
5093
5718
|
}
|
|
5094
5719
|
abandonAutomationPause({
|
|
5095
5720
|
projectId: result.project.id,
|
|
@@ -5114,6 +5739,7 @@ app.post('/tool', (req, res) => {
|
|
|
5114
5739
|
return res.status(400).json({ error: 'Invalid tool' });
|
|
5115
5740
|
}
|
|
5116
5741
|
config.tool = tool;
|
|
5742
|
+
io.emit('tool:updated', { tool });
|
|
5117
5743
|
res.json({ tool });
|
|
5118
5744
|
});
|
|
5119
5745
|
app.post('/tools/upgrade', async (req, res) => {
|
|
@@ -5141,15 +5767,21 @@ app.post('/projects/auto-config', async (req, res) => {
|
|
|
5141
5767
|
return res.status(400).json({ error: 'repo_path must be a non-empty string' });
|
|
5142
5768
|
}
|
|
5143
5769
|
try {
|
|
5144
|
-
const
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
}
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5770
|
+
const requestedTool = req.body?.tool;
|
|
5771
|
+
if (requestedTool !== undefined && requestedTool !== 'claude' && requestedTool !== 'opencode') {
|
|
5772
|
+
return res.status(400).json({ error: 'tool must be a supported agent' });
|
|
5773
|
+
}
|
|
5774
|
+
const tool = requestedTool
|
|
5775
|
+
?? (hasOwn(req.body ?? {}, 'helper_model') && validation.values.helper_model ? 'opencode' : null)
|
|
5776
|
+
?? config.tool
|
|
5777
|
+
?? 'opencode';
|
|
5778
|
+
const helperConfig = resolveHelperModelConfig({
|
|
5779
|
+
helperModelsJson: validation.values.helper_models_json,
|
|
5780
|
+
agent: tool,
|
|
5781
|
+
});
|
|
5782
|
+
const result = await autoConfigProject(validation.values.repo_path, helperConfig.model
|
|
5783
|
+
? { tool, model: helperConfig.model, variant: helperConfig.variant }
|
|
5784
|
+
: { tool });
|
|
5153
5785
|
return res.json(result);
|
|
5154
5786
|
}
|
|
5155
5787
|
catch (error) {
|
|
@@ -5229,6 +5861,10 @@ app.get('/models', async (req, res) => {
|
|
|
5229
5861
|
timing.end({ count: models.length });
|
|
5230
5862
|
return res.json(models);
|
|
5231
5863
|
});
|
|
5864
|
+
app.get('/agents', async (_req, res) => {
|
|
5865
|
+
res.set('Cache-Control', 'no-store');
|
|
5866
|
+
return res.json(await getSupportedAgentCatalog());
|
|
5867
|
+
});
|
|
5232
5868
|
app.get('/skills', async (req, res) => {
|
|
5233
5869
|
const tool = typeof req.query.tool === 'string' ? req.query.tool : '';
|
|
5234
5870
|
const projectId = typeof req.query.project_id === 'string' ? req.query.project_id : null;
|
|
@@ -6178,13 +6814,18 @@ app.get('/projects', (_req, res) => {
|
|
|
6178
6814
|
const projects = listProjects();
|
|
6179
6815
|
res.json(projects.map(serializeProject));
|
|
6180
6816
|
});
|
|
6181
|
-
app.post('/projects', (req, res) => {
|
|
6182
|
-
const
|
|
6817
|
+
app.post('/projects', async (req, res) => {
|
|
6818
|
+
const body = asProjectPayload(req.body);
|
|
6819
|
+
const validation = validateProjectPayload(body, { allowRepoInitialization: true });
|
|
6183
6820
|
if ('error' in validation) {
|
|
6184
6821
|
return res.status(400).json({ error: validation.error });
|
|
6185
6822
|
}
|
|
6823
|
+
const roleModelValidation = await validateProjectRoleModelDiscovery(body, validation.values);
|
|
6824
|
+
if ('error' in roleModelValidation) {
|
|
6825
|
+
return res.status(400).json({ error: roleModelValidation.error });
|
|
6826
|
+
}
|
|
6186
6827
|
const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
|
|
6187
|
-
const { name, repo_path, build_execution_provider, 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, reviewer_model, reviewer_variant, 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;
|
|
6828
|
+
const { name, repo_path, build_execution_provider, 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;
|
|
6188
6829
|
if (!repo_path) {
|
|
6189
6830
|
return res.status(400).json({ error: 'repo_path must be a non-empty string' });
|
|
6190
6831
|
}
|
|
@@ -6217,8 +6858,10 @@ app.post('/projects', (req, res) => {
|
|
|
6217
6858
|
preview_capability_mode,
|
|
6218
6859
|
helper_model,
|
|
6219
6860
|
helper_variant,
|
|
6861
|
+
helper_models_json,
|
|
6220
6862
|
reviewer_model,
|
|
6221
6863
|
reviewer_variant,
|
|
6864
|
+
reviewer_models_json,
|
|
6222
6865
|
commit_message_rules,
|
|
6223
6866
|
commit_after_each_ticket,
|
|
6224
6867
|
auto_park_stale_tickets,
|
|
@@ -6233,8 +6876,8 @@ app.post('/projects', (req, res) => {
|
|
|
6233
6876
|
automation_review_severity,
|
|
6234
6877
|
automation_review_max_iterations
|
|
6235
6878
|
)
|
|
6236
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6237
|
-
`).run(id, name, normalizedRepoPath, build_execution_provider ?? 'host', 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 ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, reviewer_model ?? null, reviewer_model ? reviewer_variant ?? null : null, commit_message_rules ?? null, commit_after_each_ticket ?? 0, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, scan_include_globs_json ?? null, scan_exclude_globs_json ?? null, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_pause_on_open_questions ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
|
|
6879
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6880
|
+
`).run(id, name, normalizedRepoPath, build_execution_provider ?? 'host', 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 ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, helper_models_json ?? '{}', reviewer_model ?? null, reviewer_model ? reviewer_variant ?? null : null, reviewer_models_json ?? '{}', commit_message_rules ?? null, commit_after_each_ticket ?? 0, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, scan_include_globs_json ?? null, scan_exclude_globs_json ?? null, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_pause_on_open_questions ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
|
|
6238
6881
|
replaceProjectLinks(id, linked_project_ids ?? []);
|
|
6239
6882
|
ensureProjectRootBundle(id);
|
|
6240
6883
|
if (projectCount === 0) {
|
|
@@ -7240,15 +7883,12 @@ app.post('/projects/:id/git-commit-message', async (req, res) => {
|
|
|
7240
7883
|
diff: getSelectedGitUnifiedDiff(workspace.cwd, selectedPaths),
|
|
7241
7884
|
});
|
|
7242
7885
|
try {
|
|
7243
|
-
const
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
: {
|
|
7250
|
-
tool: config.tool ?? 'opencode',
|
|
7251
|
-
}, prompt, workspace.cwd);
|
|
7886
|
+
const helperTool = config.tool ?? 'opencode';
|
|
7887
|
+
const helperConfig = resolveHelperModelConfig({
|
|
7888
|
+
helperModelsJson: workspace.project.helper_models_json,
|
|
7889
|
+
agent: helperTool,
|
|
7890
|
+
});
|
|
7891
|
+
const rawMessage = await runLightweightPrompt({ tool: helperTool, ...helperConfig }, prompt, workspace.cwd);
|
|
7252
7892
|
const message = normalizeGeneratedCommitMessage(rawMessage);
|
|
7253
7893
|
if (!message) {
|
|
7254
7894
|
return res.status(500).json({ error: 'Unable to generate a commit message right now.' });
|
|
@@ -7271,7 +7911,12 @@ app.post('/projects/:id/pull-request-description', async (req, res) => {
|
|
|
7271
7911
|
if (workspace.target_kind !== 'repo_root') {
|
|
7272
7912
|
return res.status(409).json({ error: 'Pull request descriptions for bundle targets should use the bundle route.' });
|
|
7273
7913
|
}
|
|
7274
|
-
|
|
7914
|
+
const helperTool = config.tool ?? 'opencode';
|
|
7915
|
+
const helperConfig = resolveHelperModelConfig({
|
|
7916
|
+
helperModelsJson: project.helper_models_json,
|
|
7917
|
+
agent: helperTool,
|
|
7918
|
+
});
|
|
7919
|
+
if (!helperConfig.model) {
|
|
7275
7920
|
return res.status(409).json({ error: 'Set a helper model in project settings before generating a PR description.' });
|
|
7276
7921
|
}
|
|
7277
7922
|
const title = asTrimmedString(req.body?.title);
|
|
@@ -7283,9 +7928,9 @@ app.post('/projects/:id/pull-request-description', async (req, res) => {
|
|
|
7283
7928
|
: [];
|
|
7284
7929
|
try {
|
|
7285
7930
|
const rawDescription = await runLightweightPrompt({
|
|
7286
|
-
tool:
|
|
7287
|
-
model:
|
|
7288
|
-
variant:
|
|
7931
|
+
tool: helperTool,
|
|
7932
|
+
model: helperConfig.model,
|
|
7933
|
+
variant: helperConfig.variant,
|
|
7289
7934
|
}, buildPullRequestDescriptionPrompt({
|
|
7290
7935
|
title,
|
|
7291
7936
|
ticketTitles: [],
|
|
@@ -7444,7 +8089,7 @@ app.post('/projects/:id/git-squash', async (req, res) => {
|
|
|
7444
8089
|
total_count: summary.totalCount,
|
|
7445
8090
|
});
|
|
7446
8091
|
});
|
|
7447
|
-
app.patch('/projects/:id', (req, res) => {
|
|
8092
|
+
app.patch('/projects/:id', async (req, res) => {
|
|
7448
8093
|
const project = getProjectById(req.params.id);
|
|
7449
8094
|
if (!project) {
|
|
7450
8095
|
return res.status(404).json({ error: 'Not found' });
|
|
@@ -7455,10 +8100,15 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
7455
8100
|
reason: 'execution_profile_required',
|
|
7456
8101
|
});
|
|
7457
8102
|
}
|
|
7458
|
-
const
|
|
8103
|
+
const body = asProjectPayload(req.body);
|
|
8104
|
+
const validation = validateProjectPayload(body, { partial: true, currentProjectId: project.id });
|
|
7459
8105
|
if ('error' in validation) {
|
|
7460
8106
|
return res.status(400).json({ error: validation.error });
|
|
7461
8107
|
}
|
|
8108
|
+
const roleModelValidation = await validateProjectRoleModelDiscovery(body, validation.values, project);
|
|
8109
|
+
if ('error' in roleModelValidation) {
|
|
8110
|
+
return res.status(400).json({ error: roleModelValidation.error });
|
|
8111
|
+
}
|
|
7462
8112
|
if (hasOwn(validation.values, 'repo_path') && validation.values.repo_path !== project.repo_path) {
|
|
7463
8113
|
const stats = getProjectTicketStats(project.id);
|
|
7464
8114
|
if (stats.ticketCount > 0 || stats.worktreeCount > 0 || stats.bundleCount > 0) {
|
|
@@ -7467,7 +8117,7 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
7467
8117
|
});
|
|
7468
8118
|
}
|
|
7469
8119
|
}
|
|
7470
|
-
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, reviewer_model, reviewer_variant, 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;
|
|
8120
|
+
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;
|
|
7471
8121
|
const updateProject = db.prepare(`
|
|
7472
8122
|
UPDATE projects SET
|
|
7473
8123
|
name = ?,
|
|
@@ -7487,8 +8137,10 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
7487
8137
|
preview_capability_mode = ?,
|
|
7488
8138
|
helper_model = ?,
|
|
7489
8139
|
helper_variant = ?,
|
|
8140
|
+
helper_models_json = ?,
|
|
7490
8141
|
reviewer_model = ?,
|
|
7491
8142
|
reviewer_variant = ?,
|
|
8143
|
+
reviewer_models_json = ?,
|
|
7492
8144
|
commit_message_rules = ?,
|
|
7493
8145
|
commit_after_each_ticket = ?,
|
|
7494
8146
|
auto_park_stale_tickets = ?,
|
|
@@ -7523,6 +8175,13 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
7523
8175
|
? (hasOwn(validation.values, 'reviewer_variant') ? reviewer_variant ?? null : project.reviewer_variant)
|
|
7524
8176
|
: null;
|
|
7525
8177
|
const updateResult = db.transaction(() => {
|
|
8178
|
+
const currentProject = getProjectById(project.id);
|
|
8179
|
+
if (!currentProject || JSON.stringify(currentProject) !== JSON.stringify(project)) {
|
|
8180
|
+
return {
|
|
8181
|
+
error: 'Project changed while model configuration was being validated; retry the update',
|
|
8182
|
+
status: 409,
|
|
8183
|
+
};
|
|
8184
|
+
}
|
|
7526
8185
|
const policyReferences = validateProjectRequiredCheckPolicyReferences({
|
|
7527
8186
|
projectId: project.id,
|
|
7528
8187
|
checkDefinitionsJson: nextCheckDefinitions,
|
|
@@ -7549,12 +8208,12 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
7549
8208
|
};
|
|
7550
8209
|
}
|
|
7551
8210
|
}
|
|
7552
|
-
updateProject.run(name ?? project.name, repo_path ?? project.repo_path, project.build_execution_provider, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, nextCheckDefinitions, nextRequiredCheckDefinitionIds, nextPreReviewCheckDefinitionIds, nextPostBuildCheckDefinitionIds, nextAutoFixCheckDefinitionIds, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, nextReviewerModel, nextReviewerVariant, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'commit_after_each_ticket') ? commit_after_each_ticket : project.commit_after_each_ticket, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'scan_include_globs_json') ? scan_include_globs_json : project.scan_include_globs_json, hasOwn(validation.values, 'scan_exclude_globs_json') ? scan_exclude_globs_json : project.scan_exclude_globs_json, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_pause_on_open_questions') ? automation_pause_on_open_questions : project.automation_pause_on_open_questions, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
|
|
8211
|
+
updateProject.run(name ?? project.name, repo_path ?? project.repo_path, project.build_execution_provider, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, nextCheckDefinitions, nextRequiredCheckDefinitionIds, nextPreReviewCheckDefinitionIds, nextPostBuildCheckDefinitionIds, nextAutoFixCheckDefinitionIds, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, hasOwn(validation.values, 'helper_models_json') ? helper_models_json : project.helper_models_json, nextReviewerModel, nextReviewerVariant, hasOwn(validation.values, 'reviewer_models_json') ? reviewer_models_json : project.reviewer_models_json, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'commit_after_each_ticket') ? commit_after_each_ticket : project.commit_after_each_ticket, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'scan_include_globs_json') ? scan_include_globs_json : project.scan_include_globs_json, hasOwn(validation.values, 'scan_exclude_globs_json') ? scan_exclude_globs_json : project.scan_exclude_globs_json, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_pause_on_open_questions') ? automation_pause_on_open_questions : project.automation_pause_on_open_questions, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
|
|
7553
8212
|
if (hasOwn(validation.values, 'linked_project_ids')) {
|
|
7554
8213
|
replaceProjectLinks(project.id, linked_project_ids ?? []);
|
|
7555
8214
|
}
|
|
7556
8215
|
return { ok: true };
|
|
7557
|
-
})();
|
|
8216
|
+
}).immediate();
|
|
7558
8217
|
if ('error' in updateResult) {
|
|
7559
8218
|
const status = 'status' in updateResult && typeof updateResult.status === 'number'
|
|
7560
8219
|
? updateResult.status
|
|
@@ -7643,6 +8302,13 @@ const completeClaimedProjectDeletion = async (projectId, claimId) => {
|
|
|
7643
8302
|
}
|
|
7644
8303
|
db.prepare("DELETE FROM execution_artifacts WHERE project_id = ? AND availability = 'deleted'").run(projectId);
|
|
7645
8304
|
deleteProjectAttachments(projectId);
|
|
8305
|
+
if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'review_repair_iterations'").get()) {
|
|
8306
|
+
db.prepare(`
|
|
8307
|
+
DELETE FROM review_repair_iteration_decisions
|
|
8308
|
+
WHERE iteration_id IN (SELECT id FROM review_repair_iterations WHERE project_id = ?)
|
|
8309
|
+
`).run(projectId);
|
|
8310
|
+
db.prepare('DELETE FROM review_repair_iterations WHERE project_id = ?').run(projectId);
|
|
8311
|
+
}
|
|
7646
8312
|
db.prepare(`
|
|
7647
8313
|
DELETE FROM check_gate_evaluation_attempts
|
|
7648
8314
|
WHERE evaluation_id IN (SELECT id FROM check_gate_evaluations WHERE project_id = ?)
|
|
@@ -8147,6 +8813,13 @@ app.post('/tickets/:id/retry', (req, res) => {
|
|
|
8147
8813
|
return res.status(409).json({ error: buildBlocker.error, blocker_ticket_id: buildBlocker.blocker_ticket_id });
|
|
8148
8814
|
}
|
|
8149
8815
|
}
|
|
8816
|
+
const postBuildRecovery = startPostBuildCheckRecovery(ticket, io);
|
|
8817
|
+
if (postBuildRecovery) {
|
|
8818
|
+
if (!postBuildRecovery.accepted) {
|
|
8819
|
+
return res.status(postBuildRecovery.reason === 'queue_failed' ? 503 : 409).json({ error: postBuildRecovery.error });
|
|
8820
|
+
}
|
|
8821
|
+
return res.json(getTicket(req.params.id));
|
|
8822
|
+
}
|
|
8150
8823
|
const retrySource = ticket.project_id
|
|
8151
8824
|
? db.prepare(`
|
|
8152
8825
|
SELECT er.id
|
|
@@ -8476,15 +9149,12 @@ app.post('/tickets/:id/git-commit-message', async (req, res) => {
|
|
|
8476
9149
|
diff: getSelectedGitUnifiedDiff(workspace.cwd, selectedPaths),
|
|
8477
9150
|
});
|
|
8478
9151
|
try {
|
|
8479
|
-
const
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
: {
|
|
8486
|
-
tool: config.tool ?? 'opencode',
|
|
8487
|
-
}, prompt, workspace.cwd);
|
|
9152
|
+
const helperTool = resolveTicketTool(ticket, config.tool ?? 'opencode');
|
|
9153
|
+
const helperConfig = resolveHelperModelConfig({
|
|
9154
|
+
helperModelsJson: workspace.project.helper_models_json,
|
|
9155
|
+
agent: helperTool,
|
|
9156
|
+
});
|
|
9157
|
+
const rawMessage = await runLightweightPrompt({ tool: helperTool, ...helperConfig }, prompt, workspace.cwd);
|
|
8488
9158
|
const message = normalizeGeneratedCommitMessage(rawMessage);
|
|
8489
9159
|
if (!message) {
|
|
8490
9160
|
return res.status(500).json({ error: 'Unable to generate a commit message right now.' });
|