@springbrand/agent-runtime 0.1.3-alpha.0 → 0.1.3-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 (79) hide show
  1. package/package.json +12 -3
  2. package/src/adapter/cloudflare/index.ts +56 -0
  3. package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
  4. package/src/adapter/cloudflare/sandbox/adapter.ts +1513 -0
  5. package/src/adapter/cloudflare/sandbox/id.ts +23 -0
  6. package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
  7. package/src/adapter/cloudflare/subagent/definition.ts +574 -0
  8. package/src/adapter/cloudflare/subagent/runner.ts +175 -0
  9. package/src/adapter/cloudflare/subagent/tools.ts +254 -0
  10. package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
  11. package/src/adapter/cloudflare/universal-agent/preparation.ts +277 -0
  12. package/src/adapter/cloudflare/universal-agent/tools.ts +80 -0
  13. package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
  14. package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
  15. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
  16. package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
  17. package/src/agent-tool-runtime.ts +152 -0
  18. package/src/db/agent-tool.repo.ts +27 -0
  19. package/src/db/index.ts +33 -0
  20. package/src/db/interaction.repo.ts +185 -0
  21. package/src/db/schema.ts +25 -1
  22. package/src/db/submission.repo.ts +63 -1
  23. package/src/index.ts +61 -27
  24. package/src/kernel/approval-lifecycle.ts +41 -6
  25. package/src/kernel/bindings.ts +99 -12
  26. package/src/kernel/extensions.ts +1 -1
  27. package/src/kernel/interaction-lifecycle.ts +395 -0
  28. package/src/kernel/profile.ts +3 -4
  29. package/src/kernel/public-contracts.ts +2 -0
  30. package/src/kernel/recoverable-chat-agent.ts +104 -6
  31. package/src/kernel/runtime-assembly-view.ts +37 -0
  32. package/src/kernel/runtime-assembly.ts +41 -0
  33. package/src/kernel/runtime-config.ts +4 -0
  34. package/src/kernel/runtime-load.ts +191 -0
  35. package/src/kernel/state.ts +12 -1
  36. package/src/kernel/submission-lifecycle.ts +33 -2
  37. package/src/layers/orchestration/temporary-agent/core.ts +12 -1
  38. package/src/layers/orchestration/temporary-agent/runner.ts +1 -67
  39. package/src/lib/mcp.ts +7 -3
  40. package/src/lib/prompt.ts +1 -1
  41. package/src/lib/telemetry-dev.ts +7 -4
  42. package/src/pi/assembly/context.ts +4 -6
  43. package/src/pi/assembly/extensions.ts +11 -22
  44. package/src/pi/assembly/snapshot.ts +17 -9
  45. package/src/pi/message/contract.ts +7 -0
  46. package/src/pi/message/conversion.ts +9 -1
  47. package/src/pi/runtime-adapter/assembly.ts +42 -97
  48. package/src/pi/runtime-adapter/execution.ts +216 -21
  49. package/src/pi/runtime-adapter/index.ts +24 -8
  50. package/src/pi/runtime-adapter/models.ts +382 -35
  51. package/src/pi/runtime-adapter/recovery.ts +188 -1
  52. package/src/pi/runtime-adapter/transcript.ts +61 -3
  53. package/src/pi/tool/ai-adapter.ts +58 -1
  54. package/src/pi/tool/base.ts +190 -12
  55. package/src/pi/tool/compiler.ts +39 -33
  56. package/src/pi/tool/core-host.ts +28 -30
  57. package/src/pi/tool/core.ts +37 -124
  58. package/src/pi/tool/gateway.ts +54 -0
  59. package/src/pi/tool/index.ts +2 -0
  60. package/src/pi/tool/mcp.ts +98 -70
  61. package/src/pi/tool/schedule.ts +86 -20
  62. package/src/pi/tool/skill.ts +126 -420
  63. package/src/pi/tool/subagent.ts +14 -2
  64. package/src/pi/tool/web-fetch.ts +281 -0
  65. package/src/pi/tool/web-search/api.ts +34 -18
  66. package/src/pi/tool/web-search/web-search.ts +0 -1
  67. package/src/pi/tool/workspace-revision.ts +64 -0
  68. package/src/pi/tool/workspace-sandbox.ts +105 -263
  69. package/src/pi/turn/index.ts +20 -0
  70. package/src/pi/turn/interaction.ts +181 -0
  71. package/src/pi/turn/tool-recovery.ts +244 -1
  72. package/src/runtime-agent-context.ts +112 -0
  73. package/src/runtime-agent.ts +568 -321
  74. package/src/runtime-assembler.ts +797 -0
  75. package/src/runtime-definition.ts +175 -0
  76. package/src/runtime.ts +840 -208
  77. package/src/tool-registry.ts +143 -0
  78. package/src/workspace-versioning.ts +46 -0
  79. package/src/plugins.ts +0 -1033
@@ -1,12 +1,12 @@
1
1
  import {
2
- createWorkspaceStateBackend,
3
- type FileSystemStateBackend,
4
- type WorkspaceFsLike,
5
- } from "@cloudflare/shell";
6
- import {
2
+ createBashTool,
7
3
  createDeleteTool,
4
+ createEditTool,
8
5
  createFindTool,
6
+ createGrepTool,
9
7
  createListTool,
8
+ createReadTool,
9
+ createWriteTool,
10
10
  } from "@cloudflare/think/tools/workspace";
11
11
  import type {
12
12
  AgentTool,
@@ -21,6 +21,10 @@ import type {
21
21
  import { serializeOutput } from "../../lib/artifacts";
22
22
  import { aiToolToPi } from "./ai-adapter";
23
23
  import type { PiToolCandidate } from "./compiler";
24
+ import {
25
+ toolRegistryFromPiCandidates,
26
+ type ToolRegistry,
27
+ } from "../../tool-registry";
24
28
 
25
29
  // #region Shared Pi result helpers
26
30
 
@@ -54,15 +58,6 @@ function running(
54
58
 
55
59
  // #region Workspace tools
56
60
 
57
- const workspaceWriteParameters = Type.Object({
58
- path: Type.String({
59
- minLength: 1,
60
- maxLength: 4_096,
61
- description: "Absolute Workspace path",
62
- }),
63
- content: Type.String({ description: "Content to write" }),
64
- });
65
-
66
61
  const workspaceReadParameters = Type.Object({
67
62
  path: Type.String({
68
63
  minLength: 1,
@@ -77,32 +72,6 @@ const workspaceEditParameters = Type.Object({
77
72
  old_string: Type.String(),
78
73
  new_string: Type.String(),
79
74
  });
80
- const workspaceGrepParameters = Type.Object({
81
- query: Type.String(),
82
- include: Type.Optional(Type.String({ minLength: 1, maxLength: 4_096 })),
83
- fixedString: Type.Optional(Type.Boolean()),
84
- caseSensitive: Type.Optional(Type.Boolean()),
85
- contextLines: Type.Optional(Type.Integer({ minimum: 0, maximum: 10 })),
86
- });
87
-
88
- const MAX_READ_LINES = 2_000;
89
- const MAX_READ_LINE_CHARS = 2_000;
90
- const MAX_GREP_MATCHES = 200;
91
-
92
- // 作用:计算一个非空字符串在文本中出现了多少次。
93
- // 调用:edit 在精确替换前调用,空 old_string 已由创建文件分支提前处理。
94
- // 原因:只允许唯一命中可避免模型给出短片段时误改多个位置。
95
- function countOccurrences(text: string, search: string): number {
96
- let count = 0;
97
- let position = 0;
98
- while (true) {
99
- const index = text.indexOf(search, position);
100
- if (index === -1) return count;
101
- count += 1;
102
- position = index + 1;
103
- }
104
- }
105
-
106
75
  /**
107
76
  * 把模型给的路径收敛成稳定的闸键。
108
77
  *
@@ -176,164 +145,98 @@ function serializeByPath<T>(
176
145
  *
177
146
  * 本文件中 “Workspace” 指会话作用域的持久文件视图,“Port” 指 Runtime 只依赖
178
147
  * 的窄文件接口,“候选工具”指进入 Pi 编译和统一治理前的描述。read、write、
179
- * edit、grep 在这里保留现有结果契约;list、find、delete 复用 @cloudflare/think
180
- * 的上游工厂,避免维护第二套同义文件操作。
148
+ * edit、list、find、grep、delete 复用 @cloudflare/think 的上游工厂。
181
149
  *
182
150
  * `Pi`、`Port` 与“候选工具”等项目核心术语见 `../../index.ts`。
183
151
  */
152
+ /** Pi 编译路径:从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 候选项。 */
184
153
  export function workspacePiToolCandidates(
185
154
  workspace: WorkspacePort,
186
155
  ): PiToolCandidate[] {
187
- let backend: FileSystemStateBackend | undefined;
188
- // 作用:按需创建并复用 @cloudflare/shell 的 Workspace 搜索后端。
189
- // 调用:grep execute 第一次搜索及后续搜索时调用。
190
- // 原因:只有 grep 需要该适配器,
191
- // 延迟创建可避免普通读写 Turn 支付无用初始化成本。
192
- const searchBackend = (): FileSystemStateBackend => {
193
- backend ??= createWorkspaceStateBackend(
194
- workspace as unknown as WorkspaceFsLike,
195
- );
196
- return backend;
197
- };
156
+ const readTool = aiToolToPi(
157
+ "read",
158
+ createReadTool({ ops: workspace }),
159
+ { label: "Read file" },
160
+ );
198
161
  const read: AgentTool<typeof workspaceReadParameters> = {
199
- name: "read",
200
- label: "Read file",
201
- description:
202
- "Read a Workspace text file with line numbers. Use offset and limit for large files.",
162
+ name: readTool.name,
163
+ label: readTool.label,
164
+ description: readTool.description,
203
165
  parameters: workspaceReadParameters,
204
- // 作用:分段读取一个 Workspace 文本文件,并返回带行号的内容。
205
- // 调用:Pi 需要查看文件或为后续精确编辑取上下文时调用。
206
- // 原因:先核对文件类型,再限制行数和单行长度,
207
- // 避免一次结果挤满模型上下文。
208
- async execute(_toolCallId, { path, offset, limit }, signal, onUpdate) {
166
+ async execute(toolCallId, params, signal, onUpdate) {
209
167
  signal?.throwIfAborted();
210
168
  running(onUpdate, "Reading the Workspace file.");
211
- const info = await workspace.stat(path);
212
- if (!info) throw new Error(`File not found: ${path}`);
213
- if (info.type !== "file") {
214
- throw new Error(`${path} is not a file`);
215
- }
216
- const content = await workspace.readFile(path);
217
- if (content === null) {
218
- throw new Error(`Could not read file: ${path}`);
219
- }
220
- const lines = content.split("\n");
221
- const start = (offset ?? 1) - 1;
222
- const requestedEnd = limit === undefined ? lines.length : start + limit;
223
- const end = Math.min(requestedEnd, start + MAX_READ_LINES);
224
- const numbered = lines.slice(start, end).map((line, index) => {
225
- const visible =
226
- line.length > MAX_READ_LINE_CHARS
227
- ? `${line.slice(0, MAX_READ_LINE_CHARS)}... (truncated)`
228
- : line;
229
- return `${start + index + 1}\t${visible}`;
230
- });
231
- return result({
232
- path,
233
- content: numbered.join("\n"),
234
- totalLines: lines.length,
235
- fromLine: start + 1,
236
- toLine: Math.min(end, lines.length),
237
- ...(requestedEnd > end ? { truncated: true } : {}),
238
- });
169
+ const output = await readTool.execute(
170
+ toolCallId,
171
+ params,
172
+ signal,
173
+ onUpdate,
174
+ );
175
+ const error = (output.details as { error?: unknown })?.error;
176
+ if (typeof error === "string") throw new Error(error);
177
+ return output;
239
178
  },
240
179
  };
241
180
 
242
- const write: AgentTool<typeof workspaceWriteParameters> = {
243
- name: "write",
244
- label: "Write file",
245
- description:
246
- "Write content to a file in this Chat's scoped Workspace. Parent directories are created automatically.",
247
- parameters: workspaceWriteParameters,
248
- // 作用:把完整文本写入 Workspace 文件,并按需创建父目录。
249
- // 调用:Pi 明确要创建或覆盖文件时调用。
250
- // 原因:目录创建和写入都经同一个 WorkspacePort,
251
- // 不能绕过会话路径与配额策略。
252
- async execute(_toolCallId, { path, content }, signal, onUpdate) {
181
+ const writeTool = aiToolToPi(
182
+ "write",
183
+ createWriteTool({ ops: workspace }),
184
+ { label: "Write file" },
185
+ );
186
+ const write: AgentTool<any, unknown> = {
187
+ ...writeTool,
188
+ async execute(toolCallId, params, signal, onUpdate) {
189
+ const path = (params as { path?: unknown }).path;
190
+ if (typeof path !== "string") {
191
+ return writeTool.execute(toolCallId, params, signal, onUpdate);
192
+ }
253
193
  signal?.throwIfAborted();
254
194
  running(onUpdate, "Writing the Workspace file.");
255
195
 
256
196
  return serializeByPath(workspace, path, async () => {
257
197
  // 排队期间可能已经被取消,拿到闸之后必须重新确认,否则会写一个已放弃的结果。
258
198
  signal?.throwIfAborted();
259
- const separator = path.lastIndexOf("/");
260
- const parent = separator > 0 ? path.slice(0, separator) : "";
261
- if (parent && parent !== "/") {
262
- await workspace.mkdir(parent, { recursive: true });
263
- }
264
- await workspace.writeFile(path, content);
265
-
266
- return result({
267
- path,
268
- bytesWritten: new TextEncoder().encode(content).byteLength,
269
- lines: content.split("\n").length,
270
- });
199
+ return writeTool.execute(
200
+ toolCallId,
201
+ params,
202
+ signal,
203
+ onUpdate,
204
+ );
271
205
  });
272
206
  },
273
207
  };
274
208
 
209
+ const editTool = aiToolToPi(
210
+ "edit",
211
+ createEditTool({ ops: workspace }),
212
+ { label: "Edit file" },
213
+ );
275
214
  const edit: AgentTool<typeof workspaceEditParameters> = {
276
- name: "edit",
277
- label: "Edit file",
278
- description:
279
- "Replace one exact string in a Workspace file. An empty old_string creates a new file.",
215
+ name: editTool.name,
216
+ label: editTool.label,
217
+ description: editTool.description,
280
218
  parameters: workspaceEditParameters,
281
- // 作用:唯一命中时精确替换文本,或用空 old_string 创建新文件。
282
- // 调用:Pi 已读取文件并能提供足够上下文时调用。
283
- // 原因:零命中和多命中都拒绝写入,
284
- // 防止过短片段静默改错位置。
285
- async execute(
286
- _toolCallId,
287
- { path, old_string, new_string },
288
- signal,
289
- onUpdate,
290
- ) {
219
+ async execute(toolCallId, params, signal, onUpdate) {
291
220
  signal?.throwIfAborted();
292
221
  running(onUpdate, "Editing the Workspace file.");
293
222
  // 读-比对-写整段都在闸内:闸只包住最后那次 writeFile 的话,基准内容仍然可能在
294
223
  // 比对之后被别的调用换掉,丢写照旧发生。
295
- return serializeByPath(workspace, path, async () => {
224
+ return serializeByPath(workspace, params.path, async () => {
296
225
  signal?.throwIfAborted();
297
- const content = await workspace.readFile(path);
298
- if (old_string === "") {
299
- if (content !== null) {
300
- throw new Error(
301
- "File already exists. Provide old_string to edit, or use write to overwrite.",
302
- );
303
- }
304
- await workspace.writeFile(path, new_string);
305
- return result({
306
- path,
307
- created: true,
308
- lines: new_string.split("\n").length,
309
- });
310
- }
311
- if (content === null) {
312
- throw new Error(`File not found: ${path}`);
313
- }
314
- const occurrences = countOccurrences(content, old_string);
315
- if (occurrences === 0) {
316
- throw new Error(
317
- "old_string not found in file. Read the file first and match whitespace exactly.",
318
- );
319
- }
320
- if (occurrences > 1) {
321
- throw new Error(
322
- `old_string appears ${occurrences} times. Include more surrounding context.`,
323
- );
324
- }
325
- const next = content.replace(old_string, new_string);
326
- await workspace.writeFile(path, next);
327
- return result({
328
- path,
329
- replaced: true,
330
- lines: next.split("\n").length,
331
- });
226
+ const output = await editTool.execute(
227
+ toolCallId,
228
+ params,
229
+ signal,
230
+ onUpdate,
231
+ );
232
+ const error = (output.details as { error?: unknown })?.error;
233
+ if (typeof error === "string") throw new Error(error);
234
+ return output;
332
235
  });
333
236
  },
334
237
  };
335
238
 
336
- // list / find / delete come from the upstream think factories: WorkspacePort
239
+ // list / find / grep / delete come from the upstream think factories: WorkspacePort
337
240
  // already satisfies their ops interfaces structurally, and their output shapes
338
241
  // match what this file used to build by hand.
339
242
  const list = aiToolToPi("list", createListTool({ ops: workspace }), {
@@ -344,95 +247,9 @@ export function workspacePiToolCandidates(
344
247
  label: "Find files",
345
248
  });
346
249
 
347
- const grep: AgentTool<typeof workspaceGrepParameters> = {
348
- name: "grep",
250
+ const grep = aiToolToPi("grep", createGrepTool({ ops: workspace }), {
349
251
  label: "Search files",
350
- description:
351
- "Search bounded Workspace file contents with a regex or fixed string.",
352
- parameters: workspaceGrepParameters,
353
- // 作用:在匹配 glob 的 Workspace 文件中做有上限的文本搜索。
354
- // 调用:Pi 需要定位定义、调用方或内容片段时调用。
355
- // 原因:搜索交给 @cloudflare/shell 后端并限制命中数;
356
- // 正则先在本地检查以保留既有错误契约。
357
- async execute(
358
- _toolCallId,
359
- {
360
- query,
361
- include = "**/*",
362
- fixedString = false,
363
- caseSensitive = false,
364
- contextLines = 0,
365
- },
366
- signal,
367
- onUpdate,
368
- ) {
369
- signal?.throwIfAborted();
370
- running(onUpdate, "Searching Workspace files.");
371
- // Reject bad patterns before reaching the backend so the error text stays
372
- // identical to the previous in-isolate implementation.
373
- if (!fixedString) {
374
- try {
375
- new RegExp(query);
376
- } catch {
377
- throw new Error(`Invalid regex: ${query}`);
378
- }
379
- }
380
-
381
- // TODO(待确认):@cloudflare/shell@0.4.3 的 searchFiles 会先 glob,再逐个
382
- // 调 readFile;WorkspacePort 跨 DO 时,内容可能仍会经 RPC 回到当前 isolate。
383
- const hits = await searchBackend().searchFiles(include, query, {
384
- regex: !fixedString,
385
- caseSensitive,
386
- maxMatches: MAX_GREP_MATCHES,
387
- ...(contextLines > 0
388
- ? { contextBefore: contextLines, contextAfter: contextLines }
389
- : {}),
390
- });
391
- signal?.throwIfAborted();
392
-
393
- const matches: Array<string | {
394
- file: string;
395
- line: number;
396
- context: string;
397
- }> = [];
398
- let filesWithMatches = 0;
399
-
400
- for (const hit of hits) {
401
- if (matches.length >= MAX_GREP_MATCHES) break;
402
- if (hit.matches.length > 0) filesWithMatches += 1;
403
- for (const match of hit.matches) {
404
- if (matches.length >= MAX_GREP_MATCHES) break;
405
- if (contextLines === 0) {
406
- matches.push(`${hit.path}:${match.line}: ${match.lineText}`);
407
- continue;
408
- }
409
- const before = match.beforeLines ?? [];
410
- const after = match.afterLines ?? [];
411
- const firstLine = match.line - before.length;
412
- const context = [...before, match.lineText, ...after]
413
- .map(
414
- (line, offset) =>
415
- `${firstLine + offset === match.line ? ">" : " "} ${
416
- firstLine + offset
417
- }\t${line}`,
418
- )
419
- .join("\n");
420
- matches.push({ file: hit.path, line: match.line, context });
421
- }
422
- }
423
-
424
- return result({
425
- query,
426
- filesSearched: hits.length,
427
- filesWithMatches,
428
- totalMatches: matches.length,
429
- matches,
430
- ...(matches.length >= MAX_GREP_MATCHES
431
- ? { truncated: true }
432
- : {}),
433
- });
434
- },
435
- };
252
+ });
436
253
 
437
254
  // delete 来自上游 think 工厂,只能在外面包一层闸。它自己没有读-改-写窗口,但必须与
438
255
  // edit 的窗口互斥:否则 edit 读到内容 → delete 删掉文件 → edit 落盘,已删的文件会被
@@ -456,12 +273,37 @@ export function workspacePiToolCandidates(
456
273
  },
457
274
  };
458
275
 
459
- return [read, write, edit, list, find, grep, remove].map((tool) => ({
460
- owner: "workspace",
461
- authorized: true,
462
- requiredExecutionLevel: "safe" as const,
463
- tool,
464
- }));
276
+ const bash = aiToolToPi(
277
+ "bash",
278
+ createBashTool({ ops: workspace }),
279
+ {
280
+ label: "Workspace Bash",
281
+ description:
282
+ "Run a Bash script over Workspace files for shell-style workflows that combine multiple file operations. " +
283
+ "The Workspace is mounted at / and changes are written back. This is a virtual filesystem, not a machine: " +
284
+ "there is no network or system utilities; use execute for fetch and sandbox_exec for system commands.",
285
+ },
286
+ );
287
+
288
+ return [
289
+ ...[read, write, edit, list, find, grep, remove].map((tool) => ({
290
+ owner: "workspace",
291
+ requiredExecutionLevel: "safe" as const,
292
+ tool,
293
+ })),
294
+ {
295
+ owner: "workspace",
296
+ requiredExecutionLevel: "high" as const,
297
+ summary: "Run a Bash script over Workspace files",
298
+ source: "action" as const,
299
+ tool: bash,
300
+ },
301
+ ];
302
+ }
303
+
304
+ /** 从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 集(read / write / edit …)。 */
305
+ export function createWorkspaceTools(workspace: WorkspacePort): ToolRegistry {
306
+ return toolRegistryFromPiCandidates(workspacePiToolCandidates(workspace));
465
307
  }
466
308
 
467
309
  // #endregion
@@ -630,35 +472,35 @@ export function sandboxPiToolCandidates(
630
472
  return [
631
473
  {
632
474
  owner: "sandbox",
633
- authorized: true,
634
475
  requiredExecutionLevel: "high",
635
476
  tool: exec,
636
477
  },
637
478
  {
638
479
  owner: "sandbox",
639
- authorized: true,
640
480
  requiredExecutionLevel: "high",
641
481
  tool: startProcess,
642
482
  },
643
483
  {
644
484
  owner: "sandbox",
645
- authorized: true,
646
485
  requiredExecutionLevel: "safe",
647
486
  tool: processLogs,
648
487
  },
649
488
  {
650
489
  owner: "sandbox",
651
- authorized: true,
652
490
  requiredExecutionLevel: "high",
653
491
  tool: stopProcess,
654
492
  },
655
493
  {
656
494
  owner: "sandbox",
657
- authorized: true,
658
495
  requiredExecutionLevel: "high",
659
496
  tool: publishFiles,
660
497
  },
661
498
  ];
662
499
  }
663
500
 
501
+ /** 从 {@link RuntimeSandboxPort} 生成 Sandbox Tool 集。 */
502
+ export function createSandboxTools(sandbox: RuntimeSandboxPort): ToolRegistry {
503
+ return toolRegistryFromPiCandidates(sandboxPiToolCandidates(sandbox));
504
+ }
505
+
664
506
  // #endregion
@@ -7,6 +7,8 @@
7
7
  * 本目录统一使用以下术语:
8
8
  *
9
9
  * - **审批(approval)**:Tool handler 运行前持久化的用户许可;`pending` 状态会暂停 Turn。
10
+ * - **交互(interaction)**:结果由客户端投递而非 `execute()` 产出的 Tool 调用;`pending` 状态同样暂停 Turn。
11
+ * 与审批的差别只在响应体:审批是三态枚举,交互是任意 JSON。
10
12
  * - **里程碑(milestone)**:属于同一个 `turnId` 和 `assemblyRevision` 的持久 JSON 记录。
11
13
  * - **结算结果(settled ToolResult)**:已持久化且恢复时必须复用、不能再次执行副作用的 Tool 结果。
12
14
  * - **续跑(continuation)**:审批或 Tool 结算后恢复原 Turn 的动作;先记 `pending`,再记 `committed`。
@@ -21,12 +23,16 @@ import {
21
23
  parkPiToolApproval,
22
24
  requiresPiToolApproval,
23
25
  } from "./approval";
26
+ import { parkPiToolInteraction } from "./interaction";
24
27
  import {
25
28
  applyRecoveredPiApprovalDecision,
29
+ applyRecoveredPiToolInteractionSettlement,
26
30
  commitPiRecoveryContinuation,
27
31
  encodePiToolRecoveryMilestone,
28
32
  planPiToolRecovery,
33
+ recordRecoveredPiToolInteraction,
29
34
  replayPiToolRecovery,
35
+ stagePiAssemblyRepin,
30
36
  stagePiRecoveryContinuation,
31
37
  } from "./tool-recovery";
32
38
 
@@ -51,12 +57,26 @@ export const piApproval = Object.freeze({
51
57
  */
52
58
  export const piRecovery = Object.freeze({
53
59
  applyApprovalDecision: applyRecoveredPiApprovalDecision,
60
+ applyInteractionSettlement: applyRecoveredPiToolInteractionSettlement,
54
61
  commitContinuation: commitPiRecoveryContinuation,
55
62
  encodeMilestone: encodePiToolRecoveryMilestone,
56
63
  plan: planPiToolRecovery,
64
+ recordInteraction: recordRecoveredPiToolInteraction,
57
65
  replay: replayPiToolRecovery,
66
+ stageAssemblyRepin: stagePiAssemblyRepin,
58
67
  stageContinuation: stagePiRecoveryContinuation,
59
68
  });
60
69
 
70
+ /**
71
+ * 提供 Pi Tool interaction(结果由客户端提供的 Tool)的 park 入口。
72
+ *
73
+ * 执行适配器发现 Tool 声明了 `interaction` 后调用 `park` 生成待响应状态;
74
+ * 响应与取消是有状态操作,走 `piRecovery.applyInteractionSettlement`,不放在这个门面里。
75
+ */
76
+ export const piInteraction = Object.freeze({
77
+ park: parkPiToolInteraction,
78
+ });
79
+
61
80
  export * from "./approval";
81
+ export * from "./interaction";
62
82
  export * from "./tool-recovery";