@springbrand/agent-runtime 0.2.0-alpha.39 → 0.2.0-alpha.41
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 +1 -1
- package/src/adapter/cloudflare/universal-agent/preparation.ts +1 -20
- package/src/adapter/cloudflare/universal-agent/tools.ts +0 -3
- package/src/index.ts +0 -2
- package/src/kernel/bindings.ts +6 -28
- package/src/lib/prompt.ts +13 -31
- package/src/pi/assembly/context.ts +4 -1
- package/src/pi/assembly/snapshot.ts +1 -7
- package/src/pi/message/projection.ts +4 -2
- package/src/pi/runtime-adapter/assembly.ts +5 -10
- package/src/pi/runtime-adapter/execution.ts +12 -106
- package/src/pi/tool/base.ts +10 -11
- package/src/pi/tool/compiler.ts +2 -4
- package/src/pi/tool/core-host.ts +1 -143
- package/src/pi/tool/core.ts +3 -34
- package/src/pi/tool/declared.ts +2 -2
- package/src/pi/tool/schedule.ts +1 -3
- package/src/pi/tool/skill.ts +103 -29
- package/src/pi/tool/workspace-sandbox.ts +39 -9
- package/src/runtime-agent.ts +0 -3
- package/src/runtime-assembler.ts +6 -97
- package/src/runtime-definition.ts +0 -1
- package/src/runtime.ts +0 -22
package/package.json
CHANGED
|
@@ -16,7 +16,6 @@ import { AGENT_TYPES } from "../../../layers/orchestration/subagents/agent-types
|
|
|
16
16
|
import type { RuntimeAgentConfigContext } from "../../../runtime-agent-context";
|
|
17
17
|
import {
|
|
18
18
|
createBrowserExecutionFactory,
|
|
19
|
-
createWorkspaceCodeExecutionFactory,
|
|
20
19
|
type RuntimeBrowserBinding,
|
|
21
20
|
} from "../../../pi/tool/core-host";
|
|
22
21
|
import {
|
|
@@ -63,7 +62,6 @@ const WORKSPACE_TOOL_NAMES = [
|
|
|
63
62
|
"grep",
|
|
64
63
|
"delete",
|
|
65
64
|
"bash",
|
|
66
|
-
"execute",
|
|
67
65
|
] as const;
|
|
68
66
|
|
|
69
67
|
export function workspaceRequired(input: WorkspaceRequirement): boolean {
|
|
@@ -115,11 +113,6 @@ export function createPlatformLoader<
|
|
|
115
113
|
): PlatformLoader {
|
|
116
114
|
let cached: ReturnType<PlatformLoader> | undefined;
|
|
117
115
|
return () => cached ??= Promise.resolve().then(() => {
|
|
118
|
-
const exports = (context.ctx as unknown as {
|
|
119
|
-
exports: {
|
|
120
|
-
HttpGateway(options: Record<string, never>): Fetcher;
|
|
121
|
-
};
|
|
122
|
-
}).exports;
|
|
123
116
|
return {
|
|
124
117
|
loader: context.env.LOADER,
|
|
125
118
|
// 绑定缺失时整条浏览器能力不进 Platform Port,Tool Surface 因此注册空集。
|
|
@@ -135,29 +128,17 @@ export function createPlatformLoader<
|
|
|
135
128
|
}),
|
|
136
129
|
}
|
|
137
130
|
: {}),
|
|
138
|
-
outbound: () => exports.HttpGateway({}),
|
|
139
131
|
};
|
|
140
132
|
});
|
|
141
133
|
}
|
|
142
134
|
|
|
143
135
|
export async function prepareWorkspace<Env extends Cloudflare.Env>(
|
|
144
|
-
context: RuntimeAgentConfigContext<Env>,
|
|
145
136
|
workspaceLoader: WorkspaceLoader,
|
|
146
|
-
platformLoader: PlatformLoader,
|
|
147
137
|
) {
|
|
148
|
-
const
|
|
149
|
-
workspaceLoader(),
|
|
150
|
-
platformLoader(),
|
|
151
|
-
]);
|
|
138
|
+
const workspace = await workspaceLoader();
|
|
152
139
|
if (!workspace.value) return { degradations: workspace.degradations };
|
|
153
140
|
return {
|
|
154
141
|
workspace: workspace.value,
|
|
155
|
-
codeExecution: createWorkspaceCodeExecutionFactory({
|
|
156
|
-
ctx: context.ctx,
|
|
157
|
-
loader: platform.loader,
|
|
158
|
-
outbound: platform.outbound(),
|
|
159
|
-
workspace: workspace.value,
|
|
160
|
-
}),
|
|
161
142
|
degradations: workspace.degradations,
|
|
162
143
|
};
|
|
163
144
|
}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
sandboxPiToolCandidates,
|
|
15
15
|
workspacePiToolCandidates,
|
|
16
16
|
} from "../../../pi/tool/workspace-sandbox";
|
|
17
|
-
import type { RuntimeCodeExecutionFactory } from "../../../kernel/bindings";
|
|
18
17
|
import type { ToolAssemblyResult } from "../../../runtime-definition";
|
|
19
18
|
import type { PiToolCandidate } from "../../../pi/tool/compiler";
|
|
20
19
|
|
|
@@ -22,7 +21,6 @@ export function assembleUniversalAgentTools(options: {
|
|
|
22
21
|
hostTools?: readonly PiToolCandidate[];
|
|
23
22
|
workspace?: WorkspacePort;
|
|
24
23
|
workspaceRevisions?: WorkspaceRevisionRestorePort;
|
|
25
|
-
codeExecution?: RuntimeCodeExecutionFactory;
|
|
26
24
|
sandbox?: RuntimeSandboxPort;
|
|
27
25
|
schedule?: RuntimeSchedulePort;
|
|
28
26
|
subagents?: RuntimeSubagentPort;
|
|
@@ -46,7 +44,6 @@ export function assembleUniversalAgentTools(options: {
|
|
|
46
44
|
tools,
|
|
47
45
|
bindings: {
|
|
48
46
|
...(options.workspace ? { workspace: options.workspace } : {}),
|
|
49
|
-
...(options.codeExecution ? { codeExecution: options.codeExecution } : {}),
|
|
50
47
|
...(options.memory ? { memory: options.memory } : {}),
|
|
51
48
|
...(options.subagents ? { subagents: options.subagents } : {}),
|
|
52
49
|
},
|
package/src/index.ts
CHANGED
|
@@ -115,11 +115,9 @@ export type { PiSkillBinding } from "./pi/tool";
|
|
|
115
115
|
export {
|
|
116
116
|
BROWSER_EXECUTE_TOOL_NAME,
|
|
117
117
|
browserExecutionPiToolCandidate,
|
|
118
|
-
codeExecutionPiToolCandidate,
|
|
119
118
|
} from "./pi/tool";
|
|
120
119
|
export {
|
|
121
120
|
createBrowserExecutionFactory,
|
|
122
|
-
createWorkspaceCodeExecutionFactory,
|
|
123
121
|
} from "./pi/tool";
|
|
124
122
|
export {
|
|
125
123
|
assemblePiExtensions,
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { ScheduleSpec } from "./receipts";
|
|
|
3
3
|
import type { RuntimeActivityProjection } from "./state";
|
|
4
4
|
import type { ExecutionLevel } from "../lib/execution-level";
|
|
5
5
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
6
|
-
import type { PiToolCandidate } from "../pi/tool/compiler";
|
|
7
6
|
import type {
|
|
8
7
|
RuntimeEventConfirmation,
|
|
9
8
|
RuntimeLifecycleFact,
|
|
@@ -206,11 +205,11 @@ export interface WorkspacePort {
|
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
/**
|
|
209
|
-
* 向 Runtime
|
|
208
|
+
* 向 Runtime 提供已组装的浏览器 Code Mode 执行能力。
|
|
210
209
|
*
|
|
211
210
|
* @remarks
|
|
212
|
-
* Host Adapter 负责绑定 Durable Object、Worker Loader
|
|
213
|
-
*
|
|
211
|
+
* Browser Host Adapter 负责绑定 Durable Object、Worker Loader 和 Browser Run;
|
|
212
|
+
* Tool Surface 只把这个已授权 Port 转成 `browser_execute` Tool。
|
|
214
213
|
*/
|
|
215
214
|
export interface RuntimeCodeExecutionPort {
|
|
216
215
|
readonly description: string;
|
|
@@ -933,24 +932,15 @@ export interface RuntimeProviderPort {
|
|
|
933
932
|
* 向 Runtime 提供 Cloudflare 执行平台上的可授权能力。
|
|
934
933
|
*
|
|
935
934
|
* @remarks
|
|
936
|
-
* Platform Plugin 准备它,
|
|
935
|
+
* Platform Plugin 准备它,Browser 工具、遥测和工具门卫按需使用。
|
|
937
936
|
*
|
|
938
|
-
* Worker Loader
|
|
937
|
+
* Worker Loader 由 Host 选择,使 Dynamic Worker 只获得已授权绑定;术语见 `../index.ts`。
|
|
939
938
|
*/
|
|
940
939
|
export interface RuntimePlatformPort {
|
|
941
|
-
/**
|
|
940
|
+
/** Browser Code Mode 在创建 Dynamic Worker 执行器时使用的 Worker Loader。 */
|
|
942
941
|
loader: WorkerLoader;
|
|
943
942
|
/** Platform Plugin 存在 Browser Run 绑定时用它生成 `browser_execute`;缺失即整条浏览器 Tool 面不注册。 */
|
|
944
943
|
browser?: RuntimeBrowserPort;
|
|
945
|
-
/**
|
|
946
|
-
* 为一次 Dynamic Worker 组装取得已限定的网络出口。
|
|
947
|
-
*
|
|
948
|
-
* @remarks
|
|
949
|
-
* Workspace Plugin 创建 Codemode 工具时调用,并把返回的 `Fetcher` 交给 `DynamicWorkerExecutor`。
|
|
950
|
-
*
|
|
951
|
-
* 出口由 Host 通过 Service Binding 等平台边界控制,不使用无约束的 Runtime 全局网络能力。
|
|
952
|
-
*/
|
|
953
|
-
outbound: () => Fetcher;
|
|
954
944
|
/**
|
|
955
945
|
* 在 Pi 工具真正执行前请 Host 审查本次调用。
|
|
956
946
|
*
|
|
@@ -1019,21 +1009,9 @@ export interface RuntimeSkillSourceBinding {
|
|
|
1019
1009
|
*
|
|
1020
1010
|
* 它不携带业务 ID、Repository、数据库 Key、任意能力注册表或凭据配置;术语见 `../index.ts`。
|
|
1021
1011
|
*/
|
|
1022
|
-
/**
|
|
1023
|
-
* 延迟到最终 Tool Surface 完成后再创建 Code Mode 执行能力。
|
|
1024
|
-
*
|
|
1025
|
-
* @remarks
|
|
1026
|
-
* 工厂与返回 Port 同住 Host 绑定边界;输入直接使用最终 Pi Tool candidates,
|
|
1027
|
-
* 不再经过 Registry 或另一份 Tool 元数据协议。
|
|
1028
|
-
*/
|
|
1029
|
-
export interface RuntimeCodeExecutionFactory {
|
|
1030
|
-
create(candidates: readonly PiToolCandidate[]): RuntimeCodeExecutionPort;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
1012
|
export interface RuntimeBindings {
|
|
1034
1013
|
provider: RuntimeProviderPort;
|
|
1035
1014
|
platform: RuntimePlatformPort;
|
|
1036
|
-
codeExecution?: RuntimeCodeExecutionFactory;
|
|
1037
1015
|
/** Secret-capability binding; implementations must keep Runtime Grant in closure state. */
|
|
1038
1016
|
gateway?: RuntimeGatewayPort;
|
|
1039
1017
|
workspace?: WorkspacePort;
|
package/src/lib/prompt.ts
CHANGED
|
@@ -12,18 +12,11 @@
|
|
|
12
12
|
export const PERSONA =
|
|
13
13
|
"You are a personal assistant agent running on the universal-agent runtime. " +
|
|
14
14
|
"You can recall user-managed cold memory across sessions and keep working memory within the current Session, " +
|
|
15
|
-
"manage files in your workspace, and
|
|
15
|
+
"manage files in your workspace, and call the declared Tools available to the current Runtime.";
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// Names the stable runtime environment.
|
|
18
18
|
export const RUNTIME =
|
|
19
|
-
"Runtime: this agent runs on Cloudflare Workers.
|
|
20
|
-
"instrument — code there runs with outbound network access (fetch), your " +
|
|
21
|
-
"workspace filesystem (state.*), and the tools.* methods listed in its own description. Use it for raw or customized HTTP " +
|
|
22
|
-
"requests, parsing a payload, hitting several known endpoints, or computing over a file. Write plain " +
|
|
23
|
-
"JavaScript — the sandbox evaluates " +
|
|
24
|
-
"it directly, so TypeScript type annotations (`: number`, `as Type`) are a syntax error, and there " +
|
|
25
|
-
"is no Python interpreter or package manager to invoke. Use the JS/Workers equivalent of what you " +
|
|
26
|
-
"would reach for in another ecosystem.";
|
|
19
|
+
"Runtime: this agent runs on Cloudflare Workers.";
|
|
27
20
|
|
|
28
21
|
// Shared behavioral contract for the main Agent and bounded sub-agents.
|
|
29
22
|
export const BEHAVIOR =
|
|
@@ -45,32 +38,23 @@ export const PLANNING =
|
|
|
45
38
|
"step or a purely conversational reply — just do it.";
|
|
46
39
|
|
|
47
40
|
const TOOL_ROUTING =
|
|
48
|
-
"Tools:
|
|
49
|
-
"If a required top-level Tool is not visible, do not use execute. Call top-level Tool Search with that exact name, then call the discovered Tool directly. " +
|
|
50
|
-
"codemode.search searches only methods already installed inside execute; it cannot discover deferred top-level Tools.";
|
|
41
|
+
"Tools: Call only declared Tools. If a required Tool Schema is not visible, use Provider Tool Search with that exact name. Never invent a Tool name or input field. ";
|
|
51
42
|
|
|
52
43
|
// Tool-selection guidance mirrors the actual approval and network boundaries.
|
|
53
44
|
export const TOOLS =
|
|
54
45
|
TOOL_ROUTING +
|
|
55
|
-
" Relatedness, repetition, or multiple calls never overrides this availability rule. " +
|
|
56
|
-
"When every required Tool is available inside execute, use one execute for repeated or related calls, branching, or repetition. Inside execute, " +
|
|
57
|
-
"use state.* for workspace file operations, tools.* for other host capabilities, a loop for repeated calls, and Promise.all for independent calls. " +
|
|
58
|
-
"Top-level-only Tools remain Direct even when called repeatedly. " +
|
|
59
46
|
"For web tasks, use web_search for web discovery, current facts, cited research, " +
|
|
60
|
-
"and public URL analysis.
|
|
61
|
-
"API calls, or when web_search cannot retrieve the required content; do not use execute for ordinary web " +
|
|
62
|
-
"searches. When execute is absent, use the actually exposed Direct Tools. When sandbox_* tools are present, use that isolated Linux environment for Python/Node, " +
|
|
47
|
+
"and public URL analysis. When sandbox_* tools are present, use that isolated Linux environment for Python/Node, " +
|
|
63
48
|
"package managers, system commands, builds, tests and background processes; its filesystem is temporary, " +
|
|
64
49
|
"persistent inputs are copied in automatically on first use, /userspace is read-only, and only explicitly " +
|
|
65
|
-
"published /workspace outputs survive. Use
|
|
66
|
-
"raw or customized network cases described above; every response and failure it sees is visible to you. " +
|
|
50
|
+
"published /workspace outputs survive. Use run_skill_script only for scripts supplied by an activated Skill and allowed by its policy. " +
|
|
67
51
|
"bash is a shell over the workspace filesystem only " +
|
|
68
52
|
"(no network, no system utilities) and is approval-gated — don't reach for it to read files or fetch. " +
|
|
69
|
-
"
|
|
53
|
+
"Use read for one existing Workspace file, write to create or replace one file, and edit for one localized change. " +
|
|
54
|
+
"Use bash only when a single shell workflow must coordinate multiple Workspace files; do not use it for a single-file read, write, or edit. " +
|
|
70
55
|
"Do not re-plan or explain between consecutive tool calls. When a run is within the last five model turns, stop expanding scope and " +
|
|
71
56
|
"prioritize verification, saving durable results, and the final response. " +
|
|
72
|
-
"When
|
|
73
|
-
"only independent top-level-only Direct Tools should be issued together in one turn. When you reference " +
|
|
57
|
+
"When Tool calls are independent, issue them together in one model step. When you reference " +
|
|
74
58
|
"code, cite it as file_path:line_number.";
|
|
75
59
|
|
|
76
60
|
// A real browser is the only way to observe what a page actually does, and the
|
|
@@ -83,10 +67,9 @@ export const TOOLS =
|
|
|
83
67
|
// browser tool belongs to that tool's own description, which the model sees if
|
|
84
68
|
// and only if the tool is registered.
|
|
85
69
|
export const BROWSER =
|
|
86
|
-
"Browser: a real browser
|
|
70
|
+
"Browser: a real browser observes what a page actually does: " +
|
|
87
71
|
"rendered result, console output, runtime exceptions, failed resource loads, CSP blocks, and state after an " +
|
|
88
|
-
"interaction.
|
|
89
|
-
"HTML is execute's job, not the browser's). " +
|
|
72
|
+
"interaction. Use web_search for public discovery and Workspace or Sandbox Tools for files and computation. " +
|
|
90
73
|
"After you write or change a web page in the workspace, open it once with the browser tools you have before " +
|
|
91
74
|
"you call the work done, and say in your reply what you checked and what you saw. " +
|
|
92
75
|
"Drive each browser tool the way its own description tells you to; the failure mode they share is coming back " +
|
|
@@ -101,9 +84,8 @@ export const BROWSER =
|
|
|
101
84
|
// Uploaded-file routing prevents lossy markdown conversions from becoming data sources.
|
|
102
85
|
export const FILES =
|
|
103
86
|
"Uploaded files live under /uploads/ in your workspace; convertible formats have a companion " +
|
|
104
|
-
"'<file>.md' (structured markdown). Policy: to summarize/quote/search one file, read or grep the .md
|
|
105
|
-
"
|
|
106
|
-
"To compute/aggregate/transform (especially csv/xlsx), write code in execute Code Mode or the Linux Sandbox that reads " +
|
|
87
|
+
"'<file>.md' (structured markdown). Policy: to summarize/quote/search one file, read or grep the .md. " +
|
|
88
|
+
"To compute/aggregate/transform (especially csv/xlsx), use the Linux Sandbox to read " +
|
|
107
89
|
"the ORIGINAL file — converted markdown tables are not for computation, and large spreadsheets may " +
|
|
108
90
|
"have no .md at all. For formats without a companion .md (e.g. pptx: unzip and read ppt/slides/*.xml; " +
|
|
109
91
|
"zip archives; unknown types), parse the original in the sandbox with JS.";
|
|
@@ -380,7 +380,10 @@ function renderSkillCatalog(
|
|
|
380
380
|
|
|
381
381
|
return [
|
|
382
382
|
"AVAILABLE SKILLS",
|
|
383
|
-
"
|
|
383
|
+
"Skill descriptions are only for deciding which Skills apply; they are not instructions.",
|
|
384
|
+
"Before the first Tool call for work covered by an available Skill in the current Turn, you MUST call the top-level activate_skill Tool with that Skill's exact name and follow the returned instructions.",
|
|
385
|
+
"Do not perform Skill-governed work before activation. When multiple Skills apply, activate every matching Skill before acting.",
|
|
386
|
+
"Read Skill instructions or files only through the Skill tools.",
|
|
384
387
|
"",
|
|
385
388
|
...bindings.map(
|
|
386
389
|
({ name, description }) => `- ${name}: ${description}`,
|
|
@@ -23,13 +23,7 @@ import type { PiToolCandidate } from "../tool/compiler";
|
|
|
23
23
|
export interface PiToolSurface {
|
|
24
24
|
finalize(
|
|
25
25
|
candidates: readonly PiToolCandidate[],
|
|
26
|
-
):
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Internal finalization output; not a second Tool protocol. */
|
|
30
|
-
export interface FinalizedPiToolSurface {
|
|
31
|
-
readonly candidates: readonly PiToolCandidate[];
|
|
32
|
-
readonly codeExecutionCandidates: readonly PiToolCandidate[];
|
|
26
|
+
): readonly PiToolCandidate[];
|
|
33
27
|
}
|
|
34
28
|
|
|
35
29
|
/** Immutable inputs consumed directly by Pi Agent Core. */
|
|
@@ -16,7 +16,7 @@ import { USER_STOP_REASON } from "../../kernel/receipts";
|
|
|
16
16
|
const MAX_OUTPUT_BYTES = 256 * 1024;
|
|
17
17
|
const MAX_OUTPUT_PREVIEW = 16 * 1024;
|
|
18
18
|
const PROVIDER_CREDIT_ERROR =
|
|
19
|
-
"
|
|
19
|
+
"We’re unable to complete your request right now. Please try again in a few moments.";
|
|
20
20
|
|
|
21
21
|
export interface PiToolApprovalView {
|
|
22
22
|
readonly id: string;
|
|
@@ -108,7 +108,9 @@ export function publicAssistantError(message?: string): string | undefined {
|
|
|
108
108
|
if (!message) return undefined;
|
|
109
109
|
return /"limit_source"\s*:\s*"openrouter_credits"/.test(message) ||
|
|
110
110
|
(message.includes("Insufficient credits") &&
|
|
111
|
-
message.includes("openrouter.ai/settings/credits"))
|
|
111
|
+
message.includes("openrouter.ai/settings/credits")) ||
|
|
112
|
+
(message.includes("can only afford") &&
|
|
113
|
+
/openrouter\.ai\/workspaces\/[^/\s]+\/keys\//.test(message))
|
|
112
114
|
? PROVIDER_CREDIT_ERROR
|
|
113
115
|
: message;
|
|
114
116
|
}
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import type { RuntimeProfile } from "../../kernel/profile";
|
|
11
11
|
import type { ExecutionLevel } from "../../lib/execution-level";
|
|
12
12
|
import type { RuntimeSnapshot } from "../../runtime-assembler";
|
|
13
|
-
import type {
|
|
13
|
+
import type { PiRuntimeAssembly } from "../assembly";
|
|
14
14
|
import {
|
|
15
15
|
assemblePiSystemContext,
|
|
16
16
|
assemblePiExtensions,
|
|
@@ -77,7 +77,7 @@ interface PreparedPiAssembly {
|
|
|
77
77
|
readonly loaded: readonly PiLoadedExtension[];
|
|
78
78
|
readonly context: readonly PiExtensionContextContribution[];
|
|
79
79
|
readonly degradations: PiExtensionAssembly["degradations"];
|
|
80
|
-
readonly toolSurface:
|
|
80
|
+
readonly toolSurface: readonly PiToolCandidate[];
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
@@ -193,7 +193,6 @@ export interface PreparedPiRuntimeState {
|
|
|
193
193
|
readonly snapshot: RuntimeSnapshot;
|
|
194
194
|
readonly assembly: PreparedPiAssembly;
|
|
195
195
|
readonly candidates: readonly PiToolCandidate[];
|
|
196
|
-
readonly codeExecutionCandidates: readonly PiToolCandidate[];
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
// #endregion
|
|
@@ -209,7 +208,6 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
|
|
|
209
208
|
"bind_resource",
|
|
210
209
|
"delete",
|
|
211
210
|
"edit",
|
|
212
|
-
"execute",
|
|
213
211
|
"find",
|
|
214
212
|
"get_time",
|
|
215
213
|
"grep",
|
|
@@ -236,10 +234,10 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
|
|
|
236
234
|
export function piToolRetryPolicy(
|
|
237
235
|
candidate: PiToolCandidate,
|
|
238
236
|
): "idempotent" | "non-idempotent" {
|
|
239
|
-
return IDEMPOTENT_TOOL_NAMES.has(candidate.tool.name) ||
|
|
237
|
+
return candidate.retry ?? (IDEMPOTENT_TOOL_NAMES.has(candidate.tool.name) ||
|
|
240
238
|
candidate.owner.startsWith("subagent:")
|
|
241
239
|
? "idempotent"
|
|
242
|
-
: "non-idempotent";
|
|
240
|
+
: "non-idempotent");
|
|
243
241
|
}
|
|
244
242
|
|
|
245
243
|
// 把工具候选整理成版本描述里要保存的稳定字段。
|
|
@@ -448,7 +446,7 @@ export async function preparePiRuntime(
|
|
|
448
446
|
owner: object,
|
|
449
447
|
): Promise<PreparedPiRuntime> {
|
|
450
448
|
const assembly = await preparePiAssembly(options);
|
|
451
|
-
const candidates = assembly.toolSurface
|
|
449
|
+
const candidates = assembly.toolSurface;
|
|
452
450
|
return Object.freeze(new PreparedRuntime(
|
|
453
451
|
describeRuntime(options.snapshot, candidates, assembly.loaded),
|
|
454
452
|
Object.freeze([
|
|
@@ -462,9 +460,6 @@ export async function preparePiRuntime(
|
|
|
462
460
|
snapshot: options.snapshot,
|
|
463
461
|
assembly,
|
|
464
462
|
candidates: Object.freeze([...candidates]),
|
|
465
|
-
codeExecutionCandidates: Object.freeze([
|
|
466
|
-
...assembly.toolSurface.codeExecutionCandidates,
|
|
467
|
-
]),
|
|
468
463
|
}),
|
|
469
464
|
));
|
|
470
465
|
}
|
|
@@ -25,7 +25,6 @@ import { PiChunkEncoder } from "../message";
|
|
|
25
25
|
import type { UIMessageChunk } from "ai";
|
|
26
26
|
import { ChatStreamStalledError } from "agents/chat";
|
|
27
27
|
import {
|
|
28
|
-
codeExecutionPiToolCandidate,
|
|
29
28
|
compilePiTools,
|
|
30
29
|
createPiToolGovernance,
|
|
31
30
|
normalizeUpdatePlanArguments,
|
|
@@ -543,25 +542,6 @@ export interface CreatePreparedPiTurnOptions {
|
|
|
543
542
|
readonly durability: PiTurnDurability;
|
|
544
543
|
/** Reports paired model-generation lifecycle facts to the Runtime owner. */
|
|
545
544
|
readonly onGeneration?: PiGenerationLifecycleObserver;
|
|
546
|
-
/** Reports Code Mode child Tool starts without adding Pi recovery state. */
|
|
547
|
-
readonly onNestedToolStarted?: (input: Readonly<{
|
|
548
|
-
parentToolCallId: string;
|
|
549
|
-
toolCallId: string;
|
|
550
|
-
toolName: string;
|
|
551
|
-
input: unknown;
|
|
552
|
-
occurredAt: number;
|
|
553
|
-
}>) => void;
|
|
554
|
-
/** Reports Code Mode child Tool outcomes without adding Pi settlements. */
|
|
555
|
-
readonly onNestedToolFinished?: (input: Readonly<{
|
|
556
|
-
parentToolCallId: string;
|
|
557
|
-
toolCallId: string;
|
|
558
|
-
toolName: string;
|
|
559
|
-
outcome: "completed" | "failed" | "cancelled";
|
|
560
|
-
durationMs: number;
|
|
561
|
-
output?: import("@earendil-works/pi-agent-core").AgentToolResult<unknown>;
|
|
562
|
-
error?: unknown;
|
|
563
|
-
occurredAt: number;
|
|
564
|
-
}>) => void;
|
|
565
545
|
/** Per-Submission executors for tools whose metadata is fixed at assembly time. */
|
|
566
546
|
readonly toolExecutors?: Readonly<Record<
|
|
567
547
|
string,
|
|
@@ -653,7 +633,6 @@ export class PreparedPiTurnAdapter {
|
|
|
653
633
|
private readonly turn: PiTurnAdapter;
|
|
654
634
|
private readonly abortController = new AbortController();
|
|
655
635
|
private readonly candidates: readonly PiToolCandidate[];
|
|
656
|
-
private nestedToolOrdinal = 0;
|
|
657
636
|
private assistantOrdinal: number;
|
|
658
637
|
private readonly encoder: PiChunkEncoder;
|
|
659
638
|
private readonly steerMessageIds: string[] = [];
|
|
@@ -686,8 +665,6 @@ export class PreparedPiTurnAdapter {
|
|
|
686
665
|
});
|
|
687
666
|
const bindCandidate = (
|
|
688
667
|
candidate: PiToolCandidate,
|
|
689
|
-
toolCallIdPrefix?: string,
|
|
690
|
-
recordRecoveryAttempt = true,
|
|
691
668
|
): PiToolCandidate => ({
|
|
692
669
|
...candidate,
|
|
693
670
|
tool: {
|
|
@@ -696,9 +673,6 @@ export class PreparedPiTurnAdapter {
|
|
|
696
673
|
// 实时运行由 PiCore 调用它,恢复或审批续跑则由 retryTool 进入同一路径。
|
|
697
674
|
// 顺序不能随便调整:门禁先于结果复用,审批可能生成结果,不确定的非幂等工作不能重放。
|
|
698
675
|
execute: async (toolCallId, input, signal, onUpdate) => {
|
|
699
|
-
if (toolCallIdPrefix) {
|
|
700
|
-
toolCallId = `${toolCallIdPrefix}:${toolCallId}`;
|
|
701
|
-
}
|
|
702
676
|
const requiredExecutionLevel = candidate.requiredExecutionLevelForInput
|
|
703
677
|
? await candidate.requiredExecutionLevelForInput(input)
|
|
704
678
|
: candidate.requiredExecutionLevel;
|
|
@@ -809,93 +783,25 @@ export class PreparedPiTurnAdapter {
|
|
|
809
783
|
}
|
|
810
784
|
return responded.result;
|
|
811
785
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
toolCallId,
|
|
816
|
-
toolName: candidate.tool.name,
|
|
817
|
-
input,
|
|
818
|
-
retry,
|
|
819
|
-
});
|
|
820
|
-
if (!firstAttempt && retry === "non-idempotent") {
|
|
821
|
-
throw new Error(
|
|
822
|
-
`Non-idempotent Tool outcome is uncertain after recovery: ${candidate.tool.name}`,
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
const execute = options.toolExecutors?.[candidate.tool.name] ??
|
|
827
|
-
candidate.tool.execute;
|
|
828
|
-
if (recordRecoveryAttempt || !toolCallIdPrefix) {
|
|
829
|
-
return execute(toolCallId, input, signal, onUpdate);
|
|
830
|
-
}
|
|
831
|
-
const startedAt = Date.now();
|
|
832
|
-
const telemetryToolCallId = `${toolCallId}:${++this.nestedToolOrdinal}`;
|
|
833
|
-
options.onNestedToolStarted?.({
|
|
834
|
-
parentToolCallId: toolCallIdPrefix,
|
|
835
|
-
toolCallId: telemetryToolCallId,
|
|
786
|
+
const retry = piToolRetryPolicy(candidate);
|
|
787
|
+
const firstAttempt = options.durability.appendToolInput({
|
|
788
|
+
toolCallId,
|
|
836
789
|
toolName: candidate.tool.name,
|
|
837
790
|
input,
|
|
838
|
-
|
|
791
|
+
retry,
|
|
839
792
|
});
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
parentToolCallId: toolCallIdPrefix,
|
|
845
|
-
toolCallId: telemetryToolCallId,
|
|
846
|
-
toolName: candidate.tool.name,
|
|
847
|
-
outcome: "completed",
|
|
848
|
-
durationMs: occurredAt - startedAt,
|
|
849
|
-
output,
|
|
850
|
-
occurredAt,
|
|
851
|
-
});
|
|
852
|
-
return output;
|
|
853
|
-
} catch (error) {
|
|
854
|
-
const occurredAt = Date.now();
|
|
855
|
-
options.onNestedToolFinished?.({
|
|
856
|
-
parentToolCallId: toolCallIdPrefix,
|
|
857
|
-
toolCallId: telemetryToolCallId,
|
|
858
|
-
toolName: candidate.tool.name,
|
|
859
|
-
outcome: signal?.aborted ? "cancelled" : "failed",
|
|
860
|
-
durationMs: occurredAt - startedAt,
|
|
861
|
-
error,
|
|
862
|
-
occurredAt,
|
|
863
|
-
});
|
|
864
|
-
throw error;
|
|
793
|
+
if (!firstAttempt && retry === "non-idempotent") {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Non-idempotent Tool outcome is uncertain after recovery: ${candidate.tool.name}`,
|
|
796
|
+
);
|
|
865
797
|
}
|
|
798
|
+
const execute = options.toolExecutors?.[candidate.tool.name] ??
|
|
799
|
+
candidate.tool.execute;
|
|
800
|
+
return execute(toolCallId, input, signal, onUpdate);
|
|
866
801
|
},
|
|
867
802
|
},
|
|
868
803
|
});
|
|
869
|
-
this.candidates = state.candidates.map(
|
|
870
|
-
if (candidate.tool.name !== "execute") return bindCandidate(candidate);
|
|
871
|
-
const factory = state.snapshot.bindings.codeExecution;
|
|
872
|
-
if (!factory) {
|
|
873
|
-
throw new Error("Prepared Code Mode Tool requires a Runtime factory");
|
|
874
|
-
}
|
|
875
|
-
return bindCandidate({
|
|
876
|
-
...candidate,
|
|
877
|
-
tool: {
|
|
878
|
-
...candidate.tool,
|
|
879
|
-
execute: (toolCallId, input, signal, onUpdate) => {
|
|
880
|
-
const runtimeCandidate = codeExecutionPiToolCandidate(
|
|
881
|
-
factory.create(state.codeExecutionCandidates.map((inner) =>
|
|
882
|
-
// Code Mode owns replay of its connector calls. Pi persists
|
|
883
|
-
// only the parent execute attempt/result; recording an inner
|
|
884
|
-
// input without a Pi settlement creates an orphan recovery
|
|
885
|
-
// action that cannot be found on the direct Tool surface.
|
|
886
|
-
bindCandidate(inner, toolCallId, false)
|
|
887
|
-
)),
|
|
888
|
-
);
|
|
889
|
-
return runtimeCandidate.tool.execute(
|
|
890
|
-
toolCallId,
|
|
891
|
-
input,
|
|
892
|
-
signal,
|
|
893
|
-
onUpdate,
|
|
894
|
-
);
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
});
|
|
898
|
-
});
|
|
804
|
+
this.candidates = state.candidates.map(bindCandidate);
|
|
899
805
|
this.turn = new PiTurnAdapter({
|
|
900
806
|
pi: {
|
|
901
807
|
model: state.snapshot.pi.model,
|
package/src/pi/tool/base.ts
CHANGED
|
@@ -198,12 +198,17 @@ const setContextParameters = Type.Object({
|
|
|
198
198
|
label: Type.Union([
|
|
199
199
|
Type.Literal("memory"),
|
|
200
200
|
Type.Literal("preferences"),
|
|
201
|
-
]
|
|
202
|
-
|
|
201
|
+
], {
|
|
202
|
+
description:
|
|
203
|
+
"Context block to update: memory for durable facts and active context, preferences for tone and workflow choices.",
|
|
204
|
+
}),
|
|
205
|
+
content: Type.String({ description: "Text to store in the selected context block." }),
|
|
203
206
|
action: Type.Optional(Type.Union([
|
|
204
207
|
Type.Literal("replace"),
|
|
205
208
|
Type.Literal("append"),
|
|
206
|
-
]
|
|
209
|
+
], {
|
|
210
|
+
description: 'Whether to replace the block or append to it. Defaults to "replace".',
|
|
211
|
+
})),
|
|
207
212
|
});
|
|
208
213
|
|
|
209
214
|
function result<T>(details: T): AgentToolResult<T> {
|
|
@@ -273,8 +278,7 @@ export function basePiToolCandidates(
|
|
|
273
278
|
},
|
|
274
279
|
},
|
|
275
280
|
},
|
|
276
|
-
{
|
|
277
|
-
...candidate({
|
|
281
|
+
candidate({
|
|
278
282
|
name: "suggest_followups",
|
|
279
283
|
label: "Suggest follow-ups",
|
|
280
284
|
description:
|
|
@@ -284,10 +288,7 @@ export function basePiToolCandidates(
|
|
|
284
288
|
return result({ noted: true, count: input.items.length });
|
|
285
289
|
},
|
|
286
290
|
}),
|
|
287
|
-
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
...candidate({
|
|
291
|
+
candidate({
|
|
291
292
|
name: "update_plan",
|
|
292
293
|
label: "Update plan",
|
|
293
294
|
description:
|
|
@@ -302,8 +303,6 @@ export function basePiToolCandidates(
|
|
|
302
303
|
});
|
|
303
304
|
},
|
|
304
305
|
}),
|
|
305
|
-
direct: true,
|
|
306
|
-
},
|
|
307
306
|
...(webSearch ? [webSearchPiToolCandidate(webSearch)] : []),
|
|
308
307
|
];
|
|
309
308
|
}
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -48,10 +48,6 @@ export interface PiToolCandidate {
|
|
|
48
48
|
readonly tool: AgentTool<any, any>;
|
|
49
49
|
/** @internal Send the complete schema but hide it until provider Tool Search finds it. */
|
|
50
50
|
readonly deferLoading?: true;
|
|
51
|
-
/** Keep this Tool Direct-only instead of also offering it through Code Mode. */
|
|
52
|
-
readonly direct?: true;
|
|
53
|
-
/** Offer this Tool only through Code Mode, never as a top-level Tool. */
|
|
54
|
-
readonly codeExecutionOnly?: true;
|
|
55
51
|
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
56
52
|
readonly requiredExecutionLevel: ExecutionLevel;
|
|
57
53
|
/** Trusted parameter-level policy, evaluated before approval or dispatch. */
|
|
@@ -66,6 +62,8 @@ export interface PiToolCandidate {
|
|
|
66
62
|
* 那是失效不是降级。`deny` 仍然优先,被 deny 的工具根本不会注入。
|
|
67
63
|
*/
|
|
68
64
|
readonly alwaysRequiresApproval?: boolean;
|
|
65
|
+
/** Recovery may replay this Tool Call only when the candidate guarantees convergence. */
|
|
66
|
+
readonly retry?: "idempotent" | "non-idempotent";
|
|
69
67
|
readonly summary?: string;
|
|
70
68
|
readonly source?: ApprovalReceipt["source"];
|
|
71
69
|
readonly interaction?: PiToolInteractionSpec;
|