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
@@ -1,30 +1,11 @@
1
1
  import { StageMcpError, resolveStageMcpServers, } from "../config/resolveStageMcpServers.js";
2
+ import { collectSecretValues, scrubText } from "../config/mcpRedaction.js";
3
+ import { listCachedMcpTools } from "../config/mcpToolCache.js";
2
4
  import * as piIsolatedMcp from "./piIsolatedMcp.js";
3
5
  export const PROJECT_MCP_PROBE_TIMEOUT_MS = 30_000;
4
6
  const MCP_STATUS_EVENT = "pi-mcp-adapter/status/v1";
5
7
  function scrub(message, secrets) {
6
- let out = message;
7
- for (const secret of secrets) {
8
- if (secret.length === 0)
9
- continue;
10
- out = out.split(secret).join("[redacted]");
11
- }
12
- return out;
13
- }
14
- function collectInterpolatedSecrets(entry) {
15
- const secrets = [];
16
- for (const field of ["env", "headers"]) {
17
- const value = entry[field];
18
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
19
- continue;
20
- }
21
- for (const item of Object.values(value)) {
22
- if (typeof item === "string" && item.length > 0) {
23
- secrets.push(item);
24
- }
25
- }
26
- }
27
- return secrets;
8
+ return scrubText(message, secrets);
28
9
  }
29
10
  function catalogErrorResult(name, err) {
30
11
  if (!(err instanceof StageMcpError))
@@ -35,6 +16,9 @@ function catalogErrorResult(name, err) {
35
16
  if (err.code === "missing_catalog") {
36
17
  return { name, status: "missing_catalog" };
37
18
  }
19
+ if (err.code === "host_state_corrupt") {
20
+ return { name, status: "invalid_config", error: err.message };
21
+ }
38
22
  if (err.code === "invalid_config" ||
39
23
  err.code === "reserved_name" ||
40
24
  err.code === "unknown_server") {
@@ -136,7 +120,7 @@ function instantiateProbeFactories(attached, projectRoot) {
136
120
  probeApi.emitSessionStart(projectRoot);
137
121
  return () => probeApi.emitSessionShutdown();
138
122
  }
139
- function mapConnectingError(err, aborted) {
123
+ function mapConnectingError(err, aborted, secrets = []) {
140
124
  const message = err instanceof Error ? err.message : String(err);
141
125
  if (aborted || /cancelled/i.test(message)) {
142
126
  return { kind: "cancelled" };
@@ -147,7 +131,7 @@ function mapConnectingError(err, aborted) {
147
131
  return {
148
132
  kind: "mapped",
149
133
  status: "connect_failed",
150
- error: message.length > 0 ? message : undefined,
134
+ error: message.length > 0 ? scrubText(message, secrets) : undefined,
151
135
  };
152
136
  }
153
137
  async function waitForProbeStatus(eventBus, serverName, connecting, options) {
@@ -193,7 +177,7 @@ async function waitForProbeStatus(eventBus, serverName, connecting, options) {
193
177
  const maybeUnsub = eventBus.on(MCP_STATUS_EVENT, handler);
194
178
  unsubscribe = typeof maybeUnsub === "function" ? maybeUnsub : undefined;
195
179
  options.signal?.addEventListener("abort", onAbort, { once: true });
196
- void (connecting ?? Promise.resolve()).then(() => finish({ kind: "mapped", status: "connected" }), (err) => finish(mapConnectingError(err, options.signal?.aborted === true)));
180
+ void (connecting ?? Promise.resolve()).then(() => finish({ kind: "mapped", status: "connected" }), (err) => finish(mapConnectingError(err, options.signal?.aborted === true, options.secrets)));
197
181
  timer = setTimeout(() => {
198
182
  finish({
199
183
  kind: "mapped",
@@ -206,7 +190,6 @@ async function waitForProbeStatus(eventBus, serverName, connecting, options) {
206
190
  export async function probeProjectMcpServer(options) {
207
191
  const name = options.name;
208
192
  const timeoutMs = options.timeoutMs ?? PROJECT_MCP_PROBE_TIMEOUT_MS;
209
- const env = options.env ?? process.env;
210
193
  if (options.signal?.aborted) {
211
194
  return { name, status: "cancelled" };
212
195
  }
@@ -215,7 +198,9 @@ export async function probeProjectMcpServer(options) {
215
198
  snapshot = await resolveStageMcpServers({
216
199
  projectRoot: options.projectRoot,
217
200
  allowlist: [name],
218
- env,
201
+ env: {},
202
+ hostEnv: options.hostEnv ?? process.env,
203
+ scope: { kind: "probe" },
219
204
  });
220
205
  }
221
206
  catch (err) {
@@ -228,7 +213,7 @@ export async function probeProjectMcpServer(options) {
228
213
  if (resolved === undefined) {
229
214
  return { name, status: "invalid_config" };
230
215
  }
231
- const secrets = collectInterpolatedSecrets(resolved);
216
+ const secrets = collectSecretValues(resolved);
232
217
  let attached;
233
218
  let shutdownFactories;
234
219
  const dispose = async () => {
@@ -254,7 +239,7 @@ export async function probeProjectMcpServer(options) {
254
239
  return { name, status: "invalid_config" };
255
240
  }
256
241
  shutdownFactories = instantiateProbeFactories(attached, options.projectRoot);
257
- const waited = await waitForProbeStatus(attached.eventBus, name, attached.connecting, { timeoutMs, signal: options.signal });
242
+ const waited = await waitForProbeStatus(attached.eventBus, name, attached.connecting, { timeoutMs, signal: options.signal, secrets });
258
243
  if (waited.kind === "cancelled") {
259
244
  return { name, status: "cancelled" };
260
245
  }
@@ -267,7 +252,8 @@ export async function probeProjectMcpServer(options) {
267
252
  : {}),
268
253
  };
269
254
  }
270
- return { name, status: waited.status };
255
+ const tools = waited.status === "connected" ? listCachedMcpTools(name)?.tools : undefined;
256
+ return { name, status: waited.status, ...(tools ? { tools } : {}) };
271
257
  }
272
258
  catch (err) {
273
259
  if (options.signal?.aborted) {
@@ -9,6 +9,7 @@ import type { ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
9
9
  import type { StageRoots } from "../runtime/stageRoots.js";
10
10
  import type { StageEmail } from "../email/host.js";
11
11
  import type { StageChannels } from "../channels/host.js";
12
+ import type { StageMemory } from "../memory/access.js";
12
13
  import type { QaExchange } from "../hitl/qaTrail.js";
13
14
  import type { StageUsage } from "../types/usage.js";
14
15
  /** Opaque to runtime; adapters interpret. */
@@ -67,6 +68,10 @@ export type StageRunInput = {
67
68
  skillFilePath?: string;
68
69
  email?: StageEmail;
69
70
  channels?: StageChannels;
71
+ /** Memories attached to this stage. The host reads and replaces the page. */
72
+ memory?: StageMemory;
73
+ /** Refreshed memory prompt; preferred over deriving from `memory` at prompt build time. */
74
+ memoryPrompt?: string;
70
75
  /** Bundled browser skill; set by the runtime when the stage has `browser`. */
71
76
  browserSkillFilePath?: string;
72
77
  forkEmitContext?: ForkEmitContext;
@@ -0,0 +1,99 @@
1
+ import { Type } from "typebox";
2
+ import { type SandboxFileTarget } from "../runtime/sandboxFiles.js";
3
+ export type SandboxPiToolOptions = SandboxFileTarget;
4
+ export declare function createSandboxReadTool(options: SandboxPiToolOptions): {
5
+ name: string;
6
+ label: string;
7
+ description: string;
8
+ promptSnippet: string;
9
+ promptGuidelines: string[];
10
+ parameters: Type.TObject<{
11
+ path: Type.TString;
12
+ offset: Type.TOptional<Type.TNumber>;
13
+ limit: Type.TOptional<Type.TNumber>;
14
+ }>;
15
+ execute: (_toolCallId: string, params: {
16
+ path: string;
17
+ offset?: number;
18
+ limit?: number;
19
+ }) => Promise<{
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ details: undefined;
25
+ isError?: boolean | undefined;
26
+ }>;
27
+ };
28
+ export declare function createSandboxWriteTool(options: SandboxPiToolOptions): {
29
+ name: string;
30
+ label: string;
31
+ description: string;
32
+ promptSnippet: string;
33
+ promptGuidelines: string[];
34
+ parameters: Type.TObject<{
35
+ path: Type.TString;
36
+ content: Type.TString;
37
+ }>;
38
+ execute: (_toolCallId: string, params: {
39
+ path: string;
40
+ content: string;
41
+ }) => Promise<{
42
+ content: {
43
+ type: "text";
44
+ text: string;
45
+ }[];
46
+ details: undefined;
47
+ isError?: boolean | undefined;
48
+ }>;
49
+ };
50
+ export declare function createSandboxEditTool(options: SandboxPiToolOptions): {
51
+ name: string;
52
+ label: string;
53
+ description: string;
54
+ promptSnippet: string;
55
+ promptGuidelines: string[];
56
+ parameters: Type.TObject<{
57
+ path: Type.TString;
58
+ edits: Type.TArray<Type.TObject<{
59
+ oldText: Type.TString;
60
+ newText: Type.TString;
61
+ }>>;
62
+ }>;
63
+ execute: (_toolCallId: string, params: {
64
+ path: string;
65
+ edits: Array<{
66
+ oldText: string;
67
+ newText: string;
68
+ }>;
69
+ }) => Promise<{
70
+ content: {
71
+ type: "text";
72
+ text: string;
73
+ }[];
74
+ details: undefined;
75
+ isError?: boolean | undefined;
76
+ }>;
77
+ };
78
+ export declare function createSandboxBashTool(options: SandboxPiToolOptions): {
79
+ name: string;
80
+ label: string;
81
+ description: string;
82
+ promptSnippet: string;
83
+ promptGuidelines: string[];
84
+ parameters: Type.TObject<{
85
+ command: Type.TString;
86
+ timeout: Type.TOptional<Type.TNumber>;
87
+ }>;
88
+ execute: (_toolCallId: string, params: {
89
+ command: string;
90
+ timeout?: number;
91
+ }) => Promise<{
92
+ content: {
93
+ type: "text";
94
+ text: string;
95
+ }[];
96
+ details: undefined;
97
+ isError?: boolean | undefined;
98
+ }>;
99
+ };
@@ -0,0 +1,109 @@
1
+ import { Type } from "typebox";
2
+ import { editSandboxFile, readSandboxFile, runSandboxBash, sliceSandboxLines, writeSandboxFile, } from "../runtime/sandboxFiles.js";
3
+ function textResult(text, isError = false) {
4
+ return {
5
+ content: [{ type: "text", text }],
6
+ details: undefined,
7
+ ...(isError ? { isError: true } : {}),
8
+ };
9
+ }
10
+ function failure(err) {
11
+ const message = err instanceof Error ? err.message : String(err);
12
+ return textResult(message, true);
13
+ }
14
+ export function createSandboxReadTool(options) {
15
+ return {
16
+ name: "read",
17
+ label: "read",
18
+ description: "Read a file inside this stage's sandbox. Paths are relative to /workspace (artifacts/, checkout/, grants/, work/) or absolute under /workspace. Host paths are rejected.",
19
+ promptSnippet: "Read file contents inside /workspace",
20
+ promptGuidelines: [
21
+ "Use read to examine files under /workspace instead of cat or sed.",
22
+ ],
23
+ parameters: Type.Object({
24
+ path: Type.String({ description: "Path to the file to read, relative to /workspace or absolute under /workspace" }),
25
+ offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
26
+ limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
27
+ }),
28
+ execute: async (_toolCallId, params) => {
29
+ try {
30
+ const text = await readSandboxFile(options, params.path);
31
+ return textResult(sliceSandboxLines(text, params.offset, params.limit));
32
+ }
33
+ catch (err) {
34
+ return failure(err);
35
+ }
36
+ },
37
+ };
38
+ }
39
+ export function createSandboxWriteTool(options) {
40
+ return {
41
+ name: "write",
42
+ label: "write",
43
+ description: "Write a file inside this stage's sandbox. Paths are relative to /workspace or absolute under /workspace. Parent directories are created. Host paths are rejected.",
44
+ promptSnippet: "Create or overwrite files inside /workspace",
45
+ promptGuidelines: ["Use write only for new files or complete rewrites under /workspace."],
46
+ parameters: Type.Object({
47
+ path: Type.String({ description: "Path to the file to write, relative to /workspace or absolute under /workspace" }),
48
+ content: Type.String({ description: "Content to write to the file" }),
49
+ }),
50
+ execute: async (_toolCallId, params) => {
51
+ try {
52
+ await writeSandboxFile(options, params.path, params.content);
53
+ return textResult(`Successfully wrote to ${params.path}`);
54
+ }
55
+ catch (err) {
56
+ return failure(err);
57
+ }
58
+ },
59
+ };
60
+ }
61
+ export function createSandboxEditTool(options) {
62
+ return {
63
+ name: "edit",
64
+ label: "edit",
65
+ description: "Edit a file inside this stage's sandbox with exact text replacement. Paths are relative to /workspace or absolute under /workspace. Each oldText must match exactly once in the original file.",
66
+ promptSnippet: "Make precise file edits inside /workspace",
67
+ promptGuidelines: [
68
+ "Use edit for precise changes under /workspace (edits[].oldText must match exactly once).",
69
+ ],
70
+ parameters: Type.Object({
71
+ path: Type.String({ description: "Path to the file to edit, relative to /workspace or absolute under /workspace" }),
72
+ edits: Type.Array(Type.Object({
73
+ oldText: Type.String({ description: "Exact text to replace. It must be unique in the original file." }),
74
+ newText: Type.String({ description: "Replacement text." }),
75
+ }), { description: "One or more targeted replacements against the original file." }),
76
+ }),
77
+ execute: async (_toolCallId, params) => {
78
+ try {
79
+ await editSandboxFile(options, params.path, params.edits);
80
+ return textResult(`Successfully edited ${params.path}`);
81
+ }
82
+ catch (err) {
83
+ return failure(err);
84
+ }
85
+ },
86
+ };
87
+ }
88
+ export function createSandboxBashTool(options) {
89
+ return {
90
+ name: "bash",
91
+ label: "bash",
92
+ description: "Execute a bash command in this stage's sandbox. The working directory is /workspace. The host filesystem is not mounted.",
93
+ promptSnippet: "Execute bash commands in /workspace",
94
+ promptGuidelines: ["The shell starts in /workspace. Do not use host paths."],
95
+ parameters: Type.Object({
96
+ command: Type.String({ description: "Shell command to execute" }),
97
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional)" })),
98
+ }),
99
+ execute: async (_toolCallId, params) => {
100
+ try {
101
+ const text = await runSandboxBash(options, params.command, params.timeout !== undefined ? params.timeout * 1000 : undefined);
102
+ return textResult(text);
103
+ }
104
+ catch (err) {
105
+ return failure(err);
106
+ }
107
+ },
108
+ };
109
+ }
@@ -0,0 +1,8 @@
1
+ /** What an agent id looks like. Lives here, not in the loader, so the loader can import the reserved list without a cycle. */
2
+ export declare const AGENT_ID_PATTERN: RegExp;
3
+ /** Ids that collide with fixed `/api/agents/<word>` routes. The single list: the loader and the generator both read it. */
4
+ export declare const RESERVED_AGENT_IDS: ReadonlySet<string>;
5
+ export declare function isReservedAgentId(id: string): boolean;
6
+ export declare function slugAgentId(name: string): string;
7
+ /** An id from the agent's name, made unique with a numeric suffix. Names with no usable letters become `agent`. */
8
+ export declare function generateAgentId(name: string, isTaken: (id: string) => Promise<boolean>): Promise<string>;
@@ -0,0 +1,27 @@
1
+ /** What an agent id looks like. Lives here, not in the loader, so the loader can import the reserved list without a cycle. */
2
+ export const AGENT_ID_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
3
+ /** Ids that collide with fixed `/api/agents/<word>` routes. The single list: the loader and the generator both read it. */
4
+ export const RESERVED_AGENT_IDS = new Set(["sessions", "approvals", "new"]);
5
+ export function isReservedAgentId(id) {
6
+ return RESERVED_AGENT_IDS.has(id);
7
+ }
8
+ export function slugAgentId(name) {
9
+ const slug = name
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, "_")
13
+ .replace(/^_+|_+$/g, "")
14
+ .replace(/^[^a-z]+/, "")
15
+ .slice(0, 56);
16
+ return AGENT_ID_PATTERN.test(slug) ? slug : "";
17
+ }
18
+ /** An id from the agent's name, made unique with a numeric suffix. Names with no usable letters become `agent`. */
19
+ export async function generateAgentId(name, isTaken) {
20
+ const base = slugAgentId(name) || "agent";
21
+ for (let attempt = 1; attempt < 1000; attempt += 1) {
22
+ const candidate = attempt === 1 ? base : `${base}_${attempt}`;
23
+ if (!RESERVED_AGENT_IDS.has(candidate) && !(await isTaken(candidate)))
24
+ return candidate;
25
+ }
26
+ throw new Error("Could not generate a unique agent id");
27
+ }
@@ -0,0 +1,16 @@
1
+ export type ApprovalOutcome = "approved" | "denied" | "expired";
2
+ /**
3
+ * In-process rendezvous between a turn paused at an ask-first tool and the
4
+ * decision that arrives later (console, CLI, MCP or a channel). A waiter only
5
+ * exists while the Host process that started the turn is alive.
6
+ */
7
+ export declare class ApprovalBroker {
8
+ private readonly waiters;
9
+ wait(approvalId: string, options: {
10
+ expiresAt: string;
11
+ signal: AbortSignal;
12
+ now?: () => number;
13
+ }): Promise<ApprovalOutcome | "aborted">;
14
+ settle(approvalId: string, outcome: ApprovalOutcome): void;
15
+ pending(): number;
16
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * In-process rendezvous between a turn paused at an ask-first tool and the
3
+ * decision that arrives later (console, CLI, MCP or a channel). A waiter only
4
+ * exists while the Host process that started the turn is alive.
5
+ */
6
+ export class ApprovalBroker {
7
+ waiters = new Map();
8
+ wait(approvalId, options) {
9
+ return new Promise((resolve) => {
10
+ let timer;
11
+ const finish = (outcome) => {
12
+ if (timer)
13
+ clearTimeout(timer);
14
+ options.signal.removeEventListener("abort", onAbort);
15
+ this.waiters.get(approvalId)?.delete(listener);
16
+ if (this.waiters.get(approvalId)?.size === 0)
17
+ this.waiters.delete(approvalId);
18
+ resolve(outcome);
19
+ };
20
+ const listener = (outcome) => finish(outcome);
21
+ const onAbort = () => finish("aborted");
22
+ if (options.signal.aborted)
23
+ return resolve("aborted");
24
+ options.signal.addEventListener("abort", onAbort, { once: true });
25
+ const set = this.waiters.get(approvalId) ?? new Set();
26
+ set.add(listener);
27
+ this.waiters.set(approvalId, set);
28
+ const arm = () => {
29
+ const remaining = Date.parse(options.expiresAt) - (options.now?.() ?? Date.now());
30
+ if (remaining <= 0)
31
+ return finish("expired");
32
+ // setTimeout caps at ~24.8 days; longer waits re-arm until the real expiry.
33
+ timer = setTimeout(arm, Math.min(remaining, 2 ** 31 - 1));
34
+ };
35
+ timer = setTimeout(arm, 0);
36
+ });
37
+ }
38
+ settle(approvalId, outcome) {
39
+ for (const listener of [...(this.waiters.get(approvalId) ?? [])])
40
+ listener(outcome);
41
+ }
42
+ pending() {
43
+ return this.waiters.size;
44
+ }
45
+ }
@@ -0,0 +1,19 @@
1
+ import { type SummaryLine } from "./definitionSummary.js";
2
+ import { type AgentAuthorDraft, type OperatorAgentHost } from "../operatorAgent/index.js";
3
+ export type AgentAuthorTurnResult = {
4
+ sessionId: string;
5
+ text: string;
6
+ draft: AgentAuthorDraft;
7
+ /** One line per config section this turn changed. Empty when the turn only talked. */
8
+ summaryLines: SummaryLine[];
9
+ };
10
+ export declare function resetAgentAuthorSessionsForTests(): void;
11
+ export declare function runAgentAuthorTurn(input: {
12
+ host: OperatorAgentHost;
13
+ projectRoot: string;
14
+ message: string;
15
+ sessionId?: string;
16
+ draft?: AgentAuthorDraft;
17
+ }): Promise<AgentAuthorTurnResult>;
18
+ export declare function undoAgentAuthorTurn(sessionId: string): AgentAuthorDraft;
19
+ export declare function parseAgentAuthorDraft(body: unknown): AgentAuthorDraft;
@@ -0,0 +1,63 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { describeChanges } from "./definitionSummary.js";
3
+ import { AGENT_AUTHOR_PROFILE_ID, createAgentAuthorContext, isAgentAuthorContext, readAgentAuthorDraft, } from "../operatorAgent/index.js";
4
+ const sessions = new Map();
5
+ export function resetAgentAuthorSessionsForTests() {
6
+ for (const session of sessions.values())
7
+ session.close();
8
+ sessions.clear();
9
+ }
10
+ export async function runAgentAuthorTurn(input) {
11
+ const message = input.message.trim();
12
+ if (!message)
13
+ throw new Error("agent.invalid");
14
+ let sessionId = input.sessionId?.trim();
15
+ let session = sessionId ? sessions.get(sessionId) : undefined;
16
+ if (sessionId && !session)
17
+ throw new Error("agent.session_not_found");
18
+ const posted = createAgentAuthorContext({
19
+ projectRoot: input.projectRoot,
20
+ draft: input.draft,
21
+ });
22
+ if (!session) {
23
+ sessionId = randomUUID();
24
+ session = input.host.openSession({
25
+ profileId: AGENT_AUTHOR_PROFILE_ID,
26
+ context: posted,
27
+ });
28
+ sessions.set(sessionId, session);
29
+ }
30
+ else {
31
+ session.setContext(posted);
32
+ }
33
+ const before = readAgentAuthorDraft(session.getContext());
34
+ const events = await session.send(message);
35
+ const text = events
36
+ .filter((event) => event.type === "message" && event.role === "assistant")
37
+ .map((event) => (event.type === "message" ? event.text : ""))
38
+ .filter(Boolean)
39
+ .join("\n\n");
40
+ const error = events.find((event) => event.type === "error");
41
+ const draft = readAgentAuthorDraft(session.getContext());
42
+ return {
43
+ sessionId: sessionId,
44
+ text: text || (error && error.type === "error" ? error.message : ""),
45
+ draft,
46
+ summaryLines: describeChanges(before, draft),
47
+ };
48
+ }
49
+ export function undoAgentAuthorTurn(sessionId) {
50
+ const session = sessions.get(sessionId);
51
+ if (!session)
52
+ throw new Error("agent.session_not_found");
53
+ const undone = session.undoMutation();
54
+ if (!undone.ok)
55
+ throw new Error(undone.reason === "conflict" ? "agent.conflict" : "agent.invalid");
56
+ const context = session.getContext();
57
+ if (!isAgentAuthorContext(context))
58
+ throw new Error("agent.invalid");
59
+ return readAgentAuthorDraft(context);
60
+ }
61
+ export function parseAgentAuthorDraft(body) {
62
+ return body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
63
+ }
@@ -0,0 +1,23 @@
1
+ import type { AgentSessionDetail } from "./service.js";
2
+ import { type ConversationOptions, type SendTurnInput } from "./sessions.js";
3
+ /**
4
+ * Serializes turns per session (or per agent while a session is being
5
+ * opened) and keeps a handle on the running turn so it can be stopped.
6
+ */
7
+ export declare class AgentChatController {
8
+ private readonly options;
9
+ private readonly turnChains;
10
+ private readonly activeBySession;
11
+ private readonly activeByAgent;
12
+ /** Sessions whose turn was stopped: their runtime session is released once the turn has wound down. */
13
+ private readonly stopped;
14
+ constructor(options: ConversationOptions);
15
+ private runExclusive;
16
+ send(input: SendTurnInput): Promise<AgentSessionDetail>;
17
+ /** Stop the running turn on a session. Returns false when nothing was running. */
18
+ stop(sessionId: string): boolean;
19
+ /** Let the runtime drop what it holds for a session (model session, isolated MCP processes). Never throws. */
20
+ release(sessionId: string): Promise<void>;
21
+ /** Stop a turn that is still opening its session (client left before the session id arrived). */
22
+ stopOpening(agentId: string): boolean;
23
+ }
@@ -0,0 +1,77 @@
1
+ import { AgentConversation } from "./sessions.js";
2
+ /**
3
+ * Serializes turns per session (or per agent while a session is being
4
+ * opened) and keeps a handle on the running turn so it can be stopped.
5
+ */
6
+ export class AgentChatController {
7
+ options;
8
+ turnChains = new Map();
9
+ activeBySession = new Map();
10
+ activeByAgent = new Map();
11
+ /** Sessions whose turn was stopped: their runtime session is released once the turn has wound down. */
12
+ stopped = new Set();
13
+ constructor(options) {
14
+ this.options = options;
15
+ }
16
+ runExclusive(key, work) {
17
+ const previous = this.turnChains.get(key) ?? Promise.resolve();
18
+ const run = previous.then(work, work);
19
+ this.turnChains.set(key, run.then(() => undefined, () => undefined));
20
+ return run;
21
+ }
22
+ send(input) {
23
+ return this.runExclusive(input.sessionId ?? `agent:${input.agentId}`, async () => {
24
+ const chat = new AgentConversation(this.options);
25
+ if (input.sessionId)
26
+ this.activeBySession.set(input.sessionId, chat);
27
+ else
28
+ this.activeByAgent.set(input.agentId, chat);
29
+ let openedSessionId;
30
+ try {
31
+ return await chat.send({
32
+ ...input,
33
+ onSessionOpened: (sessionId) => {
34
+ openedSessionId = sessionId;
35
+ this.activeBySession.set(sessionId, chat);
36
+ input.onSessionOpened?.(sessionId);
37
+ },
38
+ });
39
+ }
40
+ finally {
41
+ const sessionId = input.sessionId ?? openedSessionId;
42
+ if (sessionId && this.stopped.delete(sessionId))
43
+ await this.release(sessionId);
44
+ if (sessionId && this.activeBySession.get(sessionId) === chat)
45
+ this.activeBySession.delete(sessionId);
46
+ if (this.activeByAgent.get(input.agentId) === chat)
47
+ this.activeByAgent.delete(input.agentId);
48
+ }
49
+ });
50
+ }
51
+ /** Stop the running turn on a session. Returns false when nothing was running. */
52
+ stop(sessionId) {
53
+ const chat = this.activeBySession.get(sessionId);
54
+ if (!chat)
55
+ return false;
56
+ this.stopped.add(sessionId);
57
+ chat.stop();
58
+ return true;
59
+ }
60
+ /** Let the runtime drop what it holds for a session (model session, isolated MCP processes). Never throws. */
61
+ async release(sessionId) {
62
+ try {
63
+ await this.options.backend.release?.(sessionId);
64
+ }
65
+ catch (error) {
66
+ this.options.logError?.(`releasing agent session ${sessionId} failed: ${error instanceof Error ? error.message : String(error)}`);
67
+ }
68
+ }
69
+ /** Stop a turn that is still opening its session (client left before the session id arrived). */
70
+ stopOpening(agentId) {
71
+ const chat = this.activeByAgent.get(agentId);
72
+ if (!chat)
73
+ return false;
74
+ chat.stop();
75
+ return true;
76
+ }
77
+ }
@@ -0,0 +1,19 @@
1
+ import type { AgentInboundMessage } from "./port.js";
2
+ export type AgentMailInboundConfig = {
3
+ connectionId: string;
4
+ webhookSecret: string;
5
+ };
6
+ export type AgentMailParseResult = {
7
+ ok: true;
8
+ message: AgentInboundMessage;
9
+ } | {
10
+ ok: false;
11
+ code: "agent.signature_invalid" | "agent.invalid";
12
+ };
13
+ export declare function verifyAgentMailSignature(config: AgentMailInboundConfig, signatureHeader: string | undefined, rawBody: string): {
14
+ ok: true;
15
+ } | {
16
+ ok: false;
17
+ code: "agent.signature_invalid";
18
+ };
19
+ export declare function parseAgentMailInbound(config: AgentMailInboundConfig, payload: unknown): AgentMailParseResult;