@springbrand/agent-runtime 0.2.0-alpha.40 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.2.0-alpha.40",
3
+ "version": "0.2.0-alpha.41",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -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 [workspace, platform] = await Promise.all([
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,
@@ -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 提供已组装的 Workspace Code Mode 执行能力。
208
+ * 向 Runtime 提供已组装的浏览器 Code Mode 执行能力。
210
209
  *
211
210
  * @remarks
212
- * Host Adapter 负责绑定 Durable Object、Worker Loader、网络出口和
213
- * Workspace;Tool Surface 只把这个已授权 Port 转成 `execute` Tool。
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 准备它,Workspace Codemode、Browser 工具、遥测和工具门卫按需使用。
935
+ * Platform Plugin 准备它,Browser 工具、遥测和工具门卫按需使用。
937
936
  *
938
- * Worker Loader 与网络出口由 Host 选择,使 Dynamic Worker 只获得已授权绑定;术语见 `../index.ts`。
937
+ * Worker Loader Host 选择,使 Dynamic Worker 只获得已授权绑定;术语见 `../index.ts`。
939
938
  */
940
939
  export interface RuntimePlatformPort {
941
- /** Workspace Codemode 在创建 Dynamic Worker 执行器时使用的 Worker Loader。 */
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 use execute Code Mode for network requests and tool composition.";
15
+ "manage files in your workspace, and call the declared Tools available to the current Runtime.";
16
16
 
17
- // Explains how to reach capabilities instead of listing unsupported substitutes.
17
+ // Names the stable runtime environment.
18
18
  export const RUNTIME =
19
- "Runtime: this agent runs on Cloudflare Workers. When execute is present, its Code Mode Dynamic Worker is your " +
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: execute can call only the tools.* methods explicitly listed in its description; that list is exhaustive. Never guess a tools.* method. " +
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. For web access specifically, use execute Code Mode only for raw or customized network requests, structured " +
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 Code Mode for computation, multi-step data work, and the " +
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
- "When execute is present, make related file changes in one execute with state.*; when execute is absent, use one write or bash operation instead of serial edits. " +
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 related Tool calls can run independently, all are available inside execute, and execute is present, run them inside that execute rather than as parallel top-level calls; " +
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 is for the one thing execute cannot do — observe what a page actually does: " +
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. Everything else stays with execute: computation, files, and ordinary HTTP requests (fetching " +
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; when execute is present, " +
105
- "read or search multiple files through state.* in one execute instead of repeated top-level file Tool calls. " +
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
- "Use an available Skill when its description matches. Read its instructions or files only through the Skill tools.",
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
- ): FinalizedPiToolSurface;
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
- "The AI service is temporarily unavailable. Please try again later.";
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 { FinalizedPiToolSurface, PiRuntimeAssembly } from "../assembly";
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: FinalizedPiToolSurface;
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",
@@ -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.candidates;
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
- if (recordRecoveryAttempt) {
813
- const retry = piToolRetryPolicy(candidate);
814
- const firstAttempt = options.durability.appendToolInput({
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
- occurredAt: startedAt,
791
+ retry,
839
792
  });
840
- try {
841
- const output = await execute(toolCallId, input, signal, onUpdate);
842
- const occurredAt = Date.now();
843
- options.onNestedToolFinished?.({
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((candidate) => {
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,
@@ -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
- content: Type.String(),
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
- direct: true,
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
  }
@@ -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. */