@mono-agent/agent-runtime 0.15.3 → 0.16.0

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 (43) hide show
  1. package/MIGRATION.md +41 -13
  2. package/README.md +43 -6
  3. package/package.json +7 -3
  4. package/src/agent/tools/agent-tool.js +894 -0
  5. package/src/agent/tools/bash.js +241 -123
  6. package/src/agent/tools/exec.js +238 -0
  7. package/src/agent/tools/index.js +10 -3
  8. package/src/agent/tools/node-repl.js +231 -95
  9. package/src/agent/tools/pi-bridge.js +115 -24
  10. package/src/agent/tools/shared/process-runner.js +162 -0
  11. package/src/agent/tools/shared/semaphore.js +73 -0
  12. package/src/agent/tools/web-browser-render.js +221 -0
  13. package/src/agent/tools/web-controller.js +160 -0
  14. package/src/agent/tools/web-fetch.js +653 -68
  15. package/src/agent/tools/web-search.js +568 -16
  16. package/src/ai/pi-interop.js +7 -5
  17. package/src/ai/pi-oauth-compat.js +193 -0
  18. package/src/ai/providers/pi-native/stream-subscriber.js +37 -0
  19. package/src/ai/providers/pi-native/turn-runner.js +73 -8
  20. package/src/ai/providers/pi-native.js +67 -7
  21. package/src/ai/runtime/router.js +310 -166
  22. package/src/ai/types.js +54 -2
  23. package/src/pi-auth.js +2 -2
  24. package/src/runtime.js +58 -1
  25. package/types/agent/tools/agent-tool.d.ts +80 -0
  26. package/types/agent/tools/bash.d.ts +55 -7
  27. package/types/agent/tools/exec.d.ts +53 -0
  28. package/types/agent/tools/index.d.ts +5 -3
  29. package/types/agent/tools/node-repl.d.ts +28 -3
  30. package/types/agent/tools/pi-bridge.d.ts +6 -2
  31. package/types/agent/tools/shared/process-runner.d.ts +33 -0
  32. package/types/agent/tools/shared/semaphore.d.ts +29 -0
  33. package/types/agent/tools/web-browser-render.d.ts +16 -0
  34. package/types/agent/tools/web-controller.d.ts +20 -0
  35. package/types/agent/tools/web-fetch.d.ts +74 -5
  36. package/types/agent/tools/web-search.d.ts +81 -5
  37. package/types/ai/pi-oauth-compat.d.ts +57 -0
  38. package/types/ai/providers/pi-native/turn-runner.d.ts +33 -2
  39. package/types/ai/providers/pi-native.d.ts +12 -0
  40. package/types/ai/runtime/router.d.ts +23 -3
  41. package/types/ai/types.d.ts +174 -4
  42. package/types/ai/backend.d.ts +0 -57
  43. package/types/ai/registry.d.ts +0 -1
package/src/ai/types.js CHANGED
@@ -147,7 +147,8 @@
147
147
  * @property {boolean} [fastMode]
148
148
  * @property {string} [cwd]
149
149
  * @property {Object<string, Object>} [mcpServers]
150
- * @property {ReadonlyArray<Object>} [skills] Runtime skill metadata for progressive disclosure.
150
+ * @property {ReadonlyArray<{name: string, description?: string}>} [skills] Skills disclosed to this run, as `{name, description}`. Non-empty makes `supports_skills` a routing requirement (see router.js), so a chain entry that lacks it is skipped.
151
+ * @property {string} [skillsRoot] Directory holding `<name>/SKILL.md`. Required alongside `skills` for `ReadSkill` to be built.
151
152
  * @property {ReadonlyArray<string>} [allowedTools]
152
153
  * @property {ReadonlyArray<string>} [disallowedTools]
153
154
  * @property {string} [permissionMode]
@@ -161,8 +162,13 @@
161
162
  * @property {RuntimeToolLimits} [toolLimits] Typed per-run tool-output limits (supported replacement for the deprecated `settings` tool keys).
162
163
  * @property {RuntimeCompactionPolicy} [compaction] Typed per-run compaction policy (supported replacement for the deprecated `settings` compaction keys).
163
164
  * @property {RuntimePromptOverrides} [prompts] Per-run prompt-fragment overrides (run wins over the host default).
165
+ * @property {{backend?: "auto"|"searxng"|"keyless", endpoint?: string}} [webSearchConfig] Run-scoped WebSearch backend configuration.
166
+ * @property {{render?: "never"|"auto", browserCommand?: string}} [webFetchConfig] Run-scoped WebFetch extraction/render configuration.
167
+ * @property {"sequential"|"safe-parallel"} [piToolExecutionMode] Pi built-in tool scheduling mode. Safe parallelism is the default.
168
+ * @property {"one-at-a-time"|"all"} [piToolParallelismMode] DEPRECATED. Compatibility alias mapped to piToolExecutionMode.
164
169
  * @property {Object} [settings] DEPRECATED. Legacy flat settings bag; consumed only as a per-group FALLBACK when the corresponding typed object (`toolLimits` / `compaction`) is absent. Consuming any key emits one `deprecated_settings_option` runtime_warning per run. Migrate via resolveRuntimePolicies (@mono-agent/runtime-adapter).
165
170
  * @property {Object} [nativeSubagents] Same-runtime teammate helpers exposed through native provider subagent surfaces.
171
+ * @property {RuntimeSubagentsOptions} [subagents] In-process `Agent` built-in: profiles, caps, and the nested-run callback.
166
172
  * @property {Object} [diagnosticsSeed] Set by createRouterRuntime (ai/runtime/router.js) with a `resume_snapshot` when
167
173
  * failing over mid-chain; a host-level coordinator may relay it forward (see agent/transcript.js), not read by any
168
174
  * bridge in this package today.
@@ -185,6 +191,52 @@
185
191
  * hub's emit). `systemPrompt` is passed positionally, not folded into this object.
186
192
  */
187
193
 
194
+ /**
195
+ * @typedef {Object} RuntimeSubagentDefinition
196
+ * One named subagent profile the `Agent` built-in can deploy.
197
+ * @property {string} name Model-visible identifier and the tool's `name` enum value.
198
+ * @property {string} description Model-visible: when to pick this profile.
199
+ * @property {string} systemPrompt Full system prompt for the child run.
200
+ * @property {RuntimeModelRef} [model] Absent inherits the parent's configured route.
201
+ * @property {string} [effort]
202
+ * @property {ReadonlyArray<string>} [allowedTools] Absent uses the safe read-only default set.
203
+ * @property {ReadonlyArray<string>} [disallowedTools]
204
+ * @property {Object<string, Object>} [mcpServers]
205
+ * @property {number} [maxTurns]
206
+ * @property {number} [timeoutMs]
207
+ */
208
+
209
+ /**
210
+ * @callback RuntimeSubagentRun
211
+ * Owning-layer callback that actually executes one child turn. The kernel
212
+ * supplies a self-run fallback so `createRuntime` works without host wiring;
213
+ * agent-app replaces it so subagent runs get the configured fallback chain,
214
+ * same-model retries, and run recording.
215
+ * @param {Object} request
216
+ * @returns {Promise<RuntimeResult>}
217
+ */
218
+
219
+ /**
220
+ * @typedef {Object} RuntimeInlineSubagentsOptions
221
+ * Policy for subagents the model authors at call time rather than picking from
222
+ * `definitions`. Absent suppresses authoring entirely.
223
+ * @property {boolean} [enabled] Only `false` turns authoring off.
224
+ * @property {ReadonlyArray<string>} [allowedTools] Ceiling on what an authored subagent may
225
+ * request. Absent means the safe read-only default set, never every built-in.
226
+ */
227
+
228
+ /**
229
+ * @typedef {Object} RuntimeSubagentsOptions
230
+ * @property {ReadonlyArray<RuntimeSubagentDefinition>} [definitions] Named profiles.
231
+ * @property {RuntimeInlineSubagentsOptions} [inline] Call-time authoring policy.
232
+ * @property {number} [maxConcurrent] In-flight subagents per parent turn. Default 5.
233
+ * @property {number} [maxPerTurn] Total Agent calls per parent turn. Default 20.
234
+ * @property {number} [maxTurns] Default per-subagent turn cap. Default 100.
235
+ * @property {number} [timeoutMs] Default per-subagent wall clock.
236
+ * @property {RuntimeSubagentRun} [run] Nested-run callback; absent uses the kernel self-run.
237
+ * @property {number} [depth] Kernel-owned. Absent/0 is the parent; >=1 suppresses the `Agent` tool.
238
+ */
239
+
188
240
  /**
189
241
  * @typedef {Object} RuntimeResult
190
242
  * @property {string|null} [text]
@@ -206,7 +258,7 @@
206
258
  * @property {Array<Object>} [runtimeWarnings]
207
259
  * @property {Object} [diagnostics]
208
260
  * @property {Object} [capabilitiesUsed]
209
- * @property {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null), retryableSubkind?: (string|null), requirements?: Object, routeSafety?: RuntimeRouteSafetyMode, safetyContract?: RuntimeRouteSafetyContract}>} [failoverHistory] Set by createRouterRuntime (ai/runtime/router.js) on every failed/skipped attempt.
261
+ * @property {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null), retryableSubkind?: (string|null), retryIndex?: number, requirements?: Object, routeSafety?: RuntimeRouteSafetyMode, safetyContract?: RuntimeRouteSafetyContract}>} [failoverHistory] Set by createRouterRuntime (ai/runtime/router.js) on every failed/skipped attempt.
210
262
  * @property {Array<{attemptIndex: number, model: RuntimeModelRef, routeSafety: RuntimeRouteSafetyMode, safetyContract: RuntimeRouteSafetyContract, status: string}>} [routeSafetyHistory] Bounded route-safety audit emitted by createRouterRuntime.
211
263
  */
212
264
 
package/src/pi-auth.js CHANGED
@@ -4,7 +4,7 @@ import { realpathSync } from "node:fs";
4
4
  import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
6
 
7
- import { getOAuthApiKey } from "@earendil-works/pi-ai/oauth";
7
+ import { resolveOAuthApiKey } from "./ai/pi-oauth-compat.js";
8
8
 
9
9
  /**
10
10
  * @typedef {{type: "api_key", key?: string, env?: Object<string, *>}} PiApiKeyCredential
@@ -41,7 +41,7 @@ export function createPiOAuthApiKeyResolver(options = {}) {
41
41
  return undefined;
42
42
  }
43
43
 
44
- const result = await getOAuthApiKey(provider, cloneAuth(auth));
44
+ const result = await resolveOAuthApiKey(provider, cloneAuth(auth));
45
45
  if (result === null || result === undefined || typeof result.apiKey !== "string" || result.apiKey.length === 0) {
46
46
  return undefined;
47
47
  }
package/src/runtime.js CHANGED
@@ -140,7 +140,55 @@ export function createRuntime(host = {}) {
140
140
  // THIS object so later runs of this instance observe the update.
141
141
  const toolContext = createToolContext({ ...toolRuntime, runtimeBrand });
142
142
 
143
- return {
143
+ /** @type {*} */
144
+ let self;
145
+
146
+ /**
147
+ * Kernel fallback for `subagents.run`, so the `Agent` built-in works from a
148
+ * bare `createRuntime` with no host wiring. Hosts replace it to route child
149
+ * turns through their own runtime (fallback chain, retries, recording).
150
+ *
151
+ * Scope of the guarantee, precisely: this fallback rebuilds the child bag with
152
+ * stripped session/steering state, but a HOST-SUPPLIED `run` is installed
153
+ * verbatim and is a privileged seam — it is responsible for its own session
154
+ * isolation. Recursion is blocked independently of the callback: the `Agent`
155
+ * tool stamps `depth + 1` into every descriptor it hands out, and
156
+ * `getPiBuiltinTools` refuses to register the tool at depth >= 1, so a custom
157
+ * callback cannot produce a grandchild even if it ignores the rest.
158
+ * @param {*} request
159
+ */
160
+ const defaultSubagentRun = async (request) => self.run(request.systemPrompt, {
161
+ model: request.model,
162
+ // A child must never be less confined than its parent. The policy is a
163
+ // per-run option, not a host key, so without forwarding it the child would
164
+ // run with no sandbox at all — and its default tools include WebFetch and
165
+ // WebSearch, so even a read-only profile could bypass network policy.
166
+ ...(request.sandboxPolicy === undefined ? {} : { sandboxPolicy: request.sandboxPolicy }),
167
+ ...(request.sandboxEngine === undefined ? {} : { sandboxEngine: request.sandboxEngine }),
168
+ // The parent's disclosed skills, for the same reason: they are a per-run
169
+ // option, so a child that does not receive them has no ReadSkill tool and no
170
+ // index, and must rediscover by trial and error what its parent could look
171
+ // up. A host-supplied `run` may gate this; the default has no route or deny
172
+ // list of its own to consult, so it forwards what it was given.
173
+ ...(request.skills === undefined ? {} : { skills: request.skills }),
174
+ ...(request.skillsRoot === undefined ? {} : { skillsRoot: request.skillsRoot }),
175
+ ...(request.executionMode === undefined ? {} : { executionMode: request.executionMode }),
176
+ ...(request.cwd === undefined ? {} : { cwd: request.cwd }),
177
+ // A profile that pins effort — declared or authored at call time — means it
178
+ // on this path too; dropping it would silently run the child at the
179
+ // parent's level while reporting the profile's.
180
+ ...(request.definition?.effort === undefined ? {} : { effort: request.definition.effort }),
181
+ messages: [{ role: "user", content: request.prompt }],
182
+ maxTurns: request.maxTurns,
183
+ allowedTools: request.definition?.allowedTools,
184
+ disallowedTools: request.definition?.disallowedTools,
185
+ mcpServers: request.definition?.mcpServers ?? {},
186
+ abortSignal: request.abortSignal,
187
+ onEvent: request.onEvent,
188
+ subagents: { depth: (request.depth ?? 1) },
189
+ });
190
+
191
+ self = {
144
192
  /**
145
193
  * @param {string} systemPrompt
146
194
  * @param {Partial<RuntimeRunOptions>} [options] Optional only so the
@@ -162,9 +210,16 @@ export function createRuntime(host = {}) {
162
210
  });
163
211
  const liveInput = instrumentLiveInputAppliedEvents(options.liveInput, hub.emit);
164
212
  const prompts = resolvePrompts(host.prompts, options.prompts);
213
+ // Default the nested-run callback so the Agent built-in is usable without
214
+ // host wiring; the depth field is left exactly as the caller set it, since
215
+ // defaultSubagentRun is what increments it for the child.
216
+ const subagents = options.subagents === undefined
217
+ ? undefined
218
+ : { ...options.subagents, run: options.subagents.run ?? defaultSubagentRun };
165
219
  const result = await bridge.execute(systemPrompt, {
166
220
  ...hostDefaults,
167
221
  ...options,
222
+ ...(subagents === undefined ? {} : { subagents }),
168
223
  // `...options` alone doesn't carry the `options.model` narrowing above
169
224
  // (spread reads the parameter's declared — Partial — type); re-assert
170
225
  // the already-validated model so the request satisfies RuntimeRequest.
@@ -204,4 +259,6 @@ export function createRuntime(host = {}) {
204
259
  return disposeAllProviderSessions();
205
260
  },
206
261
  };
262
+
263
+ return self;
207
264
  }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * How many subagents this logical run actually spawned.
3
+ *
4
+ * A read-only accessor so a provider can report `subagent_invoked` truthfully
5
+ * without reaching into `__budgets`, which is a deliberately private,
6
+ * non-enumerable implementation detail. Returns 0 when nothing was ever
7
+ * registered — a run with no `Agent` tool never creates a budget entry, and that
8
+ * is indistinguishable from one that had the tool and never used it, which is
9
+ * exactly what "no subagent was invoked" means for this signal.
10
+ *
11
+ * @param {*} subagents The run-scoped options object, or undefined.
12
+ * @param {string|undefined} parentRunId
13
+ * @returns {number}
14
+ */
15
+ export function subagentInvocationCount(subagents: any, parentRunId: string | undefined): number;
16
+ /**
17
+ * Build the `Agent` tool, or null when subagents are unavailable for this run.
18
+ *
19
+ * @param {RuntimeSubagentsOptions|null|undefined} subagents
20
+ * @param {{model?: *, executionMode?: string, cwd?: string, parentRunId?: string, sandboxPolicy?: *, sandboxEngine?: *, skills?: {name: string, description?: string}[], skillsRoot?: string, onEvent?: (event: *) => void}} [context]
21
+ * @returns {*|null}
22
+ */
23
+ export function createAgentTool(subagents: RuntimeSubagentsOptions | null | undefined, context?: {
24
+ model?: any;
25
+ executionMode?: string;
26
+ cwd?: string;
27
+ parentRunId?: string;
28
+ sandboxPolicy?: any;
29
+ sandboxEngine?: any;
30
+ skills?: {
31
+ name: string;
32
+ description?: string;
33
+ }[];
34
+ skillsRoot?: string;
35
+ onEvent?: (event: any) => void;
36
+ }): any | null;
37
+ /**
38
+ * A subagent that fails, times out, or says nothing still returns its activity
39
+ * log: that log is the most useful artifact of a failed delegation, and a
40
+ * thrown tool error would discard it.
41
+ *
42
+ * @param {{profileName: string, label?: string, outcome: {status: string, answer: string, reason?: string}, durationMs: number, activity: ReadonlyArray<{name: string, args: unknown, ms?: number, isError: boolean}>, maxBytes?: number, cwd?: string, notice?: string}} input
43
+ * @returns {string}
44
+ */
45
+ export function formatSubagentResult({ profileName, label, outcome, durationMs, activity, maxBytes, cwd, notice }: {
46
+ profileName: string;
47
+ label?: string;
48
+ outcome: {
49
+ status: string;
50
+ answer: string;
51
+ reason?: string;
52
+ };
53
+ durationMs: number;
54
+ activity: ReadonlyArray<{
55
+ name: string;
56
+ args: unknown;
57
+ ms?: number;
58
+ isError: boolean;
59
+ }>;
60
+ maxBytes?: number;
61
+ cwd?: string;
62
+ notice?: string;
63
+ }): string;
64
+ /** @typedef {import('../../ai/types.js').RuntimeSubagentDefinition} RuntimeSubagentDefinition */
65
+ /** @typedef {import('../../ai/types.js').RuntimeSubagentsOptions} RuntimeSubagentsOptions */
66
+ export const GENERAL_PURPOSE_SUBAGENT: "general-purpose";
67
+ /**
68
+ * Read-only by default. A profile that needs a shell or writes must say so in
69
+ * config: widening a subagent's reach is an operator decision, not one the
70
+ * model makes at call time.
71
+ */
72
+ export const DEFAULT_SUBAGENT_TOOLS: readonly string[];
73
+ /**
74
+ * Never available to a subagent, whatever a profile asks for. `Agent` is the
75
+ * third independent recursion lock; the rest would let a helper hijack the
76
+ * user's conversation or post to a channel on the main agent's behalf.
77
+ */
78
+ export const SUBAGENT_HARD_DENY: readonly string[];
79
+ export type RuntimeSubagentDefinition = import("../../ai/types.js").RuntimeSubagentDefinition;
80
+ export type RuntimeSubagentsOptions = import("../../ai/types.js").RuntimeSubagentsOptions;
@@ -1,16 +1,64 @@
1
- export function normalizeBashTimeoutMs(value: any, fallback?: number): number;
2
1
  /**
3
- * @param {{command: string, timeout?: number, max_output_chars?: number, workdir?: string}} params
4
- * @param {{signal?: any, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
2
+ * Legacy Bash timeout normalization. Values up to 600 are seconds; larger
3
+ * values are milliseconds. New callers should use `timeout_ms`.
5
4
  */
6
- export function bashToolImpl({ command, timeout, max_output_chars, workdir }: {
5
+ export function normalizeBashTimeoutMs(value: any, fallback?: number): any;
6
+ /**
7
+ * Exact millisecond timeout used by Bash.timeout_ms and Exec.timeout_ms.
8
+ */
9
+ export function normalizeProcessTimeoutMs(value: any, fallback?: number): any;
10
+ /**
11
+ * Compatibility wrapper retained for direct callers and tests.
12
+ *
13
+ * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string}} params
14
+ * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
15
+ */
16
+ export function bashToolImpl(params: {
17
+ command: string;
18
+ timeout?: number;
19
+ timeout_ms?: number;
20
+ max_output_chars?: number;
21
+ workdir?: string;
22
+ }, options?: {
23
+ signal?: AbortSignal;
24
+ sandboxPolicy?: any;
25
+ sandboxEngine?: any;
26
+ ctx?: any;
27
+ }): Promise<any>;
28
+ /**
29
+ * Structured Bash execution used by the Pi bridge.
30
+ *
31
+ * @param {{command: string, timeout?: number, timeout_ms?: number, max_output_chars?: number, workdir?: string}} params
32
+ * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
33
+ */
34
+ export function bashToolRun({ command, timeout, timeout_ms, max_output_chars, workdir, }: {
7
35
  command: string;
8
36
  timeout?: number;
37
+ timeout_ms?: number;
9
38
  max_output_chars?: number;
10
39
  workdir?: string;
11
- }, { signal, sandboxPolicy, sandboxEngine, ctx }?: {
12
- signal?: any;
40
+ }, { signal, sandboxPolicy, sandboxEngine, ctx, }?: {
41
+ signal?: AbortSignal;
13
42
  sandboxPolicy?: any;
14
43
  sandboxEngine?: any;
15
44
  ctx?: any;
16
- }): Promise<string>;
45
+ }): Promise<{
46
+ text: any;
47
+ outcome: {
48
+ status: string;
49
+ code: any;
50
+ retryable: boolean;
51
+ attempts: number;
52
+ durationMs: number;
53
+ bytes: number;
54
+ truncated: boolean;
55
+ exitCode: any;
56
+ signal: any;
57
+ timedOut: boolean;
58
+ };
59
+ error: boolean;
60
+ } | {
61
+ text: string;
62
+ outcome: any;
63
+ error: boolean;
64
+ }>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number}} params
3
+ * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
4
+ */
5
+ export function execToolImpl(params: {
6
+ executable: string;
7
+ args?: string[];
8
+ workdir?: string;
9
+ timeout_ms?: number;
10
+ max_output_chars?: number;
11
+ }, options?: {
12
+ signal?: AbortSignal;
13
+ sandboxPolicy?: any;
14
+ sandboxEngine?: any;
15
+ ctx?: any;
16
+ }): Promise<any>;
17
+ /**
18
+ * Execute an argv vector directly, without shell parsing.
19
+ *
20
+ * @param {{executable: string, args?: string[], workdir?: string, timeout_ms?: number, max_output_chars?: number}} params
21
+ * @param {{signal?: AbortSignal, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any}} [options]
22
+ */
23
+ export function execToolRun({ executable, args, workdir, timeout_ms, max_output_chars, }: {
24
+ executable: string;
25
+ args?: string[];
26
+ workdir?: string;
27
+ timeout_ms?: number;
28
+ max_output_chars?: number;
29
+ }, { signal, sandboxPolicy, sandboxEngine, ctx, }?: {
30
+ signal?: AbortSignal;
31
+ sandboxPolicy?: any;
32
+ sandboxEngine?: any;
33
+ ctx?: any;
34
+ }): Promise<{
35
+ text: any;
36
+ outcome: {
37
+ status: string;
38
+ code: any;
39
+ retryable: boolean;
40
+ attempts: number;
41
+ durationMs: number;
42
+ bytes: number;
43
+ truncated: boolean;
44
+ exitCode: any;
45
+ signal: any;
46
+ timedOut: boolean;
47
+ };
48
+ error: boolean;
49
+ } | {
50
+ text: string;
51
+ outcome: any;
52
+ error: any;
53
+ }>;
@@ -3,8 +3,10 @@ export { writeToolImpl } from "./write.js";
3
3
  export { editToolImpl } from "./edit.js";
4
4
  export { globToolImpl } from "./glob.js";
5
5
  export { grepToolImpl } from "./grep.js";
6
- export { webFetchToolImpl } from "./web-fetch.js";
7
- export { webSearchToolImpl } from "./web-search.js";
6
+ export { createWebToolController } from "./web-controller.js";
8
7
  export { resolveRgPath } from "./shared/ripgrep.js";
9
- export { bashToolImpl, normalizeBashTimeoutMs } from "./bash.js";
8
+ export { bashToolImpl, bashToolRun, normalizeBashTimeoutMs, normalizeProcessTimeoutMs } from "./bash.js";
9
+ export { execToolImpl, execToolRun } from "./exec.js";
10
+ export { webFetchToolImpl, performWebFetch } from "./web-fetch.js";
11
+ export { webSearchToolImpl, performWebSearch } from "./web-search.js";
10
12
  export { isPathAllowed, isWorkdirAllowed } from "./shared/path-resolver.js";
@@ -9,11 +9,36 @@ export function createNodeReplController({ cwd, maxOutputChars, sandboxPolicy, s
9
9
  sandboxEngine?: any;
10
10
  ctx?: any;
11
11
  }): {
12
- /** @param {{code: string}} params @param {{signal?: AbortSignal}} [execution] */
13
- execute({ code }: {
12
+ execute: ({ code }: {
14
13
  code: string;
15
14
  }, { signal }?: {
16
15
  signal?: AbortSignal;
17
- }): Promise<any>;
16
+ }) => Promise<any>;
17
+ /** Structured result used by the Pi bridge so telemetry does not depend on text prefixes. */
18
+ executeDetailed(params: any, execution?: {}): Promise<{
19
+ text: any;
20
+ outcome: {
21
+ status: string;
22
+ code: string;
23
+ retryable: boolean;
24
+ attempts: number;
25
+ durationMs: number;
26
+ bytes: number;
27
+ truncated: boolean;
28
+ };
29
+ error: boolean;
30
+ } | {
31
+ text: string;
32
+ outcome: {
33
+ status: string;
34
+ code: any;
35
+ retryable: boolean;
36
+ attempts: number;
37
+ durationMs: number;
38
+ bytes: number;
39
+ truncated: boolean;
40
+ };
41
+ error: boolean;
42
+ }>;
18
43
  close(): Promise<void>;
19
44
  };
@@ -35,9 +35,9 @@ export function createStructuredOutputTool(outputSchema: any, onStructuredOutput
35
35
  };
36
36
  /**
37
37
  * @param {any} allowedTools
38
- * @param {{disallowedTools?: any[], skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, nodeReplController?: any, ctx?: any}} [options]
38
+ * @param {{disallowedTools?: any[], skillNames?: any[], skills?: any[], skillsRoot?: any, dataDir?: any, cwd?: any, onEvent?: (event: any) => void, toolLimits?: any, persistArtifact?: any, onTruncate?: any, toolPayloadMaxBytes?: number, imageInlineMaxBytes?: any, toolPolicy?: any, sandboxPolicy?: any, sandboxEngine?: any, approvalManager?: any, approvalModel?: any, nodeReplController?: any, webController?: any, toolExecutionMode?: "sequential"|"safe-parallel", subagents?: any, subagentContext?: any, ctx?: any}} [options]
39
39
  */
40
- export function getPiBuiltinTools(allowedTools: any, { disallowedTools, skillNames, skills, skillsRoot, dataDir, cwd, onEvent, toolLimits, persistArtifact, onTruncate, toolPayloadMaxBytes, imageInlineMaxBytes, toolPolicy, sandboxPolicy, sandboxEngine, approvalManager, approvalModel, nodeReplController, ctx, }?: {
40
+ export function getPiBuiltinTools(allowedTools: any, { disallowedTools, skillNames, skills, skillsRoot, dataDir, cwd, onEvent, toolLimits, persistArtifact, onTruncate, toolPayloadMaxBytes, imageInlineMaxBytes, toolPolicy, sandboxPolicy, sandboxEngine, approvalManager, approvalModel, nodeReplController, webController, subagents, subagentContext, toolExecutionMode, ctx, }?: {
41
41
  disallowedTools?: any[];
42
42
  skillNames?: any[];
43
43
  skills?: any[];
@@ -56,6 +56,10 @@ export function getPiBuiltinTools(allowedTools: any, { disallowedTools, skillNam
56
56
  approvalManager?: any;
57
57
  approvalModel?: any;
58
58
  nodeReplController?: any;
59
+ webController?: any;
60
+ toolExecutionMode?: "sequential" | "safe-parallel";
61
+ subagents?: any;
62
+ subagentContext?: any;
59
63
  ctx?: any;
60
64
  }): any[];
61
65
  export function resolveMcpStdioCwd(cfg?: {}, cwd?: any): any;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Run one already-prepared executable without adding a shell.
3
+ *
4
+ * The result is deliberately loss-aware: stdout/stderr are retained up to the
5
+ * shared byte cap even when the child times out, is aborted, exits by signal,
6
+ * or exceeds that cap.
7
+ *
8
+ * @param {{command: string, args?: string[], cwd?: string, env?: Record<string, string|undefined>}} commandSpec
9
+ * @param {{timeoutMs?: number, signal?: AbortSignal, maxBufferBytes?: number}} [options]
10
+ */
11
+ export function runPreparedProcess(commandSpec: {
12
+ command: string;
13
+ args?: string[];
14
+ cwd?: string;
15
+ env?: Record<string, string | undefined>;
16
+ }, { timeoutMs, signal, maxBufferBytes, }?: {
17
+ timeoutMs?: number;
18
+ signal?: AbortSignal;
19
+ maxBufferBytes?: number;
20
+ }): Promise<any>;
21
+ /**
22
+ * @param {import("node:child_process").ChildProcess} child
23
+ * @param {NodeJS.Signals} signal
24
+ */
25
+ export function killProcessGroup(child: import("node:child_process").ChildProcess, signal: NodeJS.Signals): void;
26
+ /**
27
+ * @param {{stdout?: string, stderr?: string}} result
28
+ */
29
+ export function combinedProcessOutput(result: {
30
+ stdout?: string;
31
+ stderr?: string;
32
+ }): string;
33
+ export const DEFAULT_PROCESS_BUFFER_BYTES: number;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @typedef {Object} CountingSemaphore
3
+ * @property {(signal?: AbortSignal) => Promise<() => void>} acquire Resolves with
4
+ * a single-use release function once a slot is free. Rejects if `signal`
5
+ * aborts while queued; an already-acquired slot is never leaked.
6
+ * @property {() => number} inFlight Slots currently held.
7
+ * @property {() => number} queued Waiters not yet admitted.
8
+ */
9
+ /**
10
+ * @param {number} limit Maximum simultaneous holders. Values below 1 are clamped.
11
+ * @returns {CountingSemaphore}
12
+ */
13
+ export function createCountingSemaphore(limit: number): CountingSemaphore;
14
+ export type CountingSemaphore = {
15
+ /**
16
+ * Resolves with
17
+ * a single-use release function once a slot is free. Rejects if `signal`
18
+ * aborts while queued; an already-acquired slot is never leaked.
19
+ */
20
+ acquire: (signal?: AbortSignal) => Promise<() => void>;
21
+ /**
22
+ * Slots currently held.
23
+ */
24
+ inFlight: () => number;
25
+ /**
26
+ * Waiters not yet admitted.
27
+ */
28
+ queued: () => number;
29
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Render one public page in a fresh anonymous agent-browser session.
3
+ *
4
+ * @param {string} url
5
+ * @param {{browserCommand?: string, namespace?: string, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, signal?: AbortSignal, registerCleanup?: (cleanup: () => Promise<void>) => () => void}} [options]
6
+ */
7
+ export function renderWithAgentBrowser(url: string, { browserCommand, namespace, sandboxPolicy, sandboxEngine, ctx, signal, registerCleanup, }?: {
8
+ browserCommand?: string;
9
+ namespace?: string;
10
+ sandboxPolicy?: any;
11
+ sandboxEngine?: any;
12
+ ctx?: any;
13
+ signal?: AbortSignal;
14
+ registerCleanup?: (cleanup: () => Promise<void>) => () => void;
15
+ }): Promise<any>;
16
+ export function extractBrowserText(output: any): any;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * One ephemeral web-tool controller for one model run. It owns in-memory
3
+ * deduplication, result caches, anonymous browser namespaces, and cleanup.
4
+ *
5
+ * @param {{searchConfig?: any, fetchConfig?: any, sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, fetchImpl?: typeof fetch, browserRenderer?: any}} [options]
6
+ */
7
+ export function createWebToolController({ searchConfig, fetchConfig, sandboxPolicy, sandboxEngine, ctx, fetchImpl, browserRenderer, }?: {
8
+ searchConfig?: any;
9
+ fetchConfig?: any;
10
+ sandboxPolicy?: any;
11
+ sandboxEngine?: any;
12
+ ctx?: any;
13
+ fetchImpl?: typeof fetch;
14
+ browserRenderer?: any;
15
+ }): {
16
+ namespace: string;
17
+ search(params: any, execution?: {}): Promise<any>;
18
+ fetch(params: any, execution?: {}): Promise<any>;
19
+ close(): Promise<void>;
20
+ };
@@ -1,13 +1,82 @@
1
1
  /**
2
- * @param {{url: string, headers?: Record<string, string>, max_output_chars?: number}} params
3
- * @param {{sandboxPolicy?: any, ctx?: any, retryDelaysMs?: number[]}} [options]
2
+ * Compatibility wrapper for direct callers.
3
+ *
4
+ * @param {{url: string, headers?: Record<string, string>, max_output_chars?: number, format?: string, render?: string}} params
5
+ * @param {{sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, signal?: AbortSignal, retryDelaysMs?: number[], fetchConfig?: any, fetchImpl?: typeof fetch, browserRenderer?: typeof renderWithAgentBrowser, namespace?: string, registerCleanup?: (cleanup: () => Promise<void>) => () => void}} [options]
4
6
  */
5
- export function webFetchToolImpl({ url, headers, max_output_chars }: {
7
+ export function webFetchToolImpl(params: {
6
8
  url: string;
7
9
  headers?: Record<string, string>;
8
10
  max_output_chars?: number;
9
- }, { sandboxPolicy, ctx, retryDelaysMs }?: {
11
+ format?: string;
12
+ render?: string;
13
+ }, options?: {
10
14
  sandboxPolicy?: any;
15
+ sandboxEngine?: any;
11
16
  ctx?: any;
17
+ signal?: AbortSignal;
12
18
  retryDelaysMs?: number[];
13
- }): Promise<string>;
19
+ fetchConfig?: any;
20
+ fetchImpl?: typeof fetch;
21
+ browserRenderer?: typeof renderWithAgentBrowser;
22
+ namespace?: string;
23
+ registerCleanup?: (cleanup: () => Promise<void>) => () => void;
24
+ }): Promise<any>;
25
+ /**
26
+ * Fetch and locally extract one public URL.
27
+ *
28
+ * @param {{url: string, headers?: Record<string, string>, max_output_chars?: number, format?: string, render?: string}} params
29
+ * @param {{sandboxPolicy?: any, sandboxEngine?: any, ctx?: any, signal?: AbortSignal, retryDelaysMs?: number[], fetchConfig?: any, fetchImpl?: typeof fetch, browserRenderer?: typeof renderWithAgentBrowser, namespace?: string, registerCleanup?: (cleanup: () => Promise<void>) => () => void}} [options]
30
+ */
31
+ export function performWebFetch({ url, headers, max_output_chars, format, render, }: {
32
+ url: string;
33
+ headers?: Record<string, string>;
34
+ max_output_chars?: number;
35
+ format?: string;
36
+ render?: string;
37
+ }, { sandboxPolicy, sandboxEngine, ctx, signal, retryDelaysMs, fetchConfig, fetchImpl, browserRenderer, namespace, registerCleanup, }?: {
38
+ sandboxPolicy?: any;
39
+ sandboxEngine?: any;
40
+ ctx?: any;
41
+ signal?: AbortSignal;
42
+ retryDelaysMs?: number[];
43
+ fetchConfig?: any;
44
+ fetchImpl?: typeof fetch;
45
+ browserRenderer?: typeof renderWithAgentBrowser;
46
+ namespace?: string;
47
+ registerCleanup?: (cleanup: () => Promise<void>) => () => void;
48
+ }): Promise<{
49
+ text: any;
50
+ outcome: {
51
+ status: string;
52
+ code: any;
53
+ retryable: boolean;
54
+ attempts: number;
55
+ backend: string;
56
+ cacheHit: boolean;
57
+ durationMs: number;
58
+ bytes: number;
59
+ truncated: boolean;
60
+ };
61
+ error: boolean;
62
+ } | {
63
+ text: string;
64
+ outcome: {
65
+ status: string;
66
+ code: string;
67
+ retryable: boolean;
68
+ attempts: number;
69
+ backend: string;
70
+ cacheHit: boolean;
71
+ durationMs: number;
72
+ bytes: number;
73
+ truncated: boolean;
74
+ statusCode: any;
75
+ redirectCount: number;
76
+ rendered: boolean;
77
+ renderFailed: boolean;
78
+ contentKind: string;
79
+ };
80
+ error: boolean;
81
+ }>;
82
+ import { renderWithAgentBrowser } from "./web-browser-render.js";