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
@@ -2,6 +2,7 @@ import type { PayloadSchemaMap } from "../envelope/payloadSchema.js";
2
2
  import type { InlinePipelineDefinition } from "../types/pipeline.js";
3
3
  import { type LoadIssue, type LoadOutcome } from "./loadOutcome.js";
4
4
  import { type ToolRequirement } from "./toolRequires.js";
5
+ import type { StageChannel } from "../types/stage.js";
5
6
  export type RawMergedEntry = {
6
7
  raw: unknown;
7
8
  declaringPath: string;
@@ -13,6 +14,8 @@ export declare function mergePipelineStages(rootPath: string): Promise<LoadOutco
13
14
  model?: string;
14
15
  schemas?: PayloadSchemaMap;
15
16
  requires?: ToolRequirement[];
17
+ channels?: StageChannel[];
18
+ memories?: string[];
16
19
  warnings: LoadIssue[];
17
20
  }>>;
18
21
  /** Synthetic `declaringPath` for stage entries with no backing file. */
@@ -24,5 +27,7 @@ export declare function mergeInlinePipelineStages(raw: InlinePipelineDefinition)
24
27
  model?: string;
25
28
  schemas?: PayloadSchemaMap;
26
29
  requires?: ToolRequirement[];
30
+ channels?: StageChannel[];
31
+ memories?: string[];
27
32
  warnings: LoadIssue[];
28
33
  }>>;
@@ -6,6 +6,8 @@ import { parseModelField } from "./modelField.js";
6
6
  import { readYamlObject } from "./readYamlObject.js";
7
7
  import { yamlParsePosition } from "./yamlParsePosition.js";
8
8
  import { parseToolRequires } from "./toolRequires.js";
9
+ import { parseChannels } from "../channels/stageChannels.js";
10
+ import { parseMemoryIdList } from "../memory/ids.js";
9
11
  import { classifyYamlDocument, collectDocumentKeys, mixedDialectIssue, } from "./yamlDialect.js";
10
12
  function isPlainObject(value) {
11
13
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -189,6 +191,14 @@ export async function mergePipelineStages(rootPath) {
189
191
  if (!requiresOutcome.ok)
190
192
  return requiresOutcome;
191
193
  const requires = requiresOutcome.value;
194
+ const channelsOutcome = parsePipelineChannels(raw.channels, `pipeline ${absRoot}`, pipelineId);
195
+ if (!channelsOutcome.ok)
196
+ return channelsOutcome;
197
+ const channels = channelsOutcome.value;
198
+ const memoriesOutcome = parsePipelineMemories(raw.memories, `pipeline ${absRoot}`, pipelineId);
199
+ if (!memoriesOutcome.ok)
200
+ return memoriesOutcome;
201
+ const memories = memoriesOutcome.value;
192
202
  const idLocations = new Map();
193
203
  const entries = [];
194
204
  const warnings = [];
@@ -211,6 +221,8 @@ export async function mergePipelineStages(rootPath) {
211
221
  ...(model !== undefined ? { model } : {}),
212
222
  ...(schemas !== undefined ? { schemas } : {}),
213
223
  ...(requires !== undefined ? { requires } : {}),
224
+ ...(channels !== undefined ? { channels } : {}),
225
+ ...(memories !== undefined ? { memories } : {}),
214
226
  warnings,
215
227
  });
216
228
  }
@@ -273,6 +285,14 @@ export async function mergeInlinePipelineStages(raw) {
273
285
  if (!requiresOutcome.ok)
274
286
  return requiresOutcome;
275
287
  const requires = requiresOutcome.value;
288
+ const channelsOutcome = parsePipelineChannels(raw.channels, `inline pipeline ${pipelineId}`, pipelineId);
289
+ if (!channelsOutcome.ok)
290
+ return channelsOutcome;
291
+ const channels = channelsOutcome.value;
292
+ const memoriesOutcome = parsePipelineMemories(raw.memories, `inline pipeline ${pipelineId}`, pipelineId);
293
+ if (!memoriesOutcome.ok)
294
+ return memoriesOutcome;
295
+ const memories = memoriesOutcome.value;
276
296
  const entries = [];
277
297
  for (let index = 0; index < raw.stages.length; index++) {
278
298
  const entry = raw.stages[index];
@@ -295,9 +315,41 @@ export async function mergeInlinePipelineStages(raw) {
295
315
  ...(model !== undefined ? { model } : {}),
296
316
  ...(schemas !== undefined ? { schemas } : {}),
297
317
  ...(requires !== undefined ? { requires } : {}),
318
+ ...(channels !== undefined ? { channels } : {}),
319
+ ...(memories !== undefined ? { memories } : {}),
298
320
  warnings: [],
299
321
  });
300
322
  }
323
+ function parsePipelineMemories(raw, label, pipelineId) {
324
+ const parsed = parseMemoryIdList(raw);
325
+ if (!parsed.ok) {
326
+ return loadFailure([
327
+ {
328
+ code: "pipeline.invalid_memories",
329
+ message: `Invalid ${label}: ${parsed.message}`,
330
+ category: "pipeline",
331
+ pipelineId,
332
+ },
333
+ ]);
334
+ }
335
+ return loadSuccess(parsed.value);
336
+ }
337
+ function parsePipelineChannels(raw, label, pipelineId) {
338
+ if (raw === undefined)
339
+ return loadSuccess(undefined);
340
+ const parsed = parseChannels(raw);
341
+ if (!parsed.ok) {
342
+ return loadFailure([
343
+ {
344
+ code: "pipeline.invalid_channels",
345
+ message: `Invalid ${label}: ${parsed.message}`,
346
+ category: "pipeline",
347
+ pipelineId,
348
+ },
349
+ ]);
350
+ }
351
+ return loadSuccess(parsed.value);
352
+ }
301
353
  function parsePipelineSchemas(raw, absPath, pipelineId) {
302
354
  if (raw === undefined)
303
355
  return loadSuccess(undefined);
@@ -29,6 +29,7 @@ export const WIRING_KEYS = new Set([
29
29
  /** Stage body keys. Target: `io` / `verify`. IR/legacy: `payload_schema`, `pre_emit_checks`, `clone_input_schema`. */
30
30
  export const BODY_KEYS = new Set([
31
31
  "email",
32
+ "channels",
32
33
  "system_prompt",
33
34
  "model",
34
35
  "payload_schema",
@@ -1,4 +1,4 @@
1
- export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "untrusted_config_origin" | "connect_failed";
1
+ export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "untrusted_config_origin" | "connect_failed" | "host_state_corrupt";
2
2
  export declare class StageMcpError extends Error {
3
3
  readonly code: StageMcpErrorCode;
4
4
  constructor(message: string, code: StageMcpErrorCode);
@@ -9,18 +9,45 @@ export declare const MCP_CATALOG_FILENAME = ".mcp.json";
9
9
  export declare const STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV = "STAGEFLOW_STAGE_ARTIFACTS_DIR";
10
10
  export declare function stampStagePromptArtifactsDir(prompt: string, artifactsDir: string): string;
11
11
  export type McpCatalogServers = Record<string, Record<string, unknown>>;
12
+ export type McpServerOrigin = "project" | "console";
12
13
  export type InspectedMcpCatalog = {
13
14
  path: string;
14
15
  servers: McpCatalogServers;
16
+ /** Where each server came from; `console` = saved from the UI under $STAGEFLOW_HOME. */
17
+ origins: Record<string, McpServerOrigin>;
18
+ };
19
+ /**
20
+ * Who is asking. Without a scope only the project `.mcp.json` is visible; with one, servers saved
21
+ * from the console are merged in (console entry wins on a name clash) and secrets are filled by
22
+ * the rules of that scope:
23
+ * - stage: console secrets whose names the stage declared in `secrets:`, plus the stage env
24
+ * - agent: console secrets plus Host env names listed in STAGEFLOW_MCP_ENV_ALLOW
25
+ * - probe: same as agent, so a probe and an agent run resolve one config identically
26
+ * - validate: placeholders are checked but no secret value is ever read into the output
27
+ * Console secrets always win over Host env for the same name.
28
+ */
29
+ export type McpResolveScope = {
30
+ kind: "stage";
31
+ secrets: readonly string[];
32
+ } | {
33
+ kind: "agent";
34
+ } | {
35
+ kind: "probe";
36
+ } | {
37
+ kind: "validate";
38
+ };
39
+ export type McpCatalogLoadOptions = {
40
+ scope?: McpResolveScope;
15
41
  };
16
42
  export declare function mcpCatalogPath(projectRoot: string): string;
17
43
  export declare function parseMcpCatalog(raw: string, catalogPath?: string): McpCatalogServers;
18
- export declare function mcpCatalogExists(projectRoot: string): Promise<boolean>;
19
- export declare function loadMcpCatalog(projectRoot: string): Promise<InspectedMcpCatalog>;
44
+ export declare function mcpCatalogExists(projectRoot: string, options?: McpCatalogLoadOptions): Promise<boolean>;
45
+ export declare function loadMcpCatalog(projectRoot: string, options?: McpCatalogLoadOptions): Promise<InspectedMcpCatalog>;
20
46
  export type ProjectMcpCatalogTransport = "stdio" | "http";
21
47
  export type ProjectMcpCatalogEntry = {
22
48
  name: string;
23
49
  transport: ProjectMcpCatalogTransport;
50
+ origin: McpServerOrigin;
24
51
  };
25
52
  export type ProjectMcpCatalogListStatus = "ok" | "missing_catalog" | "invalid_config";
26
53
  export type ProjectMcpCatalogList = {
@@ -32,7 +59,10 @@ export declare function assertMcpAllowlistKnown(servers: McpCatalogServers, allo
32
59
  export declare function resolveStageMcpServers(options: {
33
60
  projectRoot: string;
34
61
  allowlist?: readonly string[];
62
+ /** Stage env for the project-only and stage scopes; agent/probe/validate do not read it as a secret source. */
35
63
  env: NodeJS.ProcessEnv;
64
+ /** Host env that STAGEFLOW_MCP_ENV_ALLOW may draw from (agent/probe scopes). Defaults to process.env. */
65
+ hostEnv?: NodeJS.ProcessEnv;
36
66
  stageId?: string;
37
67
  bindingKind?: "repository" | "checkout" | "none";
38
68
  trustWorkspaceConfig?: string[];
@@ -40,4 +70,6 @@ export declare function resolveStageMcpServers(options: {
40
70
  trustProjectRoot?: string;
41
71
  /** When true, .mcp.json is treated as workspace-sourced. */
42
72
  workspaceSourced?: boolean;
73
+ /** Merge console-saved servers and fill `${VAR}` by the scope's rules. Omit for project-only. */
74
+ scope?: McpResolveScope;
43
75
  }): Promise<ResolvedMcpServers>;
@@ -1,6 +1,8 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { STAGEFLOW_MCP_SERVER_NAME } from "../agent/claudeTools.js";
4
+ import { isMcpHostStateError, mcpEnvAllowNames, readHostMcpServers, readMcpSecrets, } from "./mcpHostCatalog.js";
5
+ import { assertSafeMcpUrl, McpUrlPolicyError } from "./mcpRedaction.js";
4
6
  export class StageMcpError extends Error {
5
7
  code;
6
8
  constructor(message, code) {
@@ -51,7 +53,31 @@ export function parseMcpCatalog(raw, catalogPath = MCP_CATALOG_FILENAME) {
51
53
  }
52
54
  return servers;
53
55
  }
54
- export async function mcpCatalogExists(projectRoot) {
56
+ /** Console servers; a corrupt host file surfaces as a StageMcpError instead of reading as empty. */
57
+ function hostServersOrThrow() {
58
+ try {
59
+ return readHostMcpServers();
60
+ }
61
+ catch (err) {
62
+ if (isMcpHostStateError(err))
63
+ throw new StageMcpError(err.message, "host_state_corrupt");
64
+ throw err;
65
+ }
66
+ }
67
+ function hostSecretsOrThrow() {
68
+ try {
69
+ return readMcpSecrets();
70
+ }
71
+ catch (err) {
72
+ if (isMcpHostStateError(err))
73
+ throw new StageMcpError(err.message, "host_state_corrupt");
74
+ throw err;
75
+ }
76
+ }
77
+ export async function mcpCatalogExists(projectRoot, options = {}) {
78
+ if (options.scope !== undefined && Object.keys(hostServersOrThrow()).length > 0) {
79
+ return true;
80
+ }
55
81
  try {
56
82
  await readFile(mcpCatalogPath(projectRoot), "utf8");
57
83
  return true;
@@ -63,20 +89,31 @@ export async function mcpCatalogExists(projectRoot) {
63
89
  throw err;
64
90
  }
65
91
  }
66
- export async function loadMcpCatalog(projectRoot) {
92
+ export async function loadMcpCatalog(projectRoot, options = {}) {
67
93
  const catalogPath = mcpCatalogPath(projectRoot);
94
+ const hostServers = options.scope !== undefined ? hostServersOrThrow() : {};
68
95
  let raw;
69
96
  try {
70
97
  raw = await readFile(catalogPath, "utf8");
71
98
  }
72
99
  catch (err) {
73
100
  const code = err.code;
74
- if (code === "ENOENT") {
101
+ if (code !== "ENOENT") {
102
+ throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" could not be read`, "invalid_config");
103
+ }
104
+ if (Object.keys(hostServers).length === 0) {
75
105
  throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" is missing`, "missing_catalog");
76
106
  }
77
- throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" could not be read`, "invalid_config");
78
107
  }
79
- return { path: catalogPath, servers: parseMcpCatalog(raw, catalogPath) };
108
+ const servers = raw !== undefined ? parseMcpCatalog(raw, catalogPath) : {};
109
+ const origins = {};
110
+ for (const name of Object.keys(servers))
111
+ origins[name] = "project";
112
+ for (const [name, entry] of Object.entries(hostServers)) {
113
+ servers[name] = entry;
114
+ origins[name] = "console";
115
+ }
116
+ return { path: catalogPath, servers, origins };
80
117
  }
81
118
  function coarseMcpTransport(entry) {
82
119
  if (Object.hasOwn(entry, "command")) {
@@ -89,14 +126,20 @@ function coarseMcpTransport(entry) {
89
126
  }
90
127
  export async function listProjectMcpCatalog(projectRoot) {
91
128
  try {
92
- const catalog = await loadMcpCatalog(projectRoot);
129
+ const catalog = await loadMcpCatalog(projectRoot, {
130
+ scope: { kind: "validate" },
131
+ });
93
132
  const servers = [];
94
133
  for (const [name, entry] of Object.entries(catalog.servers)) {
95
134
  const transport = coarseMcpTransport(entry);
96
135
  if (transport === undefined) {
97
136
  return { status: "invalid_config", servers: [] };
98
137
  }
99
- servers.push({ name, transport });
138
+ servers.push({
139
+ name,
140
+ transport,
141
+ origin: catalog.origins[name] ?? "project",
142
+ });
100
143
  }
101
144
  return { status: "ok", servers };
102
145
  }
@@ -207,6 +250,69 @@ function interpolateServer(entry, env, serverName, projectRoot, stageId) {
207
250
  }
208
251
  return stampSpawnRoot(resolved, projectRoot, serverName);
209
252
  }
253
+ function bagOfStrings(value) {
254
+ return isPlainObject(value) && Object.values(value).every((v) => typeof v === "string");
255
+ }
256
+ /** Shape check for any entry about to be used (project or console). */
257
+ function assertEntryShape(name, entry) {
258
+ const bad = (what) => {
259
+ throw new StageMcpError(`MCP catalog server "${name}" ${what}`, "invalid_config");
260
+ };
261
+ const hasCommand = Object.hasOwn(entry, "command");
262
+ const hasUrl = Object.hasOwn(entry, "url");
263
+ if (hasCommand === hasUrl)
264
+ bad('needs exactly one of "command" or "url"');
265
+ if (hasCommand && (typeof entry.command !== "string" || entry.command.length === 0)) {
266
+ bad('"command" must be a non-empty string');
267
+ }
268
+ if (hasUrl && (typeof entry.url !== "string" || entry.url.length === 0)) {
269
+ bad('"url" must be a non-empty string');
270
+ }
271
+ if (entry.args !== undefined &&
272
+ (!Array.isArray(entry.args) || entry.args.some((a) => typeof a !== "string"))) {
273
+ bad('"args" must be an array of strings');
274
+ }
275
+ for (const key of ["env", "headers"]) {
276
+ if (entry[key] !== undefined && !bagOfStrings(entry[key]))
277
+ bad(`"${key}" must map names to strings`);
278
+ }
279
+ if (entry.cwd !== undefined && typeof entry.cwd !== "string")
280
+ bad('"cwd" must be a string');
281
+ }
282
+ function assertResolvedUrlSafe(name, resolved) {
283
+ if (typeof resolved.url !== "string")
284
+ return;
285
+ try {
286
+ assertSafeMcpUrl(resolved.url);
287
+ }
288
+ catch (err) {
289
+ if (err instanceof McpUrlPolicyError) {
290
+ throw new StageMcpError(`MCP catalog server "${name}": ${err.message}`, "invalid_config");
291
+ }
292
+ throw err;
293
+ }
294
+ }
295
+ const VALIDATE_PLACEHOLDER = "<redacted>";
296
+ /** The values `${NAME}` may be filled from, by scope. Console secrets win over Host env. */
297
+ function envForScope(scope, stageEnv, hostEnv) {
298
+ const secrets = hostSecretsOrThrow();
299
+ if (scope.kind === "stage") {
300
+ const declared = new Set(scope.secrets);
301
+ const own = Object.fromEntries(Object.entries(secrets).filter(([n]) => declared.has(n)));
302
+ return { ...stageEnv, ...own };
303
+ }
304
+ const allowed = {};
305
+ for (const n of mcpEnvAllowNames(hostEnv)) {
306
+ if (hostEnv[n] !== undefined)
307
+ allowed[n] = hostEnv[n];
308
+ }
309
+ if (scope.kind === "validate") {
310
+ const names = new Set([...Object.keys(secrets), ...Object.keys(allowed), ...Object.keys(stageEnv)]);
311
+ return Object.fromEntries([...names].map((n) => [n, VALIDATE_PLACEHOLDER]));
312
+ }
313
+ // agent and probe: one rule, so the same config resolves the same way in both.
314
+ return { ...allowed, ...secrets };
315
+ }
210
316
  export async function resolveStageMcpServers(options) {
211
317
  const allowlist = options.allowlist ?? [];
212
318
  if (allowlist.length === 0) {
@@ -224,11 +330,20 @@ export async function resolveStageMcpServers(options) {
224
330
  throw new StageMcpError(decision.message ?? "untrusted_config_origin", "untrusted_config_origin");
225
331
  }
226
332
  }
227
- const catalog = await loadMcpCatalog(options.projectRoot);
333
+ // Workspace-sourced catalogs never see console servers or secrets.
334
+ const scope = options.workspaceSourced === true ? undefined : options.scope;
335
+ const catalog = await loadMcpCatalog(options.projectRoot, scope ? { scope } : {});
228
336
  assertMcpAllowlistKnown(catalog.servers, allowlist);
337
+ const env = scope
338
+ ? envForScope(scope, options.env, options.hostEnv ?? process.env)
339
+ : options.env;
229
340
  const resolved = {};
230
341
  for (const name of allowlist) {
231
- resolved[name] = interpolateServer(catalog.servers[name], options.env, name, options.projectRoot, options.stageId);
342
+ const entry = catalog.servers[name];
343
+ assertEntryShape(name, entry);
344
+ resolved[name] = interpolateServer(entry, env, name, options.projectRoot, options.stageId);
345
+ if (scope?.kind !== "validate")
346
+ assertResolvedUrlSafe(name, resolved[name]);
232
347
  }
233
348
  return resolved;
234
349
  }
@@ -1,9 +1,10 @@
1
1
  import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
2
2
  import type { TriggerFile } from "../types/trigger.js";
3
3
  import type { LoadIssue } from "./loadOutcome.js";
4
+ import type { AnyChannelProvider } from "../channels/types.js";
4
5
  export type ValidationSeverity = "error" | "warning";
5
6
  export type ValidationCategory = "pipeline" | "stage" | "catalog" | "task" | "trigger";
6
- export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.stage_wrapper_skill_rejected" | "pipeline.invalid_completion" | "catalog.path_case_mismatch" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "pipeline.invalid_requires" | "pipeline.requires_conflict" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_browser" | "stage.invalid_secrets" | "stage.unknown_secret" | "stage.denied_secret" | "stage.invalid_requires" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mcp_command_missing" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml" | "catalog.stageflow_home_absolute_path";
7
+ export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.stage_wrapper_skill_rejected" | "pipeline.invalid_completion" | "catalog.path_case_mismatch" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "pipeline.invalid_requires" | "pipeline.requires_conflict" | "pipeline.invalid_channels" | "pipeline.invalid_memories" | "pipeline.memory_unavailable" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_channels" | "stage.channel_unavailable" | "stage.invalid_browser" | "stage.invalid_secrets" | "stage.unknown_secret" | "stage.denied_secret" | "stage.invalid_requires" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mcp_command_missing" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml" | "catalog.stageflow_home_absolute_path";
7
8
  export type ValidationFinding = {
8
9
  severity: ValidationSeverity;
9
10
  code: ValidationFindingCode | string;
@@ -50,6 +51,7 @@ export type ValidatePipelineOptions = {
50
51
  projectRoot?: string;
51
52
  validateStages?: boolean;
52
53
  strict?: boolean;
54
+ channelProviders?: readonly AnyChannelProvider[];
53
55
  };
54
56
  export declare function validatePipeline(nameOrPath: string, options?: ValidatePipelineOptions): Promise<ValidationResult>;
55
57
  export type LoadPipelineValidatedResult = {
@@ -64,6 +66,7 @@ export declare function loadPipelineValidated(nameOrPath: string | InlinePipelin
64
66
  cwd?: string;
65
67
  projectRoot?: string;
66
68
  validateStages?: boolean;
69
+ channelProviders?: readonly AnyChannelProvider[];
67
70
  }): Promise<LoadPipelineValidatedResult>;
68
71
  export declare function collectPipelineIdsFromPaths(pipelinePaths: string[]): Promise<Map<string, string[]>>;
69
72
  export declare function collectTaskIdsFromPaths(taskPaths: string[]): Promise<Set<string>>;
@@ -14,6 +14,10 @@ import { MCP_CATALOG_FILENAME, StageMcpError, assertMcpAllowlistKnown, loadMcpCa
14
14
  import { loadSecretRegistry } from "../runtime/stageSecrets.js";
15
15
  import { isForeverDeniedSecret } from "../runtime/stageEnvironment.js";
16
16
  import { findMissingMcpCommands } from "../preflight/mcpCommands.js";
17
+ import { ChannelError } from "../channels/errors.js";
18
+ import { ChannelRegistry } from "../channels/registry.js";
19
+ import { validateStageChannels } from "../channels/stageChannels.js";
20
+ import { inspectMemorySync, memoryHome } from "../memory/host.js";
17
21
  export function relPath(cwd, absPath) {
18
22
  return path.relative(cwd, absPath).replace(/\\/g, "/");
19
23
  }
@@ -256,6 +260,42 @@ function collectMcpAllowlistNames(loaded) {
256
260
  }
257
261
  return [...names];
258
262
  }
263
+ function findingsForStageChannels(cwd, pipelinePath, loaded, projectRoot, providers) {
264
+ const users = loaded.stages.filter((stage) => (stage.channels?.length ?? 0) > 0);
265
+ if (users.length === 0)
266
+ return [];
267
+ let registry;
268
+ try {
269
+ registry = new ChannelRegistry({ cwd: projectRoot, providers });
270
+ }
271
+ catch (error) {
272
+ const code = error instanceof ChannelError ? error.code : "CHANNEL_INVALID_INPUT";
273
+ const field = error instanceof ChannelError && error.detail?.connectionId
274
+ ? ` (connection "${error.detail.connectionId}"${error.detail.field ? `, field "${error.detail.field}"` : ""})`
275
+ : "";
276
+ return users.map((stage) => findingStageError(cwd, pipelinePath, `connections.yaml could not be loaded: ${code}${field}`, "stage.channel_unavailable", stage.id));
277
+ }
278
+ return users.flatMap((stage) => validateStageChannels(registry, stage).map((issue) => findingStageError(cwd, pipelinePath, issue.message, "stage.channel_unavailable", issue.stageId)));
279
+ }
280
+ const MEMORY_FINDING = {
281
+ MEMORY_NOT_FOUND: (id) => `Memory "${id}" does not exist. Create it before attaching it.`,
282
+ MEMORY_MISMATCH: (id) => `Memory "${id}" does not match the id in its file.`,
283
+ MEMORY_UNREADABLE: (id) => `Memory "${id}" could not be read.`,
284
+ MEMORY_INVALID_ID: (id) => `Memory "${id}" is not a valid id.`,
285
+ };
286
+ function findingsForMemories(cwd, pipelinePath, loaded) {
287
+ const ids = loaded.pipeline.memories;
288
+ if (!ids?.length)
289
+ return [];
290
+ const home = memoryHome();
291
+ return ids.flatMap((id) => {
292
+ const inspected = inspectMemorySync(home, id);
293
+ if (inspected.ok)
294
+ return [];
295
+ const message = (MEMORY_FINDING[inspected.code] ?? MEMORY_FINDING.MEMORY_UNREADABLE)(id);
296
+ return [findingPipelineError(cwd, pipelinePath, message, "pipeline.memory_unavailable", loaded.pipeline.id)];
297
+ });
298
+ }
259
299
  function findingsForStageSecrets(cwd, pipelinePath, loaded) {
260
300
  const registry = loadSecretRegistry(process.env);
261
301
  const findings = [];
@@ -366,7 +406,7 @@ async function findingsForStageMcpCatalog(cwd, loaded) {
366
406
  }
367
407
  let catalog;
368
408
  try {
369
- catalog = await loadMcpCatalog(projectRoot);
409
+ catalog = await loadMcpCatalog(projectRoot, { scope: { kind: "validate" } });
370
410
  }
371
411
  catch (err) {
372
412
  if (!(err instanceof StageMcpError))
@@ -438,6 +478,8 @@ async function runPipelineValidation(nameOrPath, options) {
438
478
  }
439
479
  findings.push(...(await findingsForStageMcpCatalog(cwd, outcome.value)));
440
480
  findings.push(...findingsForStageSecrets(cwd, pipelinePath, outcome.value));
481
+ findings.push(...findingsForStageChannels(cwd, pipelinePath, outcome.value, projectRoot, options.channelProviders));
482
+ findings.push(...findingsForMemories(cwd, pipelinePath, outcome.value));
441
483
  findings.push(...findingsForStageflowHomeAbsolutePaths(cwd, outcome.value));
442
484
  if (validateStages && outcome.value.stageSources) {
443
485
  for (const source of Object.values(outcome.value.stageSources)) {
@@ -461,6 +503,7 @@ export async function validatePipeline(nameOrPath, options = {}) {
461
503
  cwd,
462
504
  projectRoot,
463
505
  validateStages,
506
+ channelProviders: options.channelProviders,
464
507
  });
465
508
  return buildValidationResult("pipeline", core.findings, strict);
466
509
  }
@@ -472,6 +515,7 @@ export async function loadPipelineValidated(nameOrPath, options = {}) {
472
515
  cwd,
473
516
  projectRoot,
474
517
  validateStages,
518
+ channelProviders: options.channelProviders,
475
519
  });
476
520
  if (core.ok) {
477
521
  return { ok: true, loaded: core.loaded, findings: core.findings };
@@ -4,7 +4,7 @@ export declare const BUSY_CHECKOUT: "busy_checkout";
4
4
  /** Per-caller concurrency quota: queued when under global capacity; reject when queue full. */
5
5
  export declare const BUSY_CALLER_QUOTA: "busy_caller_quota";
6
6
  export declare const ABORTED: "aborted";
7
- export declare const NETWORK_ERROR_CODES: readonly ["busy_capacity", "busy_checkout", "busy_caller_quota", "aborted", "unknown_project_root", "absolute_path_not_allowed", "path_outside_project_root", "catalog_root_unreadable", "ensure_project_not_allowed", "config_invalid", "config_unknown_key", "provider_not_configured", "a2a_configuration_error", "untrusted_config_origin", "command_not_on_path", "missing_tool", "tool_version_mismatch", "unknown_version", "secret_unavailable", "not_ready", "internal_error", "retry_in_progress", "hitl_not_retriable", "run_not_retryable", "stage_not_failed", "manual_recovery_required", "run_not_found", "stage_not_found", "artifact_not_found", "artifact_path_denied", "envelope_not_found", "insufficient_disk", "disk_check_failed", "shutting_down", "start.token_rejected", "inline_pipeline_too_large", "skills_invalid_name", "skills_invalid_path", "skills_missing_skill_md", "skills_payload_too_large", "pinned_sha_unavailable", "store_integrity_failed", "store_unsupported_filesystem", "tmpdir_unusable", "backup_insufficient_disk"];
7
+ export declare const NETWORK_ERROR_CODES: readonly ["busy_capacity", "busy_checkout", "busy_caller_quota", "aborted", "unknown_project_root", "absolute_path_not_allowed", "path_outside_project_root", "catalog_root_unreadable", "ensure_project_not_allowed", "config_invalid", "config_unknown_key", "provider_not_configured", "a2a_configuration_error", "untrusted_config_origin", "command_not_on_path", "missing_tool", "tool_version_mismatch", "unknown_version", "secret_unavailable", "channel_check_failed", "not_ready", "internal_error", "retry_in_progress", "hitl_not_retriable", "run_not_retryable", "stage_not_failed", "manual_recovery_required", "run_not_found", "stage_not_found", "artifact_not_found", "artifact_path_denied", "envelope_not_found", "insufficient_disk", "disk_check_failed", "shutting_down", "start.token_rejected", "inline_pipeline_too_large", "skills_invalid_name", "skills_invalid_path", "skills_missing_skill_md", "skills_payload_too_large", "pinned_sha_unavailable", "store_integrity_failed", "store_unsupported_filesystem", "tmpdir_unusable", "backup_insufficient_disk"];
8
8
  export type NetworkErrorCode = (typeof NETWORK_ERROR_CODES)[number] | string;
9
9
  export type NetworkErrorBody = {
10
10
  code: NetworkErrorCode;
@@ -24,6 +24,7 @@ export const NETWORK_ERROR_CODES = [
24
24
  "tool_version_mismatch",
25
25
  "unknown_version",
26
26
  "secret_unavailable",
27
+ "channel_check_failed",
27
28
  "not_ready",
28
29
  "internal_error",
29
30
  "retry_in_progress",
@@ -0,0 +1,8 @@
1
+ import type { McpServer } from "@modelcontextprotocol/server";
2
+ import type { McpToolDeps } from "./deps.js";
3
+ /**
4
+ * Agent management for operators driving Stageflow over MCP. A running agent reaches these
5
+ * only through the Stageflow tool groups it is granted (`agents.*`); the guard in
6
+ * `toolkit/guard.ts` stops it acting on its own sessions.
7
+ */
8
+ export declare function registerAgentTools(server: McpServer, deps: McpToolDeps): void;