stageflow 0.39.0 → 0.42.0

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 (360) hide show
  1. package/README.md +46 -19
  2. package/dist/agent/claudeAdapter.js +15 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.js +38 -16
  6. package/dist/agent/sandboxPiTools.d.ts +99 -0
  7. package/dist/agent/sandboxPiTools.js +109 -0
  8. package/dist/agents/authorChat.d.ts +4 -0
  9. package/dist/agents/authorChat.js +113 -12
  10. package/dist/agents/authorSessionStore.d.ts +35 -0
  11. package/dist/agents/authorSessionStore.js +152 -0
  12. package/dist/agents/chatTurn.d.ts +2 -0
  13. package/dist/agents/chatTurn.js +17 -1
  14. package/dist/agents/errors.d.ts +1 -1
  15. package/dist/agents/errors.js +1 -0
  16. package/dist/agents/evalGate.d.ts +61 -0
  17. package/dist/agents/evalGate.js +103 -0
  18. package/dist/agents/evalSession.d.ts +26 -0
  19. package/dist/agents/evalSession.js +29 -0
  20. package/dist/agents/hostContext.d.ts +3 -0
  21. package/dist/agents/hostContext.js +4 -1
  22. package/dist/agents/runtime/piRuntime.d.ts +1 -1
  23. package/dist/agents/runtime/piRuntime.js +4 -4
  24. package/dist/agents/runtime/types.d.ts +3 -1
  25. package/dist/agents/service.d.ts +27 -2
  26. package/dist/agents/service.js +88 -13
  27. package/dist/agents/sessions.d.ts +8 -0
  28. package/dist/agents/sessions.js +50 -10
  29. package/dist/agents/store.d.ts +14 -3
  30. package/dist/agents/store.js +45 -7
  31. package/dist/agents/toolRisk.d.ts +21 -0
  32. package/dist/agents/toolRisk.js +50 -0
  33. package/dist/cli/doctorCommand.js +26 -0
  34. package/dist/cli/evalCommand.d.ts +20 -0
  35. package/dist/cli/evalCommand.js +720 -0
  36. package/dist/cli/exportAllCommand.js +1 -1
  37. package/dist/cli/hostClient.d.ts +60 -0
  38. package/dist/cli/hostClient.js +71 -0
  39. package/dist/cli/validateOutput.d.ts +2 -2
  40. package/dist/cli/validateOutput.js +2 -2
  41. package/dist/cli.js +10 -0
  42. package/dist/config/atomicWrite.d.ts +15 -0
  43. package/dist/config/atomicWrite.js +43 -0
  44. package/dist/config/browseCatalog.d.ts +2 -0
  45. package/dist/config/browseCatalog.js +4 -2
  46. package/dist/config/csv.d.ts +9 -0
  47. package/dist/config/csv.js +81 -0
  48. package/dist/config/datasetAgentCheck.d.ts +18 -0
  49. package/dist/config/datasetAgentCheck.js +109 -0
  50. package/dist/config/datasetCandidates.d.ts +22 -0
  51. package/dist/config/datasetCandidates.js +86 -0
  52. package/dist/config/datasetFromSessions.d.ts +34 -0
  53. package/dist/config/datasetFromSessions.js +105 -0
  54. package/dist/config/datasetHash.d.ts +1 -0
  55. package/dist/config/datasetHash.js +4 -0
  56. package/dist/config/datasetImport.d.ts +21 -0
  57. package/dist/config/datasetImport.js +120 -0
  58. package/dist/config/datasetStageCheck.d.ts +9 -0
  59. package/dist/config/datasetStageCheck.js +17 -0
  60. package/dist/config/datasetTypes.d.ts +27 -0
  61. package/dist/config/datasetTypes.js +1 -0
  62. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  63. package/dist/config/ensureCatalogScanRoot.js +2 -6
  64. package/dist/config/evalCatalog.d.ts +22 -0
  65. package/dist/config/evalCatalog.js +67 -0
  66. package/dist/config/evalWriteRoots.d.ts +17 -0
  67. package/dist/config/evalWriteRoots.js +109 -0
  68. package/dist/config/hostConfig.js +3 -0
  69. package/dist/config/installEvalSample.d.ts +34 -0
  70. package/dist/config/installEvalSample.js +182 -0
  71. package/dist/config/loadDataset.d.ts +16 -0
  72. package/dist/config/loadDataset.js +318 -0
  73. package/dist/config/loadEval.d.ts +32 -0
  74. package/dist/config/loadEval.js +1085 -0
  75. package/dist/config/loadOutcome.d.ts +5 -0
  76. package/dist/config/loadStageflowManifest.js +64 -0
  77. package/dist/config/loadTask.js +34 -1
  78. package/dist/config/resolveAgentTarget.d.ts +32 -0
  79. package/dist/config/resolveAgentTarget.js +118 -0
  80. package/dist/config/resolveEvalTarget.d.ts +21 -0
  81. package/dist/config/resolveEvalTarget.js +89 -0
  82. package/dist/config/scanCatalogPaths.d.ts +1 -1
  83. package/dist/config/scanCatalogPaths.js +17 -5
  84. package/dist/config/validateCatalog.d.ts +1 -1
  85. package/dist/config/validateCatalog.js +9 -1
  86. package/dist/config/validateEval.d.ts +36 -0
  87. package/dist/config/validateEval.js +405 -0
  88. package/dist/config/writeDataset.d.ts +54 -0
  89. package/dist/config/writeDataset.js +186 -0
  90. package/dist/config/writeEval.d.ts +25 -0
  91. package/dist/config/writeEval.js +398 -0
  92. package/dist/diagnostics/checks.js +1 -1
  93. package/dist/evals/agentDiff.d.ts +13 -0
  94. package/dist/evals/agentDiff.js +11 -0
  95. package/dist/evals/canonical.d.ts +3 -0
  96. package/dist/evals/canonical.js +24 -0
  97. package/dist/evals/caseTask.d.ts +3 -0
  98. package/dist/evals/caseTask.js +9 -0
  99. package/dist/evals/compare.d.ts +94 -0
  100. package/dist/evals/compare.js +201 -0
  101. package/dist/evals/constants.d.ts +60 -0
  102. package/dist/evals/constants.js +95 -0
  103. package/dist/evals/engine/FakeEngine.d.ts +33 -0
  104. package/dist/evals/engine/FakeEngine.js +176 -0
  105. package/dist/evals/engine/index.d.ts +9 -0
  106. package/dist/evals/engine/index.js +19 -0
  107. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
  108. package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
  109. package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
  110. package/dist/evals/engine/promptfoo/errors.js +8 -0
  111. package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
  112. package/dist/evals/engine/promptfoo/generate.js +342 -0
  113. package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
  114. package/dist/evals/engine/promptfoo/guard.js +116 -0
  115. package/dist/evals/engine/promptfoo/install.d.ts +17 -0
  116. package/dist/evals/engine/promptfoo/install.js +73 -0
  117. package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
  118. package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
  119. package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
  120. package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
  121. package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
  122. package/dist/evals/engine/promptfoo/locate.js +68 -0
  123. package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
  124. package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
  125. package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
  126. package/dist/evals/engine/promptfoo/normalize.js +261 -0
  127. package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
  128. package/dist/evals/engine/promptfoo/pinned.js +5 -0
  129. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
  130. package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
  131. package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
  132. package/dist/evals/engine/promptfoo/runTokens.js +31 -0
  133. package/dist/evals/engine/promptfoo/status.d.ts +13 -0
  134. package/dist/evals/engine/promptfoo/status.js +16 -0
  135. package/dist/evals/estimate.d.ts +56 -0
  136. package/dist/evals/estimate.js +66 -0
  137. package/dist/evals/graders/codeGrader.d.ts +6 -0
  138. package/dist/evals/graders/codeGrader.js +85 -0
  139. package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
  140. package/dist/evals/graders/conversationCheckGrader.js +62 -0
  141. package/dist/evals/graders/fieldGrader.d.ts +11 -0
  142. package/dist/evals/graders/fieldGrader.js +117 -0
  143. package/dist/evals/graders/goal.d.ts +3 -0
  144. package/dist/evals/graders/goal.js +14 -0
  145. package/dist/evals/graders/index.d.ts +18 -0
  146. package/dist/evals/graders/index.js +34 -0
  147. package/dist/evals/graders/notApplicable.d.ts +5 -0
  148. package/dist/evals/graders/notApplicable.js +9 -0
  149. package/dist/evals/graders/path.d.ts +26 -0
  150. package/dist/evals/graders/path.js +75 -0
  151. package/dist/evals/graders/referenceGrader.d.ts +5 -0
  152. package/dist/evals/graders/referenceGrader.js +59 -0
  153. package/dist/evals/graders/schemaGrader.d.ts +5 -0
  154. package/dist/evals/graders/schemaGrader.js +29 -0
  155. package/dist/evals/graders/subject.d.ts +6 -0
  156. package/dist/evals/graders/subject.js +33 -0
  157. package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
  158. package/dist/evals/graders/toolCallsGrader.js +100 -0
  159. package/dist/evals/graders/transcript.d.ts +12 -0
  160. package/dist/evals/graders/transcript.js +57 -0
  161. package/dist/evals/graders/types.d.ts +20 -0
  162. package/dist/evals/graders/types.js +1 -0
  163. package/dist/evals/judgeProviders.d.ts +16 -0
  164. package/dist/evals/judgeProviders.js +27 -0
  165. package/dist/evals/lineDiff.d.ts +5 -0
  166. package/dist/evals/lineDiff.js +34 -0
  167. package/dist/evals/listStatus.d.ts +31 -0
  168. package/dist/evals/listStatus.js +48 -0
  169. package/dist/evals/manager.d.ts +174 -0
  170. package/dist/evals/manager.js +787 -0
  171. package/dist/evals/plan.d.ts +58 -0
  172. package/dist/evals/plan.js +602 -0
  173. package/dist/evals/providerKeys.d.ts +1 -0
  174. package/dist/evals/providerKeys.js +21 -0
  175. package/dist/evals/read/agentTargets.d.ts +37 -0
  176. package/dist/evals/read/agentTargets.js +101 -0
  177. package/dist/evals/read/api.d.ts +196 -0
  178. package/dist/evals/read/api.js +358 -0
  179. package/dist/evals/read/compare.d.ts +4 -0
  180. package/dist/evals/read/compare.js +39 -0
  181. package/dist/evals/read/datasets.d.ts +105 -0
  182. package/dist/evals/read/datasets.js +318 -0
  183. package/dist/evals/read/list.d.ts +66 -0
  184. package/dist/evals/read/list.js +173 -0
  185. package/dist/evals/read/results.d.ts +46 -0
  186. package/dist/evals/read/results.js +107 -0
  187. package/dist/evals/read/runs.d.ts +149 -0
  188. package/dist/evals/read/runs.js +289 -0
  189. package/dist/evals/read/suggestions.d.ts +43 -0
  190. package/dist/evals/read/suggestions.js +73 -0
  191. package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
  192. package/dist/evals/runner/FakeAgentBackend.js +82 -0
  193. package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
  194. package/dist/evals/runner/agentCaseRunner.js +388 -0
  195. package/dist/evals/runner/caseRunner.d.ts +7 -0
  196. package/dist/evals/runner/caseRunner.js +17 -0
  197. package/dist/evals/runner/conversationSubject.d.ts +18 -0
  198. package/dist/evals/runner/conversationSubject.js +123 -0
  199. package/dist/evals/runner/simulatorModel.d.ts +51 -0
  200. package/dist/evals/runner/simulatorModel.js +58 -0
  201. package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
  202. package/dist/evals/runner/simulatorPrompt.js +59 -0
  203. package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
  204. package/dist/evals/runner/stageCaseRunner.js +279 -0
  205. package/dist/evals/runner/userDriver.d.ts +71 -0
  206. package/dist/evals/runner/userDriver.js +104 -0
  207. package/dist/evals/scoring.d.ts +34 -0
  208. package/dist/evals/scoring.js +67 -0
  209. package/dist/evals/snapshot.d.ts +183 -0
  210. package/dist/evals/snapshot.js +65 -0
  211. package/dist/evals/stageDiff.d.ts +8 -0
  212. package/dist/evals/stageDiff.js +48 -0
  213. package/dist/evals/stats.d.ts +95 -0
  214. package/dist/evals/stats.js +220 -0
  215. package/dist/evals/statsAdapter.d.ts +4 -0
  216. package/dist/evals/statsAdapter.js +30 -0
  217. package/dist/evals/statsTypes.d.ts +51 -0
  218. package/dist/evals/statsTypes.js +18 -0
  219. package/dist/evals/storeError.d.ts +5 -0
  220. package/dist/evals/storeError.js +8 -0
  221. package/dist/evals/storeTypes.d.ts +207 -0
  222. package/dist/evals/storeTypes.js +1 -0
  223. package/dist/evals/tTable.d.ts +3 -0
  224. package/dist/evals/tTable.js +15 -0
  225. package/dist/evals/triage/constants.d.ts +26 -0
  226. package/dist/evals/triage/constants.js +33 -0
  227. package/dist/evals/triage/credentials.d.ts +13 -0
  228. package/dist/evals/triage/credentials.js +20 -0
  229. package/dist/evals/triage/jevClassifier.d.ts +14 -0
  230. package/dist/evals/triage/jevClassifier.js +124 -0
  231. package/dist/evals/triage/judgeClassifier.d.ts +10 -0
  232. package/dist/evals/triage/judgeClassifier.js +76 -0
  233. package/dist/evals/triage/prompts.d.ts +16 -0
  234. package/dist/evals/triage/prompts.js +64 -0
  235. package/dist/evals/triage/service.d.ts +79 -0
  236. package/dist/evals/triage/service.js +217 -0
  237. package/dist/evals/triage/stateBuilder.d.ts +19 -0
  238. package/dist/evals/triage/stateBuilder.js +125 -0
  239. package/dist/evals/triage/types.d.ts +60 -0
  240. package/dist/evals/triage/types.js +8 -0
  241. package/dist/evals/types.d.ts +347 -0
  242. package/dist/evals/types.js +1 -0
  243. package/dist/mcp/catalogTools.js +11 -1
  244. package/dist/mcp/deps.d.ts +7 -0
  245. package/dist/mcp/evalTools.d.ts +3 -0
  246. package/dist/mcp/evalTools.js +280 -0
  247. package/dist/mcp/server.js +1 -0
  248. package/dist/mcp/tools.js +2 -0
  249. package/dist/models/response.d.ts +7 -2
  250. package/dist/models/response.js +6 -1
  251. package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
  252. package/dist/operatorAgent/agentAuthorContext.js +1 -1
  253. package/dist/operatorAgent/piModel.js +17 -4
  254. package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
  255. package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
  256. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  257. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
  258. package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
  259. package/dist/operatorAgent/profiles/authorReadable.js +26 -0
  260. package/dist/package-meta.d.ts +1 -1
  261. package/dist/package-meta.js +1 -1
  262. package/dist/runstore/port.d.ts +43 -0
  263. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
  264. package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
  265. package/dist/runstore/sqlite/evalStore.d.ts +20 -0
  266. package/dist/runstore/sqlite/evalStore.js +541 -0
  267. package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
  268. package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
  269. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
  270. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
  271. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
  272. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
  273. package/dist/runstore/sqlite/migrations/index.js +6 -0
  274. package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
  275. package/dist/runstore/sqlite/suggestionStore.js +132 -0
  276. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  277. package/dist/runtime/gitCheckoutCapability.js +1 -1
  278. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  279. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  280. package/dist/runtime/pipelineRunner.d.ts +2 -1
  281. package/dist/runtime/pipelineRunner.js +2 -0
  282. package/dist/runtime/runManager.d.ts +2 -1
  283. package/dist/runtime/runManager.js +8 -6
  284. package/dist/runtime/runRetentionSweep.js +2 -2
  285. package/dist/runtime/sandboxContainer.d.ts +62 -0
  286. package/dist/runtime/sandboxContainer.js +151 -0
  287. package/dist/runtime/sandboxDesk.d.ts +28 -0
  288. package/dist/runtime/sandboxDesk.js +242 -0
  289. package/dist/runtime/sandboxFiles.d.ts +20 -0
  290. package/dist/runtime/sandboxFiles.js +150 -0
  291. package/dist/runtime/stageEnvironment.js +2 -0
  292. package/dist/runtime/stageRoots.d.ts +9 -0
  293. package/dist/runtime/stageRoots.js +14 -0
  294. package/dist/runtime/stageRunner.d.ts +5 -0
  295. package/dist/runtime/stageRunner.js +38 -4
  296. package/dist/runtime/stageSandbox.d.ts +16 -0
  297. package/dist/runtime/stageSandbox.js +54 -0
  298. package/dist/runtime/stageWorker.js +138 -105
  299. package/dist/runtime/workspaceBinding.d.ts +2 -1
  300. package/dist/runtime/workspaceBinding.js +20 -4
  301. package/dist/server/agentAuthorRoutes.js +50 -1
  302. package/dist/server/bootstrap.d.ts +8 -0
  303. package/dist/server/bootstrap.js +56 -0
  304. package/dist/server/controlToken.js +3 -0
  305. package/dist/server/evalInternalRoutes.d.ts +14 -0
  306. package/dist/server/evalInternalRoutes.js +87 -0
  307. package/dist/server/evalRouteContext.d.ts +46 -0
  308. package/dist/server/evalRouteContext.js +43 -0
  309. package/dist/server/evalRoutes.d.ts +5 -0
  310. package/dist/server/evalRoutes.js +1427 -0
  311. package/dist/server/evalTriageRoutes.d.ts +2 -0
  312. package/dist/server/evalTriageRoutes.js +95 -0
  313. package/dist/server/http.d.ts +5 -0
  314. package/dist/server/http.js +32 -2
  315. package/dist/server/mcpHost.js +3 -0
  316. package/dist/toolkit/meta.d.ts +1 -1
  317. package/dist/toolkit/meta.js +15 -0
  318. package/dist/toolkit/profiles.js +1 -1
  319. package/dist/tools/readEmail.d.ts +3 -3
  320. package/dist/types/stageflowManifest.d.ts +6 -0
  321. package/dist/types/task.d.ts +11 -1
  322. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
  323. package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
  324. package/dist/ui/assets/chat-Df_wCpak.js +5 -0
  325. package/dist/ui/assets/chat-P7KQznHm.css +2 -0
  326. package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
  327. package/dist/ui/assets/main-CvVfxmMd.js +271 -0
  328. package/dist/ui/chat.html +3 -3
  329. package/dist/ui/index.html +3 -3
  330. package/examples/README.md +2 -0
  331. package/examples/document-policy-review/README.md +35 -0
  332. package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
  333. package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
  334. package/examples/document-policy-review/documents/support-runbook.md +13 -0
  335. package/examples/document-policy-review/policies/acceptable-use.md +13 -0
  336. package/examples/document-policy-review/policies/data-handling.md +17 -0
  337. package/examples/document-policy-review/policies/vendor-security.md +17 -0
  338. package/examples/evals/README.md +123 -0
  339. package/examples/evals/critique-sample.dataset.jsonl +8 -0
  340. package/examples/evals/critique-sample.eval.yaml +34 -0
  341. package/examples/evals/critique-sample.task.yaml +4 -0
  342. package/examples/evals/eval-sample.pipeline.yaml +35 -0
  343. package/examples/evals/support-sample.agent.yaml +20 -0
  344. package/examples/evals/support-sample.dataset.jsonl +6 -0
  345. package/examples/evals/support-sample.eval.yaml +38 -0
  346. package/examples/memory/README.md +2 -0
  347. package/examples/memory/probe-bash.task.yaml +3 -0
  348. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  349. package/examples/ship-feature/README.md +15 -0
  350. package/examples/stageflow.yaml +6 -0
  351. package/package.json +1 -1
  352. package/skills/stageflow/SKILL.md +5 -2
  353. package/skills/stageflow/references/control-surface.md +4 -2
  354. package/skills/stageflow/references/evals.md +68 -0
  355. package/skills/stageflow-author/SKILL.md +4 -0
  356. package/skills/stageflow-run/SKILL.md +4 -0
  357. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
  358. package/dist/ui/assets/chat-BbLukq9W.js +0 -5
  359. package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
  360. package/dist/ui/assets/main-CijYMeM0.js +0 -259
package/README.md CHANGED
@@ -35,6 +35,7 @@ The same pipeline runs three ways without rewriting anything:
35
35
  - **Verified stage execution** — after-phase `verify` checks with durable evidence, plus `on_verify_fail` repair or manual operator recovery ([guide](docs/verified-stage-execution.md))
36
36
  - **HITL gates** — operator questions in the console; CI exits `2` when a run is waiting
37
37
  - **Agents** — project-owned, revisioned agents (`agents/*.agent.yaml`) with granted tools, ask-first approvals, traces, channels, schedules and a public web chat page ([guide](docs/agents.md))
38
+ - **Evals** — check a stage or an agent against a dataset: graders, repeats, honest statistics, compare two runs, and sort failures into what to fix ([guide](docs/evals.md))
38
39
  - **Operator console** — an Inbox for gates and agent approvals, pipeline runs, agents and their sessions, connections, MCP servers and settings at `http://127.0.0.1:3847` ([guide](docs/operator-console.md))
39
40
  - **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` or `sf mcp` is running
40
41
  - **MCP servers for agents and stages** — add servers, secrets and OAuth sign-in from Settings → Project MCP, or in the project `.mcp.json` ([guide](docs/mcp-servers.md))
@@ -53,9 +54,43 @@ The scheduler owns orchestration semantics: DAG readiness, bounded parallelism,
53
54
 
54
55
  See [Architecture](docs/architecture.md) for component boundaries, execution flow, persistence, recovery behavior, and the tradeoffs behind fresh sessions and explicit handoffs.
55
56
 
56
- ## Installation
57
+ ## Get started
57
58
 
58
- Requires **Node.js ≥ 20**.
59
+ ### Docker (recommended)
60
+
61
+ Run Stageflow from the published image. It is [`ghcr.io/tejasghutukade/stageflow`](https://github.com/tejasghutukade/stageflow/pkgs/container/stageflow). `0.39` is the current minor tag (`0.39.0` is that exact release).
62
+
63
+ ```bash
64
+ docker run --rm --name stageflow \
65
+ -e STAGEFLOW_ALLOW_UNAUTHENTICATED_BIND=1 \
66
+ -p 127.0.0.1:3847:3847 \
67
+ -v stageflow-data:/data \
68
+ ghcr.io/tejasghutukade/stageflow:0.39 \
69
+ sf ui
70
+ ```
71
+
72
+ Open [http://127.0.0.1:3847](http://127.0.0.1:3847). Connect a provider under **Settings → Providers**, then **New pipeline** on the Pipelines page. The `stageflow-data` volume keeps that state across runs.
73
+
74
+ The process inside the container binds `0.0.0.0`, which is what Docker port publish needs. The `-p` address keeps the port on this machine, and the allow flag lets it start without a bearer. Do not publish the port on `0.0.0.0` with that flag set. `sf ui` is required because the image command is `sf mcp`.
75
+
76
+ To show the console a project directory:
77
+
78
+ ```bash
79
+ docker run --rm --name stageflow \
80
+ -e STAGEFLOW_ALLOW_UNAUTHENTICATED_BIND=1 \
81
+ -e STAGEFLOW_ENSURE_PROJECT_ROOTS=/project \
82
+ -p 127.0.0.1:3847:3847 \
83
+ -v stageflow-data:/data \
84
+ -v "$PWD":/project \
85
+ ghcr.io/tejasghutukade/stageflow:0.39 \
86
+ sf ui
87
+ ```
88
+
89
+ Building from this checkout, instead of pulling, is [Docker — Local try](docs/docker.md#local-try-compose).
90
+
91
+ ### Node
92
+
93
+ Install the `sf` command on the machine when you want it in the shell. Requires **Node.js ≥ 20**.
59
94
 
60
95
  **Quick install (macOS / Linux):**
61
96
 
@@ -75,37 +110,28 @@ npm i -g ./stageflow-*.tgz
75
110
 
76
111
  `better-sqlite3` ships prebuilds for common platforms. `--ignore-scripts` is fine when a prebuild exists. Benign `node-gyp` warnings during install can be ignored if `require("better-sqlite3")` works.
77
112
 
78
- **Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
79
-
80
- ```bash
81
- npx skills add tejasghutukade/stageflow
82
- ```
83
-
84
- Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
85
-
86
- ## Quick start
87
-
88
113
  In a project directory (preferably a git repo):
89
114
 
90
115
  ```bash
91
116
  sf init
117
+ sf ui
118
+ sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
92
119
  ```
93
120
 
94
- This scaffolds `stageflow.yaml`, `pipelines/hello.pipeline.yaml` (inline stage), and `tasks/hello.task.yaml`.
121
+ `sf init` scaffolds `stageflow.yaml`, `pipelines/hello.pipeline.yaml`, and `tasks/hello.task.yaml`. Connect a provider before `sf run` — see below. The console is [http://127.0.0.1:3847](http://127.0.0.1:3847).
95
122
 
96
- Run (after connecting a provider — see below):
123
+ **Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
97
124
 
98
125
  ```bash
99
- sf ui # operator console at http://127.0.0.1:3847
100
- sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
126
+ npx skills add tejasghutukade/stageflow
101
127
  ```
102
128
 
129
+ Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
130
+
103
131
  **Workshop** (**New pipeline** on the Pipelines page, or `#/workshop`; it has no rail item) drafts a pipeline in chat and saves it under `workshop/<pipeline-id>/` so it shows up on Run. Guide: [docs/workshop.md](docs/workshop.md).
104
132
 
105
133
  Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
106
134
 
107
- **Docker Compose** (UI + MCP in one Host): copy [`.env.example`](.env.example) → `.env`, then `docker compose up --build -d`. The shipped compose file publishes port 3847 on localhost only and does not require a control token. Checklist: [docs/docker.md](docs/docker.md#local-try-compose).
108
-
109
135
  ## Connect a model provider
110
136
 
111
137
  Each stage sets a **`model`** id in YAML (e.g. `anthropic/claude-sonnet-4-5`). The matching provider must be authenticated before runs succeed — `sf validate` does not check auth.
@@ -263,7 +289,7 @@ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.
263
289
  | [docs/hosted.md](docs/hosted.md) | Embed Stageflow from another application and receive ordered run events |
264
290
  | [docs/quickstart.md](docs/quickstart.md) | Expanded quick start |
265
291
  | [docs/yaml-catalog.md](docs/yaml-catalog.md) | Pipelines, stages, tasks schema |
266
- | [docs/cli-reference.md](docs/cli-reference.md) | `sf init`, `sf run`, `sf run-stage`, `sf graph`, `sf validate`, `sf ui`, `sf mcp`, `sf envelope`, `sf export-run`, `sf artifact`, `sf skills`, `sf providers` |
292
+ | [docs/cli-reference.md](docs/cli-reference.md) | `sf init`, `sf run`, `sf run-stage`, `sf graph`, `sf validate`, `sf ui`, `sf mcp`, `sf envelope`, `sf export-run`, `sf artifact`, `sf skills`, `sf providers`, `sf eval` |
267
293
  | [docs/envelopes.md](docs/envelopes.md) | Handoff envelope contract |
268
294
  | [docs/verified-stage-execution.md](docs/verified-stage-execution.md) | After-phase `verify`, evidence, and `on_verify_fail` repair / manual recovery |
269
295
  | [docs/hitl.md](docs/hitl.md) | Gate kinds, `--skip-gates`, exit code `2` |
@@ -273,6 +299,7 @@ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.
273
299
  | [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
274
300
  | [docs/docker.md](docs/docker.md) | Docker Compose local try, image pull/run, backup, self-hosting |
275
301
  | [docs/operator-console.md](docs/operator-console.md) | Console navigation, command palette, shortcuts and routes; one page per area ([Inbox](docs/console-inbox.md), [Runs](docs/console-runs.md), [Agents](docs/console-agents.md), [Connections](docs/console-connections.md), [Settings](docs/console-settings.md)) |
302
+ | [docs/evals.md](docs/evals.md) | Evals for stages and agents: datasets, graders, runs, compare, failure sorting, the engine |
276
303
  | [docs/agents.md](docs/agents.md) | Agents: definition, runtime, approvals, channels, schedules, web chat |
277
304
  | [docs/mcp-servers.md](docs/mcp-servers.md) | Add MCP servers, secrets and OAuth; use them from agents and stages |
278
305
  | [docs/channels.md](docs/channels.md) | Connections and communication channels |
@@ -41,7 +41,7 @@
41
41
  import { query } from "@anthropic-ai/claude-agent-sdk";
42
42
  import { isAdvancingEnvelope } from "../envelope/check.js";
43
43
  import { assertAnswerMatchesPrompt, parseAskOperatorAnswer, summarizeAnswer, } from "../tools/askOperator.js";
44
- import { buildStageflowMcpServer, EMIT_STAGE_ENVELOPE_TOOL_NAME, STAGEFLOW_MCP_SERVER_NAME, WRITE_STAGE_ARTIFACT_TOOL_NAME, ASK_OPERATOR_TOOL_NAME, } from "./claudeTools.js";
44
+ import { buildStageflowMcpServer, EMIT_STAGE_ENVELOPE_TOOL_NAME, STAGEFLOW_MCP_SERVER_NAME, WRITE_STAGE_ARTIFACT_TOOL_NAME, ASK_OPERATOR_TOOL_NAME, SANDBOX_BASH_TOOL_NAME, SANDBOX_EDIT_TOOL_NAME, SANDBOX_READ_TOOL_NAME, SANDBOX_WRITE_TOOL_NAME, } from "./claudeTools.js";
45
45
  import { createClaudeActivityMapper } from "./claudeActivity.js";
46
46
  import { DEFAULT_EMIT_REMINDERS, emitReminderPrompt, formatProviderError } from "./stageTurnDriver.js";
47
47
  import { readActivityVerbose } from "./activity.js";
@@ -215,6 +215,7 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
215
215
  input.onActivity?.({ event: "turn_start" });
216
216
  let sessionId = resumeSessionId;
217
217
  let providerError;
218
+ const containerName = input.roots.containerName;
218
219
  try {
219
220
  const mcpServer = buildStageflowMcpServer({
220
221
  capture: emitCapture,
@@ -238,6 +239,9 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
238
239
  },
239
240
  }
240
241
  : {}),
242
+ ...(containerName !== undefined
243
+ ? { sandboxBash: { containerName, dockerBin: input.roots.containerDockerBin } }
244
+ : {}),
241
245
  });
242
246
  const passedServers = Object.fromEntries(Object.entries(input.resolvedMcpServers ?? {}).map(([name, config]) => [
243
247
  name,
@@ -258,6 +262,16 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
258
262
  persistSession: true,
259
263
  strictMcpConfig: true,
260
264
  ...(resumeSessionId !== undefined ? { resume: resumeSessionId } : {}),
265
+ ...(containerName !== undefined
266
+ ? {
267
+ toolAliases: {
268
+ Bash: SANDBOX_BASH_TOOL_NAME,
269
+ Read: SANDBOX_READ_TOOL_NAME,
270
+ Write: SANDBOX_WRITE_TOOL_NAME,
271
+ Edit: SANDBOX_EDIT_TOOL_NAME,
272
+ },
273
+ }
274
+ : {}),
261
275
  },
262
276
  });
263
277
  for await (const message of stream) {
@@ -31,10 +31,15 @@ import { type WriteStageArtifactOptions } from "../tools/writeStageArtifact.js";
31
31
  import { type AskOperatorKind, type AskOperatorPrompt } from "../tools/askOperator.js";
32
32
  import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
33
33
  import type { PreEmitCheckOptions } from "../envelope/preEmitChecks.js";
34
+ import { execInSandboxContainer as defaultExecInSandboxContainer } from "../runtime/sandboxContainer.js";
34
35
  export declare const STAGEFLOW_MCP_SERVER_NAME = "stageflow";
35
36
  export declare const EMIT_STAGE_ENVELOPE_TOOL_NAME = "mcp__stageflow__emit_stage_envelope";
36
37
  export declare const WRITE_STAGE_ARTIFACT_TOOL_NAME = "mcp__stageflow__write_stage_artifact";
37
38
  export declare const ASK_OPERATOR_TOOL_NAME = "mcp__stageflow__ask_operator";
39
+ export declare const SANDBOX_BASH_TOOL_NAME = "mcp__stageflow__sandbox_bash";
40
+ export declare const SANDBOX_READ_TOOL_NAME = "mcp__stageflow__sandbox_read";
41
+ export declare const SANDBOX_WRITE_TOOL_NAME = "mcp__stageflow__sandbox_write";
42
+ export declare const SANDBOX_EDIT_TOOL_NAME = "mcp__stageflow__sandbox_edit";
38
43
  export type AskOperatorCapture = {
39
44
  prompt?: AskOperatorPrompt;
40
45
  };
@@ -72,6 +77,12 @@ export declare const writeStageArtifactShape: {
72
77
  path: z.ZodString;
73
78
  content: z.ZodString;
74
79
  };
80
+ type SandboxExecFn = typeof defaultExecInSandboxContainer;
81
+ export type SandboxBashOptions = {
82
+ containerName: string;
83
+ dockerBin?: string;
84
+ execute?: SandboxExecFn;
85
+ };
75
86
  export declare function buildStageflowMcpServer(options: {
76
87
  capture: EmitCapture;
77
88
  payloadSchema?: unknown;
@@ -83,4 +94,6 @@ export declare function buildStageflowMcpServer(options: {
83
94
  capture: AskOperatorCapture;
84
95
  allowedKinds?: readonly AskOperatorKind[];
85
96
  };
97
+ sandboxBash?: SandboxBashOptions;
86
98
  }): McpServerConfig;
99
+ export {};
@@ -29,10 +29,16 @@ import { z } from "zod";
29
29
  import { createEmitStageEnvelopeTool, } from "../tools/emitStageEnvelope.js";
30
30
  import { createWriteStageArtifactTool, } from "../tools/writeStageArtifact.js";
31
31
  import { ASK_OPERATOR_KINDS, AskOperatorError, normalizePromptIds, parseAskOperatorParams, } from "../tools/askOperator.js";
32
+ import { execInSandboxContainer as defaultExecInSandboxContainer } from "../runtime/sandboxContainer.js";
33
+ import { editSandboxFile, formatSandboxBashResult, readSandboxFile, sliceSandboxLines, writeSandboxFile, } from "../runtime/sandboxFiles.js";
32
34
  export const STAGEFLOW_MCP_SERVER_NAME = "stageflow";
33
35
  export const EMIT_STAGE_ENVELOPE_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__emit_stage_envelope`;
34
36
  export const WRITE_STAGE_ARTIFACT_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__write_stage_artifact`;
35
37
  export const ASK_OPERATOR_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__ask_operator`;
38
+ export const SANDBOX_BASH_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__sandbox_bash`;
39
+ export const SANDBOX_READ_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__sandbox_read`;
40
+ export const SANDBOX_WRITE_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__sandbox_write`;
41
+ export const SANDBOX_EDIT_TOOL_NAME = `mcp__${STAGEFLOW_MCP_SERVER_NAME}__sandbox_edit`;
36
42
  /**
37
43
  * Exported so a parity test can assert this stays field-for-field aligned
38
44
  * with `tools/emitStageEnvelope.ts`'s typebox schema — the two are hand
@@ -74,6 +80,9 @@ const askOperatorShape = {
74
80
  questions: z.array(z.record(z.string(), z.unknown())).optional(),
75
81
  artifacts: z.array(z.string()).optional(),
76
82
  };
83
+ const sandboxBashShape = {
84
+ command: z.string().min(1),
85
+ };
77
86
  /** Adapts a `{content, details?, isError?, terminate?}` tool result to plain MCP CallToolResult. */
78
87
  function toCallToolResult(result) {
79
88
  return {
@@ -111,6 +120,92 @@ function buildAskOperatorTool(capture, allowedKinds) {
111
120
  };
112
121
  });
113
122
  }
123
+ /**
124
+ * When a sandbox container is set, Read/Write/Edit/Bash are aliased onto
125
+ * these tools. They run against the attempt desk at /workspace. Other stage
126
+ * tools stay on the host.
127
+ */
128
+ function buildSandboxBashTool(options) {
129
+ const execute = options.execute ?? defaultExecInSandboxContainer;
130
+ return tool("sandbox_bash", "Run a shell command inside this stage attempt's sandbox. The working directory is /workspace. The host filesystem is not mounted.", sandboxBashShape, async (args) => {
131
+ let result;
132
+ try {
133
+ result = await execute({
134
+ containerName: options.containerName,
135
+ command: args.command,
136
+ dockerBin: options.dockerBin,
137
+ });
138
+ }
139
+ catch (err) {
140
+ const message = err instanceof Error ? err.message : String(err);
141
+ return {
142
+ content: [{ type: "text", text: `sandbox_bash could not run: ${message}` }],
143
+ isError: true,
144
+ };
145
+ }
146
+ return {
147
+ content: [{ type: "text", text: formatSandboxBashResult(result) }],
148
+ };
149
+ });
150
+ }
151
+ const sandboxReadShape = {
152
+ file_path: z.string().min(1),
153
+ offset: z.number().optional(),
154
+ limit: z.number().optional(),
155
+ };
156
+ const sandboxWriteShape = {
157
+ file_path: z.string().min(1),
158
+ content: z.string(),
159
+ };
160
+ const sandboxEditShape = {
161
+ file_path: z.string().min(1),
162
+ old_string: z.string(),
163
+ new_string: z.string(),
164
+ };
165
+ function sandboxFileError(err, label) {
166
+ const message = err instanceof Error ? err.message : String(err);
167
+ return {
168
+ content: [{ type: "text", text: `${label} could not run: ${message}` }],
169
+ isError: true,
170
+ };
171
+ }
172
+ function buildSandboxReadTool(options) {
173
+ return tool("sandbox_read", "Read a file inside this stage attempt's sandbox. Paths are relative to /workspace or absolute under /workspace. Host paths are rejected.", sandboxReadShape, async (args) => {
174
+ try {
175
+ const text = await readSandboxFile(options, args.file_path);
176
+ return {
177
+ content: [{ type: "text", text: sliceSandboxLines(text, args.offset, args.limit) }],
178
+ };
179
+ }
180
+ catch (err) {
181
+ return sandboxFileError(err, "sandbox_read");
182
+ }
183
+ });
184
+ }
185
+ function buildSandboxWriteTool(options) {
186
+ return tool("sandbox_write", "Write a file inside this stage attempt's sandbox. Paths are relative to /workspace or absolute under /workspace. Host paths are rejected.", sandboxWriteShape, async (args) => {
187
+ try {
188
+ await writeSandboxFile(options, args.file_path, args.content);
189
+ return { content: [{ type: "text", text: `Successfully wrote to ${args.file_path}` }] };
190
+ }
191
+ catch (err) {
192
+ return sandboxFileError(err, "sandbox_write");
193
+ }
194
+ });
195
+ }
196
+ function buildSandboxEditTool(options) {
197
+ return tool("sandbox_edit", "Edit a file inside this stage attempt's sandbox. old_string must match exactly once. Paths stay under /workspace.", sandboxEditShape, async (args) => {
198
+ try {
199
+ await editSandboxFile(options, args.file_path, [
200
+ { oldText: args.old_string, newText: args.new_string },
201
+ ]);
202
+ return { content: [{ type: "text", text: `Successfully edited ${args.file_path}` }] };
203
+ }
204
+ catch (err) {
205
+ return sandboxFileError(err, "sandbox_edit");
206
+ }
207
+ });
208
+ }
114
209
  export function buildStageflowMcpServer(options) {
115
210
  const emitDef = createEmitStageEnvelopeTool(options.capture, options.payloadSchema, options.forkEmitContext, options.cloneEmitContext, options.preEmitCheckOptions);
116
211
  const artifactDef = createWriteStageArtifactTool(options.writeStageArtifact);
@@ -123,6 +218,9 @@ export function buildStageflowMcpServer(options) {
123
218
  if (options.askOperator) {
124
219
  tools.push(buildAskOperatorTool(options.askOperator.capture, options.askOperator.allowedKinds));
125
220
  }
221
+ if (options.sandboxBash) {
222
+ tools.push(buildSandboxReadTool(options.sandboxBash), buildSandboxWriteTool(options.sandboxBash), buildSandboxEditTool(options.sandboxBash), buildSandboxBashTool(options.sandboxBash));
223
+ }
126
224
  return createSdkMcpServer({
127
225
  name: STAGEFLOW_MCP_SERVER_NAME,
128
226
  tools,
@@ -48,6 +48,7 @@ import { createEmitStageEnvelopeTool, } from "../tools/emitStageEnvelope.js";
48
48
  import { remindUntilEmitted } from "./stageTurnDriver.js";
49
49
  import { createAskOperatorTool, } from "../tools/askOperator.js";
50
50
  import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
51
+ import { createSandboxBashTool, createSandboxEditTool, createSandboxReadTool, createSandboxWriteTool, } from "./sandboxPiTools.js";
51
52
  import { createSendEmailTool } from "../tools/sendEmail.js";
52
53
  import { createSendMessageTool } from "../tools/sendMessage.js";
53
54
  import { createMemoryStoreBashDenyExtension } from "../memory/bashDeny.js";
@@ -462,6 +463,7 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
462
463
  }
463
464
  const attempt = input.roots.attempt ?? 1;
464
465
  const attemptArtifactsPath = `stages/${runtimeStageId(input)}/attempts/${attempt}/artifacts/`;
466
+ const sandboxed = input.roots.containerName !== undefined;
465
467
  const skillBaseDirs = [
466
468
  input.stage.skill !== undefined && input.skillFilePath !== undefined
467
469
  ? path.dirname(input.skillFilePath)
@@ -476,25 +478,36 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
476
478
  (input.stage.email?.length ?? 0) > 0
477
479
  ? `Large tool results are saved automatically as stage artifacts under ${attemptArtifactsPath}${TOOL_OUTPUT_ARTIFACT_DIR}/. The tool result names the path. Read or grep that file; do not copy tool output into ${artifactToolName ?? "an artifact"}.`
478
480
  : "";
479
- const artifactGuidance = input.roots.mode === "bound" && artifactToolName !== undefined
481
+ const artifactGuidance = sandboxed
480
482
  ? [
481
- `Project checkout (agent cwd): ${input.roots.checkoutRoot}`,
482
- `Factory run workspace: ${input.roots.runWorkspaceDir}`,
483
- `Builtin tools (read, bash, write, edit) are for work inside the project checkout.`,
484
- skillBaseDir !== undefined
485
- ? `Absolute read and bash against ${skillBaseDir} are in bounds for the linked skill.`
486
- : undefined,
487
- `Create factory stage artifacts only with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`,
488
- `This is soft guidance: prefer ${artifactToolName} for factory files under the run workspace; do not rely on relative builtin writes for those files.`,
489
- ]
490
- .filter(Boolean)
491
- .join("\n")
492
- : artifactToolName !== undefined
483
+ "Sandbox desk: /workspace. read, bash, write, and edit run only inside that desk.",
484
+ input.roots.checkoutRoot
485
+ ? `Checkout: /workspace/checkout. The host checkout path is not mounted.`
486
+ : "This stage has no checkout. Scratch files go in /workspace/work.",
487
+ "Attempt artifacts are at /workspace/artifacts. Scratch space is /workspace/work.",
488
+ artifactToolName !== undefined
489
+ ? `Create factory stage artifacts only with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`
490
+ : `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`,
491
+ ].join("\n")
492
+ : input.roots.mode === "bound" && artifactToolName !== undefined
493
493
  ? [
494
- `Create factory stage artifacts with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`,
494
+ `Project checkout (agent cwd): ${input.roots.checkoutRoot}`,
495
+ `Factory run workspace: ${input.roots.runWorkspaceDir}`,
496
+ `Builtin tools (read, bash, write, edit) are for work inside the project checkout.`,
497
+ skillBaseDir !== undefined
498
+ ? `Absolute read and bash against ${skillBaseDir} are in bounds for the linked skill.`
499
+ : undefined,
500
+ `Create factory stage artifacts only with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`,
495
501
  `This is soft guidance: prefer ${artifactToolName} for factory files under the run workspace; do not rely on relative builtin writes for those files.`,
496
- ].join("\n")
497
- : `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`;
502
+ ]
503
+ .filter(Boolean)
504
+ .join("\n")
505
+ : artifactToolName !== undefined
506
+ ? [
507
+ `Create factory stage artifacts with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`,
508
+ `This is soft guidance: prefer ${artifactToolName} for factory files under the run workspace; do not rely on relative builtin writes for those files.`,
509
+ ].join("\n")
510
+ : `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`;
498
511
  return [
499
512
  ...(input.stage.channels?.length ? [
500
513
  `Stageflow channels: ${JSON.stringify(input.stage.channels.map(channel => ({ channel: channel.alias ?? channel.connection, operations: channel.operations })))}. Use send_message with the channel name. Recipients are fixed by configuration and you cannot change them. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
@@ -1041,6 +1054,15 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
1041
1054
  tools.push(definition.name);
1042
1055
  }
1043
1056
  }
1057
+ if (roots.containerName !== undefined) {
1058
+ const sandboxTools = {
1059
+ containerName: roots.containerName,
1060
+ ...(roots.containerDockerBin !== undefined
1061
+ ? { dockerBin: roots.containerDockerBin }
1062
+ : {}),
1063
+ };
1064
+ customTools.push(defineTool(createSandboxReadTool(sandboxTools)), defineTool(createSandboxWriteTool(sandboxTools)), defineTool(createSandboxEditTool(sandboxTools)), defineTool(createSandboxBashTool(sandboxTools)));
1065
+ }
1044
1066
  return {
1045
1067
  sessionManager,
1046
1068
  modelRuntime,
@@ -0,0 +1,99 @@
1
+ import { Type } from "typebox";
2
+ import { type SandboxFileTarget } from "../runtime/sandboxFiles.js";
3
+ export type SandboxPiToolOptions = SandboxFileTarget;
4
+ export declare function createSandboxReadTool(options: SandboxPiToolOptions): {
5
+ name: string;
6
+ label: string;
7
+ description: string;
8
+ promptSnippet: string;
9
+ promptGuidelines: string[];
10
+ parameters: Type.TObject<{
11
+ path: Type.TString;
12
+ offset: Type.TOptional<Type.TNumber>;
13
+ limit: Type.TOptional<Type.TNumber>;
14
+ }>;
15
+ execute: (_toolCallId: string, params: {
16
+ path: string;
17
+ offset?: number;
18
+ limit?: number;
19
+ }) => Promise<{
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ details: undefined;
25
+ isError?: boolean | undefined;
26
+ }>;
27
+ };
28
+ export declare function createSandboxWriteTool(options: SandboxPiToolOptions): {
29
+ name: string;
30
+ label: string;
31
+ description: string;
32
+ promptSnippet: string;
33
+ promptGuidelines: string[];
34
+ parameters: Type.TObject<{
35
+ path: Type.TString;
36
+ content: Type.TString;
37
+ }>;
38
+ execute: (_toolCallId: string, params: {
39
+ path: string;
40
+ content: string;
41
+ }) => Promise<{
42
+ content: {
43
+ type: "text";
44
+ text: string;
45
+ }[];
46
+ details: undefined;
47
+ isError?: boolean | undefined;
48
+ }>;
49
+ };
50
+ export declare function createSandboxEditTool(options: SandboxPiToolOptions): {
51
+ name: string;
52
+ label: string;
53
+ description: string;
54
+ promptSnippet: string;
55
+ promptGuidelines: string[];
56
+ parameters: Type.TObject<{
57
+ path: Type.TString;
58
+ edits: Type.TArray<Type.TObject<{
59
+ oldText: Type.TString;
60
+ newText: Type.TString;
61
+ }>>;
62
+ }>;
63
+ execute: (_toolCallId: string, params: {
64
+ path: string;
65
+ edits: Array<{
66
+ oldText: string;
67
+ newText: string;
68
+ }>;
69
+ }) => Promise<{
70
+ content: {
71
+ type: "text";
72
+ text: string;
73
+ }[];
74
+ details: undefined;
75
+ isError?: boolean | undefined;
76
+ }>;
77
+ };
78
+ export declare function createSandboxBashTool(options: SandboxPiToolOptions): {
79
+ name: string;
80
+ label: string;
81
+ description: string;
82
+ promptSnippet: string;
83
+ promptGuidelines: string[];
84
+ parameters: Type.TObject<{
85
+ command: Type.TString;
86
+ timeout: Type.TOptional<Type.TNumber>;
87
+ }>;
88
+ execute: (_toolCallId: string, params: {
89
+ command: string;
90
+ timeout?: number;
91
+ }) => Promise<{
92
+ content: {
93
+ type: "text";
94
+ text: string;
95
+ }[];
96
+ details: undefined;
97
+ isError?: boolean | undefined;
98
+ }>;
99
+ };
@@ -0,0 +1,109 @@
1
+ import { Type } from "typebox";
2
+ import { editSandboxFile, readSandboxFile, runSandboxBash, sliceSandboxLines, writeSandboxFile, } from "../runtime/sandboxFiles.js";
3
+ function textResult(text, isError = false) {
4
+ return {
5
+ content: [{ type: "text", text }],
6
+ details: undefined,
7
+ ...(isError ? { isError: true } : {}),
8
+ };
9
+ }
10
+ function failure(err) {
11
+ const message = err instanceof Error ? err.message : String(err);
12
+ return textResult(message, true);
13
+ }
14
+ export function createSandboxReadTool(options) {
15
+ return {
16
+ name: "read",
17
+ label: "read",
18
+ description: "Read a file inside this stage's sandbox. Paths are relative to /workspace (artifacts/, checkout/, grants/, work/) or absolute under /workspace. Host paths are rejected.",
19
+ promptSnippet: "Read file contents inside /workspace",
20
+ promptGuidelines: [
21
+ "Use read to examine files under /workspace instead of cat or sed.",
22
+ ],
23
+ parameters: Type.Object({
24
+ path: Type.String({ description: "Path to the file to read, relative to /workspace or absolute under /workspace" }),
25
+ offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
26
+ limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
27
+ }),
28
+ execute: async (_toolCallId, params) => {
29
+ try {
30
+ const text = await readSandboxFile(options, params.path);
31
+ return textResult(sliceSandboxLines(text, params.offset, params.limit));
32
+ }
33
+ catch (err) {
34
+ return failure(err);
35
+ }
36
+ },
37
+ };
38
+ }
39
+ export function createSandboxWriteTool(options) {
40
+ return {
41
+ name: "write",
42
+ label: "write",
43
+ description: "Write a file inside this stage's sandbox. Paths are relative to /workspace or absolute under /workspace. Parent directories are created. Host paths are rejected.",
44
+ promptSnippet: "Create or overwrite files inside /workspace",
45
+ promptGuidelines: ["Use write only for new files or complete rewrites under /workspace."],
46
+ parameters: Type.Object({
47
+ path: Type.String({ description: "Path to the file to write, relative to /workspace or absolute under /workspace" }),
48
+ content: Type.String({ description: "Content to write to the file" }),
49
+ }),
50
+ execute: async (_toolCallId, params) => {
51
+ try {
52
+ await writeSandboxFile(options, params.path, params.content);
53
+ return textResult(`Successfully wrote to ${params.path}`);
54
+ }
55
+ catch (err) {
56
+ return failure(err);
57
+ }
58
+ },
59
+ };
60
+ }
61
+ export function createSandboxEditTool(options) {
62
+ return {
63
+ name: "edit",
64
+ label: "edit",
65
+ description: "Edit a file inside this stage's sandbox with exact text replacement. Paths are relative to /workspace or absolute under /workspace. Each oldText must match exactly once in the original file.",
66
+ promptSnippet: "Make precise file edits inside /workspace",
67
+ promptGuidelines: [
68
+ "Use edit for precise changes under /workspace (edits[].oldText must match exactly once).",
69
+ ],
70
+ parameters: Type.Object({
71
+ path: Type.String({ description: "Path to the file to edit, relative to /workspace or absolute under /workspace" }),
72
+ edits: Type.Array(Type.Object({
73
+ oldText: Type.String({ description: "Exact text to replace. It must be unique in the original file." }),
74
+ newText: Type.String({ description: "Replacement text." }),
75
+ }), { description: "One or more targeted replacements against the original file." }),
76
+ }),
77
+ execute: async (_toolCallId, params) => {
78
+ try {
79
+ await editSandboxFile(options, params.path, params.edits);
80
+ return textResult(`Successfully edited ${params.path}`);
81
+ }
82
+ catch (err) {
83
+ return failure(err);
84
+ }
85
+ },
86
+ };
87
+ }
88
+ export function createSandboxBashTool(options) {
89
+ return {
90
+ name: "bash",
91
+ label: "bash",
92
+ description: "Execute a bash command in this stage's sandbox. The working directory is /workspace. The host filesystem is not mounted.",
93
+ promptSnippet: "Execute bash commands in /workspace",
94
+ promptGuidelines: ["The shell starts in /workspace. Do not use host paths."],
95
+ parameters: Type.Object({
96
+ command: Type.String({ description: "Shell command to execute" }),
97
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional)" })),
98
+ }),
99
+ execute: async (_toolCallId, params) => {
100
+ try {
101
+ const text = await runSandboxBash(options, params.command, params.timeout !== undefined ? params.timeout * 1000 : undefined);
102
+ return textResult(text);
103
+ }
104
+ catch (err) {
105
+ return failure(err);
106
+ }
107
+ },
108
+ };
109
+ }
@@ -1,3 +1,4 @@
1
+ import { type AuthorSessionRecord } from "./authorSessionStore.js";
1
2
  import { type SummaryLine } from "./definitionSummary.js";
2
3
  import { type AgentAuthorDraft, type OperatorAgentHost } from "../operatorAgent/index.js";
3
4
  export type AgentAuthorTurnResult = {
@@ -14,6 +15,9 @@ export declare function runAgentAuthorTurn(input: {
14
15
  message: string;
15
16
  sessionId?: string;
16
17
  draft?: AgentAuthorDraft;
18
+ /** Composer override. Blank uses the same default as Workshop chat. */
19
+ model?: string | null;
17
20
  }): Promise<AgentAuthorTurnResult>;
18
21
  export declare function undoAgentAuthorTurn(sessionId: string): AgentAuthorDraft;
22
+ export declare function authorSessionForApi(sessionId: string): AuthorSessionRecord;
19
23
  export declare function parseAgentAuthorDraft(body: unknown): AgentAuthorDraft;