stageflow 0.35.0 → 0.39.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 (320) hide show
  1. package/README.md +18 -8
  2. package/dist/agent/claudeAdapter.js +12 -0
  3. package/dist/agent/piAdapter.d.ts +9 -1
  4. package/dist/agent/piAdapter.js +30 -2
  5. package/dist/agent/piIsolatedMcp.js +14 -2
  6. package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
  7. package/dist/agent/piIsolatedMcpProbe.js +16 -30
  8. package/dist/agent/port.d.ts +7 -0
  9. package/dist/agents/agentId.d.ts +8 -0
  10. package/dist/agents/agentId.js +27 -0
  11. package/dist/agents/approvalBroker.d.ts +16 -0
  12. package/dist/agents/approvalBroker.js +45 -0
  13. package/dist/agents/authorChat.d.ts +19 -0
  14. package/dist/agents/authorChat.js +63 -0
  15. package/dist/agents/chatTurn.d.ts +23 -0
  16. package/dist/agents/chatTurn.js +77 -0
  17. package/dist/agents/communications/agentmail.d.ts +19 -0
  18. package/dist/agents/communications/agentmail.js +46 -0
  19. package/dist/agents/communications/agentmailListener.d.ts +59 -0
  20. package/dist/agents/communications/agentmailListener.js +183 -0
  21. package/dist/agents/communications/allowlist.d.ts +10 -0
  22. package/dist/agents/communications/allowlist.js +36 -0
  23. package/dist/agents/communications/channelConnections.d.ts +3 -0
  24. package/dist/agents/communications/channelConnections.js +51 -0
  25. package/dist/agents/communications/delivery.d.ts +53 -0
  26. package/dist/agents/communications/delivery.js +142 -0
  27. package/dist/agents/communications/email.d.ts +26 -0
  28. package/dist/agents/communications/email.js +20 -0
  29. package/dist/agents/communications/identity.d.ts +13 -0
  30. package/dist/agents/communications/identity.js +18 -0
  31. package/dist/agents/communications/imessageListener.d.ts +41 -0
  32. package/dist/agents/communications/imessageListener.js +92 -0
  33. package/dist/agents/communications/inbound.d.ts +63 -0
  34. package/dist/agents/communications/inbound.js +143 -0
  35. package/dist/agents/communications/listener.d.ts +36 -0
  36. package/dist/agents/communications/listener.js +38 -0
  37. package/dist/agents/communications/photon.d.ts +19 -0
  38. package/dist/agents/communications/photon.js +38 -0
  39. package/dist/agents/communications/port.d.ts +27 -0
  40. package/dist/agents/communications/port.js +6 -0
  41. package/dist/agents/communications/slack.d.ts +28 -0
  42. package/dist/agents/communications/slack.js +65 -0
  43. package/dist/agents/communications/supervisor.d.ts +36 -0
  44. package/dist/agents/communications/supervisor.js +96 -0
  45. package/dist/agents/communications/telegram.d.ts +19 -0
  46. package/dist/agents/communications/telegram.js +52 -0
  47. package/dist/agents/definitionSummary.d.ts +13 -0
  48. package/dist/agents/definitionSummary.js +91 -0
  49. package/dist/agents/delegate.d.ts +3 -0
  50. package/dist/agents/delegate.js +8 -0
  51. package/dist/agents/errors.d.ts +10 -0
  52. package/dist/agents/errors.js +32 -0
  53. package/dist/agents/guardrails.d.ts +24 -0
  54. package/dist/agents/guardrails.js +39 -0
  55. package/dist/agents/historyFit.d.ts +10 -0
  56. package/dist/agents/historyFit.js +26 -0
  57. package/dist/agents/hostContext.d.ts +54 -0
  58. package/dist/agents/hostContext.js +156 -0
  59. package/dist/agents/ledgers.d.ts +29 -0
  60. package/dist/agents/ledgers.js +31 -0
  61. package/dist/agents/lineDiff.d.ts +12 -0
  62. package/dist/agents/lineDiff.js +46 -0
  63. package/dist/agents/ndjson.d.ts +9 -0
  64. package/dist/agents/ndjson.js +47 -0
  65. package/dist/agents/otlpExport.d.ts +9 -0
  66. package/dist/agents/otlpExport.js +58 -0
  67. package/dist/agents/otlpSink.d.ts +7 -0
  68. package/dist/agents/otlpSink.js +23 -0
  69. package/dist/agents/policy.d.ts +31 -0
  70. package/dist/agents/policy.js +84 -0
  71. package/dist/agents/projectRoot.d.ts +5 -0
  72. package/dist/agents/projectRoot.js +15 -0
  73. package/dist/agents/runtime/piRuntime.d.ts +52 -0
  74. package/dist/agents/runtime/piRuntime.js +474 -0
  75. package/dist/agents/runtime/types.d.ts +77 -0
  76. package/dist/agents/runtime/types.js +26 -0
  77. package/dist/agents/sandbox.d.ts +17 -0
  78. package/dist/agents/sandbox.js +10 -0
  79. package/dist/agents/scheduleSource.d.ts +32 -0
  80. package/dist/agents/scheduleSource.js +100 -0
  81. package/dist/agents/service.d.ts +298 -0
  82. package/dist/agents/service.js +1105 -0
  83. package/dist/agents/sessions.d.ts +38 -0
  84. package/dist/agents/sessions.js +321 -0
  85. package/dist/agents/store.d.ts +277 -0
  86. package/dist/agents/store.js +643 -0
  87. package/dist/agents/trace.d.ts +27 -0
  88. package/dist/agents/trace.js +29 -0
  89. package/dist/agents/turnLifecycle.d.ts +72 -0
  90. package/dist/agents/turnLifecycle.js +157 -0
  91. package/dist/agents/webChat/publicChat.d.ts +36 -0
  92. package/dist/agents/webChat/publicChat.js +290 -0
  93. package/dist/agents/webChat/rateLimit.d.ts +15 -0
  94. package/dist/agents/webChat/rateLimit.js +33 -0
  95. package/dist/agents/webChat/slug.d.ts +2 -0
  96. package/dist/agents/webChat/slug.js +9 -0
  97. package/dist/channels/config.d.ts +11 -0
  98. package/dist/channels/config.js +95 -0
  99. package/dist/channels/configWriter.d.ts +40 -0
  100. package/dist/channels/configWriter.js +148 -0
  101. package/dist/channels/connectionEdits.d.ts +34 -0
  102. package/dist/channels/connectionEdits.js +53 -0
  103. package/dist/channels/errorInfo.d.ts +18 -0
  104. package/dist/channels/errorInfo.js +71 -0
  105. package/dist/channels/errors.d.ts +12 -0
  106. package/dist/channels/errors.js +13 -0
  107. package/dist/channels/health.d.ts +41 -0
  108. package/dist/channels/health.js +99 -0
  109. package/dist/channels/host.d.ts +55 -0
  110. package/dist/channels/host.js +185 -0
  111. package/dist/channels/http.d.ts +31 -0
  112. package/dist/channels/http.js +79 -0
  113. package/dist/channels/index.d.ts +10 -0
  114. package/dist/channels/index.js +9 -0
  115. package/dist/channels/ledger.d.ts +37 -0
  116. package/dist/channels/ledger.js +95 -0
  117. package/dist/channels/providers/agentmail.d.ts +26 -0
  118. package/dist/channels/providers/agentmail.js +94 -0
  119. package/dist/channels/providers/kit.d.ts +40 -0
  120. package/dist/channels/providers/kit.js +78 -0
  121. package/dist/channels/providers/photonImessage.d.ts +45 -0
  122. package/dist/channels/providers/photonImessage.js +188 -0
  123. package/dist/channels/providers/photonWhatsapp.d.ts +49 -0
  124. package/dist/channels/providers/photonWhatsapp.js +146 -0
  125. package/dist/channels/providers/slack.d.ts +25 -0
  126. package/dist/channels/providers/slack.js +102 -0
  127. package/dist/channels/providers/telegram.d.ts +21 -0
  128. package/dist/channels/providers/telegram.js +101 -0
  129. package/dist/channels/providers.d.ts +5 -0
  130. package/dist/channels/providers.js +22 -0
  131. package/dist/channels/registry.d.ts +54 -0
  132. package/dist/channels/registry.js +186 -0
  133. package/dist/channels/secretStore.d.ts +21 -0
  134. package/dist/channels/secretStore.js +75 -0
  135. package/dist/channels/secrets.d.ts +15 -0
  136. package/dist/channels/secrets.js +50 -0
  137. package/dist/channels/service.d.ts +54 -0
  138. package/dist/channels/service.js +307 -0
  139. package/dist/channels/stageChannels.d.ts +45 -0
  140. package/dist/channels/stageChannels.js +110 -0
  141. package/dist/channels/types.d.ts +169 -0
  142. package/dist/channels/types.js +1 -0
  143. package/dist/cli/agentsCommand.d.ts +14 -0
  144. package/dist/cli/agentsCommand.js +291 -0
  145. package/dist/cli/doctorCommand.js +3 -1
  146. package/dist/cli.js +9 -0
  147. package/dist/config/browseCatalog.js +4 -11
  148. package/dist/config/draftPackage.d.ts +2 -0
  149. package/dist/config/draftPackage.js +8 -0
  150. package/dist/config/hostConfig.js +8 -0
  151. package/dist/config/loadAgent.d.ts +266 -0
  152. package/dist/config/loadAgent.js +374 -0
  153. package/dist/config/loadPipeline.js +31 -4
  154. package/dist/config/loadStage.js +15 -0
  155. package/dist/config/mcpHostCatalog.d.ts +50 -0
  156. package/dist/config/mcpHostCatalog.js +405 -0
  157. package/dist/config/mcpOAuth.d.ts +37 -0
  158. package/dist/config/mcpOAuth.js +263 -0
  159. package/dist/config/mcpRedaction.d.ts +42 -0
  160. package/dist/config/mcpRedaction.js +314 -0
  161. package/dist/config/mcpToolCache.d.ts +20 -0
  162. package/dist/config/mcpToolCache.js +43 -0
  163. package/dist/config/mergePipelineIncludes.d.ts +5 -0
  164. package/dist/config/mergePipelineIncludes.js +52 -0
  165. package/dist/config/pipelineStageKeys.js +1 -0
  166. package/dist/config/resolveStageMcpServers.d.ts +35 -3
  167. package/dist/config/resolveStageMcpServers.js +124 -9
  168. package/dist/config/validateCatalog.d.ts +4 -1
  169. package/dist/config/validateCatalog.js +45 -1
  170. package/dist/errors/codes.d.ts +1 -1
  171. package/dist/errors/codes.js +1 -0
  172. package/dist/mcp/agentTools.d.ts +8 -0
  173. package/dist/mcp/agentTools.js +132 -0
  174. package/dist/mcp/catalogTools.js +7 -9
  175. package/dist/mcp/deps.d.ts +6 -0
  176. package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
  177. package/dist/mcp/pipelineAuthorSupport.js +10 -0
  178. package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
  179. package/dist/mcp/pipelineAuthorTools.js +109 -0
  180. package/dist/mcp/server.js +5 -0
  181. package/dist/mcp/tools.js +4 -0
  182. package/dist/mcp/triggerTools.js +32 -0
  183. package/dist/memory/access.d.ts +36 -0
  184. package/dist/memory/access.js +116 -0
  185. package/dist/memory/agentIssues.d.ts +3 -0
  186. package/dist/memory/agentIssues.js +23 -0
  187. package/dist/memory/bashDeny.d.ts +6 -0
  188. package/dist/memory/bashDeny.js +35 -0
  189. package/dist/memory/host.d.ts +51 -0
  190. package/dist/memory/host.js +394 -0
  191. package/dist/memory/ids.d.ts +15 -0
  192. package/dist/memory/ids.js +31 -0
  193. package/dist/memory/worker.d.ts +1 -0
  194. package/dist/memory/worker.js +84 -0
  195. package/dist/models/catalog.d.ts +113 -0
  196. package/dist/models/catalog.js +238 -0
  197. package/dist/models/response.d.ts +29 -0
  198. package/dist/models/response.js +25 -0
  199. package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
  200. package/dist/operatorAgent/agentAuthorContext.js +32 -0
  201. package/dist/operatorAgent/fakeHost.js +2 -0
  202. package/dist/operatorAgent/host.js +6 -3
  203. package/dist/operatorAgent/index.d.ts +4 -0
  204. package/dist/operatorAgent/index.js +4 -0
  205. package/dist/operatorAgent/piModel.js +2 -0
  206. package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
  207. package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
  208. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
  209. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
  210. package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
  211. package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
  212. package/dist/operatorAgent/proposals.d.ts +1 -0
  213. package/dist/operatorAgent/proposals.js +23 -2
  214. package/dist/package-meta.d.ts +1 -1
  215. package/dist/package-meta.js +1 -1
  216. package/dist/preflight/channelChecks.d.ts +17 -0
  217. package/dist/preflight/channelChecks.js +106 -0
  218. package/dist/preflight/pipelinePreflight.d.ts +9 -2
  219. package/dist/preflight/pipelinePreflight.js +10 -1
  220. package/dist/runstore/createStore.d.ts +2 -0
  221. package/dist/runstore/createStore.js +8 -0
  222. package/dist/runstore/overviewSummary.d.ts +56 -0
  223. package/dist/runstore/overviewSummary.js +134 -0
  224. package/dist/runstore/port.d.ts +3 -0
  225. package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
  226. package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
  227. package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
  228. package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
  229. package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
  230. package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
  231. package/dist/runstore/sqlite/migrations/index.js +4 -0
  232. package/dist/runtime/pipelineRunner.js +3 -1
  233. package/dist/runtime/repositoryMaterialize.d.ts +1 -1
  234. package/dist/runtime/runManager.js +4 -1
  235. package/dist/runtime/stageAttemptBootstrap.js +31 -1
  236. package/dist/runtime/stageEnvironment.d.ts +2 -0
  237. package/dist/runtime/stageEnvironment.js +6 -1
  238. package/dist/runtime/stageProcessLauncher.js +54 -1
  239. package/dist/runtime/stageSecrets.d.ts +6 -1
  240. package/dist/runtime/stageSecrets.js +34 -1
  241. package/dist/server/agentAuthorRoutes.d.ts +5 -0
  242. package/dist/server/agentAuthorRoutes.js +64 -0
  243. package/dist/server/agentRoutes.d.ts +5 -0
  244. package/dist/server/agentRoutes.js +323 -0
  245. package/dist/server/bootstrap.d.ts +11 -0
  246. package/dist/server/bootstrap.js +40 -10
  247. package/dist/server/channelRoutes.d.ts +9 -0
  248. package/dist/server/channelRoutes.js +88 -0
  249. package/dist/server/controlToken.js +8 -0
  250. package/dist/server/http.d.ts +8 -1
  251. package/dist/server/http.js +183 -20
  252. package/dist/server/mcpHost.js +1 -0
  253. package/dist/server/memoryRoutes.d.ts +5 -0
  254. package/dist/server/memoryRoutes.js +146 -0
  255. package/dist/server/projectMcpRoutes.d.ts +1 -0
  256. package/dist/server/projectMcpRoutes.js +241 -3
  257. package/dist/toolkit/guard.d.ts +27 -0
  258. package/dist/toolkit/guard.js +98 -0
  259. package/dist/toolkit/index.d.ts +48 -0
  260. package/dist/toolkit/index.js +82 -0
  261. package/dist/toolkit/meta.d.ts +36 -0
  262. package/dist/toolkit/meta.js +117 -0
  263. package/dist/toolkit/profiles.d.ts +13 -0
  264. package/dist/toolkit/profiles.js +25 -0
  265. package/dist/toolkit/record.d.ts +15 -0
  266. package/dist/toolkit/record.js +17 -0
  267. package/dist/tools/memoryTools.d.ts +95 -0
  268. package/dist/tools/memoryTools.js +179 -0
  269. package/dist/tools/sendMessage.d.ts +21 -0
  270. package/dist/tools/sendMessage.js +25 -0
  271. package/dist/types/pipeline.d.ts +7 -1
  272. package/dist/types/stage.d.ts +15 -0
  273. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
  274. package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
  275. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
  276. package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
  277. package/dist/ui/assets/chat-BbLukq9W.js +5 -0
  278. package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
  279. package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
  280. package/dist/ui/assets/{index-CIPbbBUi.css → main-BFo79krC.css} +1 -1
  281. package/dist/ui/assets/main-CijYMeM0.js +259 -0
  282. package/dist/ui/chat.html +25 -0
  283. package/dist/ui/index.html +6 -2
  284. package/dist/workshop/modelSettings.d.ts +2 -2
  285. package/dist/workshop/modelSettings.js +5 -11
  286. package/examples/README.md +1 -0
  287. package/examples/channels-smoke/README.md +23 -0
  288. package/examples/channels-smoke/channels-smoke.pipeline.yaml +11 -0
  289. package/examples/channels-smoke/channels-smoke.task.yaml +3 -0
  290. package/examples/channels-smoke/send-email.yaml +32 -0
  291. package/examples/channels-smoke/send-text.yaml +31 -0
  292. package/examples/github-release/README.md +1 -1
  293. package/examples/memory/README.md +99 -0
  294. package/examples/memory/desk-outage.task.yaml +15 -0
  295. package/examples/memory/desk-refund.task.yaml +15 -0
  296. package/examples/memory/desk-revisit-outage.task.yaml +11 -0
  297. package/examples/memory/desk-revisit.pipeline.yaml +123 -0
  298. package/examples/memory/desk.pipeline.yaml +192 -0
  299. package/examples/memory/file-card.pipeline.yaml +39 -0
  300. package/examples/memory/file-ledger.task.yaml +22 -0
  301. package/examples/memory/file-release.task.yaml +24 -0
  302. package/examples/memory/file-rich.pipeline.yaml +40 -0
  303. package/examples/memory/file-ticket.task.yaml +7 -0
  304. package/examples/memory/file-timeline.task.yaml +28 -0
  305. package/examples/memory/find-card.pipeline.yaml +46 -0
  306. package/examples/memory/find-ticket.task.yaml +7 -0
  307. package/examples/memory/missing.pipeline.yaml +16 -0
  308. package/examples/memory/recall.pipeline.yaml +24 -0
  309. package/examples/memory/recall.task.yaml +5 -0
  310. package/examples/memory/remember-ada.task.yaml +6 -0
  311. package/examples/memory/remember-grace.task.yaml +6 -0
  312. package/examples/memory/remember.pipeline.yaml +58 -0
  313. package/examples/memory/stageflow.yaml +12 -0
  314. package/examples/memory/update-card.pipeline.yaml +43 -0
  315. package/examples/memory/update-ledger.task.yaml +23 -0
  316. package/examples/memory/update-rich.pipeline.yaml +44 -0
  317. package/examples/memory/update-ticket.task.yaml +7 -0
  318. package/examples/stage-mcp/README.md +1 -1
  319. package/package.json +9 -3
  320. package/dist/ui/assets/index-BoFPkKFg.js +0 -256
@@ -0,0 +1,134 @@
1
+ export const OVERVIEW_RANGES = ["24h", "7d", "30d"];
2
+ export function isOverviewRange(value) {
3
+ return OVERVIEW_RANGES.includes(value);
4
+ }
5
+ const HOUR_MS = 3_600_000;
6
+ const RANGE_SHAPE = {
7
+ "24h": { windowMs: 24 * HOUR_MS, bucketMs: 3 * HOUR_MS },
8
+ "7d": { windowMs: 7 * 24 * HOUR_MS, bucketMs: 24 * HOUR_MS },
9
+ "30d": { windowMs: 30 * 24 * HOUR_MS, bucketMs: 3 * 24 * HOUR_MS },
10
+ };
11
+ export function overviewWindowMs(range) {
12
+ return RANGE_SHAPE[range].windowMs;
13
+ }
14
+ function emptyTotals() {
15
+ return {
16
+ runs: 0,
17
+ succeeded: 0,
18
+ failed: 0,
19
+ cancelled: 0,
20
+ in_flight: 0,
21
+ finished: 0,
22
+ success_rate: null,
23
+ cost_usd: 0,
24
+ };
25
+ }
26
+ function tally(rows) {
27
+ const totals = emptyTotals();
28
+ for (const row of rows) {
29
+ totals.runs += 1;
30
+ if (row.status === "succeeded")
31
+ totals.succeeded += 1;
32
+ else if (row.status === "failed")
33
+ totals.failed += 1;
34
+ else if (row.status === "cancelled")
35
+ totals.cancelled += 1;
36
+ else
37
+ totals.in_flight += 1;
38
+ totals.cost_usd += row.cost_usd ?? 0;
39
+ }
40
+ totals.finished = totals.succeeded + totals.failed;
41
+ totals.success_rate = totals.finished > 0 ? totals.succeeded / totals.finished : null;
42
+ totals.cost_usd = roundCost(totals.cost_usd);
43
+ return totals;
44
+ }
45
+ function roundCost(value) {
46
+ return Math.round(value * 10_000) / 10_000;
47
+ }
48
+ function median(values) {
49
+ if (values.length === 0)
50
+ return null;
51
+ const sorted = [...values].sort((a, b) => a - b);
52
+ const mid = Math.floor(sorted.length / 2);
53
+ return sorted.length % 2 === 1 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
54
+ }
55
+ function durationMs(row) {
56
+ if (row.status !== "succeeded" && row.status !== "failed")
57
+ return null;
58
+ if (!row.finished_at)
59
+ return null;
60
+ const ms = Date.parse(row.finished_at) - Date.parse(row.created_at);
61
+ return Number.isFinite(ms) && ms >= 0 ? ms : null;
62
+ }
63
+ /**
64
+ * Rows must cover `[now - 2 * window, now]` by `created_at`; the older half feeds `previous`.
65
+ * Buckets are rolling segments ending at `now`, so they need no time zone.
66
+ */
67
+ export function buildOverview(rows, range, now = new Date()) {
68
+ const { windowMs, bucketMs } = RANGE_SHAPE[range];
69
+ const nowMs = now.getTime();
70
+ const sinceMs = nowMs - windowMs;
71
+ const previousSinceMs = sinceMs - windowMs;
72
+ const current = [];
73
+ const previous = [];
74
+ for (const row of rows) {
75
+ const created = Date.parse(row.created_at);
76
+ if (!Number.isFinite(created))
77
+ continue;
78
+ if (created >= sinceMs && created <= nowMs)
79
+ current.push(row);
80
+ else if (created >= previousSinceMs && created < sinceMs)
81
+ previous.push(row);
82
+ }
83
+ const bucketCount = windowMs / bucketMs;
84
+ const buckets = Array.from({ length: bucketCount }, (_, i) => ({
85
+ start: new Date(sinceMs + i * bucketMs).toISOString(),
86
+ succeeded: 0,
87
+ failed: 0,
88
+ cancelled: 0,
89
+ }));
90
+ for (const row of current) {
91
+ const index = Math.min(bucketCount - 1, Math.floor((Date.parse(row.created_at) - sinceMs) / bucketMs));
92
+ const bucket = buckets[index];
93
+ if (row.status === "succeeded")
94
+ bucket.succeeded += 1;
95
+ else if (row.status === "failed")
96
+ bucket.failed += 1;
97
+ else if (row.status === "cancelled")
98
+ bucket.cancelled += 1;
99
+ }
100
+ const byPipeline = new Map();
101
+ for (const row of current) {
102
+ const list = byPipeline.get(row.pipeline_id);
103
+ if (list)
104
+ list.push(row);
105
+ else
106
+ byPipeline.set(row.pipeline_id, [row]);
107
+ }
108
+ const pipelines = [...byPipeline.entries()]
109
+ .map(([pipeline_id, list]) => {
110
+ const totals = tally(list);
111
+ const durations = list.map(durationMs).filter((ms) => ms !== null);
112
+ return {
113
+ pipeline_id,
114
+ runs: totals.runs,
115
+ succeeded: totals.succeeded,
116
+ failed: totals.failed,
117
+ cancelled: totals.cancelled,
118
+ success_rate: totals.success_rate,
119
+ median_duration_ms: median(durations),
120
+ cost_usd: totals.cost_usd,
121
+ last_run_at: list.reduce((latest, row) => (row.created_at > latest ? row.created_at : latest), list[0].created_at),
122
+ };
123
+ })
124
+ .sort((a, b) => b.runs - a.runs || a.pipeline_id.localeCompare(b.pipeline_id));
125
+ return {
126
+ range,
127
+ generated_at: now.toISOString(),
128
+ since: new Date(sinceMs).toISOString(),
129
+ totals: tally(current),
130
+ previous: tally(previous),
131
+ buckets,
132
+ pipelines,
133
+ };
134
+ }
@@ -7,6 +7,7 @@ import type { FeedbackLoopConfig, InlinePipelineDefinition, ResolvedPipelineDag
7
7
  import type { CompletionCheck } from "../types/completion.js";
8
8
  import type { StageGateKind } from "../types/stage.js";
9
9
  import type { StageUsage } from "../types/usage.js";
10
+ import type { OverviewRunRow } from "./overviewSummary.js";
10
11
  export type { ConfigOriginRecord };
11
12
  export type RunStatus = "created" | "queued" | "running" | "succeeded" | "failed" | "cancelled";
12
13
  export type RunPipelineDagSnapshot = ResolvedPipelineDag & {
@@ -463,6 +464,8 @@ export interface RunStore {
463
464
  }): Promise<void>;
464
465
  listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
465
466
  listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
467
+ /** One light row per run created at or after `since` (ISO), newest first, with summed stage cost. No stages are loaded. */
468
+ listRunOverviewRows(since: string): Promise<OverviewRunRow[]>;
466
469
  /** Idempotent upsert; returns the realpath/resolve-normalized absolute key. */
467
470
  ensureProject(absPath: string): Promise<string>;
468
471
  /** Absolute roots from the durable projects registry. */
@@ -2,6 +2,7 @@ import Database from "better-sqlite3";
2
2
  import type { StageEnvelope } from "../../types/envelope.js";
3
3
  import type { StageLogLine } from "../../agent/activity.js";
4
4
  import { type CreateFeedbackLoopInput, type CreateFeedbackReplayInput, type CreateFeedbackReplayStagePassInput, type CreateForkGenerationInput, type CreateRunInput, type CreatedRun, type FeedbackLoopPatch, type FeedbackLoopRecord, type FeedbackLoopState, type FeedbackReplayPatch, type FeedbackReplayRecord, type FeedbackReplayStagePassPatch, type FeedbackReplayStagePassRecord, type ForkGenerationPatch, type ForkGenerationRecord, type ListRunsFilter, type RunDetail, type RunMeta, type RunPipelineDagSnapshot, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent, type TriggerRecord, type UpsertTriggerInput, type VerificationCheckResult, type VerificationCheckResultPatch, type ConfigOriginRecord } from "../port.js";
5
+ import type { OverviewRunRow } from "../overviewSummary.js";
5
6
  import { type RunSubmissionRecord } from "../submission.js";
6
7
  type SqliteRunStoreOpenerMode = "assert" | "migrate";
7
8
  export declare class SqliteRunStore implements RunStore {
@@ -76,6 +77,7 @@ export declare class SqliteRunStore implements RunStore {
76
77
  private updateStageStatusDenorm;
77
78
  listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
78
79
  listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
80
+ listRunOverviewRows(since: string): Promise<OverviewRunRow[]>;
79
81
  ensureProject(absPath: string): Promise<string>;
80
82
  listRegisteredProjects(): Promise<string[]>;
81
83
  upsertTrigger(input: UpsertTriggerInput): Promise<TriggerRecord>;
@@ -870,6 +870,24 @@ export class SqliteRunStore {
870
870
  }
871
871
  return summaries;
872
872
  }
873
+ async listRunOverviewRows(since) {
874
+ await this.ready();
875
+ const rows = this.db
876
+ .prepare(`SELECT r.run_id, r.pipeline_id, r.status, r.created_at, r.finished_at,
877
+ (SELECT SUM(e.cost_usd) FROM stage_executions e WHERE e.run_id = r.run_id) AS cost_usd
878
+ FROM runs r
879
+ WHERE r.created_at >= ?
880
+ ORDER BY r.created_at DESC`)
881
+ .all(since);
882
+ return rows.map((row) => ({
883
+ run_id: row.run_id,
884
+ pipeline_id: row.pipeline_id,
885
+ status: row.status,
886
+ created_at: row.created_at,
887
+ ...(row.finished_at != null ? { finished_at: row.finished_at } : {}),
888
+ ...(row.cost_usd != null ? { cost_usd: row.cost_usd } : {}),
889
+ }));
890
+ }
873
891
  async ensureProject(absPath) {
874
892
  await this.ready();
875
893
  const resolved = path.resolve(absPath);
@@ -0,0 +1,12 @@
1
+ import type Database from "better-sqlite3";
2
+ /**
3
+ * Project agents: revisions, sessions, transcript, approvals, turns, spans.
4
+ * Read and written only by `AgentStore` (src/agents/store.ts); kept out of the
5
+ * run/stage-shaped RunStore port. `project_key` scopes rows to one project root.
6
+ */
7
+ export declare const MIGRATION_013: {
8
+ version: number;
9
+ name: string;
10
+ minStageflowVersion: string;
11
+ up(db: Database.Database): void;
12
+ };
@@ -0,0 +1,156 @@
1
+ import { PACKAGE_VERSION } from "../../../package-meta.js";
2
+ /**
3
+ * Project agents: revisions, sessions, transcript, approvals, turns, spans.
4
+ * Read and written only by `AgentStore` (src/agents/store.ts); kept out of the
5
+ * run/stage-shaped RunStore port. `project_key` scopes rows to one project root.
6
+ */
7
+ export const MIGRATION_013 = {
8
+ version: 13,
9
+ name: "013_agents",
10
+ minStageflowVersion: PACKAGE_VERSION,
11
+ up(db) {
12
+ db.exec(`
13
+ CREATE TABLE agent_definitions (
14
+ project_key TEXT NOT NULL,
15
+ agent_id TEXT NOT NULL,
16
+ current_revision INTEGER NOT NULL,
17
+ archived INTEGER NOT NULL DEFAULT 0,
18
+ file_hash TEXT,
19
+ issues_json TEXT,
20
+ created_at TEXT NOT NULL,
21
+ updated_at TEXT NOT NULL,
22
+ PRIMARY KEY (project_key, agent_id)
23
+ );
24
+
25
+ CREATE TABLE agent_revisions (
26
+ project_key TEXT NOT NULL,
27
+ agent_id TEXT NOT NULL,
28
+ revision INTEGER NOT NULL,
29
+ definition_json TEXT NOT NULL,
30
+ yaml_text TEXT NOT NULL,
31
+ source TEXT NOT NULL CHECK (source IN ('console', 'file', 'author', 'restore', 'api')),
32
+ author TEXT,
33
+ summary TEXT,
34
+ restored_from INTEGER,
35
+ created_at TEXT NOT NULL,
36
+ PRIMARY KEY (project_key, agent_id, revision)
37
+ );
38
+
39
+ CREATE TABLE agent_sessions (
40
+ session_id TEXT PRIMARY KEY NOT NULL,
41
+ project_key TEXT NOT NULL,
42
+ agent_id TEXT NOT NULL,
43
+ revision INTEGER NOT NULL,
44
+ backend TEXT NOT NULL,
45
+ status TEXT NOT NULL CHECK (status IN ('open', 'running', 'waiting', 'held', 'closed')),
46
+ source TEXT NOT NULL,
47
+ source_ref TEXT,
48
+ thread_key TEXT,
49
+ title TEXT,
50
+ created_at TEXT NOT NULL,
51
+ updated_at TEXT NOT NULL
52
+ );
53
+ CREATE INDEX agent_sessions_agent_updated ON agent_sessions (project_key, agent_id, updated_at DESC);
54
+ CREATE UNIQUE INDEX agent_sessions_thread ON agent_sessions (project_key, agent_id, thread_key)
55
+ WHERE thread_key IS NOT NULL;
56
+
57
+ CREATE TABLE agent_messages (
58
+ message_id TEXT PRIMARY KEY NOT NULL,
59
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
60
+ seq INTEGER NOT NULL,
61
+ turn_id TEXT,
62
+ role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
63
+ author TEXT,
64
+ text TEXT NOT NULL,
65
+ created_at TEXT NOT NULL,
66
+ UNIQUE (session_id, seq)
67
+ );
68
+
69
+ CREATE TABLE agent_turns (
70
+ turn_id TEXT PRIMARY KEY NOT NULL,
71
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
72
+ seq INTEGER NOT NULL,
73
+ revision INTEGER NOT NULL,
74
+ trace_id TEXT NOT NULL,
75
+ status TEXT NOT NULL CHECK (status IN ('running', 'waiting', 'succeeded', 'failed', 'cancelled')),
76
+ error_code TEXT,
77
+ input_tokens INTEGER,
78
+ output_tokens INTEGER,
79
+ cost_usd REAL,
80
+ started_at TEXT NOT NULL,
81
+ ended_at TEXT,
82
+ UNIQUE (session_id, seq)
83
+ );
84
+
85
+ CREATE TABLE agent_spans (
86
+ span_id TEXT PRIMARY KEY NOT NULL,
87
+ turn_id TEXT NOT NULL REFERENCES agent_turns (turn_id) ON DELETE CASCADE,
88
+ parent_span_id TEXT,
89
+ name TEXT NOT NULL,
90
+ kind TEXT NOT NULL,
91
+ status TEXT NOT NULL,
92
+ started_at TEXT NOT NULL,
93
+ ended_at TEXT,
94
+ duration_ms INTEGER,
95
+ input_tokens INTEGER,
96
+ output_tokens INTEGER,
97
+ attributes_json TEXT NOT NULL DEFAULT '{}',
98
+ input_text TEXT,
99
+ output_text TEXT
100
+ );
101
+ CREATE INDEX agent_spans_turn ON agent_spans (turn_id, started_at);
102
+
103
+ CREATE TABLE agent_eval_scores (
104
+ turn_id TEXT NOT NULL REFERENCES agent_turns (turn_id) ON DELETE CASCADE,
105
+ metric TEXT NOT NULL,
106
+ score REAL NOT NULL,
107
+ recorded_at TEXT NOT NULL,
108
+ PRIMARY KEY (turn_id, metric)
109
+ );
110
+
111
+ CREATE TABLE agent_approvals (
112
+ approval_id TEXT PRIMARY KEY NOT NULL,
113
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
114
+ turn_id TEXT,
115
+ tool TEXT NOT NULL,
116
+ payload_json TEXT NOT NULL,
117
+ payload_hash TEXT NOT NULL,
118
+ status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'denied', 'expired')),
119
+ created_at TEXT NOT NULL,
120
+ expires_at TEXT NOT NULL,
121
+ decided_at TEXT,
122
+ actor_json TEXT
123
+ );
124
+ CREATE UNIQUE INDEX agent_approvals_one_pending ON agent_approvals (session_id) WHERE status = 'pending';
125
+
126
+ CREATE TABLE agent_executed_payloads (
127
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
128
+ payload_hash TEXT NOT NULL,
129
+ executed_at TEXT NOT NULL,
130
+ PRIMARY KEY (session_id, payload_hash)
131
+ );
132
+
133
+ CREATE TABLE agent_outbound_sends (
134
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
135
+ operation_key TEXT NOT NULL,
136
+ state TEXT NOT NULL CHECK (state IN ('submitted', 'unknown')),
137
+ updated_at TEXT NOT NULL,
138
+ PRIMARY KEY (session_id, operation_key)
139
+ );
140
+
141
+ CREATE TABLE agent_inbound_events (
142
+ project_key TEXT NOT NULL,
143
+ dedupe_key TEXT NOT NULL,
144
+ received_at TEXT NOT NULL,
145
+ PRIMARY KEY (project_key, dedupe_key)
146
+ );
147
+
148
+ CREATE TABLE agent_schedules (
149
+ project_key TEXT NOT NULL,
150
+ agent_id TEXT NOT NULL,
151
+ next_run_at TEXT NOT NULL,
152
+ PRIMARY KEY (project_key, agent_id)
153
+ );
154
+ `);
155
+ },
156
+ };
@@ -0,0 +1,11 @@
1
+ import type Database from "better-sqlite3";
2
+ /**
3
+ * Replies to people on a channel (AgentMail, iMessage): one row per turn that has to reach someone, with how
4
+ * the send went, so a failed reply can be retried or resent and a reply of unknown outcome is never duplicated.
5
+ */
6
+ export declare const MIGRATION_014: {
7
+ version: number;
8
+ name: string;
9
+ minStageflowVersion: string;
10
+ up(db: Database.Database): void;
11
+ };
@@ -0,0 +1,32 @@
1
+ import { PACKAGE_VERSION } from "../../../package-meta.js";
2
+ /**
3
+ * Replies to people on a channel (AgentMail, iMessage): one row per turn that has to reach someone, with how
4
+ * the send went, so a failed reply can be retried or resent and a reply of unknown outcome is never duplicated.
5
+ */
6
+ export const MIGRATION_014 = {
7
+ version: 14,
8
+ name: "014_agent_deliveries",
9
+ minStageflowVersion: PACKAGE_VERSION,
10
+ up(db) {
11
+ db.exec(`
12
+ CREATE TABLE agent_deliveries (
13
+ turn_id TEXT PRIMARY KEY REFERENCES agent_turns (turn_id) ON DELETE CASCADE,
14
+ session_id TEXT NOT NULL REFERENCES agent_sessions (session_id) ON DELETE CASCADE,
15
+ connection_id TEXT NOT NULL,
16
+ provider TEXT NOT NULL,
17
+ reply_to TEXT NOT NULL,
18
+ text TEXT NOT NULL,
19
+ state TEXT NOT NULL CHECK (state IN ('pending', 'sending', 'delivered', 'failed', 'unknown')),
20
+ attempts INTEGER NOT NULL DEFAULT 0,
21
+ error_code TEXT,
22
+ error_message TEXT,
23
+ retryable INTEGER NOT NULL DEFAULT 0,
24
+ next_attempt_at TEXT,
25
+ created_at TEXT NOT NULL,
26
+ updated_at TEXT NOT NULL
27
+ );
28
+ CREATE INDEX agent_deliveries_due ON agent_deliveries (state, next_attempt_at);
29
+ CREATE INDEX agent_deliveries_session ON agent_deliveries (session_id);
30
+ `);
31
+ },
32
+ };
@@ -11,6 +11,8 @@ import { MIGRATION_009 } from "./009-trigger-next-run.js";
11
11
  import { MIGRATION_010 } from "./010-trigger-adapter-state.js";
12
12
  import { MIGRATION_011 } from "./011-email-dispatch-key.js";
13
13
  import { MIGRATION_012 } from "./012-trigger-fires.js";
14
+ import { MIGRATION_013 } from "./013-agents.js";
15
+ import { MIGRATION_014 } from "./014-agent-deliveries.js";
14
16
  const DEFAULT_MIGRATIONS = [
15
17
  MIGRATION_001,
16
18
  MIGRATION_002,
@@ -24,6 +26,8 @@ const DEFAULT_MIGRATIONS = [
24
26
  MIGRATION_010,
25
27
  MIGRATION_011,
26
28
  MIGRATION_012,
29
+ MIGRATION_013,
30
+ MIGRATION_014,
27
31
  ];
28
32
  export const CURRENT_SCHEMA_VERSION = DEFAULT_MIGRATIONS[DEFAULT_MIGRATIONS.length - 1].version;
29
33
  function throwStoreSchemaTooNew(db, version, maxKnown) {
@@ -13,6 +13,7 @@ import { checkTaskEntryInput } from "./taskInput.js";
13
13
  import { pipelinePersistenceForStart } from "./startPayload.js";
14
14
  import { materializeRunSkills, } from "./runSkills.js";
15
15
  import { preflightFailureCode, runPipelinePreflight, } from "../preflight/pipelinePreflight.js";
16
+ import { channelFailureSummary } from "../preflight/channelChecks.js";
16
17
  import { networkError } from "../errors/codes.js";
17
18
  import { newRunId } from "../runstore/paths.js";
18
19
  import { bindingFromFields, buildInitialRunManifest, collectDeclaredSecretValues, finaliseStoredRunManifest, } from "../runstore/runManifest.js";
@@ -24,7 +25,8 @@ export class PipelinePreflightError extends Error {
24
25
  code;
25
26
  preflight;
26
27
  constructor(code, preflight) {
27
- super(`Pipeline preflight failed: ${code}`);
28
+ const detail = code === "channel_check_failed" ? channelFailureSummary(preflight.checks) : undefined;
29
+ super(`Pipeline preflight failed: ${code}${detail ? `: ${detail}` : ""}`);
28
30
  this.name = "PipelinePreflightError";
29
31
  this.code = code;
30
32
  this.preflight = preflight;
@@ -1,7 +1,7 @@
1
1
  import type { RunMeta, RunStore } from "../runstore/port.js";
2
2
  import type { TaskFile } from "../types/task.js";
3
3
  import type { WorkspaceBinding } from "./workspaceBinding.js";
4
- export type StartFailureCode = "busy_capacity" | "busy_checkout" | "busy_caller_quota" | "repository_auth_failed" | "repository_not_found" | "ref_not_found" | "repository_fetch_failed" | "worktree_create_failed" | "git_missing" | "insufficient_disk" | "disk_check_failed" | "pinned_sha_unavailable" | "invalid_run_branch" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "task.invalid_shape" | "task.load_error" | "trigger.task_override_not_allowed" | "trigger.task_required" | "start.token_rejected" | "inline_pipeline_too_large" | "skills_invalid_name" | "skills_invalid_path" | "skills_missing_skill_md" | "skills_payload_too_large" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "unresolved_var" | "shutting_down" | "autostart_disabled" | "ensure_project_not_allowed" | "claude_as_root";
4
+ export type StartFailureCode = "busy_capacity" | "busy_checkout" | "busy_caller_quota" | "repository_auth_failed" | "repository_not_found" | "ref_not_found" | "repository_fetch_failed" | "worktree_create_failed" | "git_missing" | "insufficient_disk" | "disk_check_failed" | "pinned_sha_unavailable" | "invalid_run_branch" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "task.invalid_shape" | "task.load_error" | "trigger.task_override_not_allowed" | "trigger.task_required" | "start.token_rejected" | "inline_pipeline_too_large" | "skills_invalid_name" | "skills_invalid_path" | "skills_missing_skill_md" | "skills_payload_too_large" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "channel_check_failed" | "unresolved_var" | "shutting_down" | "autostart_disabled" | "ensure_project_not_allowed" | "claude_as_root";
5
5
  export type MaterializedBinding = {
6
6
  runId: string;
7
7
  checkoutRoot?: string;
@@ -41,6 +41,7 @@ import { getContainerLimits } from "./containerLimits.js";
41
41
  import { stageflowCacheRoot } from "./stageCacheEnv.js";
42
42
  import { assertClaudeNotRoot, ClaudeRootError } from "../preflight/claudeRoot.js";
43
43
  import { preflightFailureCode, runPipelinePreflight, } from "../preflight/pipelinePreflight.js";
44
+ import { channelFailureSummary } from "../preflight/channelChecks.js";
44
45
  import { asAgentBackendId } from "../agent/agentBackend.js";
45
46
  import { INVALID_SLOT_COUNT_MESSAGE, parseSlotCount, readMaxConcurrentFromGlobal, writeMaxConcurrentToGlobal, } from "./settingsFile.js";
46
47
  import { StageHitlController, waitKey, } from "./stageHitl.js";
@@ -2331,7 +2332,9 @@ export class RunManager {
2331
2332
  "missing_tool";
2332
2333
  return {
2333
2334
  ok: false,
2334
- reason: `Pipeline preflight failed: ${code}`,
2335
+ reason: `Pipeline preflight failed: ${code}${code === "channel_check_failed" && channelFailureSummary(preflight.checks)
2336
+ ? `: ${channelFailureSummary(preflight.checks)}`
2337
+ : ""}`,
2335
2338
  status: 400,
2336
2339
  code,
2337
2340
  };
@@ -1,4 +1,10 @@
1
1
  import { emailHostFor, stageEmail, workerStageEmail, validateStageEmailAccounts } from "../email/host.js";
2
+ import { ChannelError } from "../channels/errors.js";
3
+ import { channelHostFor, stageChannels, validateStageChannelConnections, workerStageChannels } from "../channels/host.js";
4
+ import { bindStageMemory } from "../memory/access.js";
5
+ import { memoryHostFor } from "../memory/host.js";
6
+ import { workerMemoryForStage } from "../memory/worker.js";
7
+ import { memoryPrompt } from "../tools/memoryTools.js";
2
8
  import { EmailError } from "../email/port.js";
3
9
  import { bindingKindForOrigin, catalogOrSeededOrigin, decideWorkspaceConfigTrust, } from "../config/configOrigin.js";
4
10
  import { loginCheckPromptBlock, readStageLoginCheck, } from "../browser/loginCheck.js";
@@ -96,12 +102,13 @@ async function resolveAttemptMcpServers(allowlist, factoryCwd, artifactsDir, opt
96
102
  };
97
103
  const stageIdOpt = options.stageId !== undefined ? { stageId: options.stageId } : {};
98
104
  const bindingKind = bindingKindForOrigin(options.bindingKind);
99
- if (factoryCwd !== undefined && (await mcpCatalogExists(factoryCwd))) {
105
+ if (factoryCwd !== undefined && (await mcpCatalogExists(factoryCwd, { scope: { kind: "validate" } }))) {
100
106
  await mkdir(artifactsDir, { recursive: true });
101
107
  const resolved = await resolveStageMcpServers({
102
108
  projectRoot: factoryCwd,
103
109
  allowlist: names,
104
110
  env,
111
+ scope: { kind: "stage", secrets: options.declaredSecrets ?? [] },
105
112
  bindingKind,
106
113
  trustWorkspaceConfig: options.trustWorkspaceConfig,
107
114
  ...stageIdOpt,
@@ -185,6 +192,7 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
185
192
  trustWorkspaceConfig: resolveOptions.trustWorkspaceConfig,
186
193
  stageEnv: resolveOptions.stageEnv,
187
194
  stageId: input.stageId,
195
+ declaredSecrets: (input.stage.secrets ?? []).map((d) => d.name),
188
196
  });
189
197
  resolvedMcpServers = mcp.servers;
190
198
  origins.push(...mcp.origins);
@@ -450,6 +458,21 @@ export async function openStageAttempt(input) {
450
458
  throw error;
451
459
  }
452
460
  }
461
+ if (process.env.SF_STAGE_WORKER !== "1" && input.stage.channels?.length) {
462
+ try {
463
+ validateStageChannelConnections(channelHostFor(input.factoryCwd ?? process.cwd()), input.stage);
464
+ }
465
+ catch (error) {
466
+ if (error instanceof ChannelError)
467
+ return { ok: false, reason: error.code };
468
+ throw error;
469
+ }
470
+ }
471
+ const stageMemory = input.stage.memories?.length
472
+ ? process.env.SF_STAGE_WORKER === "1"
473
+ ? await workerMemoryForStage(input.stage.memories)
474
+ : await bindStageMemory(memoryHostFor(), input.stage.memories)
475
+ : undefined;
453
476
  const opened = await openStageWithOperatorCatalog(input.agent, {
454
477
  roots,
455
478
  stage: input.stage,
@@ -465,6 +488,13 @@ export async function openStageAttempt(input) {
465
488
  resumeToken,
466
489
  email: input.stage.email?.length ? (process.env.SF_STAGE_WORKER === "1" ? workerStageEmail()
467
490
  : stageEmail(emailHostFor(input.factoryCwd ?? process.cwd()).mailbox, input.stage, input.runId, { workspaceDir: input.workspaceDir, attempt })) : undefined,
491
+ ...(input.stage.channels?.length ? {
492
+ channels: process.env.SF_STAGE_WORKER === "1" ? workerStageChannels()
493
+ : stageChannels(channelHostFor(input.factoryCwd ?? process.cwd()), input.stage, input.runId),
494
+ } : {}),
495
+ ...(stageMemory
496
+ ? { memory: stageMemory, memoryPrompt: await memoryPrompt(stageMemory) }
497
+ : {}),
468
498
  ...(input.sessionMode !== undefined ? { sessionMode: input.sessionMode } : {}),
469
499
  onActivity: (event) => {
470
500
  input.onActivity?.(input.stage.browser !== undefined
@@ -2,6 +2,7 @@ export declare const STAGE_ENV_PASSTHROUGH = "STAGEFLOW_STAGE_ENV_PASSTHROUGH";
2
2
  export declare const STAGE_ENV_ALLOW = "STAGEFLOW_STAGE_ENV_ALLOW";
3
3
  /** Names copied from Host when present (exact-name allowlist; no prefix wildcards). */
4
4
  export declare const STAGE_ENV_ALLOWLIST: readonly string[];
5
+ export declare const PROVIDER_AND_CLOUD_NAMES: readonly ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENAI_API_KEY_FILE", "GOOGLE_API_KEY", "GEMINI_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY", "DEEPSEEK_API_KEY", "TOGETHER_API_KEY", "FIREWORKS_API_KEY", "PERPLEXITY_API_KEY", "XAI_API_KEY", "COHERE_API_KEY", "OPENROUTER_API_KEY", "AZURE_OPENAI_API_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_SECURITY_TOKEN", "AWS_PROFILE", "AWS_DEFAULT_REGION", "AWS_REGION", "GOOGLE_APPLICATION_CREDENTIALS", "GCLOUD_PROJECT", "AZURE_CLIENT_SECRET", "AZURE_TENANT_ID", "AZURE_CLIENT_ID", "NPM_TOKEN", "NODE_AUTH_TOKEN", "SSH_AUTH_SOCK", "SSH_AGENT_PID", "GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN_FILE", "GH_TOKEN_FILE"];
5
6
  /** Never grantable — even via secrets `as: env`. */
6
7
  export declare const FOREVER_DENIED_SECRET_NAMES: ReadonlySet<string>;
7
8
  /** Blocked from ambient / ALLOW / PASSTHROUGH; `as: env` grants may still inject. */
@@ -29,6 +30,7 @@ export type BuildStageEnvironmentResult = {
29
30
  passthroughActive: boolean;
30
31
  };
31
32
  export declare function isForeverDeniedSecret(name: string): boolean;
33
+ export declare function isProviderOrCloudName(name: string): boolean;
32
34
  export declare function isAmbientBlockedEnv(name: string): boolean;
33
35
  /**
34
36
  * Construct the stage child environment. Exact-name allowlist only — never a
@@ -27,7 +27,7 @@ export const STAGE_ENV_ALLOWLIST = [
27
27
  "STAGEFLOW_HOME",
28
28
  "STAGEFLOW_CURSOR_EXTENSION",
29
29
  ];
30
- const PROVIDER_AND_CLOUD_NAMES = [
30
+ export const PROVIDER_AND_CLOUD_NAMES = [
31
31
  "ANTHROPIC_API_KEY",
32
32
  "OPENAI_API_KEY",
33
33
  "OPENAI_API_KEY_FILE",
@@ -70,6 +70,8 @@ export const FOREVER_DENIED_SECRET_NAMES = new Set([
70
70
  "STAGEFLOW_CONTROL_TOKEN_FILE",
71
71
  "STAGEFLOW_READ_TOKEN",
72
72
  "STAGEFLOW_READ_TOKEN_FILE",
73
+ // Host-generated key for the MCP OAuth token store; never hand it to a stage or an MCP server.
74
+ "PI_MCP_ADAPTER_OAUTH_FILE_KEY",
73
75
  ]);
74
76
  /** Blocked from ambient / ALLOW / PASSTHROUGH; `as: env` grants may still inject. */
75
77
  export const AMBIENT_BLOCKED_ENV_NAMES = new Set([
@@ -94,6 +96,9 @@ export function isForeverDeniedSecret(name) {
94
96
  return true;
95
97
  return name.startsWith("STAGEFLOW_CONTROL_TOKEN_");
96
98
  }
99
+ export function isProviderOrCloudName(name) {
100
+ return PROVIDER_AND_CLOUD_NAMES.includes(name);
101
+ }
97
102
  export function isAmbientBlockedEnv(name) {
98
103
  return isForeverDeniedSecret(name) || AMBIENT_BLOCKED_ENV_NAMES.has(name);
99
104
  }