stageflow 0.36.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +61 -26
  2. package/dist/agent/claudeAdapter.js +21 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.d.ts +9 -1
  6. package/dist/agent/piAdapter.js +59 -18
  7. package/dist/agent/piIsolatedMcp.js +14 -2
  8. package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
  9. package/dist/agent/piIsolatedMcpProbe.js +16 -30
  10. package/dist/agent/port.d.ts +5 -0
  11. package/dist/agent/sandboxPiTools.d.ts +99 -0
  12. package/dist/agent/sandboxPiTools.js +109 -0
  13. package/dist/agents/agentId.d.ts +8 -0
  14. package/dist/agents/agentId.js +27 -0
  15. package/dist/agents/approvalBroker.d.ts +16 -0
  16. package/dist/agents/approvalBroker.js +45 -0
  17. package/dist/agents/authorChat.d.ts +19 -0
  18. package/dist/agents/authorChat.js +63 -0
  19. package/dist/agents/chatTurn.d.ts +23 -0
  20. package/dist/agents/chatTurn.js +77 -0
  21. package/dist/agents/communications/agentmail.d.ts +19 -0
  22. package/dist/agents/communications/agentmail.js +46 -0
  23. package/dist/agents/communications/agentmailListener.d.ts +59 -0
  24. package/dist/agents/communications/agentmailListener.js +183 -0
  25. package/dist/agents/communications/allowlist.d.ts +10 -0
  26. package/dist/agents/communications/allowlist.js +36 -0
  27. package/dist/agents/communications/channelConnections.d.ts +3 -0
  28. package/dist/agents/communications/channelConnections.js +51 -0
  29. package/dist/agents/communications/delivery.d.ts +53 -0
  30. package/dist/agents/communications/delivery.js +142 -0
  31. package/dist/agents/communications/email.d.ts +26 -0
  32. package/dist/agents/communications/email.js +20 -0
  33. package/dist/agents/communications/identity.d.ts +13 -0
  34. package/dist/agents/communications/identity.js +18 -0
  35. package/dist/agents/communications/imessageListener.d.ts +41 -0
  36. package/dist/agents/communications/imessageListener.js +92 -0
  37. package/dist/agents/communications/inbound.d.ts +63 -0
  38. package/dist/agents/communications/inbound.js +143 -0
  39. package/dist/agents/communications/listener.d.ts +36 -0
  40. package/dist/agents/communications/listener.js +38 -0
  41. package/dist/agents/communications/photon.d.ts +19 -0
  42. package/dist/agents/communications/photon.js +38 -0
  43. package/dist/agents/communications/port.d.ts +27 -0
  44. package/dist/agents/communications/port.js +6 -0
  45. package/dist/agents/communications/slack.d.ts +28 -0
  46. package/dist/agents/communications/slack.js +65 -0
  47. package/dist/agents/communications/supervisor.d.ts +36 -0
  48. package/dist/agents/communications/supervisor.js +96 -0
  49. package/dist/agents/communications/telegram.d.ts +19 -0
  50. package/dist/agents/communications/telegram.js +52 -0
  51. package/dist/agents/definitionSummary.d.ts +13 -0
  52. package/dist/agents/definitionSummary.js +91 -0
  53. package/dist/agents/delegate.d.ts +3 -0
  54. package/dist/agents/delegate.js +8 -0
  55. package/dist/agents/errors.d.ts +10 -0
  56. package/dist/agents/errors.js +32 -0
  57. package/dist/agents/guardrails.d.ts +24 -0
  58. package/dist/agents/guardrails.js +39 -0
  59. package/dist/agents/historyFit.d.ts +10 -0
  60. package/dist/agents/historyFit.js +26 -0
  61. package/dist/agents/hostContext.d.ts +54 -0
  62. package/dist/agents/hostContext.js +156 -0
  63. package/dist/agents/ledgers.d.ts +29 -0
  64. package/dist/agents/ledgers.js +31 -0
  65. package/dist/agents/lineDiff.d.ts +12 -0
  66. package/dist/agents/lineDiff.js +46 -0
  67. package/dist/agents/ndjson.d.ts +9 -0
  68. package/dist/agents/ndjson.js +47 -0
  69. package/dist/agents/otlpExport.d.ts +9 -0
  70. package/dist/agents/otlpExport.js +58 -0
  71. package/dist/agents/otlpSink.d.ts +7 -0
  72. package/dist/agents/otlpSink.js +23 -0
  73. package/dist/agents/policy.d.ts +31 -0
  74. package/dist/agents/policy.js +84 -0
  75. package/dist/agents/projectRoot.d.ts +5 -0
  76. package/dist/agents/projectRoot.js +15 -0
  77. package/dist/agents/runtime/piRuntime.d.ts +52 -0
  78. package/dist/agents/runtime/piRuntime.js +474 -0
  79. package/dist/agents/runtime/types.d.ts +77 -0
  80. package/dist/agents/runtime/types.js +26 -0
  81. package/dist/agents/sandbox.d.ts +17 -0
  82. package/dist/agents/sandbox.js +10 -0
  83. package/dist/agents/scheduleSource.d.ts +32 -0
  84. package/dist/agents/scheduleSource.js +100 -0
  85. package/dist/agents/service.d.ts +298 -0
  86. package/dist/agents/service.js +1105 -0
  87. package/dist/agents/sessions.d.ts +38 -0
  88. package/dist/agents/sessions.js +321 -0
  89. package/dist/agents/store.d.ts +277 -0
  90. package/dist/agents/store.js +643 -0
  91. package/dist/agents/trace.d.ts +27 -0
  92. package/dist/agents/trace.js +29 -0
  93. package/dist/agents/turnLifecycle.d.ts +72 -0
  94. package/dist/agents/turnLifecycle.js +157 -0
  95. package/dist/agents/webChat/publicChat.d.ts +36 -0
  96. package/dist/agents/webChat/publicChat.js +290 -0
  97. package/dist/agents/webChat/rateLimit.d.ts +15 -0
  98. package/dist/agents/webChat/rateLimit.js +33 -0
  99. package/dist/agents/webChat/slug.d.ts +2 -0
  100. package/dist/agents/webChat/slug.js +9 -0
  101. package/dist/channels/providers/agentmail.js +14 -1
  102. package/dist/channels/providers/photonImessage.js +36 -0
  103. package/dist/channels/providers/slack.js +2 -1
  104. package/dist/channels/providers/telegram.js +2 -1
  105. package/dist/channels/types.d.ts +27 -0
  106. package/dist/cli/agentsCommand.d.ts +14 -0
  107. package/dist/cli/agentsCommand.js +291 -0
  108. package/dist/cli.js +9 -0
  109. package/dist/config/browseCatalog.js +4 -11
  110. package/dist/config/draftPackage.d.ts +1 -0
  111. package/dist/config/draftPackage.js +4 -0
  112. package/dist/config/hostConfig.js +9 -0
  113. package/dist/config/loadAgent.d.ts +266 -0
  114. package/dist/config/loadAgent.js +374 -0
  115. package/dist/config/loadPipeline.js +5 -2
  116. package/dist/config/loadTask.js +34 -1
  117. package/dist/config/mcpHostCatalog.d.ts +50 -0
  118. package/dist/config/mcpHostCatalog.js +405 -0
  119. package/dist/config/mcpOAuth.d.ts +37 -0
  120. package/dist/config/mcpOAuth.js +263 -0
  121. package/dist/config/mcpRedaction.d.ts +42 -0
  122. package/dist/config/mcpRedaction.js +314 -0
  123. package/dist/config/mcpToolCache.d.ts +20 -0
  124. package/dist/config/mcpToolCache.js +43 -0
  125. package/dist/config/mergePipelineIncludes.d.ts +2 -0
  126. package/dist/config/mergePipelineIncludes.js +25 -0
  127. package/dist/config/resolveStageMcpServers.d.ts +35 -3
  128. package/dist/config/resolveStageMcpServers.js +124 -9
  129. package/dist/config/validateCatalog.d.ts +1 -1
  130. package/dist/config/validateCatalog.js +22 -1
  131. package/dist/mcp/agentTools.d.ts +8 -0
  132. package/dist/mcp/agentTools.js +132 -0
  133. package/dist/mcp/catalogTools.js +17 -10
  134. package/dist/mcp/deps.d.ts +6 -0
  135. package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
  136. package/dist/mcp/pipelineAuthorSupport.js +10 -0
  137. package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
  138. package/dist/mcp/pipelineAuthorTools.js +109 -0
  139. package/dist/mcp/server.js +5 -0
  140. package/dist/mcp/tools.js +4 -0
  141. package/dist/mcp/triggerTools.js +32 -0
  142. package/dist/memory/access.d.ts +36 -0
  143. package/dist/memory/access.js +116 -0
  144. package/dist/memory/agentIssues.d.ts +3 -0
  145. package/dist/memory/agentIssues.js +23 -0
  146. package/dist/memory/bashDeny.d.ts +6 -0
  147. package/dist/memory/bashDeny.js +35 -0
  148. package/dist/memory/host.d.ts +51 -0
  149. package/dist/memory/host.js +394 -0
  150. package/dist/memory/ids.d.ts +15 -0
  151. package/dist/memory/ids.js +31 -0
  152. package/dist/memory/worker.d.ts +1 -0
  153. package/dist/memory/worker.js +84 -0
  154. package/dist/models/catalog.d.ts +113 -0
  155. package/dist/models/catalog.js +238 -0
  156. package/dist/models/response.d.ts +29 -0
  157. package/dist/models/response.js +25 -0
  158. package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
  159. package/dist/operatorAgent/agentAuthorContext.js +32 -0
  160. package/dist/operatorAgent/fakeHost.js +2 -0
  161. package/dist/operatorAgent/host.js +6 -3
  162. package/dist/operatorAgent/index.d.ts +4 -0
  163. package/dist/operatorAgent/index.js +4 -0
  164. package/dist/operatorAgent/piModel.js +2 -0
  165. package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
  166. package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
  167. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
  168. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
  169. package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
  170. package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
  171. package/dist/operatorAgent/proposals.d.ts +1 -0
  172. package/dist/operatorAgent/proposals.js +23 -2
  173. package/dist/package-meta.d.ts +1 -1
  174. package/dist/package-meta.js +1 -1
  175. package/dist/preflight/pipelinePreflight.js +1 -0
  176. package/dist/runstore/createStore.d.ts +2 -0
  177. package/dist/runstore/createStore.js +8 -0
  178. package/dist/runstore/overviewSummary.d.ts +56 -0
  179. package/dist/runstore/overviewSummary.js +134 -0
  180. package/dist/runstore/port.d.ts +3 -0
  181. package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
  182. package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
  183. package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
  184. package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
  185. package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
  186. package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
  187. package/dist/runstore/sqlite/migrations/index.js +4 -0
  188. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  189. package/dist/runtime/gitCheckoutCapability.js +1 -1
  190. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  191. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  192. package/dist/runtime/sandboxContainer.d.ts +62 -0
  193. package/dist/runtime/sandboxContainer.js +151 -0
  194. package/dist/runtime/sandboxDesk.d.ts +28 -0
  195. package/dist/runtime/sandboxDesk.js +242 -0
  196. package/dist/runtime/sandboxFiles.d.ts +20 -0
  197. package/dist/runtime/sandboxFiles.js +150 -0
  198. package/dist/runtime/stageAttemptBootstrap.js +15 -1
  199. package/dist/runtime/stageEnvironment.d.ts +2 -0
  200. package/dist/runtime/stageEnvironment.js +8 -1
  201. package/dist/runtime/stageProcessLauncher.js +23 -0
  202. package/dist/runtime/stageRoots.d.ts +9 -0
  203. package/dist/runtime/stageRoots.js +14 -0
  204. package/dist/runtime/stageRunner.d.ts +5 -0
  205. package/dist/runtime/stageRunner.js +38 -4
  206. package/dist/runtime/stageSandbox.d.ts +16 -0
  207. package/dist/runtime/stageSandbox.js +54 -0
  208. package/dist/runtime/stageSecrets.d.ts +6 -1
  209. package/dist/runtime/stageSecrets.js +34 -1
  210. package/dist/runtime/stageWorker.js +138 -105
  211. package/dist/runtime/workspaceBinding.d.ts +2 -1
  212. package/dist/runtime/workspaceBinding.js +20 -4
  213. package/dist/server/agentAuthorRoutes.d.ts +5 -0
  214. package/dist/server/agentAuthorRoutes.js +64 -0
  215. package/dist/server/agentRoutes.d.ts +5 -0
  216. package/dist/server/agentRoutes.js +323 -0
  217. package/dist/server/bootstrap.d.ts +8 -0
  218. package/dist/server/bootstrap.js +36 -10
  219. package/dist/server/controlToken.js +8 -0
  220. package/dist/server/http.d.ts +8 -1
  221. package/dist/server/http.js +172 -20
  222. package/dist/server/mcpHost.js +1 -0
  223. package/dist/server/memoryRoutes.d.ts +5 -0
  224. package/dist/server/memoryRoutes.js +146 -0
  225. package/dist/server/projectMcpRoutes.d.ts +1 -0
  226. package/dist/server/projectMcpRoutes.js +241 -3
  227. package/dist/toolkit/guard.d.ts +27 -0
  228. package/dist/toolkit/guard.js +98 -0
  229. package/dist/toolkit/index.d.ts +48 -0
  230. package/dist/toolkit/index.js +82 -0
  231. package/dist/toolkit/meta.d.ts +36 -0
  232. package/dist/toolkit/meta.js +117 -0
  233. package/dist/toolkit/profiles.d.ts +13 -0
  234. package/dist/toolkit/profiles.js +25 -0
  235. package/dist/toolkit/record.d.ts +15 -0
  236. package/dist/toolkit/record.js +17 -0
  237. package/dist/tools/memoryTools.d.ts +95 -0
  238. package/dist/tools/memoryTools.js +179 -0
  239. package/dist/types/pipeline.d.ts +3 -0
  240. package/dist/types/stage.d.ts +2 -0
  241. package/dist/types/task.d.ts +11 -1
  242. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
  243. package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
  244. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
  245. package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
  246. package/dist/ui/assets/chat-BbLukq9W.js +5 -0
  247. package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
  248. package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
  249. package/dist/ui/assets/{index-Clv4sJpU.css → main-BFo79krC.css} +1 -1
  250. package/dist/ui/assets/main-CijYMeM0.js +259 -0
  251. package/dist/ui/chat.html +25 -0
  252. package/dist/ui/index.html +6 -2
  253. package/dist/workshop/modelSettings.d.ts +2 -2
  254. package/dist/workshop/modelSettings.js +5 -11
  255. package/examples/README.md +1 -0
  256. package/examples/channels-smoke/README.md +12 -1
  257. package/examples/github-release/README.md +1 -1
  258. package/examples/memory/README.md +101 -0
  259. package/examples/memory/desk-outage.task.yaml +15 -0
  260. package/examples/memory/desk-refund.task.yaml +15 -0
  261. package/examples/memory/desk-revisit-outage.task.yaml +11 -0
  262. package/examples/memory/desk-revisit.pipeline.yaml +123 -0
  263. package/examples/memory/desk.pipeline.yaml +192 -0
  264. package/examples/memory/file-card.pipeline.yaml +39 -0
  265. package/examples/memory/file-ledger.task.yaml +22 -0
  266. package/examples/memory/file-release.task.yaml +24 -0
  267. package/examples/memory/file-rich.pipeline.yaml +40 -0
  268. package/examples/memory/file-ticket.task.yaml +7 -0
  269. package/examples/memory/file-timeline.task.yaml +28 -0
  270. package/examples/memory/find-card.pipeline.yaml +46 -0
  271. package/examples/memory/find-ticket.task.yaml +7 -0
  272. package/examples/memory/missing.pipeline.yaml +16 -0
  273. package/examples/memory/probe-bash.task.yaml +3 -0
  274. package/examples/memory/recall.pipeline.yaml +24 -0
  275. package/examples/memory/recall.task.yaml +5 -0
  276. package/examples/memory/remember-ada.task.yaml +6 -0
  277. package/examples/memory/remember-grace.task.yaml +6 -0
  278. package/examples/memory/remember.pipeline.yaml +58 -0
  279. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  280. package/examples/memory/stageflow.yaml +12 -0
  281. package/examples/memory/update-card.pipeline.yaml +43 -0
  282. package/examples/memory/update-ledger.task.yaml +23 -0
  283. package/examples/memory/update-rich.pipeline.yaml +44 -0
  284. package/examples/memory/update-ticket.task.yaml +7 -0
  285. package/examples/ship-feature/README.md +15 -0
  286. package/examples/stage-mcp/README.md +1 -1
  287. package/package.json +2 -1
  288. package/dist/ui/assets/index-BOhqnPNg.js +0 -260
@@ -0,0 +1,29 @@
1
+ import { type AgentDelivery, type AgentStore } from "./store.js";
2
+ export type DeliveryInput = {
3
+ turnId: string;
4
+ sessionId: string;
5
+ connectionId: string;
6
+ provider: string;
7
+ replyTo: string;
8
+ text: string;
9
+ };
10
+ export type DeliveryClaim = "first" | "retry" | "manual";
11
+ /** What reply delivery needs of storage, and nothing else. */
12
+ export type DeliveryLedger = {
13
+ create(input: DeliveryInput): AgentDelivery;
14
+ get(turnId: string): AgentDelivery | undefined;
15
+ patch(turnId: string, patch: Parameters<AgentStore["updateDelivery"]>[1]): AgentDelivery | undefined;
16
+ due(): AgentDelivery[];
17
+ /**
18
+ * Take the right to send, atomically: moves the delivery to `sending` only if it is still in a state this kind of
19
+ * attempt may start from. Returns the claimed row, or undefined when someone else holds it or it already went out.
20
+ */
21
+ claim(turnId: string, kind: DeliveryClaim): AgentDelivery | undefined;
22
+ };
23
+ export declare function deliveryLedger(store: AgentStore, nowIso: () => string): DeliveryLedger;
24
+ /** What the scheduler needs of storage: when each scheduled agent runs next. */
25
+ export type ScheduleLedger = {
26
+ nextRun(projectRoot: string, agentId: string): string | undefined;
27
+ setNextRun(projectRoot: string, agentId: string, nextRunAt: string): void;
28
+ };
29
+ export declare function scheduleLedger(store: AgentStore): ScheduleLedger;
@@ -0,0 +1,31 @@
1
+ import { agentProjectKey } from "./store.js";
2
+ export function deliveryLedger(store, nowIso) {
3
+ return {
4
+ create(input) {
5
+ const now = nowIso();
6
+ const delivery = { ...input, state: "pending", attempts: 0, errorCode: null, errorMessage: null, retryable: false, nextAttemptAt: null, createdAt: now, updatedAt: now };
7
+ store.insertDelivery(delivery);
8
+ return delivery;
9
+ },
10
+ get: (turnId) => store.getDelivery(turnId),
11
+ patch(turnId, patch) {
12
+ store.updateDelivery(turnId, patch, nowIso());
13
+ return store.getDelivery(turnId);
14
+ },
15
+ due: () => store.dueDeliveries(nowIso()),
16
+ claim(turnId, kind) {
17
+ const now = nowIso();
18
+ return kind === "first"
19
+ ? store.claimDelivery(turnId, ["pending"], now)
20
+ : kind === "manual"
21
+ ? store.claimDelivery(turnId, ["failed", "unknown"], now)
22
+ : store.claimDelivery(turnId, ["failed"], now, { dueRetry: true });
23
+ },
24
+ };
25
+ }
26
+ export function scheduleLedger(store) {
27
+ return {
28
+ nextRun: (projectRoot, agentId) => store.getScheduleNextRun(agentProjectKey(projectRoot), agentId),
29
+ setNextRun: (projectRoot, agentId, nextRunAt) => store.setScheduleNextRun(agentProjectKey(projectRoot), agentId, nextRunAt),
30
+ };
31
+ }
@@ -0,0 +1,12 @@
1
+ export type DiffLine = {
2
+ kind: "same" | "added" | "removed";
3
+ text: string;
4
+ before?: number;
5
+ after?: number;
6
+ };
7
+ /**
8
+ * Line diff by longest common subsequence. Agent YAML files are small (tens to
9
+ * low hundreds of lines), so the O(n·m) table is fine and keeps this
10
+ * dependency-free.
11
+ */
12
+ export declare function diffLines(before: string, after: string): DiffLine[];
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Line diff by longest common subsequence. Agent YAML files are small (tens to
3
+ * low hundreds of lines), so the O(n·m) table is fine and keeps this
4
+ * dependency-free.
5
+ */
6
+ export function diffLines(before, after) {
7
+ const a = splitLines(before);
8
+ const b = splitLines(after);
9
+ const lcs = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
10
+ for (let i = a.length - 1; i >= 0; i -= 1) {
11
+ for (let j = b.length - 1; j >= 0; j -= 1) {
12
+ lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
13
+ }
14
+ }
15
+ const out = [];
16
+ let i = 0;
17
+ let j = 0;
18
+ while (i < a.length && j < b.length) {
19
+ if (a[i] === b[j]) {
20
+ out.push({ kind: "same", text: a[i], before: i + 1, after: j + 1 });
21
+ i += 1;
22
+ j += 1;
23
+ }
24
+ else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
25
+ out.push({ kind: "removed", text: a[i], before: i + 1 });
26
+ i += 1;
27
+ }
28
+ else {
29
+ out.push({ kind: "added", text: b[j], after: j + 1 });
30
+ j += 1;
31
+ }
32
+ }
33
+ for (; i < a.length; i += 1)
34
+ out.push({ kind: "removed", text: a[i], before: i + 1 });
35
+ for (; j < b.length; j += 1)
36
+ out.push({ kind: "added", text: b[j], after: j + 1 });
37
+ return out;
38
+ }
39
+ function splitLines(text) {
40
+ if (text === "")
41
+ return [];
42
+ const lines = text.split("\n");
43
+ if (lines.at(-1) === "")
44
+ lines.pop();
45
+ return lines;
46
+ }
@@ -0,0 +1,9 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ /** Write one NDJSON frame, waiting for drain. (The request is already consumed here, so only the response state matters.) */
3
+ export declare function writeFrame(res: ServerResponse, frame: unknown): Promise<void>;
4
+ export declare class BodyError extends Error {
5
+ readonly reason: "too_large" | "not_json" | "not_object";
6
+ constructor(reason: "too_large" | "not_json" | "not_object");
7
+ }
8
+ /** Read a JSON object body. An empty body is `{}`. Throws BodyError so each surface can answer in its own words. */
9
+ export declare function readJsonBody(req: IncomingMessage, maxBytes: number): Promise<Record<string, unknown>>;
@@ -0,0 +1,47 @@
1
+ /** Write one NDJSON frame, waiting for drain. (The request is already consumed here, so only the response state matters.) */
2
+ export async function writeFrame(res, frame) {
3
+ if (res.writableEnded || res.destroyed)
4
+ return;
5
+ if (res.write(`${JSON.stringify(frame)}\n`))
6
+ return;
7
+ await new Promise((resolve) => {
8
+ const done = () => {
9
+ res.off("drain", done);
10
+ res.off("close", done);
11
+ resolve();
12
+ };
13
+ res.once("drain", done);
14
+ res.once("close", done);
15
+ });
16
+ }
17
+ export class BodyError extends Error {
18
+ reason;
19
+ constructor(reason) {
20
+ super(reason);
21
+ this.reason = reason;
22
+ }
23
+ }
24
+ /** Read a JSON object body. An empty body is `{}`. Throws BodyError so each surface can answer in its own words. */
25
+ export async function readJsonBody(req, maxBytes) {
26
+ const chunks = [];
27
+ let size = 0;
28
+ for await (const chunk of req) {
29
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
30
+ size += buffer.length;
31
+ if (size > maxBytes)
32
+ throw new BodyError("too_large");
33
+ chunks.push(buffer);
34
+ }
35
+ if (chunks.length === 0)
36
+ return {};
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
40
+ }
41
+ catch {
42
+ throw new BodyError("not_json");
43
+ }
44
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
45
+ throw new BodyError("not_object");
46
+ return parsed;
47
+ }
@@ -0,0 +1,9 @@
1
+ import type { AgentSpan, AgentTurn } from "./store.js";
2
+ /** OTLP/JSON `ExportTraceServiceRequest` for the given turns. */
3
+ export declare function exportTurnsToOtlp(turns: Array<{
4
+ turn: AgentTurn;
5
+ spans: AgentSpan[];
6
+ }>, resource: {
7
+ serviceName: string;
8
+ agentId: string;
9
+ }): Record<string, unknown>;
@@ -0,0 +1,58 @@
1
+ import { redactSecrets } from "../agent/streamLogRedact.js";
2
+ function attribute(key, value) {
3
+ if (typeof value === "string")
4
+ return { key, value: { stringValue: value } };
5
+ if (typeof value === "boolean")
6
+ return { key, value: { boolValue: value } };
7
+ if (typeof value === "number") {
8
+ return Number.isInteger(value) ? { key, value: { intValue: String(value) } } : { key, value: { doubleValue: value } };
9
+ }
10
+ return undefined;
11
+ }
12
+ function unixNanos(iso) {
13
+ return `${BigInt(Date.parse(iso)) * 1000000n}`;
14
+ }
15
+ const STATUS_CODE = { ok: 1, error: 2, cancelled: 2 };
16
+ function spanAttributes(span) {
17
+ const attrs = [
18
+ attribute("gen_ai.operation.name", span.name),
19
+ attribute("stageflow.span.kind", span.kind),
20
+ span.inputTokens !== null ? attribute("gen_ai.usage.input_tokens", span.inputTokens) : undefined,
21
+ span.outputTokens !== null ? attribute("gen_ai.usage.output_tokens", span.outputTokens) : undefined,
22
+ ...Object.entries(span.attributes).map(([key, value]) => attribute(key, value)),
23
+ // Bodies were redacted when stored; redact again so a future store path can't leak through export.
24
+ span.inputText !== null ? attribute("gen_ai.input.messages", redactSecrets(span.inputText)) : undefined,
25
+ span.outputText !== null ? attribute("gen_ai.output.messages", redactSecrets(span.outputText)) : undefined,
26
+ ];
27
+ return attrs.filter((item) => item !== undefined);
28
+ }
29
+ /** OTLP/JSON `ExportTraceServiceRequest` for the given turns. */
30
+ export function exportTurnsToOtlp(turns, resource) {
31
+ return {
32
+ resourceSpans: [
33
+ {
34
+ resource: {
35
+ attributes: [
36
+ attribute("service.name", resource.serviceName),
37
+ attribute("gen_ai.agent.name", resource.agentId),
38
+ ].filter(Boolean),
39
+ },
40
+ scopeSpans: [
41
+ {
42
+ scope: { name: "stageflow.agents" },
43
+ spans: turns.flatMap(({ turn, spans }) => spans.map((span) => ({
44
+ traceId: turn.traceId,
45
+ spanId: span.id,
46
+ ...(span.parentSpanId ? { parentSpanId: span.parentSpanId } : {}),
47
+ name: span.name,
48
+ startTimeUnixNano: unixNanos(span.startedAt),
49
+ endTimeUnixNano: unixNanos(span.endedAt ?? span.startedAt),
50
+ status: { code: STATUS_CODE[span.status] ?? 0 },
51
+ attributes: spanAttributes(span),
52
+ }))),
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ };
58
+ }
@@ -0,0 +1,7 @@
1
+ export type OtlpSink = (payload: Record<string, unknown>) => Promise<void>;
2
+ /**
3
+ * Collector from the standard OTel environment (`OTEL_EXPORTER_OTLP_ENDPOINT` or
4
+ * `..._TRACES_ENDPOINT`, plus `OTEL_EXPORTER_OTLP_HEADERS`). Undefined when none
5
+ * is configured, so `observability.otlp` is a no-op without a destination.
6
+ */
7
+ export declare function otlpSinkFromEnv(env?: NodeJS.ProcessEnv, fetchImpl?: typeof fetch): OtlpSink | undefined;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Collector from the standard OTel environment (`OTEL_EXPORTER_OTLP_ENDPOINT` or
3
+ * `..._TRACES_ENDPOINT`, plus `OTEL_EXPORTER_OTLP_HEADERS`). Undefined when none
4
+ * is configured, so `observability.otlp` is a no-op without a destination.
5
+ */
6
+ export function otlpSinkFromEnv(env = process.env, fetchImpl = fetch) {
7
+ const traces = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim();
8
+ const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
9
+ const url = traces || (base ? `${base.replace(/\/$/, "")}/v1/traces` : undefined);
10
+ if (!url)
11
+ return undefined;
12
+ const headers = { "content-type": "application/json" };
13
+ for (const pair of (env.OTEL_EXPORTER_OTLP_HEADERS ?? "").split(",")) {
14
+ const eq = pair.indexOf("=");
15
+ if (eq > 0)
16
+ headers[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
17
+ }
18
+ return async (payload) => {
19
+ const res = await fetchImpl(url, { method: "POST", headers, body: JSON.stringify(payload), signal: AbortSignal.timeout(10_000) });
20
+ if (!res.ok)
21
+ throw new Error(`OTLP collector responded ${res.status}`);
22
+ };
23
+ }
@@ -0,0 +1,31 @@
1
+ import { type AgentDefinition } from "../config/loadAgent.js";
2
+ import type { ApprovalActor } from "./store.js";
3
+ export type { ApprovalActor };
4
+ type Grants = Pick<AgentDefinition, "tools" | "sub_agents">;
5
+ /** True when the revision grants this tool id (builtin name, `<server>.<tool>`, or `agent:<id>`). */
6
+ export declare function isToolGranted(definition: Grants, tool: string): boolean;
7
+ /** Tools that always need a human decision, whatever the agent's `ask_first` list says. */
8
+ export declare const ALWAYS_ASK_FIRST: readonly ["bash"];
9
+ /** The tools this agent asks about: its `ask_first` list plus any granted tool that always needs a person. */
10
+ export declare function effectiveAskFirst(definition: Grants & Pick<AgentDefinition, "approvals">): string[];
11
+ export declare function requiresAskFirst(definition: Pick<AgentDefinition, "approvals">, tool: string): boolean;
12
+ export declare function canApprove(definition: Pick<AgentDefinition, "approvals">, actor: ApprovalActor): boolean;
13
+ /**
14
+ * Agent definitions and Stageflow state are never writable by an agent through a tool that names the path, whatever its
15
+ * grants. This does not cover `bash` (see `forbiddenWriteTarget`).
16
+ */
17
+ export declare function isWritePathForbidden(projectRoot: string, stageflowHome: string, targetPath: string): boolean;
18
+ /** Every path-like string the runtime can see in a tool call's arguments (including `paths: [...]`). */
19
+ export declare function pathArgumentsOf(input: Record<string, unknown>): string[];
20
+ /**
21
+ * The first forbidden path a tool call would write, or undefined. Applies to every tool that can write a path it is
22
+ * given: `write`, `edit`, and any other tool (an MCP file tool, say) with a path argument. Only Pi's read-only builtins
23
+ * are exempt. This is a check on arguments, not a sandbox: it cannot see what a tool does with them.
24
+ *
25
+ * `bash` is NOT path-confined. The runtime sees a command string, not its effects, so a granted `bash` can modify agent
26
+ * definitions and Stageflow state however this check is configured. Because of that every `bash` call is ask-first
27
+ * (ALWAYS_ASK_FIRST) and a person reads the command before it runs. Do not grant `bash` to an agent you need confined;
28
+ * the editor warns when it is granted (agent.shell_not_path_confined).
29
+ */
30
+ export declare function forbiddenWriteTarget(projectRoot: string, stageflowHome: string, toolName: string, input: Record<string, unknown>): string | undefined;
31
+ export declare function trustedLocalNotice(): string;
@@ -0,0 +1,84 @@
1
+ import path from "node:path";
2
+ import { grantedToolIds } from "../config/loadAgent.js";
3
+ /** True when the revision grants this tool id (builtin name, `<server>.<tool>`, or `agent:<id>`). */
4
+ export function isToolGranted(definition, tool) {
5
+ if (grantedToolIds(definition).has(tool))
6
+ return true;
7
+ const dot = tool.indexOf(".");
8
+ if (dot <= 0)
9
+ return false;
10
+ return definition.tools.mcp[tool.slice(0, dot)]?.includes("*") ?? false;
11
+ }
12
+ /** Tools that always need a human decision, whatever the agent's `ask_first` list says. */
13
+ export const ALWAYS_ASK_FIRST = ["bash"];
14
+ /** The tools this agent asks about: its `ask_first` list plus any granted tool that always needs a person. */
15
+ export function effectiveAskFirst(definition) {
16
+ const forced = ALWAYS_ASK_FIRST.filter((tool) => isToolGranted(definition, tool));
17
+ return [...new Set([...definition.approvals.ask_first, ...forced])];
18
+ }
19
+ export function requiresAskFirst(definition, tool) {
20
+ return ALWAYS_ASK_FIRST.includes(tool) || definition.approvals.ask_first.includes(tool);
21
+ }
22
+ export function canApprove(definition, actor) {
23
+ const { approvers } = definition.approvals;
24
+ if (actor.kind === "operator")
25
+ return approvers.operators;
26
+ return approvers.channel_ids.includes(actor.channelId);
27
+ }
28
+ /**
29
+ * Agent definitions and Stageflow state are never writable by an agent through a tool that names the path, whatever its
30
+ * grants. This does not cover `bash` (see `forbiddenWriteTarget`).
31
+ */
32
+ export function isWritePathForbidden(projectRoot, stageflowHome, targetPath) {
33
+ const resolved = path.resolve(targetPath);
34
+ const within = (dir) => resolved === dir || resolved.startsWith(`${dir}${path.sep}`);
35
+ const root = path.resolve(projectRoot);
36
+ // The project may live under the Stageflow home (container workspace); only outside it is the home off limits.
37
+ if (!within(root) && within(path.resolve(stageflowHome)))
38
+ return true;
39
+ const relative = path.relative(path.join(root, "agents"), resolved);
40
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
41
+ return relative.endsWith(".agent.yaml");
42
+ }
43
+ return false;
44
+ }
45
+ const READ_ONLY_BUILTINS = new Set(["read", "grep", "find", "ls"]);
46
+ /** Argument names that carry a filesystem path in Pi's builtin tools and in common MCP file tools. */
47
+ const PATH_ARGUMENTS = ["path", "file_path", "filePath", "file", "target", "destination", "dest", "output_path"];
48
+ /** Every path-like string the runtime can see in a tool call's arguments (including `paths: [...]`). */
49
+ export function pathArgumentsOf(input) {
50
+ const found = [];
51
+ for (const key of PATH_ARGUMENTS) {
52
+ const value = input[key];
53
+ if (typeof value === "string" && value.length > 0)
54
+ found.push(value);
55
+ }
56
+ if (Array.isArray(input.paths))
57
+ for (const value of input.paths)
58
+ if (typeof value === "string" && value.length > 0)
59
+ found.push(value);
60
+ return found;
61
+ }
62
+ /**
63
+ * The first forbidden path a tool call would write, or undefined. Applies to every tool that can write a path it is
64
+ * given: `write`, `edit`, and any other tool (an MCP file tool, say) with a path argument. Only Pi's read-only builtins
65
+ * are exempt. This is a check on arguments, not a sandbox: it cannot see what a tool does with them.
66
+ *
67
+ * `bash` is NOT path-confined. The runtime sees a command string, not its effects, so a granted `bash` can modify agent
68
+ * definitions and Stageflow state however this check is configured. Because of that every `bash` call is ask-first
69
+ * (ALWAYS_ASK_FIRST) and a person reads the command before it runs. Do not grant `bash` to an agent you need confined;
70
+ * the editor warns when it is granted (agent.shell_not_path_confined).
71
+ */
72
+ export function forbiddenWriteTarget(projectRoot, stageflowHome, toolName, input) {
73
+ if (READ_ONLY_BUILTINS.has(toolName))
74
+ return undefined;
75
+ for (const target of pathArgumentsOf(input)) {
76
+ const resolved = path.isAbsolute(target) ? target : path.resolve(projectRoot, target);
77
+ if (isWritePathForbidden(projectRoot, stageflowHome, resolved))
78
+ return target;
79
+ }
80
+ return undefined;
81
+ }
82
+ export function trustedLocalNotice() {
83
+ return "This agent runs in trusted-local mode on the host filesystem. Use sandbox execution for isolation.";
84
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Where agents live when no project is named: the Host's own project, or the workspace catalog
3
+ * when that directory is not writable (a container runs from a read-only install dir).
4
+ */
5
+ export declare function defaultAgentProjectRoot(cwd: string): string;
@@ -0,0 +1,15 @@
1
+ import { accessSync, constants } from "node:fs";
2
+ import { workspaceCatalogPath } from "../project/workspaceCatalog.js";
3
+ /**
4
+ * Where agents live when no project is named: the Host's own project, or the workspace catalog
5
+ * when that directory is not writable (a container runs from a read-only install dir).
6
+ */
7
+ export function defaultAgentProjectRoot(cwd) {
8
+ try {
9
+ accessSync(cwd, constants.W_OK);
10
+ return cwd;
11
+ }
12
+ catch {
13
+ return workspaceCatalogPath();
14
+ }
15
+ }
@@ -0,0 +1,52 @@
1
+ import { type AgentSession } from "@earendil-works/pi-coding-agent";
2
+ import { type AgentDefinition } from "../../config/loadAgent.js";
3
+ import { type StageMemory } from "../../memory/access.js";
4
+ import { type SandboxRunner } from "../sandbox.js";
5
+ import { type ToolkitTool } from "../../toolkit/index.js";
6
+ import { type ConversationBackend, type TurnHooks } from "./types.js";
7
+ export type PiRuntimeOptions = {
8
+ cwd: string;
9
+ agentDir: string;
10
+ sandbox?: SandboxRunner;
11
+ /** Stageflow's own tools, built lazily because they need Host services that start after the runtime. */
12
+ toolkit?: () => ToolkitTool[];
13
+ /**
14
+ * How long a session may sit unused before its Pi session and isolated MCP processes are released. A later message
15
+ * reopens it from the stored transcript. Default 30 minutes; 0 keeps sessions until released or disposed.
16
+ */
17
+ idleReleaseMs?: number;
18
+ /** Test seam: replaces session construction. */
19
+ openSession?: (input: OpenSessionInput) => Promise<OpenedSession>;
20
+ };
21
+ export type OpenSessionInput = {
22
+ projectRoot: string;
23
+ definition: AgentDefinition;
24
+ systemPrompt: string;
25
+ /** Pi tool names the session may expose. */
26
+ toolNames: string[];
27
+ /** Runs before each tool call; the session wires it to `pi.on("tool_call")`. */
28
+ gate: (call: {
29
+ toolCallId: string;
30
+ toolName: string;
31
+ input: Record<string, unknown>;
32
+ }) => Promise<{
33
+ block: true;
34
+ reason: string;
35
+ terminate?: boolean;
36
+ } | undefined>;
37
+ delegateTool?: (agentId: string, message: string) => Promise<string>;
38
+ /** Stageflow tools this agent is granted. */
39
+ stageflowTools?: ToolkitTool[];
40
+ memory?: StageMemory;
41
+ hooks: () => TurnHooks | undefined;
42
+ };
43
+ export type OpenedSession = {
44
+ session: Pick<AgentSession, "prompt" | "subscribe" | "dispose"> & {
45
+ abort?: AgentSession["abort"];
46
+ };
47
+ /** Tool names that exist on the session (builtins + MCP + delegation) for grant mapping. */
48
+ toolNames: string[];
49
+ shutdown(): Promise<void>;
50
+ };
51
+ export declare const DEFAULT_IDLE_RELEASE_MS: number;
52
+ export declare function createPiAgentRuntime(options: PiRuntimeOptions): ConversationBackend;