@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10

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 (172) hide show
  1. package/package.json +7 -7
  2. package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
  3. package/src/adapter/cloudflare/agent.ts +1682 -0
  4. package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
  5. package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
  6. package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
  7. package/src/adapter/cloudflare/index.ts +18 -0
  8. package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
  9. package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
  10. package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
  11. package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
  12. package/src/{db → adapter/cloudflare}/schema.ts +28 -80
  13. package/src/adapter/cloudflare/subagent/definition.ts +63 -33
  14. package/src/adapter/cloudflare/subagent/runner.ts +174 -120
  15. package/src/adapter/cloudflare/subagent/tools.ts +13 -14
  16. package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
  17. package/src/adapter/cloudflare/universal-agent/tools.ts +10 -12
  18. package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
  19. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
  20. package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
  21. package/src/config/activation.ts +140 -0
  22. package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
  23. package/src/{runtime-assembler.ts → config/assembler.ts} +45 -46
  24. package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
  25. package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
  26. package/src/{runtime-definition.ts → config/definition.ts} +13 -13
  27. package/src/config/harness-runtime.ts +190 -0
  28. package/src/config/index.ts +14 -0
  29. package/src/config/model-guidance.ts +21 -0
  30. package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
  31. package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
  32. package/src/{kernel → config}/profile.ts +7 -0
  33. package/src/{kernel → config}/public-contracts.ts +6 -6
  34. package/src/{kernel → config}/runtime-load.ts +1 -1
  35. package/src/{pi/assembly/context.ts → context/assembly.ts} +5 -207
  36. package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
  37. package/src/context/extension/index.ts +3 -0
  38. package/src/context/index.ts +6 -0
  39. package/src/{layers/context → context/memory}/budget/gate.ts +21 -5
  40. package/src/conversation/harness-resume.ts +288 -0
  41. package/src/conversation/harness-stream.ts +99 -0
  42. package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
  43. package/src/conversation/index.ts +6 -0
  44. package/src/{pi → conversation}/message/contract.ts +3 -0
  45. package/src/{pi → conversation}/message/conversion.ts +75 -0
  46. package/src/{pi → conversation}/message/index.ts +1 -0
  47. package/src/{pi → conversation}/message/model-input.ts +1 -1
  48. package/src/{pi → conversation}/message/projection.ts +113 -30
  49. package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
  50. package/src/conversation/state.ts +196 -0
  51. package/src/harness/index.ts +7 -0
  52. package/src/harness/session-harness.ts +562 -0
  53. package/src/harness/submission-operation.ts +49 -0
  54. package/src/index.ts +38 -43
  55. package/src/lib/artifacts.ts +18 -0
  56. package/src/lib/execution-level.ts +13 -0
  57. package/src/lib/mcp.ts +1 -1
  58. package/src/lib/prompt.ts +7 -6
  59. package/src/lib/token-estimate.ts +8 -0
  60. package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
  61. package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
  62. package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
  63. package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
  64. package/src/session/index.ts +39 -0
  65. package/src/skills/springbrand-worker-website/index.ts +1 -1
  66. package/src/submission/admission.ts +286 -0
  67. package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
  68. package/src/submission/delivery.ts +212 -0
  69. package/src/submission/finalization.ts +111 -0
  70. package/src/submission/index.ts +9 -0
  71. package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
  72. package/src/{db/submission.repo.ts → submission/repository.ts} +117 -114
  73. package/src/submission/steer-delivery.ts +167 -0
  74. package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
  75. package/src/submission/unattended.ts +40 -0
  76. package/src/telemetry/contract.ts +12 -0
  77. package/src/telemetry/coordinator.ts +65 -0
  78. package/src/telemetry/delivery.ts +2 -2
  79. package/src/telemetry/index.ts +5 -0
  80. package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
  81. package/src/telemetry/recorder.ts +1 -1
  82. package/src/telemetry/runtime-telemetry.ts +12 -0
  83. package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
  84. package/src/telemetry/turn-events.ts +279 -0
  85. package/src/tools/README.md +44 -0
  86. package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
  87. package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
  88. package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
  89. package/src/tools/assistant/ask-user.ts +317 -0
  90. package/src/tools/assistant/candidate.ts +15 -0
  91. package/src/tools/assistant/followups.ts +34 -0
  92. package/src/tools/assistant/index.ts +19 -0
  93. package/src/tools/assistant/memory.ts +73 -0
  94. package/src/tools/assistant/plan.ts +98 -0
  95. package/src/{pi/tool → tools/assistant}/time.ts +1 -1
  96. package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
  97. package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
  98. package/src/tools/codemode/workspace-connector.ts +198 -0
  99. package/src/tools/extension/index.ts +42 -0
  100. package/src/tools/index.ts +32 -0
  101. package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
  102. package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
  103. package/src/tools/runtime/approval/index.ts +1 -0
  104. package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
  105. package/src/tools/runtime/compiler.ts +264 -0
  106. package/src/tools/runtime/contracts.ts +117 -0
  107. package/src/tools/runtime/decision-gate.ts +337 -0
  108. package/src/tools/runtime/human-decisions.ts +127 -0
  109. package/src/tools/runtime/interaction/index.ts +1 -0
  110. package/src/{db/interaction.repo.ts → tools/runtime/interaction/repository.ts} +1 -7
  111. package/src/tools/runtime/result.ts +22 -0
  112. package/src/tools/runtime/unattended-error.ts +4 -0
  113. package/src/tools/sandbox/index.ts +228 -0
  114. package/src/tools/schedule/index.ts +301 -0
  115. package/src/tools/skill/contracts.ts +20 -0
  116. package/src/tools/skill/index.ts +99 -0
  117. package/src/tools/skill/resources.ts +322 -0
  118. package/src/tools/skill/scripts.ts +168 -0
  119. package/src/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
  120. package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
  121. package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
  122. package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
  123. package/src/tools/subagent/temporary/approvals.ts +123 -0
  124. package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
  125. package/src/tools/subagent/temporary/core.ts +53 -0
  126. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
  127. package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
  128. package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
  129. package/src/tools/web/index.ts +2 -0
  130. package/src/tools/web/search/api.ts +1320 -0
  131. package/src/tools/web/search/index.ts +2 -0
  132. package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
  133. package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
  134. package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
  135. package/src/db/milestone.repo.ts +0 -104
  136. package/src/db/settlement.repo.ts +0 -104
  137. package/src/kernel/approval-lifecycle.ts +0 -638
  138. package/src/kernel/interaction-lifecycle.ts +0 -433
  139. package/src/kernel/recoverable-chat-agent.ts +0 -1020
  140. package/src/kernel/state.ts +0 -91
  141. package/src/pi/assembly/index.ts +0 -19
  142. package/src/pi/runtime-adapter/execution.ts +0 -1163
  143. package/src/pi/runtime-adapter/index.ts +0 -293
  144. package/src/pi/runtime-adapter/recovery.ts +0 -982
  145. package/src/pi/session/index.ts +0 -24
  146. package/src/pi/tool/base.ts +0 -550
  147. package/src/pi/tool/compiler.ts +0 -483
  148. package/src/pi/tool/index.ts +0 -38
  149. package/src/pi/tool/schedule.ts +0 -261
  150. package/src/pi/tool/skill.ts +0 -565
  151. package/src/pi/tool/web-search/api.ts +0 -1318
  152. package/src/pi/tool/web-search/index.ts +0 -2
  153. package/src/pi/tool/workspace-state-connector.ts +0 -146
  154. package/src/pi/turn/approval.ts +0 -181
  155. package/src/pi/turn/index.ts +0 -82
  156. package/src/pi/turn/interaction.ts +0 -181
  157. package/src/runtime.ts +0 -4760
  158. package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
  159. package/src/{kernel → config}/degradation.ts +0 -0
  160. package/src/{kernel → config}/runtime-config.ts +0 -0
  161. package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
  162. package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
  163. package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
  164. package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
  165. package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
  166. package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
  167. package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
  168. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/contract.ts +0 -0
  169. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/extract/index.ts +0 -0
  170. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/fanout/index.ts +0 -0
  171. package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/registry.ts +1 -1
  172. /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
@@ -0,0 +1,2 @@
1
+ export * from "./api";
2
+ export * from "./search";
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { AgentTool } from "@earendil-works/pi-agent-core";
9
9
  import { Type } from "@earendil-works/pi-ai";
10
- import type { PiToolCandidate } from "../compiler";
10
+ import type { PiToolCandidate } from "../../runtime/contracts";
11
11
  import type { WebSearch, WebSearchResult } from "./api";
12
12
 
13
13
  export const webSearchParameters = Type.Object({
@@ -93,15 +93,13 @@ export function webSearchPiToolCandidate(
93
93
  item.status,
94
94
  item.query ? `query=${item.query}` : undefined,
95
95
  ].filter(Boolean).join(", ");
96
- return `${index + 1}. ${label}${url}${
97
- metadata ? ` (${metadata})` : ""
98
- }`;
96
+ return `${index + 1}. ${label}${url}${metadata ? ` (${metadata})` : ""
97
+ }`;
99
98
  },
100
99
  ).join("\n")}`;
101
100
  if (additionalResults.length > visible.length) {
102
- summary += `\n... and ${
103
- additionalResults.length - visible.length
104
- } more results in tool details.`;
101
+ summary += `\n... and ${additionalResults.length - visible.length
102
+ } more results in tool details.`;
105
103
  }
106
104
  }
107
105
 
@@ -7,51 +7,14 @@ import {
7
7
  createReadTool,
8
8
  createWriteTool,
9
9
  } from "@cloudflare/think/tools/workspace";
10
- import type {
11
- AgentTool,
12
- AgentToolResult,
13
- AgentToolUpdateCallback,
14
- } from "@earendil-works/pi-agent-core";
10
+ import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
15
11
  import { Type } from "@earendil-works/pi-ai";
16
- import type {
17
- RuntimeSandboxPort,
18
- WorkspacePort,
19
- } from "../../kernel/bindings";
20
- import { serializeOutput } from "../../lib/artifacts";
21
- import { aiToolToPi } from "./ai-adapter";
22
- import type { PiToolCandidate } from "./compiler";
23
-
24
- // #region Shared Pi result helpers
25
-
26
- // 作用:把 Workspace 或 Sandbox 的结构化详情包装成 Pi 工具结果。
27
- // 调用:本文件所有自定义 execute 方法在成功后调用。
28
- // 原因:序列化文本供模型阅读,
29
- // 原始 details 供事件投影和统一输出预算处理,
30
- // 避免各工具自造格式。
31
- function result(details: unknown): AgentToolResult<unknown> {
32
- return {
33
- content: [{ type: "text", text: serializeOutput(details).text }],
34
- details,
35
- };
36
- }
12
+ import type { WorkspacePort } from "../../config/ports";
13
+ import { aiToolToPi } from "../adapters/ai-to-pi";
14
+ import type { PiToolCandidate } from "../runtime/contracts";
15
+ import { running } from "../runtime/result";
37
16
 
38
- // 作用:向 Pi 报告一次 Workspace 或 Sandbox 操作已经开始。
39
- // 调用:可能访问存储或容器的 execute 方法在进入 Port 前调用。
40
- // 原因:复用统一的 running 结果形状,
41
- // 让长操作有进度反馈而不复制回调协议。
42
- function running(
43
- onUpdate: AgentToolUpdateCallback<unknown> | undefined,
44
- text: string,
45
- ): void {
46
- onUpdate?.({
47
- content: [{ type: "text", text }],
48
- details: { status: "running" },
49
- });
50
- }
51
17
 
52
- // #endregion
53
-
54
- // #region Workspace tools
55
18
 
56
19
  const workspaceReadParameters = Type.Object({
57
20
  path: Type.String({
@@ -68,6 +31,7 @@ const workspaceReadParameters = Type.Object({
68
31
  description: "Maximum lines to return; a page is capped by size regardless",
69
32
  })),
70
33
  }, { additionalProperties: false });
34
+
71
35
  const workspaceEditParameters = Type.Object({
72
36
  path: Type.String({
73
37
  minLength: 1,
@@ -175,14 +139,14 @@ function pageReadResult(
175
139
  const span = `lines ${fromLine}-${toLine} of ${totalLines ?? toLine}`;
176
140
  const lossyNote = lossy
177
141
  ? " Some lines were longer than 2000 chars and are cut short; " +
178
- "paging cannot recover them — use grep on this path instead."
142
+ "paging cannot recover them — use grep on this path instead."
179
143
  : "";
180
144
  const marker = fromLine === 1 && eof && !lossy
181
145
  ? ""
182
146
  : eof
183
147
  ? `\n\n[${span} — end of file.${lossyNote}]`
184
148
  : `\n\n[${span} — more remains. ` +
185
- `Continue with read(path, offset: ${toLine + 1}).${lossyNote}]`;
149
+ `Continue with read(path, offset: ${toLine + 1}).${lossyNote}]`;
186
150
 
187
151
  return {
188
152
  ...output,
@@ -197,6 +161,7 @@ function pageReadResult(
197
161
  },
198
162
  };
199
163
  }
164
+
200
165
  /**
201
166
  * 把模型给的路径收敛成稳定的闸键。
202
167
  *
@@ -250,8 +215,8 @@ export function serializeWorkspaceMutation<T>(
250
215
  const tail = queue.get(key) ?? Promise.resolve();
251
216
  const result = tail.then(run);
252
217
  const next = result.then(
253
- () => {},
254
- () => {},
218
+ () => { },
219
+ () => { },
255
220
  );
256
221
  queue.set(key, next);
257
222
  // 自己仍是队尾时摘掉键,避免长会话里 Map 无界增长。
@@ -272,7 +237,7 @@ export function serializeWorkspaceMutation<T>(
272
237
  * 的窄文件接口,“候选工具”指进入 Pi 编译和统一治理前的描述。read、write、
273
238
  * edit、list、find、grep、delete 复用 @cloudflare/think 的上游工厂。
274
239
  *
275
- * `Pi`、`Port` 与“候选工具”等项目核心术语见 `../../index.ts`。
240
+ * `Pi`、`Port` 与“候选工具”等项目核心术语见 `src/index.ts`。
276
241
  */
277
242
  /** Pi 编译路径:从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 候选项。 */
278
243
  export function workspacePiToolCandidates(
@@ -418,228 +383,3 @@ export function workspacePiToolCandidates(
418
383
  })),
419
384
  ];
420
385
  }
421
-
422
- /** 从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 集(read / write / edit …)。 */
423
-
424
- // #endregion
425
-
426
- // #region Sandbox tools
427
-
428
- const sandboxCommandParameters = {
429
- command: Type.String({
430
- minLength: 1,
431
- maxLength: 32_768,
432
- description: "Shell command to run in the isolated Linux Sandbox.",
433
- }),
434
- cwd: Type.Optional(Type.String({
435
- maxLength: 4_096,
436
- description: "Sandbox working directory. Omit to use the Sandbox default.",
437
- })),
438
- stdin: Type.Optional(Type.String({
439
- maxLength: 65_536,
440
- description: "Optional text to pass to the command on standard input.",
441
- })),
442
- };
443
- const sandboxExecParameters = Type.Object({
444
- ...sandboxCommandParameters,
445
- timeoutMs: Type.Optional(
446
- Type.Integer({
447
- minimum: 1,
448
- maximum: 60_000,
449
- description: "Maximum command runtime in milliseconds, from 1 to 60000.",
450
- }),
451
- ),
452
- }, { additionalProperties: false });
453
- const sandboxStartParameters = Type.Object(
454
- sandboxCommandParameters,
455
- { additionalProperties: false },
456
- );
457
- const sandboxProcessParameters = Type.Object({
458
- id: Type.String({
459
- minLength: 1,
460
- maxLength: 128,
461
- description: "Process ID returned by sandbox_start_process.",
462
- }),
463
- }, { additionalProperties: false });
464
- const sandboxPublishParameters = Type.Object({
465
- paths: Type.Array(Type.String({
466
- minLength: 1,
467
- maxLength: 4_096,
468
- description: "Sandbox file path to publish.",
469
- }), {
470
- minItems: 1,
471
- maxItems: 100,
472
- description: "Files to copy from the temporary Sandbox into the persistent Workspace.",
473
- }),
474
- }, { additionalProperties: false });
475
-
476
- /**
477
- * 为现有 RuntimeSandboxPort 创建 Pi 命令、进程和文件发布工具。
478
- *
479
- * Worker 的 Sandbox Plugin 在容器能力和持久 Workspace 都可用时调用它。调用方
480
- * 应传入已绑定当前会话的 Port;前台命令适合一次性执行,后台进程适合需要持续
481
- * 运行的服务,二者不能互换结果语义。
482
- *
483
- * 这里的 “Sandbox” 指 Cloudflare Sandbox SDK 提供的隔离 Linux 容器;本层只
484
- * 面向项目自己的 Port。命令限制、cwd 校验、工作区装载、超时、取消、清理和
485
- * 冲突安全发布仍在 Port 内。容器文件不会自动写回 Workspace,只有 publish
486
- * 工具显式选中的文件才进入持久视图;执行、启动、停止和发布继续走高风险审批。
487
- *
488
- * `Pi`、`Port` 与“候选工具”等项目核心术语见 `../../index.ts`。
489
- */
490
- export function sandboxPiToolCandidates(
491
- sandbox: RuntimeSandboxPort,
492
- ): PiToolCandidate[] {
493
- const exec: AgentTool<typeof sandboxExecParameters> = {
494
- name: "sandbox_exec",
495
- label: "Run command",
496
- description:
497
- "Run a foreground command in this Chat's isolated Linux Sandbox. Files stay temporary until sandbox_publish_files is called.",
498
- parameters: sandboxExecParameters,
499
- // 作用:在 Sandbox 中运行一次前台命令并等待完整退出结果。
500
- // 调用:Pi 执行构建、测试或其他会结束的命令时调用。
501
- // 原因:输入和 AbortSignal 原样交给 Port,返回值只投影公开字段,
502
- // 避免容器内部句柄进入对话。
503
- async execute(_toolCallId, input, signal, onUpdate) {
504
- signal?.throwIfAborted();
505
- running(onUpdate, "Running the Sandbox command.");
506
- const output = await sandbox.exec(input, signal);
507
- return result({
508
- success: output.success,
509
- stdout: output.stdout,
510
- stderr: output.stderr,
511
- exitCode: output.exitCode,
512
- stdoutTruncated: output.stdoutTruncated,
513
- stderrTruncated: output.stderrTruncated,
514
- workspacePersistence: "explicit_publish_required",
515
- });
516
- },
517
- };
518
-
519
- const startProcess: AgentTool<typeof sandboxStartParameters> = {
520
- name: "sandbox_start_process",
521
- label: "Start process",
522
- description:
523
- "Start a long-running background process in this Chat's isolated Linux Sandbox.",
524
- parameters: sandboxStartParameters,
525
- // 作用:在 Sandbox 中启动一个长期运行的后台进程并返回进程标识。
526
- // 调用:Pi 启动开发服务器、监听器或其他不能等待退出的程序时调用。
527
- // 原因:Cloudflare 官方接口把长期进程与 exec 分开;
528
- // 这里只公开后续查日志和停止所需字段。
529
- async execute(_toolCallId, input, signal, onUpdate) {
530
- signal?.throwIfAborted();
531
- running(onUpdate, "Starting the Sandbox process.");
532
- const process = await sandbox.startProcess(input);
533
- return result({
534
- id: process.id,
535
- command: process.command,
536
- status: process.status,
537
- ...(process.exitCode === undefined
538
- ? {}
539
- : { exitCode: process.exitCode }),
540
- workspacePersistence: "explicit_publish_required",
541
- });
542
- },
543
- };
544
-
545
- const processLogs: AgentTool<typeof sandboxProcessParameters> = {
546
- name: "sandbox_process_logs",
547
- label: "Read process logs",
548
- description:
549
- "Read bounded stdout and stderr logs for one background Sandbox process.",
550
- parameters: sandboxProcessParameters,
551
- // 作用:读取一个 Sandbox 后台进程当前的有界日志。
552
- // 调用:Pi 拿到 startProcess 返回的 id 后检查启动或运行状态时调用。
553
- // 原因:日志截断由 Port 统一处理并显式回传标记,
554
- // 避免适配层再次缓存不断增长的输出。
555
- async execute(_toolCallId, { id }, signal, onUpdate) {
556
- signal?.throwIfAborted();
557
- running(onUpdate, "Reading the Sandbox process logs.");
558
- const logs = await sandbox.getProcessLogs(id);
559
- return result({
560
- id: logs.id,
561
- stdout: logs.stdout,
562
- stderr: logs.stderr,
563
- stdoutTruncated: logs.stdoutTruncated,
564
- stderrTruncated: logs.stderrTruncated,
565
- });
566
- },
567
- };
568
-
569
- const stopProcess: AgentTool<typeof sandboxProcessParameters> = {
570
- name: "sandbox_stop_process",
571
- label: "Stop process",
572
- description: "Stop one background Sandbox process.",
573
- parameters: sandboxProcessParameters,
574
- // 作用:停止一个由当前 Sandbox 管理的后台进程。
575
- // 调用:Pi 不再需要该进程或需要清理运行环境时调用。
576
- // 原因:只把进程 id 交给 Port,
577
- // 进程归属检查和实际清理由 Sandbox 边界统一负责。
578
- async execute(_toolCallId, { id }, signal, onUpdate) {
579
- signal?.throwIfAborted();
580
- running(onUpdate, "Stopping the Sandbox process.");
581
- const output = await sandbox.stopProcess(id);
582
- return result({ stopped: output.stopped });
583
- },
584
- };
585
-
586
- const publishFiles: AgentTool<typeof sandboxPublishParameters> = {
587
- name: "sandbox_publish_files",
588
- label: "Publish files",
589
- description:
590
- "Publish named files from the temporary Sandbox into the scoped Workspace. Shared files remain read-only.",
591
- parameters: sandboxPublishParameters,
592
- // 作用:把明确列出的 Sandbox 文件发布到持久 Workspace。
593
- // 调用:Pi 确认命令产物需要保留到会话文件区时调用。
594
- // 原因:显式发布避免临时容器状态被误当成已持久化;
595
- // 冲突、共享写保护和失败码继续由 Port 决定。
596
- async execute(_toolCallId, { paths }, signal, onUpdate) {
597
- signal?.throwIfAborted();
598
- running(onUpdate, "Publishing Sandbox files to the Workspace.");
599
- const output = await sandbox.publishFiles(paths);
600
- return result({
601
- files: output.files,
602
- skipped: output.skipped,
603
- failed: output.failed.map(({ path, errorCode }) => ({
604
- path,
605
- errorCode,
606
- })),
607
- bytes: output.bytes,
608
- workspacePersistence: "published",
609
- });
610
- },
611
- };
612
-
613
- return [
614
- {
615
- owner: "sandbox",
616
- requiredExecutionLevel: "high",
617
- tool: exec,
618
- },
619
- {
620
- owner: "sandbox",
621
- requiredExecutionLevel: "high",
622
- tool: startProcess,
623
- },
624
- {
625
- owner: "sandbox",
626
- requiredExecutionLevel: "safe",
627
- retry: "idempotent",
628
- tool: processLogs,
629
- },
630
- {
631
- owner: "sandbox",
632
- requiredExecutionLevel: "high",
633
- tool: stopProcess,
634
- },
635
- {
636
- owner: "sandbox",
637
- requiredExecutionLevel: "high",
638
- tool: publishFiles,
639
- },
640
- ];
641
- }
642
-
643
- /** 从 {@link RuntimeSandboxPort} 生成 Sandbox Tool 集。 */
644
-
645
- // #endregion
@@ -1,8 +1,8 @@
1
1
  import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
2
2
  import { Type } from "@earendil-works/pi-ai";
3
- import type { WorkspaceRevisionRestorePort } from "../../workspace-versioning";
4
3
  import { serializeOutput } from "../../lib/artifacts";
5
- import type { PiToolCandidate } from "./compiler";
4
+ import type { PiToolCandidate } from "../runtime/contracts";
5
+ import type { WorkspaceRevisionRestorePort } from "./versioning";
6
6
 
7
7
  const parameters = Type.Object({
8
8
  target: Type.Union([
@@ -1,104 +0,0 @@
1
- import type { SqlTaggedTemplate } from "agents/chat";
2
-
3
- // Recovery Milestone 的统一定义见 ./index.ts。
4
- export interface StoredMilestone {
5
- submissionId: string;
6
- seq: number;
7
- milestoneKey: string;
8
- body: string;
9
- }
10
-
11
- type MilestoneRow = {
12
- submission_id: string;
13
- seq: number;
14
- milestone_key: string;
15
- body: string;
16
- };
17
-
18
- // 把 SQLite 的 snake_case 行转成 Runtime 使用的 Milestone 对象。
19
- // 待确认:当前 Repository 只返回 body,没有任何调用方使用这个映射函数。
20
- // 这个边界可为完整 Milestone 查询隔离表列名,但在确认未来用途前不应继续围绕它增加代码。
21
- function mapRow(row: MilestoneRow): StoredMilestone {
22
- return {
23
- submissionId: row.submission_id,
24
- seq: row.seq,
25
- milestoneKey: row.milestone_key,
26
- body: row.body,
27
- };
28
- }
29
-
30
- export class RecoveryMilestoneRepository {
31
- // 保存当前 Agent SQLite 的查询入口。
32
- // RuntimeDatabase 构造时调用,Runtime 和 ApprovalLifecycle 随后共享这个 Repository。
33
- // 恢复事实必须与 Submission 位于同一 Agent 存储中,不能用另一个 sql 入口拼接。
34
- constructor(private readonly sql: SqlTaggedTemplate) {}
35
-
36
- /** The existing unique index makes active Tool writes independent of Turn history size. */
37
- findBody(submissionId: string, milestoneKey: string): string | null {
38
- return this.sql<{ body: string }>`
39
- SELECT body FROM pi_recovery_milestones
40
- WHERE submission_id = ${submissionId} AND milestone_key = ${milestoneKey}
41
- `[0]?.body ?? null;
42
- }
43
-
44
- // 按写入顺序返回某次 Submission 的全部恢复内容。
45
- // Runtime 和 ApprovalLifecycle 在计算下一个恢复决策前调用,调用方直接把结果交给 Pi 恢复器。
46
- // ORDER BY seq 是重放契约,不能改成 milestoneKey 或无排序查询,否则决策顺序会变。
47
- listBodies(submissionId: string): string[] {
48
- return this.sql<{ body: string }>`
49
- SELECT body
50
- FROM pi_recovery_milestones
51
- WHERE submission_id = ${submissionId}
52
- ORDER BY seq ASC
53
- `.map((row) => row.body);
54
- }
55
-
56
- // 按 milestoneKey 只插入一次恢复事实,并告诉调用方是否真的新增。
57
- // applyPiRecoveryMutations 在事务中调用,false 会让它对 terminal:intent 走定向更新。
58
- // seq 用 MAX + 1 保留插入顺序,UNIQUE 约束是最后的去重防线;不能改成会覆盖任意旧事实的普通 UPSERT。
59
- upsert(
60
- submissionId: string,
61
- milestoneKey: string,
62
- body: string,
63
- ): boolean {
64
- const existing =
65
- this.sql<{ count: number }>`
66
- SELECT COUNT(*) AS count
67
- FROM pi_recovery_milestones
68
- WHERE submission_id = ${submissionId}
69
- AND milestone_key = ${milestoneKey}
70
- `[0]?.count ?? 0;
71
- if (existing > 0) return false;
72
-
73
- const seq =
74
- this.sql<{ next_seq: number }>`
75
- SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq
76
- FROM pi_recovery_milestones
77
- WHERE submission_id = ${submissionId}
78
- `[0]?.next_seq ?? 1;
79
-
80
- this.sql`
81
- INSERT INTO pi_recovery_milestones (
82
- submission_id, seq, milestone_key, body
83
- ) VALUES (
84
- ${submissionId},
85
- ${seq},
86
- ${milestoneKey},
87
- ${body}
88
- )
89
- `;
90
- return true;
91
- }
92
-
93
- // 只替换已有 terminal:intent 的内容,不新建事实。
94
- // applyPiRecoveryMutations 在 upsert 返回 false 且 key 是 terminal:intent 时调用。
95
- // 该例外允许最终失败理由收敛,但不能放宽 WHERE 去改写其他已记录的恢复事实。
96
- updateTerminalIntent(submissionId: string, body: string): void {
97
- this.sql`
98
- UPDATE pi_recovery_milestones
99
- SET body = ${body}
100
- WHERE submission_id = ${submissionId}
101
- AND milestone_key = 'terminal:intent'
102
- `;
103
- }
104
- }
@@ -1,104 +0,0 @@
1
- import type { SqlTaggedTemplate } from "agents/chat";
2
-
3
- // Settlement 的统一定义见 ./index.ts。
4
- export interface StoredToolSettlement {
5
- toolCallId: string;
6
- submissionId: string;
7
- toolName: string;
8
- args: string;
9
- result: string;
10
- isError: boolean;
11
- createdAt: number;
12
- }
13
-
14
- export interface NewToolSettlement {
15
- toolCallId: string;
16
- submissionId: string;
17
- toolName: string;
18
- args: string;
19
- result: string;
20
- isError: boolean;
21
- createdAt: number;
22
- }
23
-
24
- type SettlementRow = {
25
- tool_call_id: string;
26
- submission_id: string;
27
- tool_name: string;
28
- args: string;
29
- result: string;
30
- is_error: number;
31
- created_at: number;
32
- };
33
-
34
- // 把 SQLite 行转成 Runtime 使用的工具结果对象。
35
- // find 查到记录后调用,上层因而只看 camelCase 字段和 boolean。
36
- // SQLite 用 0/1 存 is_error,转换必须留在此边界;增加列时要同步核对类型、SELECT 和映射。
37
- function mapRow(row: SettlementRow): StoredToolSettlement {
38
- return {
39
- toolCallId: row.tool_call_id,
40
- submissionId: row.submission_id,
41
- toolName: row.tool_name,
42
- args: row.args,
43
- result: row.result,
44
- isError: row.is_error === 1,
45
- createdAt: row.created_at,
46
- };
47
- }
48
-
49
- export class ToolSettlementRepository {
50
- // 保存当前 Agent SQLite 的查询入口。
51
- // RuntimeDatabase 构造时调用,工具执行和恢复流程随后共享这个 Repository。
52
- // Settlement 要与所属 Submission 同库持久化,否则重放时无法一致查重。
53
- constructor(private readonly sql: SqlTaggedTemplate) {}
54
-
55
- // 按 Submission 和工具调用 ID 读取已落盘的最终结果。
56
- // settleTool 写入前和 readToolSettlement 恢复时调用,没有记录时返回 null。
57
- // 两列共同定位表的复合主键,不能只按 toolCallId 查询而把其他 Submission 的结果当成当前结果。
58
- find(
59
- submissionId: string,
60
- toolCallId: string,
61
- ): StoredToolSettlement | null {
62
- const row = this.sql<SettlementRow>`
63
- SELECT tool_call_id, submission_id, tool_name, args, result,
64
- is_error, created_at
65
- FROM pi_tool_settlements
66
- WHERE submission_id = ${submissionId}
67
- AND tool_call_id = ${toolCallId}
68
- `[0];
69
- return row ? mapRow(row) : null;
70
- }
71
-
72
- // 持久化一条尚未存在的工具最终结果。
73
- // settleTool 在事务中确认 find 返回 null 后调用,冲突的重复结果由上层拒绝。
74
- // 复合主键会让未经查重的重复写入直接失败,不能用 INSERT OR REPLACE 掩盖不一致的工具结果。
75
- insert(s: NewToolSettlement): void {
76
- this.sql`
77
- INSERT INTO pi_tool_settlements (
78
- tool_call_id, submission_id, tool_name, args, result,
79
- is_error, created_at
80
- ) VALUES (
81
- ${s.toolCallId},
82
- ${s.submissionId},
83
- ${s.toolName},
84
- ${s.args},
85
- ${s.result},
86
- ${s.isError ? 1 : 0},
87
- ${s.createdAt}
88
- )
89
- `;
90
- }
91
-
92
- // 统计某次 Submission 已落盘的工具结果数。
93
- // Runtime 生成恢复快照时调用,没有记录时应得到 0。
94
- // 这里以数据库为真相源,不能改成内存计数,否则 Durable Object 重启后会丢失进度。
95
- countForSubmission(submissionId: string): number {
96
- return (
97
- this.sql<{ count: number }>`
98
- SELECT COUNT(*) AS count
99
- FROM pi_tool_settlements
100
- WHERE submission_id = ${submissionId}
101
- `[0]?.count ?? 0
102
- );
103
- }
104
- }