stageflow 0.36.0 → 0.40.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 (288) hide show
  1. package/README.md +61 -26
  2. package/dist/agent/claudeAdapter.js +21 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.d.ts +9 -1
  6. package/dist/agent/piAdapter.js +59 -18
  7. package/dist/agent/piIsolatedMcp.js +14 -2
  8. package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
  9. package/dist/agent/piIsolatedMcpProbe.js +16 -30
  10. package/dist/agent/port.d.ts +5 -0
  11. package/dist/agent/sandboxPiTools.d.ts +99 -0
  12. package/dist/agent/sandboxPiTools.js +109 -0
  13. package/dist/agents/agentId.d.ts +8 -0
  14. package/dist/agents/agentId.js +27 -0
  15. package/dist/agents/approvalBroker.d.ts +16 -0
  16. package/dist/agents/approvalBroker.js +45 -0
  17. package/dist/agents/authorChat.d.ts +19 -0
  18. package/dist/agents/authorChat.js +63 -0
  19. package/dist/agents/chatTurn.d.ts +23 -0
  20. package/dist/agents/chatTurn.js +77 -0
  21. package/dist/agents/communications/agentmail.d.ts +19 -0
  22. package/dist/agents/communications/agentmail.js +46 -0
  23. package/dist/agents/communications/agentmailListener.d.ts +59 -0
  24. package/dist/agents/communications/agentmailListener.js +183 -0
  25. package/dist/agents/communications/allowlist.d.ts +10 -0
  26. package/dist/agents/communications/allowlist.js +36 -0
  27. package/dist/agents/communications/channelConnections.d.ts +3 -0
  28. package/dist/agents/communications/channelConnections.js +51 -0
  29. package/dist/agents/communications/delivery.d.ts +53 -0
  30. package/dist/agents/communications/delivery.js +142 -0
  31. package/dist/agents/communications/email.d.ts +26 -0
  32. package/dist/agents/communications/email.js +20 -0
  33. package/dist/agents/communications/identity.d.ts +13 -0
  34. package/dist/agents/communications/identity.js +18 -0
  35. package/dist/agents/communications/imessageListener.d.ts +41 -0
  36. package/dist/agents/communications/imessageListener.js +92 -0
  37. package/dist/agents/communications/inbound.d.ts +63 -0
  38. package/dist/agents/communications/inbound.js +143 -0
  39. package/dist/agents/communications/listener.d.ts +36 -0
  40. package/dist/agents/communications/listener.js +38 -0
  41. package/dist/agents/communications/photon.d.ts +19 -0
  42. package/dist/agents/communications/photon.js +38 -0
  43. package/dist/agents/communications/port.d.ts +27 -0
  44. package/dist/agents/communications/port.js +6 -0
  45. package/dist/agents/communications/slack.d.ts +28 -0
  46. package/dist/agents/communications/slack.js +65 -0
  47. package/dist/agents/communications/supervisor.d.ts +36 -0
  48. package/dist/agents/communications/supervisor.js +96 -0
  49. package/dist/agents/communications/telegram.d.ts +19 -0
  50. package/dist/agents/communications/telegram.js +52 -0
  51. package/dist/agents/definitionSummary.d.ts +13 -0
  52. package/dist/agents/definitionSummary.js +91 -0
  53. package/dist/agents/delegate.d.ts +3 -0
  54. package/dist/agents/delegate.js +8 -0
  55. package/dist/agents/errors.d.ts +10 -0
  56. package/dist/agents/errors.js +32 -0
  57. package/dist/agents/guardrails.d.ts +24 -0
  58. package/dist/agents/guardrails.js +39 -0
  59. package/dist/agents/historyFit.d.ts +10 -0
  60. package/dist/agents/historyFit.js +26 -0
  61. package/dist/agents/hostContext.d.ts +54 -0
  62. package/dist/agents/hostContext.js +156 -0
  63. package/dist/agents/ledgers.d.ts +29 -0
  64. package/dist/agents/ledgers.js +31 -0
  65. package/dist/agents/lineDiff.d.ts +12 -0
  66. package/dist/agents/lineDiff.js +46 -0
  67. package/dist/agents/ndjson.d.ts +9 -0
  68. package/dist/agents/ndjson.js +47 -0
  69. package/dist/agents/otlpExport.d.ts +9 -0
  70. package/dist/agents/otlpExport.js +58 -0
  71. package/dist/agents/otlpSink.d.ts +7 -0
  72. package/dist/agents/otlpSink.js +23 -0
  73. package/dist/agents/policy.d.ts +31 -0
  74. package/dist/agents/policy.js +84 -0
  75. package/dist/agents/projectRoot.d.ts +5 -0
  76. package/dist/agents/projectRoot.js +15 -0
  77. package/dist/agents/runtime/piRuntime.d.ts +52 -0
  78. package/dist/agents/runtime/piRuntime.js +474 -0
  79. package/dist/agents/runtime/types.d.ts +77 -0
  80. package/dist/agents/runtime/types.js +26 -0
  81. package/dist/agents/sandbox.d.ts +17 -0
  82. package/dist/agents/sandbox.js +10 -0
  83. package/dist/agents/scheduleSource.d.ts +32 -0
  84. package/dist/agents/scheduleSource.js +100 -0
  85. package/dist/agents/service.d.ts +298 -0
  86. package/dist/agents/service.js +1105 -0
  87. package/dist/agents/sessions.d.ts +38 -0
  88. package/dist/agents/sessions.js +321 -0
  89. package/dist/agents/store.d.ts +277 -0
  90. package/dist/agents/store.js +643 -0
  91. package/dist/agents/trace.d.ts +27 -0
  92. package/dist/agents/trace.js +29 -0
  93. package/dist/agents/turnLifecycle.d.ts +72 -0
  94. package/dist/agents/turnLifecycle.js +157 -0
  95. package/dist/agents/webChat/publicChat.d.ts +36 -0
  96. package/dist/agents/webChat/publicChat.js +290 -0
  97. package/dist/agents/webChat/rateLimit.d.ts +15 -0
  98. package/dist/agents/webChat/rateLimit.js +33 -0
  99. package/dist/agents/webChat/slug.d.ts +2 -0
  100. package/dist/agents/webChat/slug.js +9 -0
  101. package/dist/channels/providers/agentmail.js +14 -1
  102. package/dist/channels/providers/photonImessage.js +36 -0
  103. package/dist/channels/providers/slack.js +2 -1
  104. package/dist/channels/providers/telegram.js +2 -1
  105. package/dist/channels/types.d.ts +27 -0
  106. package/dist/cli/agentsCommand.d.ts +14 -0
  107. package/dist/cli/agentsCommand.js +291 -0
  108. package/dist/cli.js +9 -0
  109. package/dist/config/browseCatalog.js +4 -11
  110. package/dist/config/draftPackage.d.ts +1 -0
  111. package/dist/config/draftPackage.js +4 -0
  112. package/dist/config/hostConfig.js +9 -0
  113. package/dist/config/loadAgent.d.ts +266 -0
  114. package/dist/config/loadAgent.js +374 -0
  115. package/dist/config/loadPipeline.js +5 -2
  116. package/dist/config/loadTask.js +34 -1
  117. package/dist/config/mcpHostCatalog.d.ts +50 -0
  118. package/dist/config/mcpHostCatalog.js +405 -0
  119. package/dist/config/mcpOAuth.d.ts +37 -0
  120. package/dist/config/mcpOAuth.js +263 -0
  121. package/dist/config/mcpRedaction.d.ts +42 -0
  122. package/dist/config/mcpRedaction.js +314 -0
  123. package/dist/config/mcpToolCache.d.ts +20 -0
  124. package/dist/config/mcpToolCache.js +43 -0
  125. package/dist/config/mergePipelineIncludes.d.ts +2 -0
  126. package/dist/config/mergePipelineIncludes.js +25 -0
  127. package/dist/config/resolveStageMcpServers.d.ts +35 -3
  128. package/dist/config/resolveStageMcpServers.js +124 -9
  129. package/dist/config/validateCatalog.d.ts +1 -1
  130. package/dist/config/validateCatalog.js +22 -1
  131. package/dist/mcp/agentTools.d.ts +8 -0
  132. package/dist/mcp/agentTools.js +132 -0
  133. package/dist/mcp/catalogTools.js +17 -10
  134. package/dist/mcp/deps.d.ts +6 -0
  135. package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
  136. package/dist/mcp/pipelineAuthorSupport.js +10 -0
  137. package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
  138. package/dist/mcp/pipelineAuthorTools.js +109 -0
  139. package/dist/mcp/server.js +5 -0
  140. package/dist/mcp/tools.js +4 -0
  141. package/dist/mcp/triggerTools.js +32 -0
  142. package/dist/memory/access.d.ts +36 -0
  143. package/dist/memory/access.js +116 -0
  144. package/dist/memory/agentIssues.d.ts +3 -0
  145. package/dist/memory/agentIssues.js +23 -0
  146. package/dist/memory/bashDeny.d.ts +6 -0
  147. package/dist/memory/bashDeny.js +35 -0
  148. package/dist/memory/host.d.ts +51 -0
  149. package/dist/memory/host.js +394 -0
  150. package/dist/memory/ids.d.ts +15 -0
  151. package/dist/memory/ids.js +31 -0
  152. package/dist/memory/worker.d.ts +1 -0
  153. package/dist/memory/worker.js +84 -0
  154. package/dist/models/catalog.d.ts +113 -0
  155. package/dist/models/catalog.js +238 -0
  156. package/dist/models/response.d.ts +29 -0
  157. package/dist/models/response.js +25 -0
  158. package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
  159. package/dist/operatorAgent/agentAuthorContext.js +32 -0
  160. package/dist/operatorAgent/fakeHost.js +2 -0
  161. package/dist/operatorAgent/host.js +6 -3
  162. package/dist/operatorAgent/index.d.ts +4 -0
  163. package/dist/operatorAgent/index.js +4 -0
  164. package/dist/operatorAgent/piModel.js +2 -0
  165. package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
  166. package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
  167. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
  168. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
  169. package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
  170. package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
  171. package/dist/operatorAgent/proposals.d.ts +1 -0
  172. package/dist/operatorAgent/proposals.js +23 -2
  173. package/dist/package-meta.d.ts +1 -1
  174. package/dist/package-meta.js +1 -1
  175. package/dist/preflight/pipelinePreflight.js +1 -0
  176. package/dist/runstore/createStore.d.ts +2 -0
  177. package/dist/runstore/createStore.js +8 -0
  178. package/dist/runstore/overviewSummary.d.ts +56 -0
  179. package/dist/runstore/overviewSummary.js +134 -0
  180. package/dist/runstore/port.d.ts +3 -0
  181. package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
  182. package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
  183. package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
  184. package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
  185. package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
  186. package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
  187. package/dist/runstore/sqlite/migrations/index.js +4 -0
  188. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  189. package/dist/runtime/gitCheckoutCapability.js +1 -1
  190. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  191. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  192. package/dist/runtime/sandboxContainer.d.ts +62 -0
  193. package/dist/runtime/sandboxContainer.js +151 -0
  194. package/dist/runtime/sandboxDesk.d.ts +28 -0
  195. package/dist/runtime/sandboxDesk.js +242 -0
  196. package/dist/runtime/sandboxFiles.d.ts +20 -0
  197. package/dist/runtime/sandboxFiles.js +150 -0
  198. package/dist/runtime/stageAttemptBootstrap.js +15 -1
  199. package/dist/runtime/stageEnvironment.d.ts +2 -0
  200. package/dist/runtime/stageEnvironment.js +8 -1
  201. package/dist/runtime/stageProcessLauncher.js +23 -0
  202. package/dist/runtime/stageRoots.d.ts +9 -0
  203. package/dist/runtime/stageRoots.js +14 -0
  204. package/dist/runtime/stageRunner.d.ts +5 -0
  205. package/dist/runtime/stageRunner.js +38 -4
  206. package/dist/runtime/stageSandbox.d.ts +16 -0
  207. package/dist/runtime/stageSandbox.js +54 -0
  208. package/dist/runtime/stageSecrets.d.ts +6 -1
  209. package/dist/runtime/stageSecrets.js +34 -1
  210. package/dist/runtime/stageWorker.js +138 -105
  211. package/dist/runtime/workspaceBinding.d.ts +2 -1
  212. package/dist/runtime/workspaceBinding.js +20 -4
  213. package/dist/server/agentAuthorRoutes.d.ts +5 -0
  214. package/dist/server/agentAuthorRoutes.js +64 -0
  215. package/dist/server/agentRoutes.d.ts +5 -0
  216. package/dist/server/agentRoutes.js +323 -0
  217. package/dist/server/bootstrap.d.ts +8 -0
  218. package/dist/server/bootstrap.js +36 -10
  219. package/dist/server/controlToken.js +8 -0
  220. package/dist/server/http.d.ts +8 -1
  221. package/dist/server/http.js +172 -20
  222. package/dist/server/mcpHost.js +1 -0
  223. package/dist/server/memoryRoutes.d.ts +5 -0
  224. package/dist/server/memoryRoutes.js +146 -0
  225. package/dist/server/projectMcpRoutes.d.ts +1 -0
  226. package/dist/server/projectMcpRoutes.js +241 -3
  227. package/dist/toolkit/guard.d.ts +27 -0
  228. package/dist/toolkit/guard.js +98 -0
  229. package/dist/toolkit/index.d.ts +48 -0
  230. package/dist/toolkit/index.js +82 -0
  231. package/dist/toolkit/meta.d.ts +36 -0
  232. package/dist/toolkit/meta.js +117 -0
  233. package/dist/toolkit/profiles.d.ts +13 -0
  234. package/dist/toolkit/profiles.js +25 -0
  235. package/dist/toolkit/record.d.ts +15 -0
  236. package/dist/toolkit/record.js +17 -0
  237. package/dist/tools/memoryTools.d.ts +95 -0
  238. package/dist/tools/memoryTools.js +179 -0
  239. package/dist/types/pipeline.d.ts +3 -0
  240. package/dist/types/stage.d.ts +2 -0
  241. package/dist/types/task.d.ts +11 -1
  242. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
  243. package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
  244. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
  245. package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
  246. package/dist/ui/assets/chat-BbLukq9W.js +5 -0
  247. package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
  248. package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
  249. package/dist/ui/assets/{index-Clv4sJpU.css → main-BFo79krC.css} +1 -1
  250. package/dist/ui/assets/main-CijYMeM0.js +259 -0
  251. package/dist/ui/chat.html +25 -0
  252. package/dist/ui/index.html +6 -2
  253. package/dist/workshop/modelSettings.d.ts +2 -2
  254. package/dist/workshop/modelSettings.js +5 -11
  255. package/examples/README.md +1 -0
  256. package/examples/channels-smoke/README.md +12 -1
  257. package/examples/github-release/README.md +1 -1
  258. package/examples/memory/README.md +101 -0
  259. package/examples/memory/desk-outage.task.yaml +15 -0
  260. package/examples/memory/desk-refund.task.yaml +15 -0
  261. package/examples/memory/desk-revisit-outage.task.yaml +11 -0
  262. package/examples/memory/desk-revisit.pipeline.yaml +123 -0
  263. package/examples/memory/desk.pipeline.yaml +192 -0
  264. package/examples/memory/file-card.pipeline.yaml +39 -0
  265. package/examples/memory/file-ledger.task.yaml +22 -0
  266. package/examples/memory/file-release.task.yaml +24 -0
  267. package/examples/memory/file-rich.pipeline.yaml +40 -0
  268. package/examples/memory/file-ticket.task.yaml +7 -0
  269. package/examples/memory/file-timeline.task.yaml +28 -0
  270. package/examples/memory/find-card.pipeline.yaml +46 -0
  271. package/examples/memory/find-ticket.task.yaml +7 -0
  272. package/examples/memory/missing.pipeline.yaml +16 -0
  273. package/examples/memory/probe-bash.task.yaml +3 -0
  274. package/examples/memory/recall.pipeline.yaml +24 -0
  275. package/examples/memory/recall.task.yaml +5 -0
  276. package/examples/memory/remember-ada.task.yaml +6 -0
  277. package/examples/memory/remember-grace.task.yaml +6 -0
  278. package/examples/memory/remember.pipeline.yaml +58 -0
  279. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  280. package/examples/memory/stageflow.yaml +12 -0
  281. package/examples/memory/update-card.pipeline.yaml +43 -0
  282. package/examples/memory/update-ledger.task.yaml +23 -0
  283. package/examples/memory/update-rich.pipeline.yaml +44 -0
  284. package/examples/memory/update-ticket.task.yaml +7 -0
  285. package/examples/ship-feature/README.md +15 -0
  286. package/examples/stage-mcp/README.md +1 -1
  287. package/package.json +2 -1
  288. package/dist/ui/assets/index-BOhqnPNg.js +0 -260
package/README.md CHANGED
@@ -34,8 +34,11 @@ The same pipeline runs three ways without rewriting anything:
34
34
  - **Envelope handoffs** — typed stage payloads and artifacts via `write_stage_artifact` / `emit_stage_envelope`
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
- - **Operator console** — triage runs, connect providers, answer gates, inspect transcripts at `http://127.0.0.1:3847`
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
+ - **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))
38
39
  - **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` or `sf mcp` is running
40
+ - **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))
41
+ - **Connections** — Slack, Telegram, iMessage, WhatsApp and AgentMail channels, and email accounts, managed from one page ([guide](docs/channels.md))
39
42
  - **Standalone stage execution** — run a single stage directly with `run_stage` (MCP tool, `sf run-stage` CLI, or the A2A `run_stage` operation), no pipeline file required; chain calls with `envelope_ref` (one or many prior results) instead of a pipeline DAG
40
43
  - **CI / headless** — `sf validate --strict --json`, `sf run --json` with exit codes `0` / `1` / `2`
41
44
  - **Parallel stages** — pipeline DAG with fan-out, join, and Clone Chains (one Clone Instance per Clone Array element; see [YAML catalog](docs/yaml-catalog.md#clone-chain))
@@ -50,9 +53,43 @@ The scheduler owns orchestration semantics: DAG readiness, bounded parallelism,
50
53
 
51
54
  See [Architecture](docs/architecture.md) for component boundaries, execution flow, persistence, recovery behavior, and the tradeoffs behind fresh sessions and explicit handoffs.
52
55
 
53
- ## Installation
56
+ ## Get started
54
57
 
55
- Requires **Node.js ≥ 20**.
58
+ ### Docker (recommended)
59
+
60
+ 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).
61
+
62
+ ```bash
63
+ docker run --rm --name stageflow \
64
+ -e STAGEFLOW_ALLOW_UNAUTHENTICATED_BIND=1 \
65
+ -p 127.0.0.1:3847:3847 \
66
+ -v stageflow-data:/data \
67
+ ghcr.io/tejasghutukade/stageflow:0.39 \
68
+ sf ui
69
+ ```
70
+
71
+ 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.
72
+
73
+ 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`.
74
+
75
+ To show the console a project directory:
76
+
77
+ ```bash
78
+ docker run --rm --name stageflow \
79
+ -e STAGEFLOW_ALLOW_UNAUTHENTICATED_BIND=1 \
80
+ -e STAGEFLOW_ENSURE_PROJECT_ROOTS=/project \
81
+ -p 127.0.0.1:3847:3847 \
82
+ -v stageflow-data:/data \
83
+ -v "$PWD":/project \
84
+ ghcr.io/tejasghutukade/stageflow:0.39 \
85
+ sf ui
86
+ ```
87
+
88
+ Building from this checkout, instead of pulling, is [Docker — Local try](docs/docker.md#local-try-compose).
89
+
90
+ ### Node
91
+
92
+ Install the `sf` command on the machine when you want it in the shell. Requires **Node.js ≥ 20**.
56
93
 
57
94
  **Quick install (macOS / Linux):**
58
95
 
@@ -72,36 +109,27 @@ npm i -g ./stageflow-*.tgz
72
109
 
73
110
  `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.
74
111
 
75
- **Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
76
-
77
- ```bash
78
- npx skills add tejasghutukade/stageflow
79
- ```
80
-
81
- Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
82
-
83
- ## Quick start
84
-
85
112
  In a project directory (preferably a git repo):
86
113
 
87
114
  ```bash
88
115
  sf init
116
+ sf ui
117
+ sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
89
118
  ```
90
119
 
91
- This scaffolds `stageflow.yaml`, `pipelines/hello.pipeline.yaml` (inline stage), and `tasks/hello.task.yaml`.
120
+ `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).
92
121
 
93
- Run (after connecting a provider — see below):
122
+ **Harness skills** (Cursor, Claude Code, Codex, Pi, OpenCode) — from a consumer project:
94
123
 
95
124
  ```bash
96
- sf ui # operator console at http://127.0.0.1:3847
97
- sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
125
+ npx skills add tejasghutukade/stageflow
98
126
  ```
99
127
 
100
- **Workshop** (rail → `#/workshop`) 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).
128
+ Then ask the agent to set up Stageflow. Details: [docs/skills-suite.md](docs/skills-suite.md).
101
129
 
102
- Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
130
+ **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).
103
131
 
104
- **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).
132
+ Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
105
133
 
106
134
  ## Connect a model provider
107
135
 
@@ -140,11 +168,14 @@ Full reference: [docs/providers.md](docs/providers.md)
140
168
 
141
169
  Start the console with `sf ui` (default `http://127.0.0.1:3847`).
142
170
 
143
- - **Runs** — active and recent pipeline runs, capacity, and status at a glance
144
- - **Run detail** — spatial stage map; select a stage for the gated workspace (logs, files, envelopes, HITL). Created runs show **not started** with a **Start run** action until history exists
145
- - **HITL reply** — answer operator gates (`ask_operator`) without leaving the browser
146
- - **Pipelines** — browse manifest-declared pipeline definitions
147
- - **Settings → Providers** — connect model providers (`pi_home` or `sf_owned` credential storage)
171
+ - **Inbox** — pipeline gates and agent approvals that wait on you, then failed and finished runs; Approve or Deny without leaving the page, with optional desktop notifications
172
+ - **Pipeline runs** — active and recent runs, capacity, and status at a glance
173
+ - **Run detail** — Timeline, Graph or List of the stages; select a stage for its transcript, events, envelope and artifacts. Created runs show **not started** with a **Start run** action until history exists
174
+ - **Pipelines, Tasks, Catalog, Triggers** — browse and edit the YAML, start runs; **New pipeline** drafts one in the Workshop
175
+ - **Agents** and **Agent sessions** — a directory with each agent's reach (web chat link, channel, schedule), cost and status; create and edit with version history; sessions, approvals and a run inspector
176
+ - **Connections** — channels, email accounts and tool servers
177
+ - **Settings** — Providers (`pi_home` or `sf_owned` credential storage), Concurrency, MCP server, Project MCP (servers, secrets, sign-in) and Notifications
178
+ - **Command palette** — `⌘K` from any page. Page-by-page guide: [docs/operator-console.md](docs/operator-console.md)
148
179
 
149
180
  Brand assets live under `docs/img/` (`stageflow-og.svg`, `stageflow-icon.svg`).
150
181
 
@@ -266,7 +297,11 @@ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.
266
297
  | [docs/a2a.md](docs/a2a.md) | Publish pipelines for other agents to call over JSON-RPC (A2A), plus the wildcard-access `run_stage` operation |
267
298
  | [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
268
299
  | [docs/docker.md](docs/docker.md) | Docker Compose local try, image pull/run, backup, self-hosting |
269
- | [docs/operator-console.md](docs/operator-console.md) | Console IA and settings |
300
+ | [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)) |
301
+ | [docs/agents.md](docs/agents.md) | Agents: definition, runtime, approvals, channels, schedules, web chat |
302
+ | [docs/mcp-servers.md](docs/mcp-servers.md) | Add MCP servers, secrets and OAuth; use them from agents and stages |
303
+ | [docs/channels.md](docs/channels.md) | Connections and communication channels |
304
+ | [docs/environment-variables.md](docs/environment-variables.md) | Every `STAGEFLOW_*` environment variable |
270
305
  | [docs/workshop.md](docs/workshop.md) | Workshop Author — chat-draft a pipeline, save it, and run it |
271
306
  | [docs/skills-suite.md](docs/skills-suite.md) | Harness skills — router + jobs for Cursor, Claude Code, Codex, Pi, OpenCode |
272
307
 
@@ -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";
@@ -84,6 +84,12 @@ function preflight(input) {
84
84
  reason: "stage declares channels; the claude agent backend does not provide send_message yet, so the stage would run without it",
85
85
  };
86
86
  }
87
+ if (input.stage.memories?.length) {
88
+ return {
89
+ ok: false,
90
+ reason: "stage pipeline declares memories; the claude agent backend does not provide read_memory, replace_memory, add_memory_card, update_memory_card, read_memory_card, and search_memory_cards yet, so the stage would run without them",
91
+ };
92
+ }
87
93
  return resolveClaudeModel(input.stage.model);
88
94
  }
89
95
  function resultFromCapture(capture, providerError) {
@@ -209,6 +215,7 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
209
215
  input.onActivity?.({ event: "turn_start" });
210
216
  let sessionId = resumeSessionId;
211
217
  let providerError;
218
+ const containerName = input.roots.containerName;
212
219
  try {
213
220
  const mcpServer = buildStageflowMcpServer({
214
221
  capture: emitCapture,
@@ -232,6 +239,9 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
232
239
  },
233
240
  }
234
241
  : {}),
242
+ ...(containerName !== undefined
243
+ ? { sandboxBash: { containerName, dockerBin: input.roots.containerDockerBin } }
244
+ : {}),
235
245
  });
236
246
  const passedServers = Object.fromEntries(Object.entries(input.resolvedMcpServers ?? {}).map(([name, config]) => [
237
247
  name,
@@ -252,6 +262,16 @@ async function runTurn(input, model, markerPath, promptText, resumeSessionId, us
252
262
  persistSession: true,
253
263
  strictMcpConfig: true,
254
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
+ : {}),
255
275
  },
256
276
  });
257
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,
@@ -1,4 +1,4 @@
1
- import { type AgentSession, type ExtensionFactory, SessionManager } from "@earendil-works/pi-coding-agent";
1
+ import { type AgentSession, type ExtensionFactory, DefaultResourceLoader, SessionManager } from "@earendil-works/pi-coding-agent";
2
2
  import type { StageRoots } from "../runtime/stageRoots.js";
3
3
  import { type AskOperatorPrompt, type AskOperatorWaitBridge } from "../tools/askOperator.js";
4
4
  import "./cursorProvider.js";
@@ -76,6 +76,14 @@ export type RouteSessionEventToProgressOptions = {
76
76
  export declare function routeSessionEventToProgress(event: Record<string, unknown>, options: RouteSessionEventToProgressOptions): void;
77
77
  export declare function composeStageUserPrompt(input: StageRunInput, emitToolName: string, emitHintOverride?: string, artifactToolName?: string): string;
78
78
  export declare function composeFeedbackResumePrompt(input: StageRunInput): string;
79
+ export declare function mcpToolMetadataMissing(loader: DefaultResourceLoader): boolean;
80
+ export declare function collectMcpExtensionToolNames(loader: DefaultResourceLoader): {
81
+ ok: true;
82
+ names: string[];
83
+ } | {
84
+ ok: false;
85
+ unrequested: string[];
86
+ };
79
87
  /** Stable Pi session JSONL path under the run workspace stage dir. */
80
88
  export declare function stageSessionFilePath(roots: Pick<StageRoots, "runWorkspaceDir" | "attempt">, stageId: string): string;
81
89
  export declare function resolveStageSessionFile(input: StageRunInput): string;
@@ -48,8 +48,11 @@ 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";
54
+ import { createMemoryStoreBashDenyExtension } from "../memory/bashDeny.js";
55
+ import { memorySession } from "../tools/memoryTools.js";
53
56
  import { createReplyEmailTool } from "../tools/replyEmail.js";
54
57
  import { createGetEmailTool, createSearchEmailTool, createDownloadEmailAttachmentTool } from "../tools/readEmail.js";
55
58
  import "./cursorProvider.js";
@@ -460,6 +463,7 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
460
463
  }
461
464
  const attempt = input.roots.attempt ?? 1;
462
465
  const attemptArtifactsPath = `stages/${runtimeStageId(input)}/attempts/${attempt}/artifacts/`;
466
+ const sandboxed = input.roots.containerName !== undefined;
463
467
  const skillBaseDirs = [
464
468
  input.stage.skill !== undefined && input.skillFilePath !== undefined
465
469
  ? path.dirname(input.skillFilePath)
@@ -474,29 +478,45 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
474
478
  (input.stage.email?.length ?? 0) > 0
475
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"}.`
476
480
  : "";
477
- const artifactGuidance = input.roots.mode === "bound" && artifactToolName !== undefined
481
+ const artifactGuidance = sandboxed
478
482
  ? [
479
- `Project checkout (agent cwd): ${input.roots.checkoutRoot}`,
480
- `Factory run workspace: ${input.roots.runWorkspaceDir}`,
481
- `Builtin tools (read, bash, write, edit) are for work inside the project checkout.`,
482
- skillBaseDir !== undefined
483
- ? `Absolute read and bash against ${skillBaseDir} are in bounds for the linked skill.`
484
- : undefined,
485
- `Create factory stage artifacts only with ${artifactToolName} (path relative to ${attemptArtifactsPath}). Use the returned run-relative path in ${emitToolName}.`,
486
- `This is soft guidance: prefer ${artifactToolName} for factory files under the run workspace; do not rely on relative builtin writes for those files.`,
487
- ]
488
- .filter(Boolean)
489
- .join("\n")
490
- : 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
491
493
  ? [
492
- `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}.`,
493
501
  `This is soft guidance: prefer ${artifactToolName} for factory files under the run workspace; do not rely on relative builtin writes for those files.`,
494
- ].join("\n")
495
- : `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.`;
496
511
  return [
497
512
  ...(input.stage.channels?.length ? [
498
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.`,
499
514
  ] : []),
515
+ ...(input.memoryPrompt !== undefined
516
+ ? [input.memoryPrompt]
517
+ : input.memory
518
+ ? [memorySession(input.memory).prompt]
519
+ : []),
500
520
  ...(input.stage.email?.length ? [
501
521
  `Stageflow email permissions: ${JSON.stringify(input.stage.email)}. Use only declared accounts and operations. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
502
522
  ] : []),
@@ -566,10 +586,10 @@ function mcpExtensionTools(loader) {
566
586
  .extensions.filter((ext) => ext.path === inlinePath)
567
587
  .flatMap((ext) => [...ext.tools.keys()]);
568
588
  }
569
- function mcpToolMetadataMissing(loader) {
589
+ export function mcpToolMetadataMissing(loader) {
570
590
  return mcpExtensionTools(loader).includes("mcp");
571
591
  }
572
- function collectMcpExtensionToolNames(loader) {
592
+ export function collectMcpExtensionToolNames(loader) {
573
593
  const names = [];
574
594
  const unrequested = [];
575
595
  for (const name of mcpExtensionTools(loader)) {
@@ -928,6 +948,12 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
928
948
  : {}),
929
949
  }),
930
950
  },
951
+ ...(input.stage.memories?.length
952
+ ? [{
953
+ name: "stageflow-memory-bash-deny",
954
+ factory: createMemoryStoreBashDenyExtension({ home: globalStageflowHome() }),
955
+ }]
956
+ : []),
931
957
  {
932
958
  name: STAGEFLOW_TOOL_OUTPUT_EXTENSION_NAME,
933
959
  factory: createToolOutputBudgetExtension({
@@ -1007,6 +1033,12 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
1007
1033
  customTools.push(defineTool(definition));
1008
1034
  tools.push(definition.name);
1009
1035
  }
1036
+ if (input.memory) {
1037
+ for (const definition of memorySession(input.memory).tools) {
1038
+ customTools.push(defineTool(definition));
1039
+ tools.push(definition.name);
1040
+ }
1041
+ }
1010
1042
  if (input.email) {
1011
1043
  const permitted = new Set(input.stage.email?.flatMap(permission => permission.operations));
1012
1044
  const definitions = [
@@ -1022,6 +1054,15 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
1022
1054
  tools.push(definition.name);
1023
1055
  }
1024
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
+ }
1025
1066
  return {
1026
1067
  sessionManager,
1027
1068
  modelRuntime,
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { createEventBus, } from "@earendil-works/pi-coding-agent";
3
+ import { prepareMcpOAuth } from "../config/mcpOAuth.js";
3
4
  import { StageMcpError, } from "../config/resolveStageMcpServers.js";
4
5
  import { globalStageflowHome } from "../project/globalHome.js";
5
6
  import { TOOL_OUTPUT_HARD_CEILING_BYTES } from "./toolOutputBudget.js";
@@ -48,7 +49,7 @@ const EMPTY_ATTACH = {
48
49
  connecting: undefined,
49
50
  };
50
51
  let cachedCreateMcpAdapter;
51
- function toIsolatedMcpConfig(snapshot, lifecycle = "lazy") {
52
+ function toIsolatedMcpConfig(snapshot, lifecycle = "lazy", oauth = false) {
52
53
  const mcpServers = {};
53
54
  for (const [name, entry] of Object.entries(snapshot)) {
54
55
  const isolated = {
@@ -65,6 +66,7 @@ function toIsolatedMcpConfig(snapshot, lifecycle = "lazy") {
65
66
  mcpServers,
66
67
  settings: {
67
68
  ...ISOLATED_MCP_SETTINGS,
69
+ ...(oauth ? { oauthCredentialStore: "encrypted-file" } : {}),
68
70
  outputGuard: { ...ISOLATED_MCP_SETTINGS.outputGuard },
69
71
  },
70
72
  };
@@ -235,11 +237,21 @@ export async function attachIsolatedMcp(snapshot, options) {
235
237
  return EMPTY_ATTACH;
236
238
  }
237
239
  const createMcpAdapter = await loadCreateMcpAdapter();
240
+ let oauth = false;
241
+ if (Object.values(snapshot).some((entry) => typeof entry.url === "string")) {
242
+ try {
243
+ prepareMcpOAuth();
244
+ oauth = true;
245
+ }
246
+ catch {
247
+ // Sign-in is optional; a store that cannot be prepared must not stop servers that need none.
248
+ }
249
+ }
238
250
  const extensionFactories = [
239
251
  {
240
252
  name: STAGEFLOW_PI_MCP_EXTENSION_NAME,
241
253
  factory: createMcpAdapter({
242
- config: toIsolatedMcpConfig(snapshot, options?.lifecycle ?? "lazy"),
254
+ config: toIsolatedMcpConfig(snapshot, options?.lifecycle ?? "lazy", oauth),
243
255
  }),
244
256
  },
245
257
  ];
@@ -1,15 +1,19 @@
1
1
  import { type ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
2
+ import { type McpToolInfo } from "../config/mcpToolCache.js";
2
3
  export declare const PROJECT_MCP_PROBE_TIMEOUT_MS = 30000;
3
4
  export type ProjectMcpProbeStatus = "connected" | "needs_auth" | "connect_failed" | "unresolved_var" | "invalid_config" | "missing_catalog" | "cancelled";
4
5
  export type ProjectMcpProbeResult = {
5
6
  name: string;
6
7
  status: ProjectMcpProbeStatus;
7
8
  error?: string;
9
+ /** Tools the server lists, set when the probe connected. */
10
+ tools?: McpToolInfo[];
8
11
  };
9
12
  export type ProbeProjectMcpServerOptions = {
10
13
  projectRoot: string;
11
14
  name: string;
12
- env?: NodeJS.ProcessEnv;
15
+ /** Host env that STAGEFLOW_MCP_ENV_ALLOW may draw from. Defaults to process.env; nothing else is read. */
16
+ hostEnv?: NodeJS.ProcessEnv;
13
17
  signal?: AbortSignal;
14
18
  timeoutMs?: number;
15
19
  };