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,192 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { agentAuthorContextAdapter, emptyAgentAuthorDraft, isAgentAuthorContext, readAgentAuthorDraft, } from "../agentAuthorContext.js";
4
+ import { AGENT_ID_PATTERN, parseAgentDefinition } from "../../config/loadAgent.js";
5
+ import { listProjectMcpCatalog } from "../../config/resolveStageMcpServers.js";
6
+ import { AGENT_AUTHOR_PLAYBOOK } from "./agentAuthorPlaybook.js";
7
+ export const AGENT_AUTHOR_PROFILE_ID = "agent-author";
8
+ const MAX_READ_CHARS = 32_000;
9
+ let proposalSeq = 0;
10
+ function nextProposalId() {
11
+ proposalSeq += 1;
12
+ return `agent-author-${proposalSeq}`;
13
+ }
14
+ function projectRootOf(ctx) {
15
+ const context = ctx.getContext();
16
+ if (!isAgentAuthorContext(context))
17
+ return undefined;
18
+ const root = context.projectRoot?.trim();
19
+ return root || undefined;
20
+ }
21
+ function rejectEscape(root, rel) {
22
+ if (!rel || path.isAbsolute(rel) || rel.includes("\0"))
23
+ return "path must be a relative project path";
24
+ const parts = rel.split(/[/\\]/);
25
+ if (parts.some((part) => part === ".." || part === ".git" || part === "node_modules")) {
26
+ return "path is outside the readable project surface";
27
+ }
28
+ const target = path.resolve(root, rel);
29
+ const base = path.resolve(root);
30
+ if (target !== base && !target.startsWith(base + path.sep))
31
+ return "path escapes the project";
32
+ return null;
33
+ }
34
+ function stringArg(args, key) {
35
+ const value = args[key];
36
+ return typeof value === "string" ? value : undefined;
37
+ }
38
+ function isPlainObject(value) {
39
+ return value !== null && typeof value === "object" && !Array.isArray(value);
40
+ }
41
+ /** Objects merge key by key; arrays and scalars replace; `null` removes the key. */
42
+ export function mergeDraftPatch(draft, patch) {
43
+ const next = structuredClone(draft);
44
+ for (const [key, value] of Object.entries(patch)) {
45
+ if (value === null)
46
+ delete next[key];
47
+ else if (isPlainObject(value) && isPlainObject(next[key])) {
48
+ next[key] = mergeDraftPatch(next[key], value);
49
+ }
50
+ else
51
+ next[key] = structuredClone(value);
52
+ }
53
+ return next;
54
+ }
55
+ function slugId(name) {
56
+ const slug = name
57
+ .trim()
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "_")
60
+ .replace(/^_+|_+$/g, "")
61
+ .replace(/^[^a-z]+/, "")
62
+ .slice(0, 64);
63
+ return AGENT_ID_PATTERN.test(slug) ? slug : "";
64
+ }
65
+ const readProjectFile = {
66
+ name: "read_project_file",
67
+ description: "Read a text file inside the operator project. Relative path only. Refuses .git, node_modules, and paths outside the project. Does not write.",
68
+ async handler(args, ctx) {
69
+ const root = projectRootOf(ctx);
70
+ const rel = stringArg(args, "path")?.trim();
71
+ if (!root)
72
+ return { ok: false, content: null, error: "project root is not set" };
73
+ if (!rel)
74
+ return { ok: false, content: null, error: "path is required" };
75
+ const rejected = rejectEscape(root, rel);
76
+ if (rejected)
77
+ return { ok: false, content: null, error: rejected };
78
+ try {
79
+ const bytes = await readFile(path.resolve(root, rel));
80
+ if (bytes.includes(0))
81
+ return { ok: false, content: null, error: "file is not text" };
82
+ const text = bytes.toString("utf8");
83
+ return {
84
+ ok: true,
85
+ content: {
86
+ path: rel,
87
+ text: text.length > MAX_READ_CHARS ? `${text.slice(0, MAX_READ_CHARS)}\n…truncated` : text,
88
+ },
89
+ };
90
+ }
91
+ catch {
92
+ return { ok: false, content: null, error: "file is not readable" };
93
+ }
94
+ },
95
+ };
96
+ const listProjectFiles = {
97
+ name: "list_project_files",
98
+ description: "List readable project files: AGENTS.md when present, and YAML files under agents/. Does not write.",
99
+ async handler(_args, ctx) {
100
+ const root = projectRootOf(ctx);
101
+ if (!root)
102
+ return { ok: false, content: null, error: "project root is not set" };
103
+ const files = [];
104
+ try {
105
+ await readFile(path.join(root, "AGENTS.md"));
106
+ files.push("AGENTS.md");
107
+ }
108
+ catch {
109
+ /* absent */
110
+ }
111
+ try {
112
+ const entries = await readdir(path.join(root, "agents"), { withFileTypes: true });
113
+ for (const entry of entries) {
114
+ if (entry.isFile() && /\.ya?ml$/i.test(entry.name))
115
+ files.push(`agents/${entry.name}`);
116
+ }
117
+ }
118
+ catch {
119
+ /* no agents dir */
120
+ }
121
+ return { ok: true, content: { files } };
122
+ },
123
+ };
124
+ const listMcpServers = {
125
+ name: "list_mcp_servers",
126
+ description: "List MCP server names declared in the project's .mcp.json. Use these names under tools.mcp. Does not connect.",
127
+ async handler(_args, ctx) {
128
+ const root = projectRootOf(ctx);
129
+ if (!root)
130
+ return { ok: false, content: null, error: "project root is not set" };
131
+ const catalog = await listProjectMcpCatalog(root);
132
+ return { ok: true, content: { status: catalog.status, servers: catalog.servers.map((server) => server.name) } };
133
+ },
134
+ };
135
+ const updateAgentDraft = {
136
+ name: "update_agent_draft",
137
+ description: [
138
+ "Change the unsaved agent draft. Arguments: patch (object, required) and summary (one short sentence).",
139
+ "patch uses the agent YAML schema: objects merge key by key, arrays replace, null removes a key.",
140
+ "The result lists validation issues; fix them in a follow-up patch. Does not write YAML.",
141
+ ].join(" "),
142
+ handler(args, ctx) {
143
+ const patch = args.patch;
144
+ if (!isPlainObject(patch))
145
+ return { ok: false, content: null, error: "patch must be an object" };
146
+ const currentContext = ctx.getContext();
147
+ const current = readAgentAuthorDraft(currentContext);
148
+ const next = mergeDraftPatch(current, patch);
149
+ if (typeof next.name === "string" && next.name.trim() && !(typeof next.id === "string" && next.id.trim())) {
150
+ const id = slugId(next.name);
151
+ if (id)
152
+ next.id = id;
153
+ }
154
+ const parsed = parseAgentDefinition(next);
155
+ const summary = stringArg(args, "summary")?.trim() || "Updated the agent draft";
156
+ const proposal = {
157
+ id: nextProposalId(),
158
+ summary,
159
+ baseContext: currentContext,
160
+ nextContext: {
161
+ kind: "agent_author",
162
+ ...(isAgentAuthorContext(currentContext) && currentContext.projectRoot
163
+ ? { projectRoot: currentContext.projectRoot }
164
+ : {}),
165
+ draft: next,
166
+ },
167
+ };
168
+ ctx.emitProposal(proposal);
169
+ return {
170
+ ok: true,
171
+ content: { summary, valid: parsed.ok, issues: parsed.ok ? [] : parsed.issues.map((issue) => `${issue.path || "(draft)"}: ${issue.message}`) },
172
+ };
173
+ },
174
+ };
175
+ export const AGENT_AUTHOR_TOOL_NAMES = ["read_project_file", "list_project_files", "list_mcp_servers", "update_agent_draft"];
176
+ export function createAgentAuthorProfile() {
177
+ return {
178
+ id: AGENT_AUTHOR_PROFILE_ID,
179
+ title: "Agent author",
180
+ playbook: AGENT_AUTHOR_PLAYBOOK,
181
+ greeting: "What should this agent do?",
182
+ tools: [readProjectFile, listProjectFiles, listMcpServers, updateAgentDraft],
183
+ contextAdapter: agentAuthorContextAdapter(),
184
+ };
185
+ }
186
+ export function createAgentAuthorContext(input) {
187
+ return {
188
+ kind: "agent_author",
189
+ projectRoot: input.projectRoot,
190
+ draft: { ...emptyAgentAuthorDraft(), ...structuredClone(input.draft ?? {}) },
191
+ };
192
+ }
@@ -0,0 +1 @@
1
+ export declare const AGENT_AUTHOR_PLAYBOOK = "You are the Stageflow Agent author. You draft one sealed project agent with the operator. You are not a task runner, and you do not write files.\n\n## How you talk\n- Short turns, plain words, present tense. One to four sentences.\n- Do not narrate tool names. Say what changed in the config.\n- The config panel shows the draft. The operator can edit it directly; your next patch builds on what they changed.\n\n## Tools\n- read_project_file and list_project_files are read-only. Use them when the operator points at the project, an existing agent file, or AGENTS.md.\n- list_mcp_servers names the servers in .mcp.json. Only grant MCP servers that exist there; if the operator wants one that isn't there, say so.\n- update_agent_draft applies a patch to the draft immediately. Call it as soon as you know enough. Do not wait for Keep. If it reports issues, fix them with another patch.\n- Never use bash, write, or edit. Save is the operator's button, not a tool.\n\n## The draft (agents/<id>.agent.yaml schema)\n- id (snake_case, starts with a letter; derived from name when omitted), name, description, greeting, instructions (required).\n- model: { backend: pi | claude, id?: \"provider/model\", thinking: low | medium | high }. Leave id out to use the project default unless they name one, and say so.\n- tools: { builtin: [read | write | shell] (whole groups), builtin_tools: [read | grep | find | ls | write | edit | bash] (single tools), stageflow: { groups: [...], tools: [...] }, mcp: { <server>: [tool names] or [\"*\"] }, skills: [names], browser: bool }.\n- approvals: { ask_first: [tool ids], approvers: { operators: bool, channel_ids: [] }, timeout_minutes, on_timeout: skip | deny }.\n Tool ids: builtin names (read, grep, find, ls, write, edit, bash), \"<server>.<tool>\" for MCP, \"agent:<id>\" for sub-agents. ask_first may only name granted tools.\n- sub_agents: [agent ids], observability: { capture: off | metadata | full }, limits: { session_minutes, max_turns, cost_usd | null, long_context: summarize | truncate }, guardrails: { block_patterns: [JavaScript regex] }, schedule: { cron, timezone?, message? }.\n\n## Defaults\n- A new agent saves disabled and starts sealed: read only. Write and shell stay off unless the job needs them.\n- Anything that sends, posts, writes, merges or deletes goes in approvals.ask_first.\n- Capture stays metadata unless they ask for full content.\n\n## After an edit\n- One sentence on what the agent will do now. Mention any assumption or open question. Do not paste YAML unless they ask.\n";
@@ -0,0 +1,29 @@
1
+ export const AGENT_AUTHOR_PLAYBOOK = `You are the Stageflow Agent author. You draft one sealed project agent with the operator. You are not a task runner, and you do not write files.
2
+
3
+ ## How you talk
4
+ - Short turns, plain words, present tense. One to four sentences.
5
+ - Do not narrate tool names. Say what changed in the config.
6
+ - The config panel shows the draft. The operator can edit it directly; your next patch builds on what they changed.
7
+
8
+ ## Tools
9
+ - read_project_file and list_project_files are read-only. Use them when the operator points at the project, an existing agent file, or AGENTS.md.
10
+ - list_mcp_servers names the servers in .mcp.json. Only grant MCP servers that exist there; if the operator wants one that isn't there, say so.
11
+ - update_agent_draft applies a patch to the draft immediately. Call it as soon as you know enough. Do not wait for Keep. If it reports issues, fix them with another patch.
12
+ - Never use bash, write, or edit. Save is the operator's button, not a tool.
13
+
14
+ ## The draft (agents/<id>.agent.yaml schema)
15
+ - id (snake_case, starts with a letter; derived from name when omitted), name, description, greeting, instructions (required).
16
+ - model: { backend: pi | claude, id?: "provider/model", thinking: low | medium | high }. Leave id out to use the project default unless they name one, and say so.
17
+ - tools: { builtin: [read | write | shell] (whole groups), builtin_tools: [read | grep | find | ls | write | edit | bash] (single tools), stageflow: { groups: [...], tools: [...] }, mcp: { <server>: [tool names] or ["*"] }, skills: [names], browser: bool }.
18
+ - approvals: { ask_first: [tool ids], approvers: { operators: bool, channel_ids: [] }, timeout_minutes, on_timeout: skip | deny }.
19
+ Tool ids: builtin names (read, grep, find, ls, write, edit, bash), "<server>.<tool>" for MCP, "agent:<id>" for sub-agents. ask_first may only name granted tools.
20
+ - sub_agents: [agent ids], observability: { capture: off | metadata | full }, limits: { session_minutes, max_turns, cost_usd | null, long_context: summarize | truncate }, guardrails: { block_patterns: [JavaScript regex] }, schedule: { cron, timezone?, message? }.
21
+
22
+ ## Defaults
23
+ - A new agent saves disabled and starts sealed: read only. Write and shell stay off unless the job needs them.
24
+ - Anything that sends, posts, writes, merges or deletes goes in approvals.ask_first.
25
+ - Capture stays metadata unless they ask for full content.
26
+
27
+ ## After an edit
28
+ - One sentence on what the agent will do now. Mention any assumption or open question. Do not paste YAML unless they ask.
29
+ `;
@@ -1,4 +1,5 @@
1
1
  import { parse as parseYaml } from "yaml";
2
+ import { peekDefaultModel } from "../../models/catalog.js";
2
3
  import { validateDraftPackage, } from "../../config/draftPackage.js";
3
4
  import { createFilesystemDocsRetriever, } from "../docsRetrieval.js";
4
5
  import { readDraftFromContext, workshopDraftContextAdapter, WORKSHOP_AUTHOR_GREETING, } from "../draftContext.js";
@@ -8,7 +9,6 @@ import { buildCreateStageDraft, buildCreateTaskDraft, createSaveTool, createWork
8
9
  export const WORKSHOP_AUTHOR_PROFILE_ID = "workshop-author";
9
10
  export { WORKSHOP_AUTHOR_PLAYBOOK };
10
11
  export { WORKSHOP_AUTHOR_TOOL_NAMES, WORKSHOP_FORBIDDEN_AGENT_TOOLS, buildCreateStageDraft, buildCreateTaskDraft, buildEditStageDraft, buildEditTaskDraft, buildCreatePipelineDraft, buildEditPipelineDraft, createSaveTool, createWorkshopAuthorMutatingTools, } from "./workshopAuthorTools.js";
11
- const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5";
12
12
  const REQUIRED_IO = {
13
13
  io: {
14
14
  input: { schema: { type: "object" } },
@@ -56,7 +56,7 @@ export function buildStageAddProposal(draft, userText) {
56
56
  body: {
57
57
  id: stageId,
58
58
  system_prompt: `Stage for: ${userText.trim() || "workshop draft"}`,
59
- model: DEFAULT_MODEL,
59
+ model: peekDefaultModel(),
60
60
  ...REQUIRED_IO,
61
61
  },
62
62
  },
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { peekDefaultModel } from "../../models/catalog.js";
2
3
  import { parse as parseYaml } from "yaml";
3
4
  import { CatalogPathError, resolveDefaultWriteRoot, } from "../../config/catalogRelativePath.js";
4
5
  import { defaultWorkshopPackageDirectory } from "../../config/ensureCatalogScanRoot.js";
@@ -33,7 +34,6 @@ export const WORKSHOP_AUTHOR_TOOL_NAMES = [
33
34
  "retrieve_docs",
34
35
  ];
35
36
  export const WORKSHOP_FORBIDDEN_AGENT_TOOLS = ["bash", "write", "edit"];
36
- const DEFAULT_MODEL = "anthropic/claude-sonnet-4-5";
37
37
  const REQUIRED_IO = {
38
38
  io: {
39
39
  input: { schema: { type: "object" } },
@@ -193,7 +193,7 @@ export function buildCreateStageDraft(draft, args) {
193
193
  system_prompt: stringArg(args, "system_prompt") ??
194
194
  stringArg(args, "systemPrompt") ??
195
195
  `Stage for: ${stringArg(args, "summary") ?? stageId}`,
196
- model: stringArg(args, "model") ?? DEFAULT_MODEL,
196
+ model: stringArg(args, "model") ?? peekDefaultModel(),
197
197
  ...REQUIRED_IO,
198
198
  ...(bodyFromArgs ?? {}),
199
199
  };
@@ -265,7 +265,7 @@ export function buildEditStageDraft(draft, args) {
265
265
  ...(existingArt?.body ?? {
266
266
  id: stageId,
267
267
  system_prompt: `Stage ${stageId}`,
268
- model: DEFAULT_MODEL,
268
+ model: peekDefaultModel(),
269
269
  ...REQUIRED_IO,
270
270
  }),
271
271
  ...patch,
@@ -21,6 +21,7 @@ export type UndoMutationResult = {
21
21
  notice?: string;
22
22
  };
23
23
  export declare function draftFingerprint(draft: DraftPackage): string;
24
+ export declare function operatorContextFingerprint(context: unknown): string;
24
25
  export declare function formatArtifact(value: unknown): string;
25
26
  export declare function diffDraftPackages(before: DraftPackage, after: DraftPackage): ProposalArtifactDiff[];
26
27
  export declare function affectedStageIds(before: DraftPackage, after: DraftPackage): string[];
@@ -1,8 +1,14 @@
1
+ import { isAgentAuthorContext } from "./agentAuthorContext.js";
1
2
  import { readDraftFromContext } from "./draftContext.js";
2
3
  const UNDO_CONFLICT_NOTICE = "Undo blocked: the draft changed after this mutation. Ask the agent to reverse the change instead of rejecting.";
3
4
  export function draftFingerprint(draft) {
4
5
  return JSON.stringify(draft);
5
6
  }
7
+ export function operatorContextFingerprint(context) {
8
+ if (isAgentAuthorContext(context))
9
+ return JSON.stringify(context.draft);
10
+ return draftFingerprint(readDraftFromContext(context));
11
+ }
6
12
  export function formatArtifact(value) {
7
13
  return JSON.stringify(value, null, 2);
8
14
  }
@@ -117,6 +123,21 @@ export function affectedStageIds(before, after) {
117
123
  return [...ids];
118
124
  }
119
125
  export function enrichProposal(proposal, currentContext) {
126
+ if (isAgentAuthorContext(proposal.nextContext) ||
127
+ isAgentAuthorContext(currentContext) ||
128
+ isAgentAuthorContext(proposal.baseContext)) {
129
+ const base = isAgentAuthorContext(proposal.baseContext)
130
+ ? proposal.baseContext
131
+ : currentContext;
132
+ return {
133
+ ...proposal,
134
+ baseContext: base,
135
+ baseFingerprint: proposal.baseFingerprint ?? operatorContextFingerprint(base),
136
+ appliedFingerprint: proposal.appliedFingerprint ?? operatorContextFingerprint(proposal.nextContext),
137
+ artifacts: proposal.artifacts ?? [],
138
+ affectedStageIds: proposal.affectedStageIds ?? [],
139
+ };
140
+ }
120
141
  const baseDraft = readDraftFromContext(proposal.baseContext ?? currentContext);
121
142
  const nextDraft = readDraftFromContext(proposal.nextContext);
122
143
  const baseContext = proposal.baseContext ??
@@ -132,11 +153,11 @@ export function enrichProposal(proposal, currentContext) {
132
153
  }
133
154
  /** True when the live draft no longer matches the post-apply fingerprint. */
134
155
  export function isMutationConflict(mutation, currentContext) {
135
- const current = draftFingerprint(readDraftFromContext(currentContext));
156
+ const current = operatorContextFingerprint(currentContext);
136
157
  if (mutation.appliedFingerprint) {
137
158
  return mutation.appliedFingerprint !== current;
138
159
  }
139
- const next = draftFingerprint(readDraftFromContext(mutation.nextContext));
160
+ const next = operatorContextFingerprint(mutation.nextContext);
140
161
  return next !== current;
141
162
  }
142
163
  export const UNDO_MUTATION_CONFLICT_NOTICE = UNDO_CONFLICT_NOTICE;
@@ -1,3 +1,3 @@
1
1
  export declare const PACKAGE_NAME = "stageflow";
2
- export declare const PACKAGE_VERSION = "0.35.0";
2
+ export declare const PACKAGE_VERSION = "0.39.0";
3
3
  export declare const BUILD_SHA: string;
@@ -1,3 +1,3 @@
1
1
  export const PACKAGE_NAME = "stageflow";
2
- export const PACKAGE_VERSION = "0.35.0";
2
+ export const PACKAGE_VERSION = "0.39.0";
3
3
  export const BUILD_SHA = process.env.STAGEFLOW_BUILD_SHA ?? "unknown";
@@ -0,0 +1,17 @@
1
+ import type { AnyChannelProvider } from "../channels/types.js";
2
+ import type { LoadedPipeline } from "../types/pipeline.js";
3
+ import type { PreflightItem } from "./pipelinePreflight.js";
4
+ export type ChannelCheckOptions = {
5
+ projectRoot: string;
6
+ hostEnv?: NodeJS.ProcessEnv;
7
+ providers?: readonly AnyChannelProvider[];
8
+ /** Waits between attempts. Each entry allows one more retry. */
9
+ retryDelaysMs?: readonly number[];
10
+ attemptTimeoutMs?: number;
11
+ };
12
+ export declare const DEFAULT_CHANNEL_RETRY_DELAYS_MS: readonly [500, 1500];
13
+ export declare const DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS = 8000;
14
+ /** Live check of every connection a pipeline's stages declare. Never sends a message. */
15
+ export declare function checkStageChannels(loaded: LoadedPipeline, options: ChannelCheckOptions): Promise<PreflightItem[]>;
16
+ /** One line naming each failed connection and why. Codes and plain reasons only, never a secret. */
17
+ export declare function channelFailureSummary(checks: readonly PreflightItem[]): string | undefined;
@@ -0,0 +1,106 @@
1
+ import { ChannelError } from "../channels/errors.js";
2
+ import { ChannelRegistry } from "../channels/registry.js";
3
+ import { channelErrorReason } from "../channels/errorInfo.js";
4
+ import { channelTarget } from "../channels/stageChannels.js";
5
+ export const DEFAULT_CHANNEL_RETRY_DELAYS_MS = [500, 1500];
6
+ export const DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS = 8000;
7
+ function failed(code, retryable) {
8
+ return { state: "failed", checkedAt: new Date().toISOString(), error: { code, retryable } };
9
+ }
10
+ async function attempt(run, timeoutMs) {
11
+ const controller = new AbortController();
12
+ let timer;
13
+ const timeout = new Promise(resolve => {
14
+ timer = setTimeout(() => { controller.abort(); resolve(failed("CHANNEL_TIMEOUT", true)); }, timeoutMs);
15
+ });
16
+ try {
17
+ return await Promise.race([run(controller.signal), timeout]);
18
+ }
19
+ catch (error) {
20
+ return error instanceof ChannelError
21
+ ? failed(error.code, error.retryable)
22
+ : failed("CHANNEL_CONNECTION_FAILED", false);
23
+ }
24
+ finally {
25
+ clearTimeout(timer);
26
+ }
27
+ }
28
+ /**
29
+ * Retries only failures the provider marked transient. Authentication, configuration,
30
+ * permission and target problems return at once so a bad setup is never slowed or hidden.
31
+ */
32
+ async function withRetry(run, options) {
33
+ let result = await attempt(run, options.timeoutMs);
34
+ for (const delay of options.delays) {
35
+ if (result.state === "ok" || result.error?.retryable !== true)
36
+ break;
37
+ await new Promise(resolve => setTimeout(resolve, delay));
38
+ result = await attempt(run, options.timeoutMs);
39
+ }
40
+ return result;
41
+ }
42
+ function item(connection, health) {
43
+ if (health.state === "ok")
44
+ return { kind: "channel", status: "ok", connection };
45
+ const code = health.error?.code ?? "CHANNEL_CONNECTION_FAILED";
46
+ return {
47
+ kind: "channel", status: "channel_check_failed", connection, channelCode: code,
48
+ retryable: health.error?.retryable ?? false, message: `connection "${connection}": ${code}`,
49
+ };
50
+ }
51
+ /** Live check of every connection a pipeline's stages declare. Never sends a message. */
52
+ export async function checkStageChannels(loaded, options) {
53
+ const targetsByConnection = new Map();
54
+ for (const stage of loaded.stages) {
55
+ for (const channel of stage.channels ?? []) {
56
+ const targets = targetsByConnection.get(channel.connection) ?? new Map();
57
+ targets.set(JSON.stringify(channelTarget(channel)), channelTarget(channel));
58
+ targetsByConnection.set(channel.connection, targets);
59
+ }
60
+ }
61
+ if (targetsByConnection.size === 0)
62
+ return [];
63
+ let registry;
64
+ try {
65
+ registry = new ChannelRegistry({ cwd: options.projectRoot, env: options.hostEnv, providers: options.providers });
66
+ }
67
+ catch (error) {
68
+ const code = error instanceof ChannelError ? error.code : "CHANNEL_INVALID_INPUT";
69
+ return [...targetsByConnection.keys()].map(connection => item(connection, failed(code, false)));
70
+ }
71
+ const retry = {
72
+ delays: options.retryDelaysMs ?? DEFAULT_CHANNEL_RETRY_DELAYS_MS,
73
+ timeoutMs: options.attemptTimeoutMs ?? DEFAULT_CHANNEL_ATTEMPT_TIMEOUT_MS,
74
+ };
75
+ return Promise.all([...targetsByConnection].map(async ([id, targets]) => {
76
+ let resolved;
77
+ let provider;
78
+ try {
79
+ provider = registry.providerFor(id);
80
+ resolved = registry.resolve(id);
81
+ }
82
+ catch (error) {
83
+ return item(id, error instanceof ChannelError ? failed(error.code, false) : failed("CHANNEL_CONNECTION_FAILED", false));
84
+ }
85
+ const health = await withRetry(signal => provider.check(resolved, signal), retry);
86
+ if (health.state !== "ok" || !provider.checkTargets)
87
+ return item(id, health);
88
+ const validated = [...targets.values()].map(target => provider.validateTarget(target));
89
+ if (validated.some(result => !result.ok))
90
+ return item(id, failed("CHANNEL_TARGET_INVALID", false));
91
+ const values = validated.flatMap(result => (result.ok ? [result.value] : []));
92
+ return item(id, await withRetry(signal => provider.checkTargets(resolved, values, signal), retry));
93
+ }));
94
+ }
95
+ /** One line naming each failed connection and why. Codes and plain reasons only, never a secret. */
96
+ export function channelFailureSummary(checks) {
97
+ const failed = checks.filter(check => check.kind === "channel" && check.status === "channel_check_failed");
98
+ if (failed.length === 0)
99
+ return undefined;
100
+ return failed
101
+ .map(check => {
102
+ const code = check.channelCode ?? "CHANNEL_CONNECTION_FAILED";
103
+ return `${check.connection}: ${channelErrorReason(code)} (${code})`;
104
+ })
105
+ .join("; ");
106
+ }
@@ -1,10 +1,11 @@
1
1
  import type { LoadedPipeline } from "../types/pipeline.js";
2
2
  import { type ToolRequirement } from "../config/toolRequires.js";
3
+ import { type ChannelCheckOptions } from "./channelChecks.js";
3
4
  import { type CheckToolchainOptions, type ToolchainCheckResult } from "./toolchain.js";
4
- export type PreflightCheckKind = "toolchain" | "secret" | "mcp";
5
+ export type PreflightCheckKind = "toolchain" | "secret" | "mcp" | "channel";
5
6
  export type PreflightItem = {
6
7
  kind: PreflightCheckKind;
7
- status: "ok" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "stage.unknown_secret" | "stage.denied_secret" | "unresolved_var" | "missing_catalog" | "unknown_server" | "invalid_config" | "untrusted_config_origin";
8
+ status: "ok" | "missing_tool" | "tool_version_mismatch" | "unknown_version" | "secret_unavailable" | "stage.unknown_secret" | "stage.denied_secret" | "unresolved_var" | "missing_catalog" | "unknown_server" | "invalid_config" | "untrusted_config_origin" | "channel_check_failed";
8
9
  tool?: string;
9
10
  required?: string;
10
11
  found?: string;
@@ -12,6 +13,11 @@ export type PreflightItem = {
12
13
  secret?: string;
13
14
  stageId?: string;
14
15
  server?: string;
16
+ /** Connection id for `kind: "channel"`. */
17
+ connection?: string;
18
+ /** Provider-neutral channel error code, such as CHANNEL_AUTH_FAILED. */
19
+ channelCode?: string;
20
+ retryable?: boolean;
15
21
  message?: string;
16
22
  };
17
23
  export type PipelinePreflightResult = {
@@ -33,6 +39,7 @@ export declare function runPipelinePreflight(loaded: LoadedPipeline, options: {
33
39
  strict?: boolean;
34
40
  /** start_run: unknown_version passes; missing/mismatch fail. */
35
41
  forStart?: boolean;
42
+ channels?: Pick<ChannelCheckOptions, "providers" | "retryDelaysMs" | "attemptTimeoutMs">;
36
43
  }): Promise<PipelinePreflightResult>;
37
44
  export declare function preflightFailureCode(result: PipelinePreflightResult, options?: {
38
45
  strict?: boolean;
@@ -2,6 +2,7 @@ import { collectEffectiveRequires, } from "../config/toolRequires.js";
2
2
  import { resolveStageMcpServers, StageMcpError, } from "../config/resolveStageMcpServers.js";
3
3
  import { assertSecretPresent, loadSecretRegistry, SecretUnavailableError, } from "../runtime/stageSecrets.js";
4
4
  import { isForeverDeniedSecret } from "../runtime/stageEnvironment.js";
5
+ import { checkStageChannels } from "./channelChecks.js";
5
6
  import { checkToolchainRequirements, curatedStageEnv, } from "./toolchain.js";
6
7
  function toolchainItems(checks) {
7
8
  return checks.map((c) => ({
@@ -114,6 +115,7 @@ export async function checkStageMcpResolution(loaded, options) {
114
115
  allowlist,
115
116
  env: stageEnv,
116
117
  stageId: stage.id,
118
+ scope: { kind: "stage", secrets: (stage.secrets ?? []).map((d) => d.name) },
117
119
  });
118
120
  for (const server of allowlist) {
119
121
  items.push({
@@ -165,14 +167,21 @@ export async function runPipelinePreflight(loaded, options) {
165
167
  projectRoot: options.projectRoot,
166
168
  hostEnv,
167
169
  });
170
+ const channelChecks = await checkStageChannels(loaded, {
171
+ projectRoot: options.projectRoot,
172
+ hostEnv,
173
+ ...options.channels,
174
+ });
168
175
  const checks = [
169
176
  ...toolchainItems(toolchain.checks),
170
177
  ...secretChecks,
171
178
  ...mcpChecks,
179
+ ...channelChecks,
172
180
  ];
173
181
  const ok = toolchain.ok &&
174
182
  !secretChecks.some((c) => c.status !== "ok") &&
175
- !mcpChecks.some((c) => c.status !== "ok");
183
+ !mcpChecks.some((c) => c.status !== "ok") &&
184
+ !channelChecks.some((c) => c.status !== "ok");
176
185
  return { ok, checks, requirements, toolchain };
177
186
  }
178
187
  export function preflightFailureCode(result, options = {}) {
@@ -40,3 +40,5 @@ export declare function createRunStoreWithConnection(config: RunStoreConfig): {
40
40
  */
41
41
  export declare function createRunStore(config: RunStoreConfig): RunStore;
42
42
  export type { RunStore };
43
+ /** The SQLite connection behind a RunStore, when it has one (tests may inject a fake store). */
44
+ export declare function sqliteConnectionFromStore(store: RunStore): Database.Database | undefined;
@@ -89,3 +89,11 @@ export function createRunStoreWithConnection(config) {
89
89
  export function createRunStore(config) {
90
90
  return createRunStoreWithConnection(config).store;
91
91
  }
92
+ /** The SQLite connection behind a RunStore, when it has one (tests may inject a fake store). */
93
+ export function sqliteConnectionFromStore(store) {
94
+ const connection = store.connection;
95
+ if (connection !== null && typeof connection?.prepare === "function") {
96
+ return connection;
97
+ }
98
+ return undefined;
99
+ }
@@ -0,0 +1,56 @@
1
+ import type { RunStatus } from "./port.js";
2
+ export declare const OVERVIEW_RANGES: readonly ["24h", "7d", "30d"];
3
+ export type OverviewRange = (typeof OVERVIEW_RANGES)[number];
4
+ export declare function isOverviewRange(value: string): value is OverviewRange;
5
+ export declare function overviewWindowMs(range: OverviewRange): number;
6
+ /** One run, as much as the overview needs; no stages. */
7
+ export type OverviewRunRow = {
8
+ run_id: string;
9
+ pipeline_id: string;
10
+ status: RunStatus;
11
+ created_at: string;
12
+ finished_at?: string;
13
+ cost_usd?: number;
14
+ };
15
+ export type OverviewTotals = {
16
+ runs: number;
17
+ succeeded: number;
18
+ failed: number;
19
+ cancelled: number;
20
+ in_flight: number;
21
+ /** succeeded + failed. Cancelled runs are neither a success nor a failure. */
22
+ finished: number;
23
+ success_rate: number | null;
24
+ cost_usd: number;
25
+ };
26
+ export type OverviewBucket = {
27
+ start: string;
28
+ succeeded: number;
29
+ failed: number;
30
+ cancelled: number;
31
+ };
32
+ export type OverviewPipeline = {
33
+ pipeline_id: string;
34
+ runs: number;
35
+ succeeded: number;
36
+ failed: number;
37
+ cancelled: number;
38
+ success_rate: number | null;
39
+ median_duration_ms: number | null;
40
+ cost_usd: number;
41
+ last_run_at: string;
42
+ };
43
+ export type OverviewSummary = {
44
+ range: OverviewRange;
45
+ generated_at: string;
46
+ since: string;
47
+ totals: OverviewTotals;
48
+ previous: OverviewTotals;
49
+ buckets: OverviewBucket[];
50
+ pipelines: OverviewPipeline[];
51
+ };
52
+ /**
53
+ * Rows must cover `[now - 2 * window, now]` by `created_at`; the older half feeds `previous`.
54
+ * Buckets are rolling segments ending at `now`, so they need no time zone.
55
+ */
56
+ export declare function buildOverview(rows: readonly OverviewRunRow[], range: OverviewRange, now?: Date): OverviewSummary;