@straiffi/archon 1.8.23 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/client/assets/{ActivityView-BaIZ8eHC.js → ActivityView-C5OrI0uK.js} +1 -1
  2. package/dist/client/assets/GitHubActionsDialog-BABPYwJP.js +1 -0
  3. package/dist/client/assets/VerificationDialog-LN87YY0r.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Dl5UF67P.js → architectureDiagram-3BPJPVTR-DBXhcjPI.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-DCcERfrZ.js → blockDiagram-GPEHLZMM-c3auwNH7.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-BGhbASFm.js → c4Diagram-AAUBKEIU-DH2YznEQ.js} +1 -1
  7. package/dist/client/assets/channel-CYsnLOcX.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-C1ARozTV.js → chunk-2J33WTMH-D0_MoyXZ.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-BEovLizF.js → chunk-3OPIFGDE-DLvSp1DN.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-C5MmlLa_.js → chunk-5ZQYHXKU-B4sX8Htk.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-8y8jBk4n.js → chunk-727SXJPM-CkdnShG1.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-q-mByAd3.js → chunk-AQP2D5EJ-BdPfgkcs.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-BOx2mLSz.js → chunk-CSCIHK7Q-CK-DpVF6.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-zEwQFq7q.js → chunk-KSCS5N6A-DSbTWVl2.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-C80z6rY_.js → chunk-L5ZTLDWV-DuQ4EPLl.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-BLaEsGPd.js → chunk-LZXEDZCA-CtpoXWMY.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-x26BZ95L.js → chunk-ND2GUHAM-CDfNOP6o.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-D2WAxr1p.js → chunk-NZK2D7GU-s6ReY4ev.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-DDPCrzKh.js → chunk-O5CBEL6O-DxqYGKhS.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CE-JcvIc.js → chunk-WU5MYG2G-BvrhxyV8.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-CElk31Q4.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-lnAahwmt.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-Bf7wpUk_.js → dagre-BM42HDAG-DEK_rftr.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-BQoKmc3B.js → diagram-2AECGRRQ-CpobcZnv.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-D2jarPLn.js → diagram-5GNKFQAL-zL1D1L_8.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-Bcbbjvsl.js → diagram-KO2AKTUF-gyW-Vc7c.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-t862RRBQ.js → diagram-LMA3HP47-DDEYc8sh.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CacbMcO0.js → diagram-OG6HWLK6-BtUrfJnE.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DRQ79baI.js → erDiagram-TEJ5UH35-qGrmpmmk.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-BfW4KEdc.js → flowDiagram-I6XJVG4X-DO1Nm1FA.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BPIl0TAj.js → ganttDiagram-6RSMTGT7-DGuMVCAc.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CRIusBMV.js → gitGraphDiagram-PVQCEYII-CehPAc8-.js} +1 -1
  33. package/dist/client/assets/index-1CokwSFm.css +2 -0
  34. package/dist/client/assets/index-DtefHOHC.js +276 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-DTKzK8EJ.js → infoDiagram-5YYISTIA-BkRaMDId.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DVIHZLvL.js → ishikawaDiagram-YF4QCWOH-B45ubODJ.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-CQBZ4tCx.js → journeyDiagram-JHISSGLW-BLxrlitK.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-D7naegR0.js → kanban-definition-UN3LZRKU-DmyeJDyy.js} +1 -1
  39. package/dist/client/assets/{line-CizZlaeV.js → line-BhwjsX6t.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-NrDqgpZa.js → mermaid-parser.core-CPvivy_u.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CwVO3oX0.js → mermaid.core-Dr0kld-g.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BWxy3Aa_.js → mindmap-definition-RKZ34NQL-dfudPzBR.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-B3gXpmBn.js → pieDiagram-4H26LBE5-BhsIQVNX.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Dpp4g8Al.js → quadrantDiagram-W4KKPZXB-CAKxScLD.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-lFKXnxml.js → requirementDiagram-4Y6WPE33-Br6jIbke.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BBdtwqrS.js → sankeyDiagram-5OEKKPKP-BqujNgnY.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ANpVZzqm.js → sequenceDiagram-3UESZ5HK-Dr_VM_ei.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-D58ZxmKY.js → stateDiagram-AJRCARHV-CDbSbbTW.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-p-kfFJi7.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-B6FK8IQC.js → timeline-definition-PNZ67QCA-CnuLuWDs.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-lFWtZEEY.js → vennDiagram-CIIHVFJN-CVAhxdBZ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DteAVp4l.js → wardleyDiagram-YWT4CUSO-Dmczey27.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CNcDlRLN.js → xychartDiagram-2RQKCTM6-Bl6JGLCU.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +225 -12
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +88 -27
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +368 -143
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/agentProvider.js.map +1 -1
  62. package/dist/server/lib/approvalReconciliation.js +14 -2
  63. package/dist/server/lib/approvalReconciliation.js.map +1 -1
  64. package/dist/server/lib/buildExecutionEnvironment.js +255 -58
  65. package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
  66. package/dist/server/lib/chatOperations.js +125 -3
  67. package/dist/server/lib/chatOperations.js.map +1 -1
  68. package/dist/server/lib/chatPostBuildRuns.js +12 -6
  69. package/dist/server/lib/chatPostBuildRuns.js.map +1 -1
  70. package/dist/server/lib/chatTargets.js +2 -1
  71. package/dist/server/lib/chatTargets.js.map +1 -1
  72. package/dist/server/lib/checkDefinitionCwd.js +34 -0
  73. package/dist/server/lib/checkDefinitionCwd.js.map +1 -0
  74. package/dist/server/lib/checkExecutionEnvironment.js +95 -0
  75. package/dist/server/lib/checkExecutionEnvironment.js.map +1 -0
  76. package/dist/server/lib/checkPolicyDefinitionIds.js +41 -0
  77. package/dist/server/lib/checkPolicyDefinitionIds.js.map +1 -0
  78. package/dist/server/lib/checkRepairDispatch.js +2 -0
  79. package/dist/server/lib/checkRepairDispatch.js.map +1 -1
  80. package/dist/server/lib/checkRunner.js +371 -175
  81. package/dist/server/lib/checkRunner.js.map +1 -1
  82. package/dist/server/lib/checkRuns.js +99 -8
  83. package/dist/server/lib/checkRuns.js.map +1 -1
  84. package/dist/server/lib/claudeAgentProvider.js +3 -6
  85. package/dist/server/lib/claudeAgentProvider.js.map +1 -1
  86. package/dist/server/lib/claudeStdin.js +27 -0
  87. package/dist/server/lib/claudeStdin.js.map +1 -0
  88. package/dist/server/lib/claudeValidationOutput.js +4 -4
  89. package/dist/server/lib/claudeValidationOutput.js.map +1 -1
  90. package/dist/server/lib/completedAssistantOutput.js +38 -0
  91. package/dist/server/lib/completedAssistantOutput.js.map +1 -0
  92. package/dist/server/lib/consequentialActionAuthority.js +99 -8
  93. package/dist/server/lib/consequentialActionAuthority.js.map +1 -1
  94. package/dist/server/lib/consequentialActions.js +42 -2
  95. package/dist/server/lib/consequentialActions.js.map +1 -1
  96. package/dist/server/lib/dockerSandboxCredentials.js +10 -3
  97. package/dist/server/lib/dockerSandboxCredentials.js.map +1 -1
  98. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +9 -8
  99. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
  100. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +197 -81
  101. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  102. package/dist/server/lib/dockerSandboxExecutionProvider.js +22 -14
  103. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
  104. package/dist/server/lib/dockerSandboxNetworkPolicy.js +23 -14
  105. package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -1
  106. package/dist/server/lib/dockerSandboxReleaseProfiles.js +91 -0
  107. package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -0
  108. package/dist/server/lib/dockerSandboxRuntimeController.js +16 -1
  109. package/dist/server/lib/dockerSandboxRuntimeController.js.map +1 -1
  110. package/dist/server/lib/executionCredentialCapabilities.js +9 -6
  111. package/dist/server/lib/executionCredentialCapabilities.js.map +1 -1
  112. package/dist/server/lib/executionEnvironment.js.map +1 -1
  113. package/dist/server/lib/executionEnvironmentLifecycle.js +7 -0
  114. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
  115. package/dist/server/lib/executionEnvironmentOperations.js +241 -0
  116. package/dist/server/lib/executionEnvironmentOperations.js.map +1 -0
  117. package/dist/server/lib/executionEnvironmentReconciliation.js +98 -3
  118. package/dist/server/lib/executionEnvironmentReconciliation.js.map +1 -1
  119. package/dist/server/lib/executionEnvironmentRoutes.js +66 -27
  120. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
  121. package/dist/server/lib/executionEnvironmentValidation.js +21 -12
  122. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
  123. package/dist/server/lib/executionEnvironmentValidationOperations.js +58 -31
  124. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
  125. package/dist/server/lib/executionEnvironments.js +187 -9
  126. package/dist/server/lib/executionEnvironments.js.map +1 -1
  127. package/dist/server/lib/executionProfileCheckPolicy.js +14 -0
  128. package/dist/server/lib/executionProfileCheckPolicy.js.map +1 -0
  129. package/dist/server/lib/executionProfileRoutes.js +9 -1
  130. package/dist/server/lib/executionProfileRoutes.js.map +1 -1
  131. package/dist/server/lib/executionProfileStore.js +5 -3
  132. package/dist/server/lib/executionProfileStore.js.map +1 -1
  133. package/dist/server/lib/executionProfiles.js +97 -15
  134. package/dist/server/lib/executionProfiles.js.map +1 -1
  135. package/dist/server/lib/executionReconciliation.js +43 -6
  136. package/dist/server/lib/executionReconciliation.js.map +1 -1
  137. package/dist/server/lib/executionRunAuthority.js +145 -24
  138. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  139. package/dist/server/lib/executionTargets.js +30 -4
  140. package/dist/server/lib/executionTargets.js.map +1 -1
  141. package/dist/server/lib/executionWorkloads.js +75 -0
  142. package/dist/server/lib/executionWorkloads.js.map +1 -0
  143. package/dist/server/lib/git.js +10 -1
  144. package/dist/server/lib/git.js.map +1 -1
  145. package/dist/server/lib/opencodeAgentProvider.js +3 -1
  146. package/dist/server/lib/opencodeAgentProvider.js.map +1 -1
  147. package/dist/server/lib/opencodeSessionOperations.js +5 -3
  148. package/dist/server/lib/opencodeSessionOperations.js.map +1 -1
  149. package/dist/server/lib/planQuestions.js +2 -1
  150. package/dist/server/lib/planQuestions.js.map +1 -1
  151. package/dist/server/lib/postBuildCheckRuns.js +88 -7
  152. package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
  153. package/dist/server/lib/preview.js +47 -6
  154. package/dist/server/lib/preview.js.map +1 -1
  155. package/dist/server/lib/previewProxy.js +11 -3
  156. package/dist/server/lib/previewProxy.js.map +1 -1
  157. package/dist/server/lib/productionCheckApprovals.js +97 -35
  158. package/dist/server/lib/productionCheckApprovals.js.map +1 -1
  159. package/dist/server/lib/projectMemorySuggestions.js +29 -6
  160. package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
  161. package/dist/server/lib/requiredCheckPolicy.js +3 -40
  162. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  163. package/dist/server/lib/reviewAgentWatchdog.js +75 -0
  164. package/dist/server/lib/reviewAgentWatchdog.js.map +1 -0
  165. package/dist/server/lib/reviewFindingReconciliation.js +176 -0
  166. package/dist/server/lib/reviewFindingReconciliation.js.map +1 -0
  167. package/dist/server/lib/reviewFindings.js +33 -11
  168. package/dist/server/lib/reviewFindings.js.map +1 -1
  169. package/dist/server/lib/run.js +370 -80
  170. package/dist/server/lib/run.js.map +1 -1
  171. package/dist/server/lib/serviceRuns.js +196 -0
  172. package/dist/server/lib/serviceRuns.js.map +1 -0
  173. package/dist/server/lib/sessionTargets.js +34 -0
  174. package/dist/server/lib/sessionTargets.js.map +1 -1
  175. package/dist/server/lib/shell.js +20 -0
  176. package/dist/server/lib/shell.js.map +1 -1
  177. package/dist/server/lib/specArtifact.js +36 -15
  178. package/dist/server/lib/specArtifact.js.map +1 -1
  179. package/dist/server/lib/specBundleGroups.js +4 -4
  180. package/dist/server/lib/specBundleGroups.js.map +1 -1
  181. package/dist/server/lib/specOperations.js +2 -0
  182. package/dist/server/lib/specOperations.js.map +1 -1
  183. package/dist/server/lib/specReviewOperations.js +2 -0
  184. package/dist/server/lib/specReviewOperations.js.map +1 -1
  185. package/dist/server/lib/specTicketMaterialization.js +37 -24
  186. package/dist/server/lib/specTicketMaterialization.js.map +1 -1
  187. package/dist/server/lib/ticketFollowUpOperations.js +28 -7
  188. package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
  189. package/dist/server/lib/ticketRunOperations.js +1 -0
  190. package/dist/server/lib/ticketRunOperations.js.map +1 -1
  191. package/dist/server/lib/types.js +1 -0
  192. package/dist/server/lib/types.js.map +1 -1
  193. package/dist/server/lib/workloadExecutionEnvironment.js +2 -0
  194. package/dist/server/lib/workloadExecutionEnvironment.js.map +1 -0
  195. package/dist/server/lib/workspaceIdentity.js +30 -6
  196. package/dist/server/lib/workspaceIdentity.js.map +1 -1
  197. package/dist/server/workers/build.js +150 -9
  198. package/dist/server/workers/build.js.map +1 -1
  199. package/dist/server/workers/chat.js +142 -15
  200. package/dist/server/workers/chat.js.map +1 -1
  201. package/dist/server/workers/followUp.js +97 -28
  202. package/dist/server/workers/followUp.js.map +1 -1
  203. package/dist/server/workers/plan.js +295 -48
  204. package/dist/server/workers/plan.js.map +1 -1
  205. package/dist/server/workers/planFollowUp.js +77 -7
  206. package/dist/server/workers/planFollowUp.js.map +1 -1
  207. package/dist/server/workers/review.js +149 -38
  208. package/dist/server/workers/review.js.map +1 -1
  209. package/dist/server/workers/specReview.js +43 -5
  210. package/dist/server/workers/specReview.js.map +1 -1
  211. package/dist/server/workers/specSession.js +156 -31
  212. package/dist/server/workers/specSession.js.map +1 -1
  213. package/package.json +1 -1
  214. package/dist/client/assets/GitHubActionsDialog-BFCqikDY.js +0 -1
  215. package/dist/client/assets/VerificationDialog-B1_6HMdt.js +0 -4
  216. package/dist/client/assets/channel-BfskvH2B.js +0 -1
  217. package/dist/client/assets/classDiagram-4FO5ZUOK-D4HPfxL3.js +0 -1
  218. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DQQ_WqAU.js +0 -1
  219. package/dist/client/assets/index-CZ1z72fo.js +0 -276
  220. package/dist/client/assets/index-SMj5ZmMs.css +0 -2
  221. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cv8cmDfz.js +0 -1
@@ -1,15 +1,20 @@
1
1
  import { spawn } from "child_process";
2
- import { existsSync } from "fs";
3
- import { join } from "path";
2
+ import { existsSync, realpathSync } from "fs";
3
+ import { isAbsolute, relative, resolve } from "path";
4
4
  import { resolveWorktreePath } from "./git.js";
5
5
  import { getProjectById, getProjectRunConfig } from "./projects.js";
6
6
  import { applyDetectedPreviewTarget, buildPreviewProxyUrl, createIdlePreviewStatus, createRunPreviewStatus, detectPreviewTargets, inferPreviewTargetsFromLogChunk, isPreviewService, normalizePreviewLogText, resolveReachablePreviewTarget, } from "./preview.js";
7
7
  import { deactivatePreviewProxyForProject, ensurePreviewProxyForProject, } from "./previewProxy.js";
8
- import { resolveShell } from "./shell.js";
8
+ import { getHostShellArguments, resolveShell } from "./shell.js";
9
9
  import { redactRepositorySecrets } from "./projectMemoryScan.js";
10
10
  import { isTicketRunSetupReady } from "./ticketDependencies.js";
11
11
  import { listTicketsInRunContext, resolveTicketBranch, resolveTicketContextKey, } from "./tickets.js";
12
12
  import { createWorkspaceProcessEnvironment } from "./workspaceProcessEnvironment.js";
13
+ import { resolveWorkloadExecutionEnvironment } from "./buildExecutionEnvironment.js";
14
+ import { getActiveExecutionProfileRevision } from "./executionProfileStore.js";
15
+ import { classifyTicketExecutionTarget, resolveExecutionTarget, } from "./executionTargets.js";
16
+ import { completeServiceRun, createServiceRun, recordServiceRunReleaseFailure, recordServiceRunPlacement, } from "./serviceRuns.js";
17
+ import { transitionExecutionRun } from "./executionRuns.js";
13
18
  const runningProcesses = new Map();
14
19
  const MAX_PREVIEW_LOG_BUFFER_CHARS = 16_000;
15
20
  const getRunConfig = (project) => getProjectRunConfig(project);
@@ -109,6 +114,10 @@ const trackProcess = (state, proc) => {
109
114
  proc.on("error", cleanup);
110
115
  return proc;
111
116
  };
117
+ const trackExecutionHandle = (state, handle) => {
118
+ state.execution_handles?.push(handle);
119
+ return trackProcess(state, handle.child);
120
+ };
112
121
  const spawnManagedProcess = (cmd, cwd, stdio, state) => {
113
122
  const env = createWorkspaceProcessEnvironment(cwd);
114
123
  delete env.PORT;
@@ -219,9 +228,8 @@ export const runSetupCommandsInWorkspace = async (project, cwd, execution) => {
219
228
  const hostShell = execution.provider.kind === "host" ? resolveShell() : null;
220
229
  const command = hostShell ?? "sh";
221
230
  const args = hostShell
222
- ? process.platform === "win32"
223
- ? ["/d", "/s", "/c", cmd]
224
- : ["-i", "-c", cmd]
231
+ ? getHostShellArguments(hostShell, cmd, execution.runtimeEnvironment.kind !== "host"
232
+ || execution.runtimeEnvironment.inheritance !== "governed")
225
233
  : ["-lc", cmd];
226
234
  const handle = execution.provider.execute({
227
235
  phase: "setup",
@@ -276,7 +284,88 @@ const clearRunState = (contextKey, state, onStopped) => {
276
284
  runningProcesses.delete(contextKey);
277
285
  onStopped?.();
278
286
  };
279
- const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, runConfig, skipSetup = false, }) => {
287
+ const canCreateDurableServiceRun = (project, targetDescriptor) => Boolean(targetDescriptor
288
+ && project.id
289
+ && project.active_execution_profile_revision_id);
290
+ const getServiceExecutionAgent = (project) => {
291
+ const profile = getActiveExecutionProfileRevision(project.id)?.profile;
292
+ return profile?.check_template_policy.mode === "agent_template"
293
+ ? profile.check_template_policy.agent
294
+ : "opencode";
295
+ };
296
+ const getExecutionShell = (provider, runtimeEnvironment, cmd) => {
297
+ const hostShell = provider.kind === "host" ? resolveShell() : null;
298
+ return {
299
+ command: hostShell ?? "sh",
300
+ args: hostShell
301
+ ? getHostShellArguments(hostShell, cmd, runtimeEnvironment.kind !== "host" || runtimeEnvironment.inheritance !== "governed")
302
+ : ["-lc", cmd],
303
+ };
304
+ };
305
+ const resolveServiceCwd = (workspacePath, configuredCwd) => {
306
+ if (!configuredCwd) {
307
+ return workspacePath;
308
+ }
309
+ if (isAbsolute(configuredCwd)) {
310
+ throw new Error("Service working directories must be relative to the canonical target");
311
+ }
312
+ const serviceCwd = resolve(workspacePath, configuredCwd);
313
+ const relativePath = relative(workspacePath, serviceCwd);
314
+ if (relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
315
+ throw new Error("Service working directories must remain inside the canonical target");
316
+ }
317
+ const realWorkspacePath = realpathSync(workspacePath);
318
+ const realServiceCwd = realpathSync(serviceCwd);
319
+ const realRelativePath = relative(realWorkspacePath, realServiceCwd);
320
+ if (realRelativePath === ".." || realRelativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
321
+ throw new Error("Service working directories must not escape the canonical target through symbolic links");
322
+ }
323
+ return serviceCwd;
324
+ };
325
+ const finalizeDurableRun = async (state, status, eventType, message) => {
326
+ const runId = state.execution_run_id;
327
+ if (!runId) {
328
+ return;
329
+ }
330
+ const release = state.release_execution_environment;
331
+ if (release) {
332
+ try {
333
+ await release();
334
+ state.release_execution_environment = null;
335
+ }
336
+ catch (error) {
337
+ recordServiceRunReleaseFailure({
338
+ projectId: state.execution_project_id,
339
+ executionRunId: runId,
340
+ message: getErrorMessage(error),
341
+ intendedStatus: status,
342
+ intendedEventType: eventType,
343
+ });
344
+ throw error;
345
+ }
346
+ }
347
+ completeServiceRun({
348
+ projectId: state.execution_project_id,
349
+ executionRunId: runId,
350
+ status,
351
+ code: status === "failed" ? "service_run_failed" : null,
352
+ message,
353
+ eventType,
354
+ });
355
+ };
356
+ const stopRunProcesses = async (state) => {
357
+ for (const proc of state.processes) {
358
+ killManagedProcess(proc, "SIGTERM");
359
+ }
360
+ await waitForProcessesToExit(state.processes, 1000);
361
+ for (const proc of state.processes) {
362
+ if (isProcessAlive(proc)) {
363
+ killManagedProcess(proc, "SIGKILL");
364
+ }
365
+ }
366
+ await waitForProcessesToExit(state.processes, 1000);
367
+ };
368
+ const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, runConfig, skipSetup = false, targetDescriptor, }) => {
280
369
  const contextKey = activeRun.context_key;
281
370
  const state = {
282
371
  processes: [],
@@ -284,6 +373,10 @@ const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, ru
284
373
  active_run: activeRun,
285
374
  preview_status: createRunPreviewStatus(project, runConfig),
286
375
  preview_log_buffer: "",
376
+ execution_run_id: null,
377
+ execution_handles: [],
378
+ release_execution_environment: null,
379
+ execution_project_id: null,
287
380
  };
288
381
  runningProcesses.set(contextKey, state);
289
382
  if (!existsSync(cwd)) {
@@ -291,12 +384,102 @@ const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, ru
291
384
  clearRunState(contextKey, state, onStopped);
292
385
  return;
293
386
  }
387
+ let execution = null;
388
+ let executionTarget = null;
389
+ if (canCreateDurableServiceRun(project, targetDescriptor)) {
390
+ try {
391
+ const target = await resolveExecutionTarget({
392
+ project,
393
+ descriptor: targetDescriptor,
394
+ expectedWorkspacePath: cwd,
395
+ });
396
+ if (!isActiveRunState(contextKey, state)) {
397
+ return;
398
+ }
399
+ executionTarget = target;
400
+ const source = activeRun.kind === "ticket"
401
+ ? {
402
+ kind: "ticket",
403
+ id: activeRun.ticket_id,
404
+ bundleId: target.identity.kind === "bundle-worktree"
405
+ ? target.identity.bundleId
406
+ : null,
407
+ }
408
+ : { kind: "project", id: project.id };
409
+ const durableRun = createServiceRun({
410
+ projectId: project.id,
411
+ contextKey,
412
+ source,
413
+ target,
414
+ services: runConfig.services ?? [],
415
+ });
416
+ state.execution_run_id = durableRun.id;
417
+ state.execution_project_id = project.id;
418
+ if (!isActiveRunState(contextKey, state)) {
419
+ await finalizeDurableRun(state, "cancelled", "service_run_cancelled");
420
+ return;
421
+ }
422
+ execution = await resolveWorkloadExecutionEnvironment({
423
+ project,
424
+ executionRunId: durableRun.id,
425
+ agent: getServiceExecutionAgent(project),
426
+ model: project.helper_model,
427
+ target,
428
+ workload: "service",
429
+ onLeaseAcquired: release => {
430
+ state.release_execution_environment = release;
431
+ },
432
+ });
433
+ state.release_execution_environment = execution.release;
434
+ if (!isActiveRunState(contextKey, state)) {
435
+ await finalizeDurableRun(state, "cancelled", "service_run_cancelled");
436
+ return;
437
+ }
438
+ state.execution_provider = execution.provider.kind === "docker_sandbox"
439
+ ? "docker_sandbox"
440
+ : "host";
441
+ state.preview_status = createRunPreviewStatus(project, runConfig, state.execution_provider);
442
+ transitionExecutionRun({
443
+ projectId: project.id,
444
+ runId: durableRun.id,
445
+ status: "running",
446
+ domainStatus: "starting",
447
+ eventType: "service_run_starting",
448
+ eventMessage: "Service execution provider and canonical target authority were resolved.",
449
+ });
450
+ }
451
+ catch (error) {
452
+ if (state.execution_run_id) {
453
+ await finalizeDurableRun(state, "failed", "service_run_start_failed", getErrorMessage(error)).catch(releaseError => {
454
+ console.error("[run] service environment release failed:", getErrorMessage(releaseError));
455
+ });
456
+ }
457
+ console.error(`[run] durable service start failed for ${logLabelPrefix}:`, getErrorMessage(error));
458
+ clearRunState(contextKey, state, onStopped);
459
+ return;
460
+ }
461
+ }
294
462
  if (!skipSetup) {
295
463
  try {
296
- for (const cmd of runConfig.setup ?? []) {
297
- await runCommand(cmd, cwd, state);
298
- if (!isActiveRunState(contextKey, state)) {
299
- return;
464
+ if (execution) {
465
+ await runSetupCommandsInWorkspace(project, cwd, {
466
+ provider: execution.provider,
467
+ target: executionTarget,
468
+ launcherEnvironment: execution.launcherEnvironment,
469
+ runtimeEnvironment: execution.runtimeEnvironment,
470
+ onHandle: handle => {
471
+ if (handle) {
472
+ trackExecutionHandle(state, handle);
473
+ }
474
+ },
475
+ });
476
+ }
477
+ else {
478
+ for (const cmd of runConfig.setup ?? []) {
479
+ await runCommand(cmd, cwd, state);
480
+ if (!isActiveRunState(contextKey, state)) {
481
+ return;
482
+ }
300
483
  }
301
484
  }
302
485
  }
@@ -305,6 +488,7 @@ const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, ru
305
488
  return;
306
489
  }
307
490
  console.error(`[run] setup failed for ${logLabelPrefix}:`, getErrorMessage(err));
491
+ await finalizeDurableRun(state, "failed", "service_setup_failed", getErrorMessage(err)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
308
492
  clearRunState(contextKey, state, onStopped);
309
493
  return;
310
494
  }
@@ -312,80 +496,175 @@ const startRun = async ({ activeRun, cwd, logLabelPrefix, onStopped, project, ru
312
496
  if (!isActiveRunState(contextKey, state)) {
313
497
  return;
314
498
  }
499
+ let serviceFailureMessage = null;
500
+ let startupComplete = false;
315
501
  const cleanupIfAllDead = () => {
316
- if (runningProcesses.get(contextKey) !== state || state.cancelled) {
502
+ if (!startupComplete || runningProcesses.get(contextKey) !== state || state.cancelled) {
317
503
  return;
318
504
  }
319
505
  const anyAlive = state.processes.some(isProcessAlive);
320
506
  if (!anyAlive) {
321
- clearRunState(contextKey, state, onStopped);
507
+ void finalizeDurableRun(state, serviceFailureMessage ? "failed" : "completed", serviceFailureMessage ? "service_run_failed" : "service_run_completed", serviceFailureMessage ?? undefined)
508
+ .catch(error => console.error("[run] service environment release failed:", getErrorMessage(error)))
509
+ .finally(() => clearRunState(contextKey, state, onStopped));
322
510
  }
323
511
  };
324
- for (const service of runConfig.services ?? []) {
325
- const serviceCwd = service.cwd ? join(cwd, service.cwd) : cwd;
326
- const label = service.name ?? service.cmd;
327
- const proc = spawnManagedProcess(service.cmd, serviceCwd, ["ignore", "pipe", "pipe"], state);
328
- const handleLogChunk = (data) => {
329
- if (!isPreviewService(service, state.preview_status)) {
330
- return;
331
- }
332
- state.preview_log_buffer = normalizePreviewLogText(`${state.preview_log_buffer}${data.toString()}`).slice(-MAX_PREVIEW_LOG_BUFFER_CHARS);
333
- const detectedTargets = detectPreviewTargets(state.preview_log_buffer, state.preview_status.capability);
334
- const previewTargets = detectedTargets.length > 0
335
- ? detectedTargets
336
- : inferPreviewTargetsFromLogChunk(state.preview_log_buffer, project, service, state.preview_status.capability);
337
- if (previewTargets.length === 0) {
338
- return;
339
- }
340
- void (async () => {
341
- for (const detectedTarget of previewTargets) {
342
- const reachableTarget = await resolveReachablePreviewTarget(detectedTarget, project.preview_path);
343
- if (!reachableTarget || !isActiveRunState(contextKey, state)) {
344
- continue;
345
- }
346
- if (state.preview_status.detected_url === reachableTarget.url &&
347
- state.preview_status.proxy_url) {
512
+ let configuredServices;
513
+ try {
514
+ configuredServices = (runConfig.services ?? []).map(service => ({
515
+ service,
516
+ cwd: resolveServiceCwd(cwd, service.cwd),
517
+ }));
518
+ }
519
+ catch (error) {
520
+ await finalizeDurableRun(state, "failed", "service_configuration_invalid", getErrorMessage(error)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
521
+ clearRunState(contextKey, state, onStopped);
522
+ return;
523
+ }
524
+ try {
525
+ for (const { service, cwd: serviceCwd } of configuredServices) {
526
+ const label = service.name ?? service.cmd;
527
+ let handle = null;
528
+ const proc = execution
529
+ ? (() => {
530
+ const shell = getExecutionShell(execution.provider, execution.runtimeEnvironment, service.cmd);
531
+ handle = execution.provider.execute({
532
+ phase: "service",
533
+ target: executionTarget,
534
+ command: shell.command,
535
+ args: shell.args,
536
+ cwd: serviceCwd,
537
+ stdio: ["ignore", "pipe", "pipe"],
538
+ launcherEnvironment: execution.launcherEnvironment,
539
+ runtimeEnvironment: execution.runtimeEnvironment,
540
+ detached: process.platform !== "win32",
541
+ });
542
+ return trackExecutionHandle(state, handle);
543
+ })()
544
+ : spawnManagedProcess(service.cmd, serviceCwd, ["ignore", "pipe", "pipe"], state);
545
+ const handleLogChunk = (data) => {
546
+ if (!isPreviewService(service, state.preview_status)) {
547
+ return;
548
+ }
549
+ state.preview_log_buffer = normalizePreviewLogText(`${state.preview_log_buffer}${data.toString()}`).slice(-MAX_PREVIEW_LOG_BUFFER_CHARS);
550
+ const detectedTargets = detectPreviewTargets(state.preview_log_buffer, state.preview_status.capability);
551
+ const previewTargets = detectedTargets.length > 0
552
+ ? detectedTargets
553
+ : inferPreviewTargetsFromLogChunk(state.preview_log_buffer, project, service, state.preview_status.capability);
554
+ if (previewTargets.length === 0) {
555
+ return;
556
+ }
557
+ void (async () => {
558
+ for (const detectedTarget of previewTargets) {
559
+ const reachableTarget = await resolveReachablePreviewTarget(detectedTarget, project.preview_path);
560
+ if (!reachableTarget || !isActiveRunState(contextKey, state)) {
561
+ continue;
562
+ }
563
+ if (state.preview_status.detected_url === reachableTarget.url &&
564
+ state.preview_status.proxy_url) {
565
+ return;
566
+ }
567
+ try {
568
+ const proxyOrigin = await ensurePreviewProxyForProject(project.id, reachableTarget.url);
569
+ state.preview_status = {
570
+ ...applyDetectedPreviewTarget(state.preview_status, reachableTarget),
571
+ proxy_url: buildPreviewProxyUrl(proxyOrigin, reachableTarget.url),
572
+ };
573
+ }
574
+ catch (error) {
575
+ state.preview_status = {
576
+ ...state.preview_status,
577
+ state: "error",
578
+ proxy_url: null,
579
+ message: getErrorMessage(error) || "Preview proxy failed",
580
+ };
581
+ }
348
582
  return;
349
583
  }
350
- try {
351
- const proxyOrigin = await ensurePreviewProxyForProject(project.id, reachableTarget.url);
352
- state.preview_status = {
353
- ...applyDetectedPreviewTarget(state.preview_status, reachableTarget),
354
- proxy_url: buildPreviewProxyUrl(proxyOrigin, reachableTarget.url),
355
- };
356
- }
357
- catch (error) {
358
- state.preview_status = {
359
- ...state.preview_status,
360
- state: "error",
361
- proxy_url: null,
362
- message: getErrorMessage(error) || "Preview proxy failed",
363
- };
364
- }
584
+ })();
585
+ };
586
+ proc.stdout?.on("data", (data) => {
587
+ console.log(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
588
+ handleLogChunk(data);
589
+ });
590
+ proc.stderr?.on("data", (data) => {
591
+ console.error(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
592
+ handleLogChunk(data);
593
+ });
594
+ proc.on("close", (code) => {
595
+ console.log(`[run:${logLabelPrefix}:${label}] exited with code ${code}`);
596
+ if (code !== 0 && !state.cancelled) {
597
+ serviceFailureMessage = `Service ${label} exited with code ${code ?? "unknown"}.`;
598
+ }
599
+ cleanupIfAllDead();
600
+ });
601
+ proc.on("error", (err) => {
602
+ console.error(`[run:${logLabelPrefix}:${label}] error: ${getErrorMessage(err)}`);
603
+ if (!state.cancelled) {
604
+ serviceFailureMessage = `Service ${label} failed: ${getErrorMessage(err)}`;
605
+ }
606
+ cleanupIfAllDead();
607
+ });
608
+ if (handle) {
609
+ const placementFailure = await handle.placementFailure;
610
+ if (!isActiveRunState(contextKey, state)) {
365
611
  return;
366
612
  }
367
- })();
368
- };
369
- proc.stdout?.on("data", (data) => {
370
- console.log(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
371
- handleLogChunk(data);
372
- });
373
- proc.stderr?.on("data", (data) => {
374
- console.error(`[run:${logLabelPrefix}:${label}] ${data.toString().trimEnd()}`);
375
- handleLogChunk(data);
376
- });
377
- proc.on("close", (code) => {
378
- console.log(`[run:${logLabelPrefix}:${label}] exited with code ${code}`);
379
- cleanupIfAllDead();
380
- });
381
- proc.on("error", (err) => {
382
- console.error(`[run:${logLabelPrefix}:${label}] error: ${getErrorMessage(err)}`);
383
- cleanupIfAllDead();
613
+ if (placementFailure) {
614
+ throw placementFailure;
615
+ }
616
+ const placement = await handle.placementIdentity;
617
+ if (!isActiveRunState(contextKey, state)) {
618
+ return;
619
+ }
620
+ recordServiceRunPlacement({
621
+ projectId: project.id,
622
+ executionRunId: state.execution_run_id,
623
+ provider: state.execution_provider,
624
+ environmentId: execution?.environment?.id ?? null,
625
+ environmentRevision: execution?.environment?.environment_revision ?? null,
626
+ placement,
627
+ });
628
+ }
629
+ }
630
+ }
631
+ catch (error) {
632
+ await stopRunProcesses(state);
633
+ await finalizeDurableRun(state, "failed", "service_placement_failed", getErrorMessage(error)).catch(releaseError => console.error("[run] service environment release failed:", getErrorMessage(releaseError)));
634
+ clearRunState(contextKey, state, onStopped);
635
+ return;
636
+ }
637
+ if (state.execution_run_id && execution && (runConfig.services ?? []).length === 0) {
638
+ recordServiceRunPlacement({
639
+ projectId: project.id,
640
+ executionRunId: state.execution_run_id,
641
+ provider: state.execution_provider,
642
+ environmentId: execution.environment?.id ?? null,
643
+ environmentRevision: execution.environment?.environment_revision ?? null,
384
644
  });
385
645
  }
386
646
  if (!isActiveRunState(contextKey, state)) {
387
647
  return;
388
648
  }
649
+ if (state.execution_run_id) {
650
+ transitionExecutionRun({
651
+ projectId: project.id,
652
+ runId: state.execution_run_id,
653
+ status: "running",
654
+ domainStatus: "active",
655
+ eventType: "service_run_active",
656
+ eventMessage: state.execution_provider === "docker_sandbox"
657
+ ? "Services are active in a shared Docker Sandbox environment; interruption applies to the whole environment."
658
+ : "Services are active as verified host processes.",
659
+ });
660
+ }
661
+ if ((runConfig.services ?? []).length === 0) {
662
+ await finalizeDurableRun(state, "completed", "service_run_completed").catch(error => console.error("[run] service environment release failed:", getErrorMessage(error)));
663
+ clearRunState(contextKey, state, onStopped);
664
+ return;
665
+ }
666
+ startupComplete = true;
667
+ cleanupIfAllDead();
389
668
  };
390
669
  export const stopAllTickets = async () => {
391
670
  const contextKeys = [...runningProcesses.keys()];
@@ -401,17 +680,10 @@ export const stopTicket = async (ticketOrKey) => {
401
680
  return;
402
681
  }
403
682
  state.cancelled = true;
404
- const { processes } = state;
405
- for (const proc of processes) {
406
- killManagedProcess(proc, "SIGTERM");
407
- }
408
- await waitForProcessesToExit(processes, 5000);
409
- for (const proc of processes) {
410
- if (isProcessAlive(proc)) {
411
- killManagedProcess(proc, "SIGKILL");
412
- }
413
- }
414
- await waitForProcessesToExit(processes, 1000);
683
+ await stopRunProcesses(state);
684
+ await finalizeDurableRun(state, "cancelled", "service_run_cancelled", state.execution_provider === "docker_sandbox"
685
+ ? "The service run was stopped. Docker Sandbox interruption stops the whole environment and every attached service."
686
+ : "The service run host processes were stopped.").catch(error => console.error("[run] service environment release failed:", getErrorMessage(error)));
415
687
  if (runningProcesses.get(contextKey) === state) {
416
688
  deactivatePreviewProxyForProject(state.active_run.project_id);
417
689
  state.preview_log_buffer = "";
@@ -445,6 +717,13 @@ export const runTicketInWorkspace = async (ticket, io, project = null, workspace
445
717
  project: project,
446
718
  runConfig,
447
719
  skipSetup: isTicketRunSetupReady(ticket),
720
+ targetDescriptor: branch
721
+ ? classifyTicketExecutionTarget({
722
+ branch,
723
+ bundleId: ticket.worktree_bundle_id ?? null,
724
+ bundleKind: ticket.bundle?.kind ?? null,
725
+ })
726
+ : undefined,
448
727
  });
449
728
  };
450
729
  export const runProject = async (project, options = {}) => {
@@ -469,6 +748,17 @@ export const runProject = async (project, options = {}) => {
469
748
  logLabelPrefix: `project:${project.id}`,
470
749
  project,
471
750
  runConfig,
751
+ targetDescriptor: targetKind === "bundle" && targetBundleId
752
+ ? {
753
+ kind: "bundle-worktree",
754
+ bundleId: targetBundleId,
755
+ branch: options.targetBranch ?? "",
756
+ }
757
+ : {
758
+ kind: "project-root",
759
+ bundleId: null,
760
+ branch: options.targetBranch ?? null,
761
+ },
472
762
  });
473
763
  };
474
764
  export const stopProjectRun = async (projectId, target = {}) => {