@straiffi/archon 1.10.9 → 1.10.12
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-BFn_Nhj4.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-iZD86Yju.js +1 -0
- package/dist/client/assets/VerificationDialog-CqKE8t1d.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BAIGV9Mj.js → architectureDiagram-3BPJPVTR-C8KefY67.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B-5v5jSZ.js → blockDiagram-GPEHLZMM-BO2Q_Osr.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-C6wU415o.js → c4Diagram-AAUBKEIU-DGPkhbrK.js} +1 -1
- package/dist/client/assets/channel-DXKn3TOv.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-COmvX2tP.js → chunk-2J33WTMH-Dtb7Xu8C.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-BlANptdY.js → chunk-3OPIFGDE-Bnf4GbOF.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-ETyA2_GU.js → chunk-5ZQYHXKU-BWGid3g7.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-D5-DudwF.js → chunk-727SXJPM-ScA8IRGP.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-k1AJ4sA3.js → chunk-AQP2D5EJ-Bygg04Ip.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-jfksXwY9.js → chunk-CSCIHK7Q-CxdDDtfN.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-CHj0FDj9.js → chunk-KSCS5N6A-BgpZFsB5.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-1ODGxXhG.js → chunk-L5ZTLDWV-CKSew0OL.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Bgh9X2W0.js → chunk-LZXEDZCA-DVG9Rx0K.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DDkb3sp4.js → chunk-ND2GUHAM-CHBqNmuF.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-B6B8PZ_-.js → chunk-NZK2D7GU-DvZYWn7V.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DhiUIFgj.js → chunk-O5CBEL6O-BL7lRTSe.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-Dzd0fbP3.js → chunk-WU5MYG2G-C-il2wHZ.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-U0fU20hT.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C6bQNSxL.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-DoNDMCXI.js → dagre-BM42HDAG-B00l5tLi.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-ETDXSa5q.js → diagram-2AECGRRQ-Tum6g1_5.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CgTUep7e.js → diagram-5GNKFQAL-BK1GX5jc.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BKTx2iRe.js → diagram-KO2AKTUF-BrzmD5NB.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-9iEiScHT.js → diagram-LMA3HP47-BhrlgXiJ.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-UIZRh7Qa.js → diagram-OG6HWLK6-K-fWsJ3a.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BpNJmJva.js → erDiagram-TEJ5UH35-Der6PjHE.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C-rK0XZ1.js → flowDiagram-I6XJVG4X-BVWgGhXe.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DWyfPvv_.js → ganttDiagram-6RSMTGT7-DcoZ5ggj.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-x71bzdc_.js → gitGraphDiagram-PVQCEYII-BhhbMTBu.js} +1 -1
- package/dist/client/assets/index-B6BltHSr.css +2 -0
- package/dist/client/assets/index-CFOYm3j9.js +276 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CbRus0Xu.js → infoDiagram-5YYISTIA-Lwc-agyc.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DvaMHrgN.js → ishikawaDiagram-YF4QCWOH-B95U1Xcl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BQBu4YGn.js → journeyDiagram-JHISSGLW-xFpigy5D.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-8rU-U7L1.js → kanban-definition-UN3LZRKU-D3mudQnR.js} +1 -1
- package/dist/client/assets/{line-CSZnwjLA.js → line-DtTL3ZI2.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CTOl2slE.js → mermaid-parser.core-Dau7K6zV.js} +1 -1
- package/dist/client/assets/{mermaid.core-B9gr0Vqf.js → mermaid.core-B8qtvATu.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-But1_PWQ.js → mindmap-definition-RKZ34NQL-Cus04Yq3.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CnlVmfSG.js → pieDiagram-4H26LBE5-BCMGpBnn.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dww1ZGuR.js → quadrantDiagram-W4KKPZXB-CAmOr-PJ.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CNf_loe8.js → requirementDiagram-4Y6WPE33-kisD3jc4.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DBhi37Jj.js → sankeyDiagram-5OEKKPKP-CsCedmM0.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B64hb4E4.js → sequenceDiagram-3UESZ5HK-B-dAi954.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DcS6oB-S.js → stateDiagram-AJRCARHV-X0BnedIW.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-6B8YH8Y2.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-Dq-4tXfh.js → timeline-definition-PNZ67QCA-DfFoSjhr.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CeDklSE_.js → vennDiagram-CIIHVFJN-CKSCBwhG.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-D2cJISMm.js → wardleyDiagram-YWT4CUSO-DNIBQmHO.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CBF-tTpF.js → xychartDiagram-2RQKCTM6-FvB55TQN.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +368 -5
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +194 -250
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agentRuns.js +14 -1
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationLoopProjection.js +55 -12
- package/dist/server/lib/automationLoopProjection.js.map +1 -1
- package/dist/server/lib/automationProgression.js +2 -5
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +19 -1
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/buildControlEligibility.js +4 -0
- package/dist/server/lib/buildControlEligibility.js.map +1 -0
- package/dist/server/lib/buildExecutionAdmission.js +76 -0
- package/dist/server/lib/buildExecutionAdmission.js.map +1 -0
- package/dist/server/lib/buildSequenceResume.js +79 -0
- package/dist/server/lib/buildSequenceResume.js.map +1 -0
- package/dist/server/lib/buildWorkload.js +11 -0
- package/dist/server/lib/buildWorkload.js.map +1 -0
- package/dist/server/lib/checkAutoFixDetails.js +36 -1
- package/dist/server/lib/checkAutoFixDetails.js.map +1 -1
- package/dist/server/lib/executionEnvironmentReconciliation.js +15 -2
- package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
- package/dist/server/lib/executionEnvironments.js +15 -0
- package/dist/server/lib/executionEnvironments.js.map +1 -1
- package/dist/server/lib/executionRetry.js +13 -1
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +2 -2
- package/dist/server/lib/executionRunAuthority.js.map +1 -1
- package/dist/server/lib/executionTargets.js +7 -4
- package/dist/server/lib/executionTargets.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +8 -13
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/executionTimelineHierarchy.js +83 -0
- package/dist/server/lib/executionTimelineHierarchy.js.map +1 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js +133 -0
- package/dist/server/lib/failedBuildSequenceRecovery.js.map +1 -0
- package/dist/server/lib/hostRunSession.js +62 -0
- package/dist/server/lib/hostRunSession.js.map +1 -0
- package/dist/server/lib/hostServiceRecovery.js +159 -0
- package/dist/server/lib/hostServiceRecovery.js.map +1 -0
- package/dist/server/lib/hostServiceRunProvider.js +32 -0
- package/dist/server/lib/hostServiceRunProvider.js.map +1 -0
- package/dist/server/lib/mobileAccess.js +6 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +5 -2
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projectTargets.js +16 -2
- package/dist/server/lib/projectTargets.js.map +1 -1
- package/dist/server/lib/projects.js +25 -1
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/reviewFindingReconciliation.js +9 -2
- package/dist/server/lib/reviewFindingReconciliation.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +82 -21
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/reviewRepairAssessmentRecords.js +41 -0
- package/dist/server/lib/reviewRepairAssessmentRecords.js.map +1 -0
- package/dist/server/lib/reviewRepairCompletion.js +78 -18
- package/dist/server/lib/reviewRepairCompletion.js.map +1 -1
- package/dist/server/lib/reviewRepairIterationRecords.js +4 -2
- package/dist/server/lib/reviewRepairIterationRecords.js.map +1 -1
- package/dist/server/lib/reviewRepairIterations.js +8 -2
- package/dist/server/lib/reviewRepairIterations.js.map +1 -1
- package/dist/server/lib/reviewRepairProgress.js +177 -0
- package/dist/server/lib/reviewRepairProgress.js.map +1 -0
- package/dist/server/lib/reviewRepairReconciliation.js +119 -140
- package/dist/server/lib/reviewRepairReconciliation.js.map +1 -1
- package/dist/server/lib/reviewRepairResumeOverride.js +55 -0
- package/dist/server/lib/reviewRepairResumeOverride.js.map +1 -0
- package/dist/server/lib/run.js +541 -504
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/runProcessIdentity.js +71 -0
- package/dist/server/lib/runProcessIdentity.js.map +1 -0
- package/dist/server/lib/runProcesses.js +273 -0
- package/dist/server/lib/runProcesses.js.map +1 -0
- package/dist/server/lib/runServices.js +223 -0
- package/dist/server/lib/runServices.js.map +1 -0
- package/dist/server/lib/runSessionLifecycle.js +77 -0
- package/dist/server/lib/runSessionLifecycle.js.map +1 -0
- package/dist/server/lib/runStatus.js +8 -0
- package/dist/server/lib/runStatus.js.map +1 -0
- package/dist/server/lib/serviceRunCleanup.js +70 -0
- package/dist/server/lib/serviceRunCleanup.js.map +1 -0
- package/dist/server/lib/serviceRuns.js +458 -19
- package/dist/server/lib/serviceRuns.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +4 -27
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +103 -34
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +0 -13
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +44 -69
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/plan.js +0 -7
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +95 -146
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ActivityView-CKvbDXeA.js +0 -1
- package/dist/client/assets/GitHubActionsDialog-Dzo0vtak.js +0 -1
- package/dist/client/assets/VerificationDialog-BJnOitrJ.js +0 -4
- package/dist/client/assets/channel-5mIy7nm1.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BiQ-X2Sg.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CF-wCYW-.js +0 -1
- package/dist/client/assets/index-CIAO5aE_.css +0 -2
- package/dist/client/assets/index-nCGyalrn.js +0 -276
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-PWeeL0tR.js +0 -1
- package/dist/server/lib/automationDeliveryDecision.js +0 -34
- package/dist/server/lib/automationDeliveryDecision.js.map +0 -1
package/dist/server/lib/run.js
CHANGED
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { assertHostSessionRequest, createHostRunSession, createRunConfigSnapshot, finishHostService, queueHostServices, serviceIdentity } from "./hostRunSession.js";
|
|
3
|
+
import { isProcessAlive, killManagedProcess, runCommand, runWorkspaceSetup, stopRunProcesses, trackExecutionHandle, waitForProcessesToExit } from "./runProcesses.js";
|
|
4
|
+
import { createRunServiceLauncher } from "./runServices.js";
|
|
5
|
+
import { withHostServiceRecovery } from "./hostServiceRunProvider.js";
|
|
6
|
+
import { createHostSessionSettler, finalizeDurableRun } from "./runSessionLifecycle.js";
|
|
7
|
+
import { existsSync } from "fs";
|
|
4
8
|
import { resolveWorktreePath } from "./git.js";
|
|
5
9
|
import { getProjectById, getProjectRunConfig } from "./projects.js";
|
|
6
10
|
import { resolveHelperModelConfig } from "./agentModelConfig.js";
|
|
7
11
|
import config from "./config.js";
|
|
8
|
-
import {
|
|
9
|
-
import { deactivatePreviewProxyForProject,
|
|
10
|
-
import {
|
|
11
|
-
import { redactRepositorySecrets } from "./projectMemoryScan.js";
|
|
12
|
+
import { createIdlePreviewStatus, createRunPreviewStatus, isPreviewService, } from "./preview.js";
|
|
13
|
+
import { deactivatePreviewProxyForProject, } from "./previewProxy.js";
|
|
14
|
+
import { resolveShell } from "./shell.js";
|
|
12
15
|
import { isTicketRunSetupReady } from "./ticketDependencies.js";
|
|
13
16
|
import { listTicketsInRunContext, resolveTicketBranch, resolveTicketContextKey, resolveTicketTool, } from "./tickets.js";
|
|
14
17
|
import { createWorkspaceProcessEnvironment } from "./workspaceProcessEnvironment.js";
|
|
15
18
|
import { resolveWorkloadExecutionEnvironment } from "./buildExecutionEnvironment.js";
|
|
16
|
-
import { classifyTicketExecutionTarget, resolveExecutionTarget, } from "./executionTargets.js";
|
|
17
|
-
import {
|
|
18
|
-
import { transitionExecutionRun } from "./executionRuns.js";
|
|
19
|
+
import { classifyTicketExecutionTarget, resolveExecutionTarget, resolveExecutionTargetLocation, } from "./executionTargets.js";
|
|
20
|
+
import { createServiceRun, markServiceRunAllocationStarted, hasActiveProjectServiceRun, recordServiceRunPlacement, getRecoverableHostServiceRun, retryInterruptedHostServiceRunForContext, } from "./serviceRuns.js";
|
|
21
|
+
import { appendLifecycleEvent, transitionExecutionRun } from "./executionRuns.js";
|
|
22
|
+
import { forceReleaseProjectServiceCleanup, retryProjectServiceCleanup } from "./serviceRunCleanup.js";
|
|
19
23
|
const runningProcesses = new Map();
|
|
20
|
-
const
|
|
24
|
+
const lastRunStatuses = new Map();
|
|
25
|
+
const STOP_SETTLEMENT_TIMEOUT_MS = 5_000;
|
|
26
|
+
const settleBeforeStopDeadline = (promise) => new Promise(resolve => {
|
|
27
|
+
const timer = setTimeout(resolve, STOP_SETTLEMENT_TIMEOUT_MS);
|
|
28
|
+
timer.unref();
|
|
29
|
+
void promise.then(() => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
resolve();
|
|
32
|
+
}, () => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
export const getLastRunStatusForProject = (projectId) => lastRunStatuses.get(projectId) ?? null;
|
|
38
|
+
export class RunAdmissionError extends Error {
|
|
39
|
+
code;
|
|
40
|
+
status = 409;
|
|
41
|
+
constructor(code, message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.name = "RunAdmissionError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
21
47
|
const getRunConfig = (project) => getProjectRunConfig(project);
|
|
22
48
|
export const resolveProjectRunContextKey = (projectId, target = {}) => {
|
|
23
49
|
if (target.kind === "bundle" && target.bundleId) {
|
|
@@ -58,7 +84,70 @@ const resolveRunContextKey = (ticketOrKey) => {
|
|
|
58
84
|
export const hasRunConfig = (project = null) => hasRunnableCommands(getRunConfig(project));
|
|
59
85
|
export const isRunning = (ticketOrKey) => {
|
|
60
86
|
const contextKey = resolveRunContextKey(ticketOrKey);
|
|
61
|
-
|
|
87
|
+
const state = contextKey ? runningProcesses.get(contextKey) : null;
|
|
88
|
+
if (state?.host_session && state.start_services) {
|
|
89
|
+
return state.host_session.services.some(service => ["starting", "running", "stopping"].includes(service.status));
|
|
90
|
+
}
|
|
91
|
+
return Boolean(state);
|
|
92
|
+
};
|
|
93
|
+
export const validateRunControl = (project, options) => {
|
|
94
|
+
const active = getActiveRunStatusForProject(project.id);
|
|
95
|
+
const state = active ? runningProcesses.get(active.context_key) : undefined;
|
|
96
|
+
if (!state && hasActiveProjectServiceRun(project.id)) {
|
|
97
|
+
if (options.starting) {
|
|
98
|
+
return "Durable service authority exists without a live runtime owner. Retry stopping the whole session before starting services.";
|
|
99
|
+
}
|
|
100
|
+
const recovered = getRecoverableHostServiceRun(project.id);
|
|
101
|
+
if (options.contextKey && recovered && options.contextKey !== recovered.context_key) {
|
|
102
|
+
return "Another service session is already active for this project";
|
|
103
|
+
}
|
|
104
|
+
if (options.sessionId !== undefined && recovered && options.sessionId !== recovered.sessionId) {
|
|
105
|
+
return "The service session has changed. Refresh before retrying.";
|
|
106
|
+
}
|
|
107
|
+
return options.serviceId !== undefined ? "Restarted sessions require whole-session cleanup" : null;
|
|
108
|
+
}
|
|
109
|
+
if (state?.admission_pending && options.starting) {
|
|
110
|
+
return "Service admission is still pending. Retry after admission settles.";
|
|
111
|
+
}
|
|
112
|
+
if (state?.cancelled && options.starting) {
|
|
113
|
+
return "The service session is stopping. Stop the whole session before retrying.";
|
|
114
|
+
}
|
|
115
|
+
if (options.contextKey && active && options.contextKey !== active.context_key) {
|
|
116
|
+
return "Another service session is already active for this project";
|
|
117
|
+
}
|
|
118
|
+
if (state && options.starting && options.executionProvider !== undefined) {
|
|
119
|
+
const requestedProvider = options.executionProvider === "host" ? "host" : project.build_execution_provider ?? "host";
|
|
120
|
+
if (requestedProvider !== state.execution_provider) {
|
|
121
|
+
return "The requested execution provider is incompatible with the active service session";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (state?.host_session) {
|
|
125
|
+
if (options.starting && state.host_session.services.some(service => (options.serviceId === undefined || service.service_id === options.serviceId) && service.status === "stopping")) {
|
|
126
|
+
return "Service process cleanup is pending. Retry stopping the service before restarting it.";
|
|
127
|
+
}
|
|
128
|
+
if (state.host_session.setup_error && options.starting) {
|
|
129
|
+
return "Shared setup failed. Stop the session before retrying.";
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
assertHostSessionRequest(state.host_session, options.serviceId, options.sessionId);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return getErrorMessage(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (options.sessionId !== undefined) {
|
|
140
|
+
return "The service session is no longer active";
|
|
141
|
+
}
|
|
142
|
+
if (options.serviceId !== undefined) {
|
|
143
|
+
if (state || (options.executionProvider !== "host" && project.build_execution_provider === "docker_sandbox")) {
|
|
144
|
+
return "Individual service controls are not supported by Docker Sandbox";
|
|
145
|
+
}
|
|
146
|
+
if (!(getRunConfig(project)?.services ?? []).some((service, index) => serviceIdentity(service, index) === options.serviceId)) {
|
|
147
|
+
return "Unknown service ID";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
62
151
|
};
|
|
63
152
|
export const getActiveRunStatus = () => {
|
|
64
153
|
const iterator = runningProcesses.values().next();
|
|
@@ -67,6 +156,28 @@ export const getActiveRunStatus = () => {
|
|
|
67
156
|
export const getActiveRunStatusForContextKey = (contextKey) => {
|
|
68
157
|
return runningProcesses.get(contextKey)?.active_run ?? null;
|
|
69
158
|
};
|
|
159
|
+
export const startActiveRunServices = (contextKey, serviceId, sessionId) => {
|
|
160
|
+
const state = runningProcesses.get(contextKey);
|
|
161
|
+
if (!state?.host_session || state.cancelled || state.admission_pending) {
|
|
162
|
+
throw new RunAdmissionError("service_session_unavailable", "The host service session is no longer active");
|
|
163
|
+
}
|
|
164
|
+
assertHostSessionRequest(state.host_session, serviceId, sessionId);
|
|
165
|
+
if (state.host_session.services.some(service => (serviceId === undefined || service.service_id === serviceId) && service.status === "stopping")) {
|
|
166
|
+
throw new RunAdmissionError("service_cleanup_pending", "Service process cleanup is pending. Retry stopping the service before restarting it.");
|
|
167
|
+
}
|
|
168
|
+
if (state.start_services) {
|
|
169
|
+
return state.start_services(serviceId).then(() => {
|
|
170
|
+
if (runningProcesses.get(contextKey) === state && !state.cancelled) {
|
|
171
|
+
state.on_state_changed?.();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
queueHostServices(state.host_session, serviceId);
|
|
177
|
+
state.on_state_changed?.();
|
|
178
|
+
}
|
|
179
|
+
return Promise.resolve();
|
|
180
|
+
};
|
|
70
181
|
export const getActiveRunStatusForProject = (projectId) => {
|
|
71
182
|
if (!projectId) {
|
|
72
183
|
return null;
|
|
@@ -99,38 +210,6 @@ export const getPreviewStatusForProject = (projectId) => {
|
|
|
99
210
|
const isActiveRunState = (contextKey, state) => {
|
|
100
211
|
return runningProcesses.get(contextKey) === state && !state.cancelled;
|
|
101
212
|
};
|
|
102
|
-
const isProcessAlive = (proc) => proc.exitCode === null && proc.signalCode === null;
|
|
103
|
-
const untrackProcess = (state, proc) => {
|
|
104
|
-
const index = state.processes.indexOf(proc);
|
|
105
|
-
if (index >= 0) {
|
|
106
|
-
state.processes.splice(index, 1);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const trackProcess = (state, proc) => {
|
|
110
|
-
const cleanup = () => {
|
|
111
|
-
untrackProcess(state, proc);
|
|
112
|
-
};
|
|
113
|
-
state.processes.push(proc);
|
|
114
|
-
proc.on("close", cleanup);
|
|
115
|
-
proc.on("error", cleanup);
|
|
116
|
-
return proc;
|
|
117
|
-
};
|
|
118
|
-
const trackExecutionHandle = (state, handle) => {
|
|
119
|
-
state.execution_handles?.push(handle);
|
|
120
|
-
return trackProcess(state, handle.child);
|
|
121
|
-
};
|
|
122
|
-
const spawnManagedProcess = (cmd, cwd, stdio, state) => {
|
|
123
|
-
const env = createWorkspaceProcessEnvironment(cwd);
|
|
124
|
-
delete env.PORT;
|
|
125
|
-
const shell = resolveShell();
|
|
126
|
-
const proc = spawn(shell, ["-i", "-c", cmd], {
|
|
127
|
-
cwd,
|
|
128
|
-
stdio,
|
|
129
|
-
detached: process.platform !== "win32",
|
|
130
|
-
env,
|
|
131
|
-
});
|
|
132
|
-
return trackProcess(state, proc);
|
|
133
|
-
};
|
|
134
213
|
export const openIde = (cwd, ideCommand) => {
|
|
135
214
|
spawn(resolveShell(), ["-i", "-c", `${ideCommand} .`], {
|
|
136
215
|
cwd,
|
|
@@ -152,552 +231,495 @@ export const openTicketIdeInWorkspace = (ticket, project = null, workspaceCwd =
|
|
|
152
231
|
openIde(cwd, runConfig.ide);
|
|
153
232
|
return true;
|
|
154
233
|
};
|
|
155
|
-
const
|
|
156
|
-
if (!isProcessAlive(proc)) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (proc.pid && process.platform !== "win32") {
|
|
160
|
-
try {
|
|
161
|
-
process.kill(-proc.pid, signal);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
if (error.code === "ESRCH") {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
proc.kill(signal);
|
|
172
|
-
}
|
|
173
|
-
catch { }
|
|
174
|
-
};
|
|
175
|
-
const waitForProcessesToExit = async (processes, timeoutMs) => {
|
|
176
|
-
const deadline = Date.now() + timeoutMs;
|
|
177
|
-
await new Promise((resolve) => {
|
|
178
|
-
const check = () => {
|
|
179
|
-
const allDone = processes.every((proc) => !isProcessAlive(proc));
|
|
180
|
-
if (allDone || Date.now() >= deadline) {
|
|
181
|
-
resolve();
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
setTimeout(check, 100);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
setTimeout(check, 100);
|
|
188
|
-
});
|
|
189
|
-
};
|
|
190
|
-
const runCommand = (cmd, cwd, state) => new Promise((resolve, reject) => {
|
|
191
|
-
const proc = spawnManagedProcess(cmd, cwd, ["ignore", "pipe", "pipe"], state);
|
|
192
|
-
let output = "";
|
|
193
|
-
const appendOutput = (chunk) => {
|
|
194
|
-
output = `${output}${chunk.toString()}`;
|
|
195
|
-
if (output.length > 8000) {
|
|
196
|
-
output = output.slice(-8000);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
proc.stdout?.on("data", appendOutput);
|
|
200
|
-
proc.stderr?.on("data", appendOutput);
|
|
201
|
-
proc.on("close", (code) => {
|
|
202
|
-
if (code === 0) {
|
|
203
|
-
resolve();
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
const suffix = output.trim() ? `\n${output.trim()}` : "";
|
|
207
|
-
reject(new Error(`Setup command failed (exit ${code}): ${cmd}${suffix}`));
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
proc.on("error", (err) => {
|
|
211
|
-
reject(err);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
export const runSetupCommandsInWorkspace = async (project, cwd, execution) => {
|
|
215
|
-
const runConfig = getRunConfig(project);
|
|
216
|
-
const setupCommands = runConfig?.setup ?? [];
|
|
217
|
-
if (setupCommands.length === 0) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const state = { processes: [] };
|
|
221
|
-
for (const cmd of setupCommands) {
|
|
222
|
-
if (execution?.signal?.aborted) {
|
|
223
|
-
throw new Error("Build was stopped.");
|
|
224
|
-
}
|
|
225
|
-
if (!execution) {
|
|
226
|
-
await runCommand(cmd, cwd, state);
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
const shell = getExecutionShellCommand({
|
|
230
|
-
provider: execution.provider.kind,
|
|
231
|
-
command: cmd,
|
|
232
|
-
loadHostStartupFiles: execution.runtimeEnvironment.kind !== "host"
|
|
233
|
-
|| execution.runtimeEnvironment.inheritance !== "governed",
|
|
234
|
-
});
|
|
235
|
-
const handle = execution.provider.execute({
|
|
236
|
-
phase: "setup",
|
|
237
|
-
target: execution.target,
|
|
238
|
-
command: shell.command,
|
|
239
|
-
args: shell.args,
|
|
240
|
-
cwd,
|
|
241
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
242
|
-
launcherEnvironment: execution.launcherEnvironment,
|
|
243
|
-
runtimeEnvironment: execution.runtimeEnvironment,
|
|
244
|
-
detached: process.platform !== "win32",
|
|
245
|
-
});
|
|
246
|
-
execution.onHandle?.(handle);
|
|
247
|
-
try {
|
|
248
|
-
let output = "";
|
|
249
|
-
const appendOutput = (chunk) => {
|
|
250
|
-
output = `${output}${chunk.toString()}`;
|
|
251
|
-
if (output.length > 8000) {
|
|
252
|
-
output = output.slice(-8000);
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
handle.child.stdout?.on("data", appendOutput);
|
|
256
|
-
handle.child.stderr?.on("data", appendOutput);
|
|
257
|
-
await new Promise((resolve, reject) => {
|
|
258
|
-
handle.child.once("error", reject);
|
|
259
|
-
handle.child.once("close", code => {
|
|
260
|
-
if (code === 0) {
|
|
261
|
-
resolve();
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const diagnostic = redactRepositorySecrets(output).content.trim();
|
|
265
|
-
const suffix = diagnostic ? `\n${diagnostic}` : "";
|
|
266
|
-
reject(new Error(`Setup command failed (exit ${code ?? "unknown"}).${suffix}`));
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
const placementFailure = await handle.placementFailure;
|
|
270
|
-
if (placementFailure) {
|
|
271
|
-
throw placementFailure;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
finally {
|
|
275
|
-
execution.onHandle?.(null);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
};
|
|
234
|
+
export const runSetupCommandsInWorkspace = runWorkspaceSetup;
|
|
279
235
|
const clearRunState = (contextKey, state, onStopped) => {
|
|
280
236
|
if (runningProcesses.get(contextKey) !== state) {
|
|
281
237
|
return;
|
|
282
238
|
}
|
|
239
|
+
if (state.release_execution_environment || state.finalization_failed) {
|
|
240
|
+
state.cancelled = true;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
283
243
|
deactivatePreviewProxyForProject(state.active_run.project_id);
|
|
284
244
|
state.preview_log_buffer = "";
|
|
245
|
+
if (state.active_run.project_id && state.active_run.config_snapshot) {
|
|
246
|
+
lastRunStatuses.set(state.active_run.project_id, {
|
|
247
|
+
...structuredClone(state.active_run),
|
|
248
|
+
config_snapshot: state.active_run.config_snapshot,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
285
251
|
runningProcesses.delete(contextKey);
|
|
286
252
|
onStopped?.();
|
|
287
253
|
};
|
|
288
254
|
const canCreateDurableServiceRun = (project, targetDescriptor) => Boolean(targetDescriptor
|
|
289
255
|
&& project.id
|
|
290
256
|
&& project.active_execution_profile_revision_id);
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const relativePath = relative(workspacePath, serviceCwd);
|
|
300
|
-
if (relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
301
|
-
throw new Error("Service working directories must remain inside the canonical target");
|
|
302
|
-
}
|
|
303
|
-
const realWorkspacePath = realpathSync(workspacePath);
|
|
304
|
-
const realServiceCwd = realpathSync(serviceCwd);
|
|
305
|
-
const realRelativePath = relative(realWorkspacePath, realServiceCwd);
|
|
306
|
-
if (realRelativePath === ".." || realRelativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
307
|
-
throw new Error("Service working directories must not escape the canonical target through symbolic links");
|
|
308
|
-
}
|
|
309
|
-
return serviceCwd;
|
|
310
|
-
};
|
|
311
|
-
const finalizeDurableRun = async (state, status, eventType, message) => {
|
|
312
|
-
const runId = state.execution_run_id;
|
|
313
|
-
if (!runId) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const release = state.release_execution_environment;
|
|
317
|
-
if (release) {
|
|
318
|
-
try {
|
|
319
|
-
await release();
|
|
320
|
-
state.release_execution_environment = null;
|
|
257
|
+
const startRun = ({ activeRun, cwd, executionAgent, executionModel, logLabelPrefix, onStopped, project, runConfig, skipSetup = false, targetDescriptor, profileSelection, serviceId, sessionId, }) => {
|
|
258
|
+
const contextKey = activeRun.context_key;
|
|
259
|
+
const admittedProvider = profileSelection?.reason === "explicit_service_host_override"
|
|
260
|
+
? "host" : project?.build_execution_provider ?? "host";
|
|
261
|
+
const existing = runningProcesses.get(contextKey);
|
|
262
|
+
if (existing) {
|
|
263
|
+
if (existing.admission_pending) {
|
|
264
|
+
throw new RunAdmissionError("service_admission_pending", "Service admission is still pending. Retry after admission settles.");
|
|
321
265
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
intendedStatus: status,
|
|
328
|
-
intendedEventType: eventType,
|
|
329
|
-
});
|
|
330
|
-
throw error;
|
|
266
|
+
if (existing.cancelled) {
|
|
267
|
+
throw new Error("The service session is stopping. Retry the stop before starting services.");
|
|
268
|
+
}
|
|
269
|
+
if (existing.execution_provider !== admittedProvider) {
|
|
270
|
+
throw new RunAdmissionError("service_provider_conflict", "The requested execution provider is incompatible with the active service session");
|
|
331
271
|
}
|
|
272
|
+
if (existing.host_session) {
|
|
273
|
+
return startActiveRunServices(contextKey, serviceId, sessionId);
|
|
274
|
+
}
|
|
275
|
+
else if (serviceId || sessionId) {
|
|
276
|
+
throw new Error("Individual service controls are only available for host sessions");
|
|
277
|
+
}
|
|
278
|
+
return Promise.resolve();
|
|
332
279
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
executionRunId: runId,
|
|
336
|
-
status,
|
|
337
|
-
code: status === "failed" ? "service_run_failed" : null,
|
|
338
|
-
message,
|
|
339
|
-
eventType,
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
const stopRunProcesses = async (state) => {
|
|
343
|
-
for (const proc of state.processes) {
|
|
344
|
-
killManagedProcess(proc, "SIGTERM");
|
|
280
|
+
if (sessionId !== undefined) {
|
|
281
|
+
throw new Error("The service session is no longer active");
|
|
345
282
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (isProcessAlive(proc)) {
|
|
349
|
-
killManagedProcess(proc, "SIGKILL");
|
|
350
|
-
}
|
|
283
|
+
if (getActiveRunStatusForProject(activeRun.project_id)) {
|
|
284
|
+
throw new Error("Another service session is already active for this project");
|
|
351
285
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
286
|
+
if (project?.id && hasActiveProjectServiceRun(project.id)) {
|
|
287
|
+
throw new RunAdmissionError("service_authority_without_runtime", "Durable service authority exists without a live runtime owner. Reconcile it before starting services.");
|
|
288
|
+
}
|
|
289
|
+
if (!existsSync(cwd)) {
|
|
290
|
+
throw new RunAdmissionError("service_workspace_unavailable", `Service workspace does not exist: ${cwd}`);
|
|
291
|
+
}
|
|
292
|
+
runConfig = structuredClone(runConfig);
|
|
293
|
+
activeRun = { ...activeRun, execution_provider: admittedProvider, config_snapshot: createRunConfigSnapshot(runConfig, {
|
|
294
|
+
preview_service_name: project?.preview_service_name,
|
|
295
|
+
preview_path: project?.preview_path,
|
|
296
|
+
preview_capability_mode: project?.preview_capability_mode,
|
|
297
|
+
}) };
|
|
298
|
+
project = structuredClone(project);
|
|
299
|
+
profileSelection = profileSelection ? structuredClone(profileSelection) : undefined;
|
|
356
300
|
const state = {
|
|
301
|
+
admission_pending: true,
|
|
302
|
+
execution_provider: admittedProvider,
|
|
357
303
|
processes: [],
|
|
358
304
|
cancelled: false,
|
|
359
305
|
active_run: activeRun,
|
|
360
|
-
preview_status: createRunPreviewStatus(project, runConfig),
|
|
306
|
+
preview_status: createRunPreviewStatus(project, runConfig, admittedProvider),
|
|
361
307
|
preview_log_buffer: "",
|
|
362
308
|
execution_run_id: null,
|
|
363
309
|
execution_handles: [],
|
|
364
310
|
release_execution_environment: null,
|
|
365
311
|
execution_project_id: null,
|
|
312
|
+
setup_abort_controller: new AbortController(),
|
|
313
|
+
launch_operations: 0,
|
|
314
|
+
launch_promises: new Set(),
|
|
315
|
+
on_state_changed: () => {
|
|
316
|
+
if (state.execution_run_id && !state.cancelled) {
|
|
317
|
+
appendLifecycleEvent({
|
|
318
|
+
projectId: project.id,
|
|
319
|
+
runId: state.execution_run_id,
|
|
320
|
+
eventType: "service_status_changed",
|
|
321
|
+
metadata: { session_id: state.host_session?.id, services: state.host_session?.services },
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
onStopped?.();
|
|
325
|
+
},
|
|
366
326
|
};
|
|
367
|
-
|
|
368
|
-
if (!
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
327
|
+
const expectsHost = admittedProvider === "host";
|
|
328
|
+
if (!expectsHost && serviceId !== undefined) {
|
|
329
|
+
throw new Error("Individual service controls are not supported by Docker Sandbox");
|
|
330
|
+
}
|
|
331
|
+
if (expectsHost) {
|
|
332
|
+
state.host_session = createHostRunSession(runConfig);
|
|
333
|
+
assertHostSessionRequest(state.host_session, serviceId, state.host_session.id);
|
|
334
|
+
state.active_run = {
|
|
335
|
+
...activeRun,
|
|
336
|
+
session_id: state.host_session.id,
|
|
337
|
+
services: state.host_session.services,
|
|
338
|
+
};
|
|
339
|
+
queueHostServices(state.host_session, serviceId);
|
|
372
340
|
}
|
|
373
|
-
|
|
374
|
-
let executionTarget = null;
|
|
341
|
+
runningProcesses.set(contextKey, state);
|
|
375
342
|
if (canCreateDurableServiceRun(project, targetDescriptor)) {
|
|
376
343
|
try {
|
|
377
|
-
const target =
|
|
378
|
-
|
|
379
|
-
descriptor: targetDescriptor,
|
|
380
|
-
expectedWorkspacePath: cwd,
|
|
381
|
-
});
|
|
382
|
-
if (!isActiveRunState(contextKey, state)) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
executionTarget = target;
|
|
386
|
-
const source = activeRun.kind === "ticket"
|
|
387
|
-
? {
|
|
388
|
-
kind: "ticket",
|
|
389
|
-
id: activeRun.ticket_id,
|
|
390
|
-
bundleId: target.identity.kind === "bundle-worktree"
|
|
391
|
-
? target.identity.bundleId
|
|
392
|
-
: null,
|
|
393
|
-
}
|
|
394
|
-
: { kind: "project", id: project.id };
|
|
344
|
+
const target = resolveExecutionTargetLocation({ project, descriptor: targetDescriptor, expectedWorkspacePath: cwd });
|
|
345
|
+
cwd = target.workspacePath;
|
|
395
346
|
const durableRun = createServiceRun({
|
|
396
|
-
projectId: project.id,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
services: runConfig.services ?? [],
|
|
401
|
-
profileSelection,
|
|
347
|
+
projectId: project.id, contextKey, sessionId: state.host_session?.id,
|
|
348
|
+
source: activeRun.kind === "ticket"
|
|
349
|
+
? { kind: "ticket", id: activeRun.ticket_id, bundleId: target.identity.kind === "bundle-worktree" ? target.identity.bundleId : null }
|
|
350
|
+
: { kind: "project", id: project.id },
|
|
351
|
+
target, services: runConfig.services ?? [], profileSelection,
|
|
402
352
|
});
|
|
403
353
|
state.execution_run_id = durableRun.id;
|
|
404
354
|
state.execution_project_id = project.id;
|
|
405
|
-
if (!isActiveRunState(contextKey, state)) {
|
|
406
|
-
await finalizeDurableRun(state, "cancelled", "service_run_cancelled");
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
execution = await resolveWorkloadExecutionEnvironment({
|
|
410
|
-
project,
|
|
411
|
-
executionRunId: durableRun.id,
|
|
412
|
-
agent: executionAgent,
|
|
413
|
-
model: executionModel,
|
|
414
|
-
target,
|
|
415
|
-
workload: "service",
|
|
416
|
-
onLeaseAcquired: release => {
|
|
417
|
-
state.release_execution_environment = release;
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
state.release_execution_environment = execution.release;
|
|
421
|
-
if (!isActiveRunState(contextKey, state)) {
|
|
422
|
-
await finalizeDurableRun(state, "cancelled", "service_run_cancelled");
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
state.execution_provider = execution.provider.kind === "docker_sandbox"
|
|
426
|
-
? "docker_sandbox"
|
|
427
|
-
: "host";
|
|
428
|
-
state.preview_status = createRunPreviewStatus(project, runConfig, state.execution_provider);
|
|
429
|
-
transitionExecutionRun({
|
|
430
|
-
projectId: project.id,
|
|
431
|
-
runId: durableRun.id,
|
|
432
|
-
status: "running",
|
|
433
|
-
domainStatus: "starting",
|
|
434
|
-
eventType: "service_run_starting",
|
|
435
|
-
eventMessage: "Service execution provider and canonical target authority were resolved.",
|
|
436
|
-
});
|
|
437
355
|
}
|
|
438
356
|
catch (error) {
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
console.error("[run] service environment release failed:", getErrorMessage(releaseError));
|
|
442
|
-
});
|
|
357
|
+
if (runningProcesses.get(contextKey) === state) {
|
|
358
|
+
runningProcesses.delete(contextKey);
|
|
443
359
|
}
|
|
444
|
-
|
|
445
|
-
clearRunState(contextKey, state, onStopped);
|
|
446
|
-
return;
|
|
360
|
+
throw new RunAdmissionError("service_admission_rejected", getErrorMessage(error));
|
|
447
361
|
}
|
|
448
362
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
363
|
+
state.admission_pending = false;
|
|
364
|
+
state.settle_session = createHostSessionSettler(state, () => runningProcesses.get(contextKey) === state, () => clearRunState(contextKey, state, onStopped));
|
|
365
|
+
const startup = async () => {
|
|
366
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
let execution = null;
|
|
370
|
+
let executionTarget = null;
|
|
371
|
+
if (canCreateDurableServiceRun(project, targetDescriptor)) {
|
|
372
|
+
try {
|
|
373
|
+
const target = await resolveExecutionTarget({
|
|
374
|
+
project,
|
|
375
|
+
descriptor: targetDescriptor,
|
|
376
|
+
expectedWorkspacePath: cwd,
|
|
377
|
+
});
|
|
378
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
executionTarget = target;
|
|
382
|
+
markServiceRunAllocationStarted(project.id, state.execution_run_id);
|
|
383
|
+
execution = await resolveWorkloadExecutionEnvironment({
|
|
384
|
+
project,
|
|
385
|
+
executionRunId: state.execution_run_id,
|
|
386
|
+
agent: executionAgent,
|
|
387
|
+
model: executionModel,
|
|
388
|
+
target,
|
|
389
|
+
workload: "service",
|
|
390
|
+
signal: state.setup_abort_controller?.signal,
|
|
391
|
+
onLeaseAcquired: release => {
|
|
392
|
+
state.release_execution_environment = release;
|
|
461
393
|
},
|
|
462
394
|
});
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
395
|
+
state.release_execution_environment = execution.release;
|
|
396
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (execution.provider.kind !== state.execution_provider) {
|
|
400
|
+
throw new Error("The resolved execution provider differs from the admitted service provider");
|
|
401
|
+
}
|
|
402
|
+
execution = { ...execution, provider: withHostServiceRecovery(execution.provider, project.id, state.execution_run_id) };
|
|
403
|
+
if (state.execution_provider === "docker_sandbox") {
|
|
404
|
+
if (serviceId !== undefined) {
|
|
405
|
+
throw new Error("Individual service controls are not supported by Docker Sandbox");
|
|
469
406
|
}
|
|
407
|
+
state.host_session = undefined;
|
|
408
|
+
state.active_run = activeRun;
|
|
470
409
|
}
|
|
410
|
+
state.preview_status = createRunPreviewStatus(project, runConfig, state.execution_provider);
|
|
411
|
+
transitionExecutionRun({
|
|
412
|
+
projectId: project.id,
|
|
413
|
+
runId: state.execution_run_id,
|
|
414
|
+
status: "running",
|
|
415
|
+
domainStatus: "starting",
|
|
416
|
+
eventType: "service_run_starting",
|
|
417
|
+
eventMessage: "Service execution provider and canonical target authority were resolved.",
|
|
418
|
+
});
|
|
471
419
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (state.execution_run_id) {
|
|
422
|
+
for (const service of state.host_session?.services ?? []) {
|
|
423
|
+
if (state.host_session?.pending.has(service.service_id)) {
|
|
424
|
+
service.status = "failed";
|
|
425
|
+
service.error = getErrorMessage(error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
state.host_session?.pending.clear();
|
|
429
|
+
await finalizeDurableRun(state, "failed", "service_run_start_failed", getErrorMessage(error)).catch(releaseError => {
|
|
430
|
+
console.error("[run] service environment release failed:", getErrorMessage(releaseError));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
console.error(`[run] durable service start failed for ${logLabelPrefix}:`, getErrorMessage(error));
|
|
434
|
+
clearRunState(contextKey, state, onStopped);
|
|
475
435
|
return;
|
|
476
436
|
}
|
|
477
|
-
console.error(`[run] setup failed for ${logLabelPrefix}:`, getErrorMessage(err));
|
|
478
|
-
await finalizeDurableRun(state, "failed", "service_setup_failed", getErrorMessage(err)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
|
|
479
|
-
clearRunState(contextKey, state, onStopped);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (!isActiveRunState(contextKey, state)) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
let serviceFailureMessage = null;
|
|
487
|
-
let startupComplete = false;
|
|
488
|
-
const cleanupIfAllDead = () => {
|
|
489
|
-
if (!startupComplete || runningProcesses.get(contextKey) !== state || state.cancelled) {
|
|
490
|
-
return;
|
|
491
437
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
let configuredServices;
|
|
500
|
-
try {
|
|
501
|
-
configuredServices = (runConfig.services ?? []).map(service => ({
|
|
502
|
-
service,
|
|
503
|
-
cwd: resolveServiceCwd(cwd, service.cwd),
|
|
504
|
-
}));
|
|
505
|
-
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
await finalizeDurableRun(state, "failed", "service_configuration_invalid", getErrorMessage(error)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
|
|
508
|
-
clearRunState(contextKey, state, onStopped);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
try {
|
|
512
|
-
for (const { service, cwd: serviceCwd } of configuredServices) {
|
|
513
|
-
const label = service.name ?? service.cmd;
|
|
514
|
-
let handle = null;
|
|
515
|
-
const proc = execution
|
|
516
|
-
? (() => {
|
|
517
|
-
const shell = getExecutionShellCommand({
|
|
518
|
-
provider: execution.provider.kind,
|
|
519
|
-
command: service.cmd,
|
|
520
|
-
loadHostStartupFiles: execution.runtimeEnvironment.kind !== "host"
|
|
521
|
-
|| execution.runtimeEnvironment.inheritance !== "governed",
|
|
522
|
-
});
|
|
523
|
-
handle = execution.provider.execute({
|
|
524
|
-
phase: "service",
|
|
438
|
+
if (!skipSetup) {
|
|
439
|
+
try {
|
|
440
|
+
if (execution) {
|
|
441
|
+
await runSetupCommandsInWorkspace(project, cwd, {
|
|
442
|
+
provider: execution.provider,
|
|
525
443
|
target: executionTarget,
|
|
526
|
-
command: shell.command,
|
|
527
|
-
args: shell.args,
|
|
528
|
-
cwd: serviceCwd,
|
|
529
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
530
444
|
launcherEnvironment: execution.launcherEnvironment,
|
|
531
445
|
runtimeEnvironment: execution.runtimeEnvironment,
|
|
532
|
-
|
|
446
|
+
signal: state.setup_abort_controller?.signal,
|
|
447
|
+
onHandle: handle => {
|
|
448
|
+
if (handle) {
|
|
449
|
+
trackExecutionHandle(state, handle);
|
|
450
|
+
if (state.cancelled) {
|
|
451
|
+
killManagedProcess(handle.child, "SIGTERM");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
},
|
|
533
455
|
});
|
|
534
|
-
return trackExecutionHandle(state, handle);
|
|
535
|
-
})()
|
|
536
|
-
: spawnManagedProcess(service.cmd, serviceCwd, ["ignore", "pipe", "pipe"], state);
|
|
537
|
-
const handleLogChunk = (data) => {
|
|
538
|
-
if (!isPreviewService(service, state.preview_status)) {
|
|
539
|
-
return;
|
|
540
456
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
: inferPreviewTargetsFromLogChunk(state.preview_log_buffer, project, service, state.preview_status.capability);
|
|
546
|
-
if (previewTargets.length === 0) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
void (async () => {
|
|
550
|
-
for (const detectedTarget of previewTargets) {
|
|
551
|
-
const reachableTarget = await resolveReachablePreviewTarget(detectedTarget, project.preview_path);
|
|
552
|
-
if (!reachableTarget || !isActiveRunState(contextKey, state)) {
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
if (state.preview_status.detected_url === reachableTarget.url &&
|
|
556
|
-
state.preview_status.proxy_url) {
|
|
457
|
+
else {
|
|
458
|
+
for (const cmd of runConfig.setup ?? []) {
|
|
459
|
+
await runCommand(cmd, cwd, state);
|
|
460
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
557
461
|
return;
|
|
558
462
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
if (state.cancelled) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
console.error(`[run] setup failed for ${logLabelPrefix}:`, getErrorMessage(err));
|
|
471
|
+
await finalizeDurableRun(state, "failed", "service_setup_failed", getErrorMessage(err)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
|
|
472
|
+
if (state.host_session) {
|
|
473
|
+
state.host_session.setup_error = getErrorMessage(err);
|
|
474
|
+
for (const service of state.host_session.services) {
|
|
475
|
+
if (state.host_session.pending.has(service.service_id)) {
|
|
476
|
+
service.status = "failed";
|
|
477
|
+
service.error = getErrorMessage(err);
|
|
573
478
|
}
|
|
574
|
-
return;
|
|
575
479
|
}
|
|
576
|
-
|
|
577
|
-
};
|
|
578
|
-
proc.stdout?.on("data", (data) => {
|
|
579
|
-
console.log(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
|
|
580
|
-
handleLogChunk(data);
|
|
581
|
-
});
|
|
582
|
-
proc.stderr?.on("data", (data) => {
|
|
583
|
-
console.error(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
|
|
584
|
-
handleLogChunk(data);
|
|
585
|
-
});
|
|
586
|
-
proc.on("close", (code) => {
|
|
587
|
-
console.log(`[run:${logLabelPrefix}:${label}] exited with code ${code}`);
|
|
588
|
-
if (code !== 0 && !state.cancelled) {
|
|
589
|
-
serviceFailureMessage = `Service ${label} exited with code ${code ?? "unknown"}.`;
|
|
480
|
+
state.host_session.pending.clear();
|
|
590
481
|
}
|
|
482
|
+
clearRunState(contextKey, state, onStopped);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
let serviceFailureMessage = null;
|
|
490
|
+
let startupComplete = false;
|
|
491
|
+
const cleanupIfAllDead = () => {
|
|
492
|
+
if (state.host_session) {
|
|
493
|
+
void state.settle_session?.().catch(error => console.error("[run] service finalization failed:", error));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (!startupComplete || runningProcesses.get(contextKey) !== state || state.cancelled) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const anyAlive = state.processes.some(isProcessAlive);
|
|
500
|
+
if (!anyAlive) {
|
|
501
|
+
state.cancelled = true;
|
|
502
|
+
void finalizeDurableRun(state, serviceFailureMessage ? "failed" : "completed", serviceFailureMessage ? "service_run_failed" : "service_run_completed", serviceFailureMessage ?? undefined)
|
|
503
|
+
.catch(error => console.error("[run] service environment release failed:", getErrorMessage(error)))
|
|
504
|
+
.finally(() => clearRunState(contextKey, state, onStopped));
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const launchServices = createRunServiceLauncher({
|
|
508
|
+
state, project, runConfig, cwd, execution, executionTarget, logLabelPrefix,
|
|
509
|
+
isActive: () => isActiveRunState(contextKey, state),
|
|
510
|
+
onExit: failure => {
|
|
511
|
+
serviceFailureMessage = failure ?? serviceFailureMessage;
|
|
591
512
|
cleanupIfAllDead();
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
console.error(`[run:${logLabelPrefix}:${label}] error: ${getErrorMessage(err)}`);
|
|
595
|
-
if (!state.cancelled) {
|
|
596
|
-
serviceFailureMessage = `Service ${label} failed: ${getErrorMessage(err)}`;
|
|
513
|
+
if (state.host_session && isActiveRunState(contextKey, state)) {
|
|
514
|
+
state.on_state_changed?.();
|
|
597
515
|
}
|
|
598
|
-
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
try {
|
|
519
|
+
state.start_services = requestedId => {
|
|
520
|
+
state.launch_operations = (state.launch_operations ?? 0) + 1;
|
|
521
|
+
const launching = (async () => {
|
|
522
|
+
try {
|
|
523
|
+
await launchServices(requestedId);
|
|
524
|
+
}
|
|
525
|
+
finally {
|
|
526
|
+
state.launch_operations -= 1;
|
|
527
|
+
await state.settle_session?.();
|
|
528
|
+
}
|
|
529
|
+
})();
|
|
530
|
+
state.launch_promises.add(launching);
|
|
531
|
+
const remove = () => { state.launch_promises.delete(launching); };
|
|
532
|
+
void launching.then(remove, remove);
|
|
533
|
+
return launching;
|
|
534
|
+
};
|
|
535
|
+
if (state.host_session) {
|
|
536
|
+
const pending = [...state.host_session.pending];
|
|
537
|
+
await Promise.all(pending.map(id => state.start_services(id)));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
await launchServices();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
await stopRunProcesses(state);
|
|
545
|
+
await finalizeDurableRun(state, "failed", "service_placement_failed", getErrorMessage(error)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
|
|
546
|
+
clearRunState(contextKey, state, onStopped);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (state.execution_run_id && execution && (runConfig.services ?? []).length === 0) {
|
|
550
|
+
recordServiceRunPlacement({
|
|
551
|
+
projectId: project.id,
|
|
552
|
+
executionRunId: state.execution_run_id,
|
|
553
|
+
provider: state.execution_provider,
|
|
554
|
+
environmentId: execution.environment?.id ?? null,
|
|
555
|
+
environmentRevision: execution.environment?.environment_revision ?? null,
|
|
599
556
|
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
557
|
+
}
|
|
558
|
+
if (!isActiveRunState(contextKey, state)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (state.execution_run_id) {
|
|
562
|
+
transitionExecutionRun({
|
|
563
|
+
projectId: project.id,
|
|
564
|
+
runId: state.execution_run_id,
|
|
565
|
+
status: "running",
|
|
566
|
+
domainStatus: "active",
|
|
567
|
+
eventType: "service_run_active",
|
|
568
|
+
eventMessage: state.execution_provider === "docker_sandbox"
|
|
569
|
+
? "Services are active in a shared Docker Sandbox environment; interruption applies to the whole environment."
|
|
570
|
+
: "Services are active as verified host processes.",
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if ((runConfig.services ?? []).length === 0) {
|
|
574
|
+
await finalizeDurableRun(state, "completed", "service_run_completed").catch(error => console.error("[run] service environment release failed:", getErrorMessage(error)));
|
|
575
|
+
clearRunState(contextKey, state, onStopped);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
startupComplete = true;
|
|
579
|
+
cleanupIfAllDead();
|
|
580
|
+
};
|
|
581
|
+
state.startup_promise = startup().finally(async () => {
|
|
582
|
+
state.startup_complete = true;
|
|
583
|
+
if (!state.finalization_failed) {
|
|
584
|
+
await state.settle_session?.();
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
return state.startup_promise;
|
|
588
|
+
};
|
|
589
|
+
export const stopAllTickets = async () => {
|
|
590
|
+
const contextKeys = [...runningProcesses.keys()];
|
|
591
|
+
await Promise.all(contextKeys.map(contextKey => stopTicket(contextKey)));
|
|
592
|
+
};
|
|
593
|
+
export const stopTicket = async (ticketOrKey, options = {}) => {
|
|
594
|
+
let contextKey = resolveRunContextKey(ticketOrKey);
|
|
595
|
+
if (options.sessionId && ticketOrKey && typeof ticketOrKey !== "string") {
|
|
596
|
+
const frozen = [...runningProcesses.values()].find(state => state.host_session?.id === options.sessionId
|
|
597
|
+
&& state.active_run.kind === "ticket" && state.active_run.ticket_id === ticketOrKey.id);
|
|
598
|
+
contextKey = frozen?.active_run.context_key ?? contextKey;
|
|
599
|
+
}
|
|
600
|
+
if (!contextKey) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const state = runningProcesses.get(contextKey);
|
|
604
|
+
if (!state) {
|
|
605
|
+
if (retryInterruptedHostServiceRunForContext(contextKey, options)) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (options.sessionId) {
|
|
609
|
+
throw new Error("The service session is no longer active");
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (state.host_session) {
|
|
614
|
+
assertHostSessionRequest(state.host_session, options.serviceId, options.sessionId);
|
|
615
|
+
if (options.serviceId !== undefined) {
|
|
616
|
+
const session = state.host_session;
|
|
617
|
+
session.pending.delete(options.serviceId);
|
|
618
|
+
const status = session.services.find(service => service.service_id === options.serviceId);
|
|
619
|
+
const proc = session.processes.get(options.serviceId);
|
|
620
|
+
if (!proc) {
|
|
621
|
+
status.status = "stopped";
|
|
622
|
+
if (!state.start_services && session.pending.size === 0 && session.processes.size === 0) {
|
|
623
|
+
await stopTicket(contextKey, { sessionId: session.id });
|
|
603
624
|
return;
|
|
604
625
|
}
|
|
605
|
-
|
|
606
|
-
|
|
626
|
+
await state.settle_session?.();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
state.launch_operations = (state.launch_operations ?? 0) + 1;
|
|
630
|
+
try {
|
|
631
|
+
status.status = "stopping";
|
|
632
|
+
const configuredService = session.config.services.find((service, index) => serviceIdentity(service, index) === options.serviceId);
|
|
633
|
+
if (configuredService && isPreviewService(configuredService, state.preview_status)) {
|
|
634
|
+
deactivatePreviewProxyForProject(state.active_run.project_id);
|
|
635
|
+
state.preview_status = { ...state.preview_status, state: "idle", detected_url: null, detected_origin: null, detected_path: null, proxy_url: null };
|
|
636
|
+
state.preview_log_buffer = "";
|
|
607
637
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
638
|
+
killManagedProcess(proc, "SIGTERM");
|
|
639
|
+
await waitForProcessesToExit([proc], 1000);
|
|
640
|
+
killManagedProcess(proc, "SIGKILL");
|
|
641
|
+
await waitForProcessesToExit([proc], 1000);
|
|
642
|
+
if (isProcessAlive(proc)) {
|
|
643
|
+
throw new Error("The service did not exit. Retry stopping it before restarting.");
|
|
611
644
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
executionRunId: state.execution_run_id,
|
|
615
|
-
provider: state.execution_provider,
|
|
616
|
-
environmentId: execution?.environment?.id ?? null,
|
|
617
|
-
environmentRevision: execution?.environment?.environment_revision ?? null,
|
|
618
|
-
placement,
|
|
619
|
-
});
|
|
645
|
+
finishHostService(session, options.serviceId, proc, proc.exitCode);
|
|
646
|
+
state.on_state_changed?.();
|
|
620
647
|
}
|
|
648
|
+
finally {
|
|
649
|
+
state.launch_operations -= 1;
|
|
650
|
+
await state.settle_session?.();
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
621
653
|
}
|
|
622
654
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
await finalizeDurableRun(state, "failed", "service_placement_failed", getErrorMessage(error)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
|
|
626
|
-
clearRunState(contextKey, state, onStopped);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
if (state.execution_run_id && execution && (runConfig.services ?? []).length === 0) {
|
|
630
|
-
recordServiceRunPlacement({
|
|
631
|
-
projectId: project.id,
|
|
632
|
-
executionRunId: state.execution_run_id,
|
|
633
|
-
provider: state.execution_provider,
|
|
634
|
-
environmentId: execution.environment?.id ?? null,
|
|
635
|
-
environmentRevision: execution.environment?.environment_revision ?? null,
|
|
636
|
-
});
|
|
655
|
+
else if (options.serviceId !== undefined || options.sessionId !== undefined) {
|
|
656
|
+
throw new Error("Individual service controls are not supported by Docker Sandbox");
|
|
637
657
|
}
|
|
638
|
-
|
|
639
|
-
|
|
658
|
+
const stoppingWork = !state.finalization_intent && (!state.startup_complete
|
|
659
|
+
|| Boolean(state.launch_operations) || Boolean(state.host_session?.pending.size)
|
|
660
|
+
|| state.processes.some(isProcessAlive));
|
|
661
|
+
state.cancelled = true;
|
|
662
|
+
state.setup_abort_controller?.abort();
|
|
663
|
+
state.host_session?.pending.clear();
|
|
664
|
+
if (state.host_session && stoppingWork) {
|
|
665
|
+
state.host_session.stop_requested = true;
|
|
666
|
+
}
|
|
667
|
+
for (const service of state.host_session?.services ?? []) {
|
|
668
|
+
if (["starting", "running"].includes(service.status)) {
|
|
669
|
+
service.status = "stopping";
|
|
670
|
+
}
|
|
640
671
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
status: "running",
|
|
646
|
-
domainStatus: "active",
|
|
647
|
-
eventType: "service_run_active",
|
|
648
|
-
eventMessage: state.execution_provider === "docker_sandbox"
|
|
649
|
-
? "Services are active in a shared Docker Sandbox environment; interruption applies to the whole environment."
|
|
650
|
-
: "Services are active as verified host processes.",
|
|
651
|
-
});
|
|
672
|
+
deactivatePreviewProxyForProject(state.active_run.project_id);
|
|
673
|
+
await stopRunProcesses(state);
|
|
674
|
+
if (state.startup_promise) {
|
|
675
|
+
await settleBeforeStopDeadline(state.startup_promise);
|
|
652
676
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
clearRunState(contextKey, state, onStopped);
|
|
677
|
+
await settleBeforeStopDeadline(Promise.allSettled([...(state.launch_promises ?? [])]));
|
|
678
|
+
if (runningProcesses.get(contextKey) !== state) {
|
|
656
679
|
return;
|
|
657
680
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
};
|
|
661
|
-
export const stopAllTickets = async () => {
|
|
662
|
-
const contextKeys = [...runningProcesses.keys()];
|
|
663
|
-
await Promise.all(contextKeys.map(stopTicket));
|
|
664
|
-
};
|
|
665
|
-
export const stopTicket = async (ticketOrKey) => {
|
|
666
|
-
const contextKey = resolveRunContextKey(ticketOrKey);
|
|
667
|
-
if (!contextKey) {
|
|
681
|
+
if (state.finalization) {
|
|
682
|
+
await state.finalization;
|
|
668
683
|
return;
|
|
669
684
|
}
|
|
670
|
-
|
|
671
|
-
|
|
685
|
+
await stopRunProcesses(state);
|
|
686
|
+
for (const [id, proc] of state.host_session?.processes ?? []) {
|
|
687
|
+
finishHostService(state.host_session, id, proc, proc.exitCode);
|
|
688
|
+
}
|
|
689
|
+
for (const service of state.host_session?.services ?? []) {
|
|
690
|
+
if (service.status === "stopping") {
|
|
691
|
+
service.status = "stopped";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
await state.settle_session?.();
|
|
695
|
+
if (state.finalization) {
|
|
696
|
+
await state.finalization;
|
|
672
697
|
return;
|
|
673
698
|
}
|
|
674
|
-
state.cancelled
|
|
675
|
-
await stopRunProcesses(state);
|
|
676
|
-
await finalizeDurableRun(state, "cancelled", "service_run_cancelled", state.execution_provider === "docker_sandbox"
|
|
699
|
+
state.finalization ??= finalizeDurableRun(state, "cancelled", "service_run_cancelled", state.execution_provider === "docker_sandbox"
|
|
677
700
|
? "The service run was stopped. Docker Sandbox interruption stops the whole environment and every attached service."
|
|
678
|
-
: "The service run host processes were stopped.").
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
701
|
+
: "The service run host processes were stopped.").then(() => clearRunState(contextKey, state)).catch(error => {
|
|
702
|
+
state.finalization = null;
|
|
703
|
+
throw error;
|
|
704
|
+
});
|
|
705
|
+
await state.finalization;
|
|
684
706
|
};
|
|
685
707
|
export const runTicket = async (ticket, io, project = null) => {
|
|
686
708
|
return runTicketInWorkspace(ticket, io, project, null);
|
|
687
709
|
};
|
|
688
|
-
export const runTicketInWorkspace =
|
|
710
|
+
export const runTicketInWorkspace = (ticket, io, project = null, workspaceCwd = null, profileSelection, controls = {}) => {
|
|
689
711
|
const runConfig = getRunConfig(project);
|
|
690
712
|
if (!runConfig || !hasRunnableCommands(runConfig)) {
|
|
691
|
-
return;
|
|
713
|
+
return Promise.resolve();
|
|
692
714
|
}
|
|
693
715
|
const contextKey = resolveTicketContextKey(ticket);
|
|
694
716
|
const branch = resolveTicketBranch(ticket);
|
|
695
717
|
if (!contextKey || (!branch && !workspaceCwd)) {
|
|
696
|
-
return;
|
|
718
|
+
return Promise.resolve();
|
|
697
719
|
}
|
|
698
720
|
const cwd = workspaceCwd ?? resolveWorktreePath(branch, project);
|
|
699
721
|
const executionAgent = resolveTicketTool(ticket, config.tool ?? "opencode");
|
|
700
|
-
|
|
722
|
+
return startRun({
|
|
701
723
|
activeRun: {
|
|
702
724
|
kind: "ticket",
|
|
703
725
|
context_key: contextKey,
|
|
@@ -713,7 +735,7 @@ export const runTicketInWorkspace = async (ticket, io, project = null, workspace
|
|
|
713
735
|
preferWorkflow: true,
|
|
714
736
|
}).model,
|
|
715
737
|
logLabelPrefix: `ticket:${ticket.id}`,
|
|
716
|
-
onStopped: () => emitRunStateForContext(io, ticket,
|
|
738
|
+
onStopped: () => emitRunStateForContext(io, ticket, isRunning(ticket)),
|
|
717
739
|
project: project,
|
|
718
740
|
runConfig,
|
|
719
741
|
skipSetup: isTicketRunSetupReady(ticket),
|
|
@@ -725,17 +747,18 @@ export const runTicketInWorkspace = async (ticket, io, project = null, workspace
|
|
|
725
747
|
})
|
|
726
748
|
: undefined,
|
|
727
749
|
profileSelection,
|
|
750
|
+
...controls,
|
|
728
751
|
});
|
|
729
752
|
};
|
|
730
|
-
export const runProject =
|
|
753
|
+
export const runProject = (project, options = {}) => {
|
|
731
754
|
const runConfig = getRunConfig(project);
|
|
732
755
|
if (!runConfig || !hasRunnableCommands(runConfig)) {
|
|
733
|
-
return;
|
|
756
|
+
return Promise.resolve();
|
|
734
757
|
}
|
|
735
758
|
const targetKind = options.targetKind ?? "repo_root";
|
|
736
759
|
const targetBundleId = targetKind === "bundle" ? (options.targetBundleId ?? null) : null;
|
|
737
760
|
const executionAgent = config.tool ?? "opencode";
|
|
738
|
-
|
|
761
|
+
return startRun({
|
|
739
762
|
activeRun: {
|
|
740
763
|
kind: "project",
|
|
741
764
|
context_key: resolveProjectRunContextKey(project.id, {
|
|
@@ -767,9 +790,23 @@ export const runProject = async (project, options = {}) => {
|
|
|
767
790
|
branch: options.targetBranch ?? null,
|
|
768
791
|
},
|
|
769
792
|
profileSelection: options.profileSelection,
|
|
793
|
+
serviceId: options.serviceId,
|
|
794
|
+
sessionId: options.sessionId,
|
|
770
795
|
});
|
|
771
796
|
};
|
|
772
797
|
export const stopProjectRun = async (projectId, target = {}) => {
|
|
773
|
-
|
|
798
|
+
const active = getActiveRunStatusForProject(projectId);
|
|
799
|
+
const recovered = active ? null : getRecoverableHostServiceRun(projectId);
|
|
800
|
+
if (!active && hasActiveProjectServiceRun(projectId)) {
|
|
801
|
+
await retryProjectServiceCleanup(projectId);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
await stopTicket(active?.context_key ?? recovered?.context_key ?? resolveProjectRunContextKey(projectId, target), target);
|
|
805
|
+
};
|
|
806
|
+
export const forceReleaseProjectRunCleanup = (projectId) => {
|
|
807
|
+
if (getActiveRunStatusForProject(projectId)) {
|
|
808
|
+
throw new Error("A live service session cannot be force released. Stop it normally first.");
|
|
809
|
+
}
|
|
810
|
+
return forceReleaseProjectServiceCleanup(projectId);
|
|
774
811
|
};
|
|
775
812
|
//# sourceMappingURL=run.js.map
|