@springbrand/agent-runtime 0.1.3-alpha.3 → 0.1.3-alpha.5
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.
- package/package.json +3 -1
- package/src/adapter/cloudflare/index.ts +60 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +86 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +1509 -0
- package/src/adapter/cloudflare/sandbox/id.ts +23 -0
- package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +574 -0
- package/src/adapter/cloudflare/subagent/runner.ts +175 -0
- package/src/adapter/cloudflare/subagent/tools.ts +256 -0
- package/src/adapter/cloudflare/universal-agent/definition.ts +71 -0
- package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
- package/src/adapter/cloudflare/universal-agent/preparation.ts +273 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +74 -0
- package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
- package/src/agent-tool-runtime.ts +152 -0
- package/src/db/agent-tool.repo.ts +27 -0
- package/src/db/index.ts +33 -0
- package/src/db/interaction.repo.ts +185 -0
- package/src/db/schema.ts +15 -0
- package/src/db/submission.repo.ts +29 -0
- package/src/index.ts +53 -21
- package/src/kernel/approval-lifecycle.ts +41 -6
- package/src/kernel/bindings.ts +37 -0
- package/src/kernel/interaction-lifecycle.ts +395 -0
- package/src/kernel/public-contracts.ts +2 -0
- package/src/kernel/recoverable-chat-agent.ts +10 -2
- package/src/kernel/runtime-assembly-view.ts +37 -0
- package/src/kernel/runtime-assembly.ts +41 -0
- package/src/kernel/runtime-config.ts +4 -0
- package/src/kernel/runtime-load.ts +102 -0
- package/src/kernel/state.ts +8 -1
- package/src/kernel/submission-lifecycle.ts +30 -0
- package/src/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +17 -3
- package/src/pi/runtime-adapter/execution.ts +109 -9
- package/src/pi/runtime-adapter/index.ts +15 -5
- package/src/pi/runtime-adapter/recovery.ts +188 -1
- package/src/pi/tool/base.ts +79 -9
- package/src/pi/tool/compiler.ts +34 -0
- package/src/pi/tool/core.ts +13 -0
- package/src/pi/tool/gateway.ts +54 -0
- package/src/pi/tool/index.ts +1 -0
- package/src/pi/tool/mcp.ts +93 -64
- package/src/pi/tool/schedule.ts +11 -0
- package/src/pi/tool/subagent.ts +14 -0
- package/src/pi/tool/workspace-sandbox.ts +15 -0
- package/src/pi/turn/index.ts +20 -0
- package/src/pi/turn/interaction.ts +181 -0
- package/src/pi/turn/tool-recovery.ts +244 -1
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +569 -322
- package/src/{plugins.ts → runtime-assembler.ts} +312 -379
- package/src/runtime-definition.ts +173 -0
- package/src/runtime.ts +572 -164
- package/src/tool-registry.ts +143 -0
package/src/pi/tool/base.ts
CHANGED
|
@@ -8,6 +8,10 @@ import type { RuntimeMemoryPort } from "../../kernel/bindings";
|
|
|
8
8
|
import type { RuntimeMemoryProfile } from "../../kernel/profile";
|
|
9
9
|
import { serializeOutput } from "../../lib/artifacts";
|
|
10
10
|
import type { PiToolCandidate } from "./compiler";
|
|
11
|
+
import {
|
|
12
|
+
toolRegistryFromPiCandidates,
|
|
13
|
+
type ToolRegistry,
|
|
14
|
+
} from "../../tool-registry";
|
|
11
15
|
import { webSearchPiToolCandidate } from "./web-search";
|
|
12
16
|
import type { WebSearch } from "./web-search/api";
|
|
13
17
|
|
|
@@ -26,6 +30,34 @@ const askUserParameters = Type.Object({
|
|
|
26
30
|
})),
|
|
27
31
|
});
|
|
28
32
|
|
|
33
|
+
/**
|
|
34
|
+
* `ask_user` 的客户端响应体。
|
|
35
|
+
*
|
|
36
|
+
* `selections` 是选中的选项原文(多选时多于一项),`text` 是可选的补充说明。
|
|
37
|
+
* 刻意用结构化数组而不是拼好的字符串 —— 选项本身可能含分隔符,拼了再拆是有损的。
|
|
38
|
+
*/
|
|
39
|
+
const askUserResponse = Type.Object({
|
|
40
|
+
selections: Type.Array(Type.String()),
|
|
41
|
+
text: Type.Optional(Type.String()),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 手写校验而不是拉 TypeBox 的 compiler:这里只需要判真假,
|
|
45
|
+
// 且校验必须在 Worker 冷启动路径上零成本。
|
|
46
|
+
function isAskUserResponse(value: unknown): boolean {
|
|
47
|
+
if (typeof value !== "object" || value === null) return false;
|
|
48
|
+
const record = value as Record<string, unknown>;
|
|
49
|
+
if (!Array.isArray(record.selections)) return false;
|
|
50
|
+
if (!record.selections.every((item) => typeof item === "string")) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (record.text !== undefined && typeof record.text !== "string") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// 什么都没选、也没写字,等于没回答 —— 不该被当成一次有效结算。
|
|
57
|
+
return record.selections.length > 0 ||
|
|
58
|
+
(typeof record.text === "string" && record.text.trim().length > 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
const suggestFollowupsParameters = Type.Object({
|
|
30
62
|
items: Type.Array(Type.String({
|
|
31
63
|
description: "A follow-up the user could ask next, phrased as a request.",
|
|
@@ -124,16 +156,41 @@ export function basePiToolCandidates(
|
|
|
124
156
|
webSearch?: WebSearch,
|
|
125
157
|
): PiToolCandidate[] {
|
|
126
158
|
return [
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
159
|
+
// ask_user 是 client-settled tool:它没有 execute,结果由用户点选后经
|
|
160
|
+
// respondToolInteraction 投递回来。工具调用会一直 park 到那时候。
|
|
161
|
+
{
|
|
162
|
+
...candidate({
|
|
163
|
+
name: "ask_user",
|
|
164
|
+
label: "Ask user",
|
|
165
|
+
description:
|
|
166
|
+
"Ask the user to choose among a small set of options. Call this tool whenever you need the user to make a choice from limited options — do NOT write plain text like 'please pick A / B / C'. The user's choice comes back to you as this tool's result, so just continue once you have it. Do NOT end your turn after calling this tool.",
|
|
167
|
+
parameters: askUserParameters,
|
|
168
|
+
// 永远不会被调用:执行链在 interaction 闸就 park 住了。留一个失败关闭的
|
|
169
|
+
// 实现,是为了万一哪次改动绕过了那道闸,能立刻炸出来而不是静默返回空答案。
|
|
170
|
+
async execute() {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"ask_user is client-settled and must not execute on the server",
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
interaction: {
|
|
177
|
+
validateResponse: isAskUserResponse,
|
|
178
|
+
settle: (_input, response) => {
|
|
179
|
+
const answer = response as Static<typeof askUserResponse>;
|
|
180
|
+
const parts = [
|
|
181
|
+
...answer.selections,
|
|
182
|
+
...(answer.text?.trim() ? [answer.text.trim()] : []),
|
|
183
|
+
];
|
|
184
|
+
return {
|
|
185
|
+
content: [{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: `The user answered: ${parts.join(" / ")}`,
|
|
188
|
+
}],
|
|
189
|
+
details: answer,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
135
192
|
},
|
|
136
|
-
}
|
|
193
|
+
},
|
|
137
194
|
candidate({
|
|
138
195
|
name: "suggest_followups",
|
|
139
196
|
label: "Suggest follow-ups",
|
|
@@ -218,3 +275,16 @@ export function memoryPiToolCandidate(
|
|
|
218
275
|
tool,
|
|
219
276
|
};
|
|
220
277
|
}
|
|
278
|
+
|
|
279
|
+
/** 从 Memory Port 生成 `set_context` Tool。 */
|
|
280
|
+
export function createMemoryTools(
|
|
281
|
+
memory: RuntimeMemoryPort,
|
|
282
|
+
profile: Pick<
|
|
283
|
+
RuntimeMemoryProfile,
|
|
284
|
+
"memoryTokens" | "preferencesTokens"
|
|
285
|
+
>,
|
|
286
|
+
): ToolRegistry {
|
|
287
|
+
return toolRegistryFromPiCandidates([
|
|
288
|
+
memoryPiToolCandidate(memory, profile),
|
|
289
|
+
]);
|
|
290
|
+
}
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -13,14 +13,48 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// #region Public contracts
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* 声明一个 Tool 的结果由客户端投递,而不是由 `execute()` 产出。
|
|
18
|
+
*
|
|
19
|
+
* 带这个字段的 Tool 会在执行链里 park 住,等宿主经 `respondToolInteraction` 把响应送进来,
|
|
20
|
+
* 再用 `settle` 把响应映射成这次 `toolCallId` 的权威 ToolResult。它的 `execute` 不会被调用。
|
|
21
|
+
*
|
|
22
|
+
* 这是 AI SDK 里「没有 execute 的 user-interaction tool」的等价物;差别是我们 park 住整个 Turn,
|
|
23
|
+
* 而不是让 tool part 悬空到下一次请求 —— 我们的 transcript 是服务端权威的,悬空的 tool_use
|
|
24
|
+
* 会让下一次模型调用非法。
|
|
25
|
+
*/
|
|
26
|
+
export interface PiToolInteractionSpec {
|
|
27
|
+
/** 校验客户端响应体;不通过则拒绝投递,park 保持不变。 */
|
|
28
|
+
readonly validateResponse: (response: unknown) => boolean;
|
|
29
|
+
/** 把通过校验的响应映射成 ToolResult;缺省是原样回传。 */
|
|
30
|
+
readonly settle?: (
|
|
31
|
+
input: unknown,
|
|
32
|
+
response: unknown,
|
|
33
|
+
) => AgentToolResult<unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
/** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
|
|
17
37
|
export interface PiToolCandidate {
|
|
18
38
|
readonly owner: string;
|
|
19
39
|
readonly authorized: boolean;
|
|
20
40
|
readonly tool: AgentTool<any, any>;
|
|
41
|
+
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
21
42
|
readonly requiredExecutionLevel: ExecutionLevel;
|
|
43
|
+
/** Trusted parameter-level policy, evaluated before approval or dispatch. */
|
|
44
|
+
readonly requiredExecutionLevelForInput?: (
|
|
45
|
+
input: unknown,
|
|
46
|
+
) => ExecutionLevel | Promise<ExecutionLevel>;
|
|
47
|
+
/**
|
|
48
|
+
* 无条件需要人来决定:不看执行档位矩阵,每次调用都 park 出审批。
|
|
49
|
+
*
|
|
50
|
+
* 档位差是「够不够格自己跑」,不是「该不该问人」。花用户钱、改变账户
|
|
51
|
+
* 状态这类工具,一旦 Agent 档位被 `allow_level` 永久拉高就会静默执行,
|
|
52
|
+
* 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
|
|
53
|
+
*/
|
|
54
|
+
readonly alwaysRequiresApproval?: boolean;
|
|
22
55
|
readonly summary?: string;
|
|
23
56
|
readonly source?: ApprovalReceipt["source"];
|
|
57
|
+
readonly interaction?: PiToolInteractionSpec;
|
|
24
58
|
}
|
|
25
59
|
|
|
26
60
|
/** 控制候选 Pi 工具如何被编译为可执行工具集。 */
|
package/src/pi/tool/core.ts
CHANGED
|
@@ -12,6 +12,10 @@ import { serializeOutput } from "../../lib/artifacts";
|
|
|
12
12
|
import type { PiLoadedExtension } from "../assembly/extensions";
|
|
13
13
|
import { aiToolToPi } from "./ai-adapter";
|
|
14
14
|
import type { PiToolCandidate } from "./compiler";
|
|
15
|
+
import {
|
|
16
|
+
toolRegistryFromPiCandidates,
|
|
17
|
+
type ToolRegistry,
|
|
18
|
+
} from "../../tool-registry";
|
|
15
19
|
|
|
16
20
|
// 本文件沿用 `../../index.ts` 入口定义的 Extension、Port 和 Tool Candidate 术语。
|
|
17
21
|
|
|
@@ -178,4 +182,13 @@ export function codeExecutionPiToolCandidate(
|
|
|
178
182
|
};
|
|
179
183
|
}
|
|
180
184
|
|
|
185
|
+
/** 从 Codemode Runtime Port 生成 `execute` Tool。 */
|
|
186
|
+
export function createCodeExecutionTool(
|
|
187
|
+
runtime: RuntimeCodeExecutionPort,
|
|
188
|
+
): ToolRegistry {
|
|
189
|
+
return toolRegistryFromPiCandidates([
|
|
190
|
+
codeExecutionPiToolCandidate(runtime),
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
|
|
181
194
|
// #endregion
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { RuntimeGatewaySession } from "../../kernel/bindings";
|
|
2
|
+
import type { PiToolCandidate } from "./compiler";
|
|
3
|
+
import { createPiMcpToolCandidate } from "./mcp";
|
|
4
|
+
|
|
5
|
+
const GATEWAY_TOOLS = [
|
|
6
|
+
"search_capabilities",
|
|
7
|
+
"execute_capability",
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
/** Builds the two platform Gateway tools from one authenticated logical MCP Session. */
|
|
11
|
+
export function createPiGatewayToolCandidates(
|
|
12
|
+
session: RuntimeGatewaySession,
|
|
13
|
+
): PiToolCandidate[] {
|
|
14
|
+
const tools = new Map(session.tools.map((tool) => [tool.name, tool]));
|
|
15
|
+
if (
|
|
16
|
+
tools.size !== GATEWAY_TOOLS.length ||
|
|
17
|
+
GATEWAY_TOOLS.some((name) => !tools.has(name))
|
|
18
|
+
) {
|
|
19
|
+
throw new Error("Connector Gateway returned an invalid MCP catalog");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return GATEWAY_TOOLS.map((name) => {
|
|
23
|
+
const tool = tools.get(name)!;
|
|
24
|
+
const execute = name === "execute_capability";
|
|
25
|
+
return createPiMcpToolCandidate(
|
|
26
|
+
{ ...tool, serverId: "gateway" },
|
|
27
|
+
(input, signal) => session.callTool(name, input, signal),
|
|
28
|
+
{
|
|
29
|
+
owner: "gateway",
|
|
30
|
+
modelName: name,
|
|
31
|
+
requiredExecutionLevel: execute ? "high" : "safe",
|
|
32
|
+
...(execute
|
|
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
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
package/src/pi/tool/index.ts
CHANGED
package/src/pi/tool/mcp.ts
CHANGED
|
@@ -9,6 +9,22 @@ interface McpCallResult {
|
|
|
9
9
|
isError?: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface PiDiscoveredMcpTool {
|
|
13
|
+
readonly serverId: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly title?: string;
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly inputSchema?: unknown;
|
|
18
|
+
readonly annotations?: { readonly title?: string } & Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PiMcpCandidatePolicy {
|
|
22
|
+
readonly owner: string;
|
|
23
|
+
readonly modelName: string;
|
|
24
|
+
readonly requiredExecutionLevel: PiToolCandidate["requiredExecutionLevel"];
|
|
25
|
+
readonly requiredExecutionLevelForInput?: PiToolCandidate["requiredExecutionLevelForInput"];
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Pi 的 MCP 适配器所需的最小 Host 能力。
|
|
14
30
|
*
|
|
@@ -200,6 +216,67 @@ function errorMessage(result: McpCallResult): string {
|
|
|
200
216
|
: "MCP tool call failed";
|
|
201
217
|
}
|
|
202
218
|
|
|
219
|
+
/** 共享 MCP 结果投影;Gateway 与用户配置的 Remote MCP 均经过此边界。 */
|
|
220
|
+
export function createPiMcpToolCandidate(
|
|
221
|
+
mcpTool: PiDiscoveredMcpTool,
|
|
222
|
+
callTool: (
|
|
223
|
+
input: Readonly<Record<string, unknown>>,
|
|
224
|
+
signal?: AbortSignal,
|
|
225
|
+
) => Promise<unknown>,
|
|
226
|
+
policy: PiMcpCandidatePolicy,
|
|
227
|
+
): PiToolCandidate {
|
|
228
|
+
const label =
|
|
229
|
+
mcpTool.title ??
|
|
230
|
+
mcpTool.annotations?.title ??
|
|
231
|
+
mcpTool.name;
|
|
232
|
+
const tool: AgentTool<any, {
|
|
233
|
+
kind: "mcp";
|
|
234
|
+
toolName: string;
|
|
235
|
+
}> = {
|
|
236
|
+
name: policy.modelName,
|
|
237
|
+
label,
|
|
238
|
+
description: mcpTool.description ?? label,
|
|
239
|
+
parameters: structuredClone(
|
|
240
|
+
mcpTool.inputSchema ?? {
|
|
241
|
+
type: "object",
|
|
242
|
+
additionalProperties: true,
|
|
243
|
+
},
|
|
244
|
+
) as AgentTool["parameters"],
|
|
245
|
+
execute: async (_toolCallId, args, signal) => {
|
|
246
|
+
const result = normalizeCallResult(
|
|
247
|
+
await callTool(args as Record<string, unknown>, signal),
|
|
248
|
+
);
|
|
249
|
+
if (result.isError) throw new Error(errorMessage(result));
|
|
250
|
+
const structuredContent = result.structuredContent === undefined
|
|
251
|
+
? undefined
|
|
252
|
+
: publicMcpValue(result.structuredContent);
|
|
253
|
+
const content = resultContent({ ...result, structuredContent });
|
|
254
|
+
return {
|
|
255
|
+
content,
|
|
256
|
+
details: {
|
|
257
|
+
kind: "mcp",
|
|
258
|
+
toolName: mcpTool.name,
|
|
259
|
+
output: structuredContent ?? content,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
return {
|
|
265
|
+
owner: policy.owner,
|
|
266
|
+
authorized: true,
|
|
267
|
+
tool,
|
|
268
|
+
summary: label,
|
|
269
|
+
requiredExecutionLevel: policy.requiredExecutionLevel,
|
|
270
|
+
...(policy.requiredExecutionLevelForInput
|
|
271
|
+
? {
|
|
272
|
+
requiredExecutionLevelForInput:
|
|
273
|
+
policy.requiredExecutionLevelForInput,
|
|
274
|
+
}
|
|
275
|
+
: {}),
|
|
276
|
+
source: "action",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
203
280
|
/**
|
|
204
281
|
* 把当前已连接且获授权的 MCP 工具转换成 Pi 候选工具。
|
|
205
282
|
*
|
|
@@ -249,71 +326,23 @@ export function createPiMcpToolCandidates(
|
|
|
249
326
|
state: "ready",
|
|
250
327
|
})
|
|
251
328
|
.filter((tool) => selectedIds.has(tool.serverId))
|
|
252
|
-
.map((mcpTool)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
toolName: string;
|
|
260
|
-
}> = {
|
|
261
|
-
name: modelVisibleName(mcpTool.serverId, mcpTool.name),
|
|
262
|
-
label,
|
|
263
|
-
description: mcpTool.description ?? label,
|
|
264
|
-
parameters: structuredClone(
|
|
265
|
-
mcpTool.inputSchema ?? {
|
|
266
|
-
type: "object",
|
|
267
|
-
additionalProperties: true,
|
|
268
|
-
},
|
|
269
|
-
) as AgentTool["parameters"],
|
|
270
|
-
// 作用:执行模型选中的远端 MCP 工具并返回公开结果。
|
|
271
|
-
// 调用:Pi 在参数校验和统一工具治理通过后调用。
|
|
272
|
-
// 原因:调用交给 Agents SDK,并在边界处统一处理错误、
|
|
273
|
-
// 私有元数据和非原生内容。
|
|
274
|
-
execute: async (_toolCallId, args, signal) => {
|
|
275
|
-
const result = normalizeCallResult(
|
|
276
|
-
await host.mcp.callTool(
|
|
277
|
-
{
|
|
278
|
-
serverId: mcpTool.serverId,
|
|
279
|
-
name: mcpTool.name,
|
|
280
|
-
arguments: args as Record<string, unknown>,
|
|
281
|
-
},
|
|
282
|
-
undefined,
|
|
283
|
-
{ signal },
|
|
284
|
-
),
|
|
285
|
-
);
|
|
286
|
-
if (result.isError) {
|
|
287
|
-
throw new Error(errorMessage(result));
|
|
288
|
-
}
|
|
289
|
-
const structuredContent =
|
|
290
|
-
result.structuredContent === undefined
|
|
291
|
-
? undefined
|
|
292
|
-
: publicMcpValue(result.structuredContent);
|
|
293
|
-
const content = resultContent({
|
|
294
|
-
...result,
|
|
295
|
-
structuredContent,
|
|
296
|
-
});
|
|
297
|
-
return {
|
|
298
|
-
content,
|
|
299
|
-
details: {
|
|
300
|
-
kind: "mcp",
|
|
301
|
-
toolName: mcpTool.name,
|
|
302
|
-
output: structuredContent ?? content,
|
|
303
|
-
},
|
|
304
|
-
};
|
|
329
|
+
.map((mcpTool) => createPiMcpToolCandidate(
|
|
330
|
+
mcpTool,
|
|
331
|
+
(args, signal) => host.mcp.callTool(
|
|
332
|
+
{
|
|
333
|
+
serverId: mcpTool.serverId,
|
|
334
|
+
name: mcpTool.name,
|
|
335
|
+
arguments: args,
|
|
305
336
|
},
|
|
306
|
-
|
|
307
|
-
|
|
337
|
+
undefined,
|
|
338
|
+
{ signal },
|
|
339
|
+
),
|
|
340
|
+
{
|
|
308
341
|
owner: `mcp:${mcpTool.serverId}`,
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// MCP annotations are supplied by the remote server and are not an
|
|
313
|
-
// authorization boundary. Until the Host supplies trusted per-method
|
|
314
|
-
// policy, every dynamic MCP method must take the approval path.
|
|
342
|
+
modelName: modelVisibleName(mcpTool.serverId, mcpTool.name),
|
|
343
|
+
// Remote MCP annotations are untrusted; all user-configured methods
|
|
344
|
+
// retain the existing high-risk approval policy.
|
|
315
345
|
requiredExecutionLevel: "high",
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
});
|
|
346
|
+
},
|
|
347
|
+
));
|
|
319
348
|
}
|
package/src/pi/tool/schedule.ts
CHANGED
|
@@ -10,6 +10,10 @@ import type {
|
|
|
10
10
|
import type { ScheduleSpec } from "../../kernel/receipts";
|
|
11
11
|
import { serializeOutput } from "../../lib/artifacts";
|
|
12
12
|
import type { PiToolCandidate } from "./compiler";
|
|
13
|
+
import {
|
|
14
|
+
toolRegistryFromPiCandidates,
|
|
15
|
+
type ToolRegistry,
|
|
16
|
+
} from "../../tool-registry";
|
|
13
17
|
|
|
14
18
|
const scheduleTriggerParameters = Type.Union([
|
|
15
19
|
Type.Object({
|
|
@@ -240,3 +244,10 @@ export function schedulePiToolCandidates(
|
|
|
240
244
|
),
|
|
241
245
|
];
|
|
242
246
|
}
|
|
247
|
+
|
|
248
|
+
/** 从 {@link RuntimeSchedulePort} 生成定时任务 Tool 集。 */
|
|
249
|
+
export function createScheduleTools(
|
|
250
|
+
schedule: RuntimeSchedulePort,
|
|
251
|
+
): ToolRegistry {
|
|
252
|
+
return toolRegistryFromPiCandidates(schedulePiToolCandidates(schedule));
|
|
253
|
+
}
|
package/src/pi/tool/subagent.ts
CHANGED
|
@@ -4,6 +4,10 @@ import type { RuntimeSubagentPort } from "../../kernel/bindings";
|
|
|
4
4
|
import { AGENT_TYPES } from "../../layers/orchestration/subagents/agent-types/registry";
|
|
5
5
|
import { serializeOutput } from "../../lib/artifacts";
|
|
6
6
|
import type { PiToolCandidate } from "./compiler";
|
|
7
|
+
import {
|
|
8
|
+
toolRegistryFromPiCandidates,
|
|
9
|
+
type ToolRegistry,
|
|
10
|
+
} from "../../tool-registry";
|
|
7
11
|
|
|
8
12
|
const BACKGROUND_MAX_BUDGET_MS = 10 * 60 * 1_000;
|
|
9
13
|
|
|
@@ -146,3 +150,13 @@ export function subagentPiToolCandidates(
|
|
|
146
150
|
});
|
|
147
151
|
return candidates;
|
|
148
152
|
}
|
|
153
|
+
|
|
154
|
+
/** 从 {@link RuntimeSubagentPort} 生成已启用 SubAgent Tool 集。 */
|
|
155
|
+
export function createSubagentTools(
|
|
156
|
+
subagents: RuntimeSubagentPort | undefined,
|
|
157
|
+
enabledSubagents: readonly string[],
|
|
158
|
+
): ToolRegistry {
|
|
159
|
+
return toolRegistryFromPiCandidates(
|
|
160
|
+
subagentPiToolCandidates(subagents, enabledSubagents),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -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
|
|
|
@@ -145,6 +149,7 @@ function serializeByPath<T>(
|
|
|
145
149
|
*
|
|
146
150
|
* `Pi`、`Port` 与“候选工具”等项目核心术语见 `../../index.ts`。
|
|
147
151
|
*/
|
|
152
|
+
/** Pi 编译路径:从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 候选项。 */
|
|
148
153
|
export function workspacePiToolCandidates(
|
|
149
154
|
workspace: WorkspacePort,
|
|
150
155
|
): PiToolCandidate[] {
|
|
@@ -298,6 +303,11 @@ export function workspacePiToolCandidates(
|
|
|
298
303
|
];
|
|
299
304
|
}
|
|
300
305
|
|
|
306
|
+
/** 从 {@link WorkspacePort} 生成标准 workspace 文件 Tool 集(read / write / edit …)。 */
|
|
307
|
+
export function createWorkspaceTools(workspace: WorkspacePort): ToolRegistry {
|
|
308
|
+
return toolRegistryFromPiCandidates(workspacePiToolCandidates(workspace));
|
|
309
|
+
}
|
|
310
|
+
|
|
301
311
|
// #endregion
|
|
302
312
|
|
|
303
313
|
// #region Sandbox tools
|
|
@@ -495,4 +505,9 @@ export function sandboxPiToolCandidates(
|
|
|
495
505
|
];
|
|
496
506
|
}
|
|
497
507
|
|
|
508
|
+
/** 从 {@link RuntimeSandboxPort} 生成 Sandbox Tool 集。 */
|
|
509
|
+
export function createSandboxTools(sandbox: RuntimeSandboxPort): ToolRegistry {
|
|
510
|
+
return toolRegistryFromPiCandidates(sandboxPiToolCandidates(sandbox));
|
|
511
|
+
}
|
|
512
|
+
|
|
498
513
|
// #endregion
|
package/src/pi/turn/index.ts
CHANGED
|
@@ -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";
|