@springbrand/agent-runtime 0.3.0-alpha.8 → 0.3.0-alpha.9

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 (87) hide show
  1. package/package.json +2 -2
  2. package/src/adapter/cloudflare/subagent/definition.ts +4 -4
  3. package/src/adapter/cloudflare/subagent/runner.ts +1 -1
  4. package/src/adapter/cloudflare/subagent/tools.ts +2 -2
  5. package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
  6. package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
  7. package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
  8. package/src/config/agent-context.ts +1 -1
  9. package/src/config/assembler.ts +17 -31
  10. package/src/config/assembly-snapshot.ts +1 -1
  11. package/src/config/definition.ts +1 -1
  12. package/src/config/model-guidance.ts +1 -1
  13. package/src/config/ports.ts +3 -3
  14. package/src/config/public-contracts.ts +2 -2
  15. package/src/context/extension/assembly.ts +2 -2
  16. package/src/context/memory/budget/gate.ts +1 -1
  17. package/src/conversation/state.ts +1 -1
  18. package/src/index.ts +7 -7
  19. package/src/lib/prompt.ts +3 -5
  20. package/src/models/index.ts +1 -0
  21. package/src/session/cloudflare/migration.ts +29 -8
  22. package/src/skills/springbrand-worker-website/index.ts +1 -1
  23. package/src/submission/delivery.ts +1 -1
  24. package/src/submission/finalization.ts +1 -1
  25. package/src/telemetry/coordinator.ts +1 -1
  26. package/src/telemetry/subagent-lifecycle.ts +1 -1
  27. package/src/telemetry/turn-events.ts +1 -1
  28. package/src/tools/README.md +44 -0
  29. package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
  30. package/src/tools/{declared.ts → adapters/declared.ts} +2 -2
  31. package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +2 -2
  32. package/src/tools/assistant/ask-user.ts +317 -0
  33. package/src/tools/assistant/candidate.ts +15 -0
  34. package/src/tools/assistant/followups.ts +34 -0
  35. package/src/tools/assistant/index.ts +19 -0
  36. package/src/tools/assistant/memory.ts +73 -0
  37. package/src/tools/assistant/plan.ts +98 -0
  38. package/src/tools/{time.ts → assistant/time.ts} +1 -1
  39. package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -49
  40. package/src/tools/{core.ts → codemode/index.ts} +36 -92
  41. package/src/tools/codemode/workspace-connector.ts +198 -0
  42. package/src/tools/extension/index.ts +42 -0
  43. package/src/tools/index.ts +28 -26
  44. package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
  45. package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
  46. package/src/tools/{approval → runtime/approval}/repository.ts +3 -9
  47. package/src/tools/{compiler.ts → runtime/compiler.ts} +24 -157
  48. package/src/tools/runtime/contracts.ts +117 -0
  49. package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +6 -9
  50. package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
  51. package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
  52. package/src/tools/runtime/result.ts +22 -0
  53. package/src/tools/sandbox/index.ts +228 -0
  54. package/src/tools/{schedule.ts → schedule/index.ts} +50 -49
  55. package/src/tools/skill/contracts.ts +20 -0
  56. package/src/tools/skill/index.ts +99 -0
  57. package/src/tools/skill/resources.ts +322 -0
  58. package/src/tools/skill/scripts.ts +168 -0
  59. package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
  60. package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
  61. package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
  62. package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
  63. package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
  64. package/src/tools/{temporary-agent → subagent/temporary}/core.ts +1 -1
  65. package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
  66. package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
  67. package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
  68. package/src/tools/web/index.ts +2 -0
  69. package/src/tools/web/search/api.ts +1320 -0
  70. package/src/tools/web/search/index.ts +2 -0
  71. package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
  72. package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
  73. package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
  74. package/src/tools/base.ts +0 -542
  75. package/src/tools/skill.ts +0 -565
  76. package/src/tools/web-search/api.ts +0 -1318
  77. package/src/tools/web-search/index.ts +0 -2
  78. package/src/tools/workspace-state-connector.ts +0 -146
  79. package/src/tools/{approval → runtime/approval}/index.ts +0 -0
  80. package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
  81. package/src/tools/{unattended-error.ts → runtime/unattended-error.ts} +0 -0
  82. package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
  83. package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
  84. package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
  85. package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
  86. package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
  87. /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
@@ -1,6 +1,6 @@
1
- import type { RuntimeGatewaySession } from "../config/ports";
2
- import type { PiToolCandidate } from "./compiler";
3
- import { createPiMcpToolCandidate } from "./mcp";
1
+ import type { RuntimeGatewaySession } from "../../config/ports";
2
+ import type { PiToolCandidate } from "../runtime/contracts";
3
+ import { createPiMcpToolCandidate } from "./index";
4
4
 
5
5
  const GATEWAY_TOOLS = [
6
6
  "search_capabilities",
@@ -31,22 +31,22 @@ export function createPiGatewayToolCandidates(
31
31
  requiredExecutionLevel: execute ? "high" : "safe",
32
32
  ...(execute
33
33
  ? {
34
- requiredExecutionLevelForInput: async (input: unknown) => {
35
- const reference = input && typeof input === "object" &&
36
- !Array.isArray(input)
37
- ? (input as Record<string, unknown>).name
38
- : undefined;
39
- if (typeof reference !== "string" || !reference) {
40
- throw new Error(
41
- "Gateway Capability Reference is not verified",
42
- );
43
- }
44
- return (await session.resolveCapabilityRisk(reference)) ===
45
- "none"
46
- ? "safe" as const
47
- : "high" as const;
48
- },
49
- }
34
+ requiredExecutionLevelForInput: async (input: unknown) => {
35
+ const reference = input && typeof input === "object" &&
36
+ !Array.isArray(input)
37
+ ? (input as Record<string, unknown>).name
38
+ : undefined;
39
+ if (typeof reference !== "string" || !reference) {
40
+ throw new Error(
41
+ "Gateway Capability Reference is not verified",
42
+ );
43
+ }
44
+ return (await session.resolveCapabilityRisk(reference)) ===
45
+ "none"
46
+ ? "safe" as const
47
+ : "high" as const;
48
+ },
49
+ }
50
50
  : {}),
51
51
  },
52
52
  );
@@ -1,6 +1,6 @@
1
- import type { RuntimeMcpServer } from "../config/profile";
2
- import type { PiToolCandidate } from "./compiler";
3
- import { createPiDeclaredToolCandidate } from "./declared";
1
+ import type { RuntimeMcpServer } from "../../config/profile";
2
+ import { createPiDeclaredToolCandidate } from "../adapters/declared";
3
+ import type { PiToolCandidate } from "../runtime/contracts";
4
4
 
5
5
  interface McpCallResult {
6
6
  content: unknown[];
@@ -34,7 +34,7 @@ interface PiMcpCandidatePolicy {
34
34
  * 单个 Server 的客户端,Server 是对外提供工具的服务。连接持久化、OAuth 和
35
35
  * 恢复仍由 Agents SDK 的 MCP Client 管理,本接口只读取和调用。
36
36
  *
37
- * `Host` 与 `Pi` 等项目核心术语见 `../../index.ts`。
37
+ * `Host` 与 `Pi` 等项目核心术语见 `src/index.ts`。
38
38
  */
39
39
  export interface PiMcpHost {
40
40
  /**
@@ -284,7 +284,7 @@ export function createPiMcpToolCandidate(
284
284
  * 远端 Server 提供的 annotations 只是提示,不是本地审批边界,所以所有动态
285
285
  * MCP 工具继续标为高风险,不能据此绕过审批。
286
286
  *
287
- * `Host`、`Pi`、`Runtime Snapshot` 与“候选工具”见 `../../index.ts`。
287
+ * `Host`、`Pi`、`Runtime Snapshot` 与“候选工具”见 `src/index.ts`。
288
288
  */
289
289
  export async function createPiMcpToolCandidates(
290
290
  host: PiMcpHost,
@@ -1,8 +1,7 @@
1
1
  import type { SqlTaggedTemplate } from "agents/chat";
2
- import type { ExecutionLevel } from "../../lib/execution-level";
2
+ import type { ExecutionLevel } from "../../../lib/execution-level";
3
3
 
4
4
  // Approval 的统一定义见 ./index.ts。
5
- // #region 状态与行类型
6
5
  export type ApprovalStatus = "pending" | "approved" | "rejected";
7
6
 
8
7
  export interface StoredApproval {
@@ -76,8 +75,6 @@ type ApprovalRow = {
76
75
  reason: string | null;
77
76
  };
78
77
 
79
- // #endregion
80
- // #region 行映射
81
78
  // 把 SQLite 的 Approval 行转成 Runtime 对象。
82
79
  // find 查到记录后调用,上层因而不需要理解 snake_case 列名。
83
80
  // 状态由表内字符串转成联合类型;新增列或状态时必须同步核对类型、SELECT 和映射。
@@ -101,13 +98,11 @@ function mapRow(row: ApprovalRow): StoredApproval {
101
98
  };
102
99
  }
103
100
 
104
- // #endregion
105
- // #region Repository
106
101
  export class ApprovalRepository {
107
102
  // 保存当前 Agent SQLite 的查询入口。
108
103
  // RuntimeDatabase 构造时调用,HarnessDecisionGate 和 Transcript 随后共享这个 Repository。
109
104
  // Approval 必须与所属 Submission 在同一 Agent 存储中查询,不能跨 Durable Object 拼接状态。
110
- constructor(private readonly sql: SqlTaggedTemplate) {}
105
+ constructor(private readonly sql: SqlTaggedTemplate) { }
111
106
 
112
107
  // 按 executionId 读取一条完整 Approval,不存在时返回 null。
113
108
  // HarnessDecisionGate 在请求去重、决策、继续和拒绝时调用,调用方必须处理 null。
@@ -198,7 +193,7 @@ export class ApprovalRepository {
198
193
  execution_level: string;
199
194
  required_level: string;
200
195
  input_json: string;
201
- confirmation_json: string | null;
196
+ confirmation_json: string | null;
202
197
  request_id: string;
203
198
  }>`
204
199
  SELECT approval.execution_id, approval.tool_call_id, approval.source,
@@ -278,4 +273,3 @@ export class ApprovalRepository {
278
273
  }));
279
274
  }
280
275
  }
281
- // #endregion
@@ -1,145 +1,16 @@
1
- import type {
2
- AgentHarnessTool,
3
- AgentTool,
4
- AgentToolResult,
5
- } from "@earendil-works/pi-agent-core";
6
- import type { ApprovalReceipt } from "../submission/contracts";
7
- import {
8
- boundDurableToolOutput,
9
- type BudgetPolicy,
10
- spillDurableToolOutput,
11
- } from "../context/memory/budget/gate";
12
- import {
13
- type ArtifactRef,
14
- serializeOutput,
15
- type SpillWorkspace,
16
- } from "../lib/artifacts";
17
- import {
18
- type ExecutionLevel,
19
- } from "../lib/execution-level";
1
+ import type { AgentHarnessTool, AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import { boundDurableToolOutput, spillDurableToolOutput } from "../../context/memory/budget/gate";
3
+ import { serializeOutput, type ArtifactRef, type SpillWorkspace } from "../../lib/artifacts";
4
+ import type { CompileHarnessToolsOptions, PiToolCandidate } from "./contracts";
20
5
  import { UnattendedExecutionError } from "./unattended-error";
21
6
 
22
- // 本文件沿用 `../../index.ts` 入口定义的 Tool Candidate 和 Tool Settlement 术语。
23
-
24
- // #region Public contracts
25
-
26
- /**
27
- * 声明一个 Tool 的结果由客户端投递,而不是由 `execute()` 产出。
28
- *
29
- * 带这个字段的 Tool 会在执行链里 park 住,等宿主经 `respondToolInteraction` 把响应送进来,
30
- * 再用 `settle` 把响应映射成这次 `toolCallId` 的权威 ToolResult。它的 `execute` 不会被调用。
31
- *
32
- * 这是 AI SDK 里「没有 execute 的 user-interaction tool」的等价物;差别是我们 park 住整个 Turn,
33
- * 而不是让 tool part 悬空到下一次请求 —— 我们的 transcript 是服务端权威的,悬空的 tool_use
34
- * 会让下一次模型调用非法。
35
- */
36
- export interface PiToolInteractionSpec {
37
- /** 结合 Tool 输入校验客户端响应体;不通过则拒绝投递,park 保持不变。 */
38
- readonly validateResponse: (input: unknown, response: unknown) => boolean;
39
- /** 把通过校验的响应映射成 ToolResult;缺省是原样回传。 */
40
- readonly settle?: (
41
- input: unknown,
42
- response: unknown,
43
- ) => Omit<AgentToolResult<unknown>, "terminate">;
44
- }
45
-
46
- /** Trusted Tool metadata used to evaluate a scheduled task grant. */
47
- export interface PiToolUnattendedExecutionSpec {
48
- readonly capability: string;
49
- readonly operation: string;
50
- readonly objectForInput?: (
51
- input: unknown,
52
- ) => unknown | Promise<unknown>;
53
- }
54
-
55
- /** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
56
- export type ToolExposureMode = "direct" | "codemode" | "both";
57
-
58
- /** Host-prepared confirmation travels through the existing durable approval. */
59
- export interface PiToolConfirmationSpec {
60
- prepare(input: unknown, context: { intentId: string }): Promise<unknown>;
61
- execute(prepared: unknown, context: { intentId: string; signal: AbortSignal }): Promise<AgentToolResult<unknown>>;
62
- }
63
-
64
- export interface PiToolCandidate {
65
- /** Presentation only, resolved at the model boundary; never changes approved execution inputs. */
66
- readonly modelGuidance?: () => Promise<{ description: string; systemPrompt?: string }>;
67
- readonly confirmation?: PiToolConfirmationSpec;
68
- readonly owner: string;
69
- readonly tool: AgentTool<any, any>;
70
- /** @internal Send the complete schema but hide it until provider Tool Search finds it. */
71
- readonly deferLoading?: true;
72
- /** Omitted means visible both directly and through Code Mode. */
73
- readonly exposureMode?: ToolExposureMode;
74
- /** Conservative maximum used in the stable Runtime descriptor. */
75
- readonly requiredExecutionLevel: ExecutionLevel;
76
- /** Trusted parameter-level policy, evaluated before approval or dispatch. */
77
- readonly requiredExecutionLevelForInput?: (
78
- input: unknown,
79
- ) => ExecutionLevel | Promise<ExecutionLevel>;
80
- /**
81
- * 无条件需要人来决定:不看执行档位矩阵,每次调用都 park 出审批。
82
- *
83
- * 档位差是「够不够格自己跑」,不是「该不该问人」。花用户钱、改变账户
84
- * 状态这类工具,一旦 Agent 档位被 `allow_level` 永久拉高就会静默执行,
85
- * 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
86
- */
87
- readonly alwaysRequiresApproval?: boolean;
88
- /** Declares the concrete grant identity derived from actual Tool input. */
89
- readonly unattendedExecution?: PiToolUnattendedExecutionSpec;
90
- /** Explicitly forbids this Tool from unattended Scheduled Runs. */
91
- readonly unattendedMode?: "forbidden";
92
- /** Recovery may replay this Tool Call only when the candidate guarantees convergence. Omitted means non-idempotent. */
93
- readonly retry?: "idempotent" | "non-idempotent";
94
- readonly summary?: string;
95
- readonly source?: ApprovalReceipt["source"];
96
- readonly interaction?: PiToolInteractionSpec;
97
- /** Large payloads spill by default; protocol-shaped results opt into structure. */
98
- readonly outputBudget?: BudgetPolicy;
99
- }
100
-
101
- export interface CompileHarnessToolsOptions {
102
- readonly workspace?: SpillWorkspace;
103
- readonly resolveConfirmation?: (candidate: PiToolCandidate, toolCallId: string, args: unknown) => { prepared: unknown; intentId: string };
104
- /** Recheck live authorization at the effect boundary, including safe replay. */
105
- readonly authorize?: (
106
- candidate: PiToolCandidate,
107
- args: unknown,
108
- invocation: { readonly toolCallId: string; readonly signal: AbortSignal },
109
- ) => void | Promise<void>;
110
- /** Return an already decided interaction result without invoking the handler. */
111
- readonly resolveReplay?: (
112
- candidate: PiToolCandidate,
113
- toolCallId: string,
114
- args: unknown,
115
- ) => AgentToolResult<unknown> | undefined | Promise<AgentToolResult<unknown> | undefined>;
116
- /** Optional business projection; Pi remains the authoritative Tool result. */
117
- readonly onSettlement?: (
118
- call: SettledPiToolCall,
119
- ) => void | Promise<void>;
120
- }
121
-
122
- /** 描述一次已进入持久化结算边界的 Pi 工具调用。 */
123
- export interface SettledPiToolCall {
124
- readonly toolCallId: string;
125
- readonly toolName: string;
126
- readonly args: unknown;
127
- readonly result: AgentToolResult<unknown>;
128
- readonly isError: boolean;
129
- /** Diagnostic-only original error; callers must not persist or expose it. */
130
- readonly cause?: unknown;
131
- readonly summary?: string;
132
- readonly source?: ApprovalReceipt["source"];
133
- }
134
-
135
7
  // DO SQLite rows also contain arguments and transcript/recovery envelopes.
136
8
  // Keep each serialized result below half the 2 MiB row limit.
137
9
  const DURABLE_TOOL_RESULT_MAX_BYTES = 1024 * 1024;
10
+
138
11
  const MEDIA_CAPTION_MAX_CHARS = 4 * 1024;
139
12
 
140
- // #endregion
141
13
 
142
- // #region Governance helpers
143
14
 
144
15
  // 把任意失败值收敛为可安全显示的简短消息。
145
16
  // 受治理工具在工具执行或结算抛错时调用它。
@@ -178,11 +49,11 @@ function artifactResult(
178
49
  let captionRemaining = MEDIA_CAPTION_MAX_CHARS;
179
50
  const mediaContent = result.content.some((part) => part.type !== "text")
180
51
  ? result.content.flatMap((part): AgentToolResult<unknown>["content"] => {
181
- if (part.type !== "text") return [part];
182
- const text = part.text.slice(0, captionRemaining);
183
- captionRemaining -= text.length;
184
- return text ? [{ ...part, text }] : [];
185
- })
52
+ if (part.type !== "text") return [part];
53
+ const text = part.text.slice(0, captionRemaining);
54
+ captionRemaining -= text.length;
55
+ return text ? [{ ...part, text }] : [];
56
+ })
186
57
  : [];
187
58
  return {
188
59
  ...result,
@@ -245,9 +116,7 @@ async function untilAborted<T>(
245
116
  }
246
117
  }
247
118
 
248
- // #endregion
249
119
 
250
- // #region Governance and compilation
251
120
 
252
121
  /**
253
122
  * Convert the configured candidate surface once at the AgentHarness boundary.
@@ -263,19 +132,19 @@ export function compileHarnessTools(
263
132
  const tool = source.constrainedSampling === false
264
133
  ? source
265
134
  : {
266
- ...source,
267
- constrainedSampling: source.constrainedSampling ?? {
268
- type: "json_schema" as const,
269
- strict: "prefer" as const,
270
- },
271
- };
135
+ ...source,
136
+ constrainedSampling: source.constrainedSampling ?? {
137
+ type: "json_schema" as const,
138
+ strict: "prefer" as const,
139
+ },
140
+ };
272
141
  return {
273
142
  ...tool,
274
143
  replay: candidate.retry === "idempotent" &&
275
- candidate.requiredExecutionLevel === "safe" &&
276
- !candidate.alwaysRequiresApproval &&
277
- !candidate.requiredExecutionLevelForInput &&
278
- !candidate.interaction
144
+ candidate.requiredExecutionLevel === "safe" &&
145
+ !candidate.alwaysRequiresApproval &&
146
+ !candidate.requiredExecutionLevelForInput &&
147
+ !candidate.interaction
279
148
  ? "safe" as const
280
149
  : "never" as const,
281
150
  ...(candidate.deferLoading ? { deferLoading: true as const } : {}),
@@ -305,10 +174,10 @@ export function compileHarnessTools(
305
174
  async () => durableResult(
306
175
  await (candidate.confirmation
307
176
  ? (() => {
308
- const confirmed = options.resolveConfirmation?.(candidate, toolCallId, args);
309
- if (!confirmed) throw new Error("User confirmation is required");
310
- return candidate.confirmation.execute(confirmed.prepared, { intentId: confirmed.intentId, signal });
311
- })()
177
+ const confirmed = options.resolveConfirmation?.(candidate, toolCallId, args);
178
+ if (!confirmed) throw new Error("User confirmation is required");
179
+ return candidate.confirmation.execute(confirmed.prepared, { intentId: confirmed.intentId, signal });
180
+ })()
312
181
  : source.execute(toolCallId, args, signal, (update) => onUpdate(update))),
313
182
  candidate,
314
183
  options.workspace,
@@ -351,5 +220,3 @@ export function compileHarnessTools(
351
220
  } satisfies AgentHarnessTool<object | undefined>;
352
221
  });
353
222
  }
354
-
355
- // #endregion
@@ -0,0 +1,117 @@
1
+ import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import type { ApprovalReceipt } from "../../submission/contracts";
3
+ import { type BudgetPolicy } from "../../context/memory/budget/gate";
4
+ import { type SpillWorkspace } from "../../lib/artifacts";
5
+ import { type ExecutionLevel } from "../../lib/execution-level";
6
+
7
+ // 本文件沿用 `src/index.ts` 入口定义的 Tool Candidate 和 Tool Settlement 术语。
8
+
9
+
10
+ /**
11
+ * 声明一个 Tool 的结果由客户端投递,而不是由 `execute()` 产出。
12
+ *
13
+ * 带这个字段的 Tool 会在执行链里 park 住,等宿主经 `respondToolInteraction` 把响应送进来,
14
+ * 再用 `settle` 把响应映射成这次 `toolCallId` 的权威 ToolResult。它的 `execute` 不会被调用。
15
+ *
16
+ * 这是 AI SDK 里「没有 execute 的 user-interaction tool」的等价物;差别是我们 park 住整个 Turn,
17
+ * 而不是让 tool part 悬空到下一次请求 —— 我们的 transcript 是服务端权威的,悬空的 tool_use
18
+ * 会让下一次模型调用非法。
19
+ */
20
+ export interface PiToolInteractionSpec {
21
+ /** 结合 Tool 输入校验客户端响应体;不通过则拒绝投递,park 保持不变。 */
22
+ readonly validateResponse: (input: unknown, response: unknown) => boolean;
23
+ /** 把通过校验的响应映射成 ToolResult;缺省是原样回传。 */
24
+ readonly settle?: (
25
+ input: unknown,
26
+ response: unknown,
27
+ ) => Omit<AgentToolResult<unknown>, "terminate">;
28
+ }
29
+
30
+ /** Trusted Tool metadata used to evaluate a scheduled task grant. */
31
+ export interface PiToolUnattendedExecutionSpec {
32
+ readonly capability: string;
33
+ readonly operation: string;
34
+ readonly objectForInput?: (
35
+ input: unknown,
36
+ ) => unknown | Promise<unknown>;
37
+ }
38
+
39
+ /** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
40
+ export type ToolExposureMode = "direct" | "codemode" | "both";
41
+
42
+ /** Host-prepared confirmation travels through the existing durable approval. */
43
+ export interface PiToolConfirmationSpec {
44
+ prepare(input: unknown, context: { intentId: string }): Promise<unknown>;
45
+ execute(prepared: unknown, context: { intentId: string; signal: AbortSignal }): Promise<AgentToolResult<unknown>>;
46
+ }
47
+
48
+ export interface PiToolCandidate {
49
+ /** Presentation only, resolved at the model boundary; never changes approved execution inputs. */
50
+ readonly modelGuidance?: () => Promise<{ description: string; systemPrompt?: string }>;
51
+ readonly confirmation?: PiToolConfirmationSpec;
52
+ readonly owner: string;
53
+ readonly tool: AgentTool<any, any>;
54
+ /** @internal Send the complete schema but hide it until provider Tool Search finds it. */
55
+ readonly deferLoading?: true;
56
+ /** Omitted means visible both directly and through Code Mode. */
57
+ readonly exposureMode?: ToolExposureMode;
58
+ /** Conservative maximum used in the stable Runtime descriptor. */
59
+ readonly requiredExecutionLevel: ExecutionLevel;
60
+ /** Trusted parameter-level policy, evaluated before approval or dispatch. */
61
+ readonly requiredExecutionLevelForInput?: (
62
+ input: unknown,
63
+ ) => ExecutionLevel | Promise<ExecutionLevel>;
64
+ /**
65
+ * 无条件需要人来决定:不看执行档位矩阵,每次调用都 park 出审批。
66
+ *
67
+ * 档位差是「够不够格自己跑」,不是「该不该问人」。花用户钱、改变账户
68
+ * 状态这类工具,一旦 Agent 档位被 `allow_level` 永久拉高就会静默执行,
69
+ * 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
70
+ */
71
+ readonly alwaysRequiresApproval?: boolean;
72
+ /** Declares the concrete grant identity derived from actual Tool input. */
73
+ readonly unattendedExecution?: PiToolUnattendedExecutionSpec;
74
+ /** Explicitly forbids this Tool from unattended Scheduled Runs. */
75
+ readonly unattendedMode?: "forbidden";
76
+ /** Recovery may replay this Tool Call only when the candidate guarantees convergence. Omitted means non-idempotent. */
77
+ readonly retry?: "idempotent" | "non-idempotent";
78
+ readonly summary?: string;
79
+ readonly source?: ApprovalReceipt["source"];
80
+ readonly interaction?: PiToolInteractionSpec;
81
+ /** Large payloads spill by default; protocol-shaped results opt into structure. */
82
+ readonly outputBudget?: BudgetPolicy;
83
+ }
84
+
85
+ export interface CompileHarnessToolsOptions {
86
+ readonly workspace?: SpillWorkspace;
87
+ readonly resolveConfirmation?: (candidate: PiToolCandidate, toolCallId: string, args: unknown) => { prepared: unknown; intentId: string };
88
+ /** Recheck live authorization at the effect boundary, including safe replay. */
89
+ readonly authorize?: (
90
+ candidate: PiToolCandidate,
91
+ args: unknown,
92
+ invocation: { readonly toolCallId: string; readonly signal: AbortSignal },
93
+ ) => void | Promise<void>;
94
+ /** Return an already decided interaction result without invoking the handler. */
95
+ readonly resolveReplay?: (
96
+ candidate: PiToolCandidate,
97
+ toolCallId: string,
98
+ args: unknown,
99
+ ) => AgentToolResult<unknown> | undefined | Promise<AgentToolResult<unknown> | undefined>;
100
+ /** Optional business projection; Pi remains the authoritative Tool result. */
101
+ readonly onSettlement?: (
102
+ call: SettledPiToolCall,
103
+ ) => void | Promise<void>;
104
+ }
105
+
106
+ /** 描述一次已进入持久化结算边界的 Pi 工具调用。 */
107
+ export interface SettledPiToolCall {
108
+ readonly toolCallId: string;
109
+ readonly toolName: string;
110
+ readonly args: unknown;
111
+ readonly result: AgentToolResult<unknown>;
112
+ readonly isError: boolean;
113
+ /** Diagnostic-only original error; callers must not persist or expose it. */
114
+ readonly cause?: unknown;
115
+ readonly summary?: string;
116
+ readonly source?: ApprovalReceipt["source"];
117
+ }
@@ -1,17 +1,14 @@
1
- import type {
2
- AgentToolResult,
3
- HookHandler,
4
- } from "@earendil-works/pi-agent-core";
5
- import type { ExecutionLevel } from "../lib/execution-level";
1
+ import type { AgentToolResult, HookHandler } from "@earendil-works/pi-agent-core";
2
+ import type { ExecutionLevel } from "../../lib/execution-level";
6
3
  import {
7
4
  EXECUTION_LEVELS,
8
5
  minExecutionLevel,
9
6
  requiresExecutionApproval,
10
- } from "../lib/execution-level";
11
- import type { PiToolCandidate } from "./compiler";
7
+ } from "../../lib/execution-level";
8
+ import type { RuntimeTelemetryCoordinator } from "../../telemetry/coordinator";
12
9
  import type { ApprovalRepository } from "./approval";
10
+ import type { PiToolCandidate } from "./contracts";
13
11
  import type { ToolInteractionRepository } from "./interaction";
14
- import type { RuntimeTelemetryCoordinator } from "../telemetry/coordinator";
15
12
  import { UnattendedExecutionError } from "./unattended-error";
16
13
 
17
14
  export interface HarnessDecisionSubmission {
@@ -300,7 +297,7 @@ export class HarnessDecisionGate {
300
297
  const intentId = `${this.options.submission.submissionId}:${toolCallId}`;
301
298
  const approval = this.options.approvals.find(intentId);
302
299
  if (!approval || approval.status !== "approved" || !approval.confirmationJson ||
303
- approval.toolName !== candidate.tool.name || approval.inputJson !== stableInput(args)) {
300
+ approval.toolName !== candidate.tool.name || approval.inputJson !== stableInput(args)) {
304
301
  throw new Error("Matching user confirmation is required");
305
302
  }
306
303
  return { prepared: JSON.parse(approval.confirmationJson), intentId };
@@ -1,7 +1,7 @@
1
- import type { ApprovalDecision } from "../submission/contracts";
2
- import type { RuntimeTelemetryCoordinator } from "../telemetry/coordinator";
1
+ import type { ApprovalDecision } from "../../submission/contracts";
2
+ import type { RuntimeTelemetryCoordinator } from "../../telemetry/coordinator";
3
3
  import type { ApprovalRepository } from "./approval/repository";
4
- import type { PiToolInteractionSpec } from "./compiler";
4
+ import type { PiToolInteractionSpec } from "./contracts";
5
5
  import type { StoredToolInteraction, ToolInteractionRepository } from "./interaction/repository";
6
6
 
7
7
  /** Human decisions own validation and durable facts; the caller resumes Pi. */
@@ -12,7 +12,7 @@ export class HarnessHumanDecisions {
12
12
  readonly transaction: <T>(run: () => T) => T;
13
13
  readonly isSubmissionActive: (submissionId: string) => boolean;
14
14
  readonly telemetry: Pick<RuntimeTelemetryCoordinator, "capture">;
15
- }) {}
15
+ }) { }
16
16
 
17
17
  isWaitingForApproval(submissionId: string): boolean {
18
18
  return this.options.approvals.listPendingForSubmission(submissionId).length > 0;
@@ -1,7 +1,6 @@
1
1
  import type { SqlTaggedTemplate } from "agents/chat";
2
2
 
3
3
  // Tool Interaction 的统一定义见 ./index.ts。
4
- // #region 状态与行类型
5
4
  export type ToolInteractionStatus = "pending" | "responded" | "cancelled";
6
5
 
7
6
  export interface StoredToolInteraction {
@@ -41,8 +40,6 @@ type ToolInteractionRow = {
41
40
  responded_at: number | null;
42
41
  };
43
42
 
44
- // #endregion
45
- // #region 行映射
46
43
  // 把 SQLite 的 Interaction 行转成 Runtime 对象。
47
44
  // find 与 findPending* 查到记录后调用,上层因而不需要理解 snake_case 列名。
48
45
  // 状态由表内字符串转成联合类型;新增列或状态时必须同步核对类型、SELECT 和映射。
@@ -61,13 +58,11 @@ function mapRow(row: ToolInteractionRow): StoredToolInteraction {
61
58
  };
62
59
  }
63
60
 
64
- // #endregion
65
- // #region Repository
66
61
  export class ToolInteractionRepository {
67
62
  // 保存当前 Agent SQLite 的查询入口。
68
63
  // RuntimeDatabase 构造时调用,InteractionLifecycle 随后共享这个 Repository。
69
64
  // Interaction 必须与所属 Submission 在同一 Agent 存储中查询,不能跨 Durable Object 拼接状态。
70
- constructor(private readonly sql: SqlTaggedTemplate) {}
65
+ constructor(private readonly sql: SqlTaggedTemplate) { }
71
66
 
72
67
  // 按 interactionId 读取一条完整 Interaction,不存在时返回 null。
73
68
  // InteractionLifecycle 在请求去重、响应、取消和收尾时调用,调用方必须处理 null。
@@ -182,4 +177,3 @@ export class ToolInteractionRepository {
182
177
  `.map(mapRow);
183
178
  }
184
179
  }
185
- // #endregion
@@ -0,0 +1,22 @@
1
+ import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
2
+ import { serializeOutput } from "../../lib/artifacts";
3
+
4
+ export function result<T>(details: T): AgentToolResult<T> {
5
+ return {
6
+ content: [{ type: "text", text: serializeOutput(details).text }],
7
+ details,
8
+ };
9
+ }
10
+ // 作用:向 Pi 报告一次 Workspace 或 Sandbox 操作已经开始。
11
+ // 调用:可能访问存储或容器的 execute 方法在进入 Port 前调用。
12
+ // 原因:复用统一的 running 结果形状,
13
+ // 让长操作有进度反馈而不复制回调协议。
14
+ export function running(
15
+ onUpdate: AgentToolUpdateCallback<unknown> | undefined,
16
+ text: string,
17
+ ): void {
18
+ onUpdate?.({
19
+ content: [{ type: "text", text }],
20
+ details: { status: "running" },
21
+ });
22
+ }