@mono-agent/agent-runtime 0.20.4 → 0.20.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/agent/tools/bash.js +29 -2
- package/src/agent/tools/exec.js +4 -2
- package/src/agent/tools/index.js +2 -0
- package/src/agent/tools/pi-bridge.js +116 -17
- package/src/agent/tools/shared/process-jobs.js +5 -2
- package/src/ai/types.js +4 -3
- package/types/agent/tools/bash.d.ts +14 -0
- package/types/agent/tools/index.d.ts +1 -1
- package/types/agent/tools/pi-bridge.d.ts +1 -0
- package/types/agent/tools/shared/process-jobs.d.ts +2 -1
- package/types/ai/types.d.ts +8 -6
package/README.md
CHANGED
package/package.json
CHANGED
package/src/agent/tools/bash.js
CHANGED
|
@@ -54,6 +54,31 @@ export function normalizeProcessTimeoutMs(value, fallback = DEFAULT_BASH_TIMEOUT
|
|
|
54
54
|
return Math.max(1, Math.min(Math.floor(n), cap));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Background process-job timeout: positive-integer milliseconds with no
|
|
59
|
+
* foreground ceiling. A background budget belongs to the host's `processJobs`
|
|
60
|
+
* settings (`maxRuntimeMs`), which clamp it on their own side; reusing the
|
|
61
|
+
* foreground default as a cap here silently discarded the long runtime a caller
|
|
62
|
+
* deliberately asked for. `undefined` means "no explicit request", leaving the
|
|
63
|
+
* host default in force.
|
|
64
|
+
*/
|
|
65
|
+
export function normalizeBackgroundTimeoutMs(value) {
|
|
66
|
+
const n = Number(value);
|
|
67
|
+
if (!Number.isFinite(n) || n <= 0) return undefined;
|
|
68
|
+
return Math.max(1, Math.floor(n));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Legacy Bash `timeout` for a background job: the same seconds-vs-milliseconds
|
|
73
|
+
* heuristic as {@link normalizeBashTimeoutMs}, minus the foreground ceiling.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeBackgroundBashTimeoutMs(value) {
|
|
76
|
+
const n = Number(value);
|
|
77
|
+
if (!Number.isFinite(n) || n <= 0) return undefined;
|
|
78
|
+
const floored = Math.floor(n);
|
|
79
|
+
return Math.max(1_000, floored <= 600 ? floored * 1_000 : floored);
|
|
80
|
+
}
|
|
81
|
+
|
|
57
82
|
/**
|
|
58
83
|
* Compatibility wrapper retained for direct callers and tests.
|
|
59
84
|
*
|
|
@@ -131,9 +156,11 @@ export async function bashToolRun(
|
|
|
131
156
|
}
|
|
132
157
|
|
|
133
158
|
if (background === true && processJobsController) {
|
|
159
|
+
// Deliberately re-derived from the raw params: `timeoutMs` above carries the
|
|
160
|
+
// foreground ceiling, which is not this job's budget.
|
|
134
161
|
const requestedTimeoutMs = timeout_ms !== undefined
|
|
135
|
-
?
|
|
136
|
-
: (timeout
|
|
162
|
+
? normalizeBackgroundTimeoutMs(timeout_ms)
|
|
163
|
+
: normalizeBackgroundBashTimeoutMs(timeout);
|
|
137
164
|
const handedOff = await handOffProcessJob({
|
|
138
165
|
controller: processJobsController,
|
|
139
166
|
tool: "Bash",
|
package/src/agent/tools/exec.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { passthroughSandbox } from "../sandbox-seam.js";
|
|
5
5
|
import { DEFAULT_MAX_BASH_OUTPUT_CHARS } from "./shared/constants.js";
|
|
6
|
-
import { normalizeProcessTimeoutMs } from "./bash.js";
|
|
6
|
+
import { normalizeBackgroundTimeoutMs, normalizeProcessTimeoutMs } from "./bash.js";
|
|
7
7
|
import { capChars } from "./shared/output-truncation.js";
|
|
8
8
|
import {
|
|
9
9
|
isPathAllowed,
|
|
@@ -101,7 +101,9 @@ export async function execToolRun(
|
|
|
101
101
|
tool: "Exec",
|
|
102
102
|
prepared,
|
|
103
103
|
summary: `Exec command (${args.length} argument${args.length === 1 ? "" : "s"}; values redacted)`,
|
|
104
|
-
|
|
104
|
+
// Re-derived from the raw param: `timeoutMs` carries the foreground
|
|
105
|
+
// ceiling, and a background job is bounded by processJobs instead.
|
|
106
|
+
timeoutMs: timeout_ms === undefined ? undefined : normalizeBackgroundTimeoutMs(timeout_ms),
|
|
105
107
|
maxOutputChars: max_output_chars === undefined ? undefined : maxChars,
|
|
106
108
|
startedAt,
|
|
107
109
|
failed,
|
package/src/agent/tools/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
4
4
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
5
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
6
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
7
8
|
import { passthroughSandbox } from "../sandbox-seam.js";
|
|
8
9
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
10
|
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
@@ -185,6 +186,20 @@ function writeFileChangeDetails(path, before, after) {
|
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Render a budget for a tool description: the exact milliseconds the host
|
|
191
|
+
* enforces, plus a human unit, so the model can both reason about the scale and
|
|
192
|
+
* pass an exact value back.
|
|
193
|
+
*/
|
|
194
|
+
function formatDurationForModel(ms) {
|
|
195
|
+
const hours = ms / 3_600_000;
|
|
196
|
+
const minutes = ms / 60_000;
|
|
197
|
+
const human = hours >= 1
|
|
198
|
+
? `${Number.isInteger(hours) ? String(hours) : hours.toFixed(1)}h`
|
|
199
|
+
: (minutes >= 1 ? `${Number.isInteger(minutes) ? String(minutes) : minutes.toFixed(1)}m` : `${String(Math.round(ms / 1_000))}s`);
|
|
200
|
+
return `${human} (${String(ms)} ms)`;
|
|
201
|
+
}
|
|
202
|
+
|
|
188
203
|
function limitedNumber(value, fallback) {
|
|
189
204
|
const n = Number(value);
|
|
190
205
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -208,10 +223,19 @@ function withToolLimits(name, params, limits = {}) {
|
|
|
208
223
|
if (name === "Bash" || name === "Exec") {
|
|
209
224
|
const timeoutLimit = limits.bashTimeoutMs || DEFAULT_BASH_TIMEOUT_MS;
|
|
210
225
|
next.max_output_chars = limitedNumber(next.max_output_chars, limits.bashOutputLimitChars || limits.toolTextLimitChars || 20000);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
// A background hand-off is bounded by the host's processJobs budget, so the
|
|
227
|
+
// FOREGROUND DEFAULT must never bound it: falling back to that default is what
|
|
228
|
+
// silently capped multi-hour background jobs at two minutes, and the `else`
|
|
229
|
+
// branch injects it even when no timeout was requested at all. An explicitly
|
|
230
|
+
// configured bashTimeoutMs still narrows them — a host that sets one means it
|
|
231
|
+
// for background work too.
|
|
232
|
+
const effectiveTimeoutLimit = next.background === true ? limits.bashTimeoutMs : timeoutLimit;
|
|
233
|
+
if (effectiveTimeoutLimit !== undefined) {
|
|
234
|
+
if (name === "Bash" && next.timeout_ms === undefined && next.timeout !== undefined) {
|
|
235
|
+
next.timeout = normalizeBashTimeoutMs(next.timeout, effectiveTimeoutLimit);
|
|
236
|
+
} else {
|
|
237
|
+
next.timeout_ms = normalizeProcessTimeoutMs(next.timeout_ms, effectiveTimeoutLimit);
|
|
238
|
+
}
|
|
215
239
|
}
|
|
216
240
|
}
|
|
217
241
|
return next;
|
|
@@ -468,10 +492,16 @@ export function getPiBuiltinTools(allowedTools, {
|
|
|
468
492
|
type: "integer",
|
|
469
493
|
description: "Deprecated compatibility timeout. Values up to 600 mean seconds and larger values mean milliseconds; use timeout_ms instead.",
|
|
470
494
|
};
|
|
495
|
+
const foregroundTimeoutLimitMs = toolLimits?.bashTimeoutMs || DEFAULT_BASH_TIMEOUT_MS;
|
|
496
|
+
const backgroundLimitMs = processJobsController?.limits?.maxRuntimeMs;
|
|
471
497
|
const processTimeoutSchema = {
|
|
472
498
|
type: "integer",
|
|
473
499
|
minimum: 1,
|
|
474
|
-
description:
|
|
500
|
+
description: `Exact timeout in milliseconds. A foreground run is capped at ${formatDurationForModel(foregroundTimeoutLimitMs)} and is killed at that point, so anything longer belongs in the background${
|
|
501
|
+
backgroundLimitMs === undefined
|
|
502
|
+
? ""
|
|
503
|
+
: `, where this host allows up to ${formatDurationForModel(backgroundLimitMs)}`
|
|
504
|
+
}.`,
|
|
475
505
|
};
|
|
476
506
|
// Shared by Exec and Bash, and injected only when the host supplies a
|
|
477
507
|
// process-job controller. House style for a tool description is
|
|
@@ -479,7 +509,11 @@ export function getPiBuiltinTools(allowedTools, {
|
|
|
479
509
|
// the model which commands belong here rather than in the foreground.
|
|
480
510
|
const backgroundSchema = {
|
|
481
511
|
type: "boolean",
|
|
482
|
-
description:
|
|
512
|
+
description: `Run as a durable background process job and notify this conversation when it finishes. Prefer this for work that outlives a reply — builds, full test suites, long installs, migrations, long-running watchers — and leave it off whenever you need the output to answer right now. Do not use for commands that daemonize into another POSIX process group or session.${
|
|
513
|
+
backgroundLimitMs === undefined
|
|
514
|
+
? ""
|
|
515
|
+
: ` This host runs a background job for up to ${formatDurationForModel(backgroundLimitMs)}; \`timeout_ms\` may lower that but never raise it, and the start receipt reports \`max_runtime_ms\`, the budget actually granted — check it, because a job is killed at that limit.`
|
|
516
|
+
}`,
|
|
483
517
|
};
|
|
484
518
|
// Per-tool closure config (cwd/event sink/limits/policy) plus the per-instance
|
|
485
519
|
// ToolContext `ctx` that the tool impls and shared helpers read from.
|
|
@@ -697,6 +731,9 @@ async function connectMcpClient(name, cfg, { cwd, sandboxPolicy, sandboxEngine,
|
|
|
697
731
|
retainedByMcpApps: false,
|
|
698
732
|
privateCapabilityUrl,
|
|
699
733
|
closed: false,
|
|
734
|
+
// AbortControllers for calls currently on the wire. Teardown aborts these
|
|
735
|
+
// BEFORE closing so the server is told to stop — see closeConnectedMcpClient.
|
|
736
|
+
inFlight: new Set(),
|
|
700
737
|
};
|
|
701
738
|
} catch (error) {
|
|
702
739
|
try { await transport?.close?.(); } catch { /* best-effort */ }
|
|
@@ -872,6 +909,25 @@ export async function initPiMcpTools(mcpConfig, reservedNames = new Set(), {
|
|
|
872
909
|
parameters: sourceTool.inputSchema || /** @type {any} */ (sourceTool).input_schema || objectSchema({}),
|
|
873
910
|
async execute(toolCallId, params, signal) {
|
|
874
911
|
if (signal?.aborted) throw new Error("tool execution aborted");
|
|
912
|
+
// OWN this request's abort signal rather than forwarding the caller's
|
|
913
|
+
// directly, so run teardown can cancel the call ON THE WIRE. The SDK
|
|
914
|
+
// turns an abort into `notifications/cancelled`; closing the client
|
|
915
|
+
// only rejects the caller locally (Protocol._onclose) and then
|
|
916
|
+
// SIGTERM/SIGKILLs a stdio child with its request still outstanding —
|
|
917
|
+
// which is how a proxy's upstream work was left dangling in #664.
|
|
918
|
+
const callAbort = new AbortController();
|
|
919
|
+
const forwardAbort = () => callAbort.abort(signal?.reason);
|
|
920
|
+
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
921
|
+
const inFlight = { controller: callAbort, label: `${serverName}:${sourceTool.name}` };
|
|
922
|
+
connected.inFlight.add(inFlight);
|
|
923
|
+
try {
|
|
924
|
+
return await callMcpTool();
|
|
925
|
+
} finally {
|
|
926
|
+
connected.inFlight.delete(inFlight);
|
|
927
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
async function callMcpTool() {
|
|
875
931
|
const textLimit = limits.mcpTextLimitChars || MCP_TEXT_RESULT_LIMIT;
|
|
876
932
|
const imageInlineMaxBytes = limits.imageInlineMaxBytes ?? MCP_IMAGE_INLINE_MAX_BYTES;
|
|
877
933
|
const normalizedParams = normalizeMcpToolParams(serverName, sourceTool.name, params || {}, { qaOutputDir, ctx });
|
|
@@ -917,7 +973,7 @@ export async function initPiMcpTools(mcpConfig, reservedNames = new Set(), {
|
|
|
917
973
|
timeout: mcpCallTimeoutMs,
|
|
918
974
|
resetTimeoutOnProgress: true,
|
|
919
975
|
maxTotalTimeout: mcpCallMaxTotalTimeoutMs,
|
|
920
|
-
signal,
|
|
976
|
+
signal: callAbort.signal,
|
|
921
977
|
onprogress,
|
|
922
978
|
},
|
|
923
979
|
).catch((error) => {
|
|
@@ -929,7 +985,7 @@ export async function initPiMcpTools(mcpConfig, reservedNames = new Set(), {
|
|
|
929
985
|
const out = await withTimeout(
|
|
930
986
|
request,
|
|
931
987
|
mcpCallTimeoutMs,
|
|
932
|
-
signal,
|
|
988
|
+
callAbort.signal,
|
|
933
989
|
`${serverName}:${sourceTool.name}`,
|
|
934
990
|
(reset) => {
|
|
935
991
|
resetInactivityTimeout = reset;
|
|
@@ -980,6 +1036,7 @@ export async function initPiMcpTools(mcpConfig, reservedNames = new Set(), {
|
|
|
980
1036
|
} : {}),
|
|
981
1037
|
},
|
|
982
1038
|
};
|
|
1039
|
+
}
|
|
983
1040
|
},
|
|
984
1041
|
});
|
|
985
1042
|
}
|
|
@@ -1071,15 +1128,30 @@ async function registerMcpAppForToolResult({
|
|
|
1071
1128
|
const connection = {
|
|
1072
1129
|
connectionId: connected.connectionId,
|
|
1073
1130
|
readResource: async (uri) => await connected.client.readResource({ uri }),
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1131
|
+
// Same in-flight registration as the tool path: a retained MCP App
|
|
1132
|
+
// connection torn down mid-call must cancel on the wire, not be closed out
|
|
1133
|
+
// from under the request. See closeConnectedMcpClient.
|
|
1134
|
+
callTool: async (name, args, signal) => {
|
|
1135
|
+
const callAbort = new AbortController();
|
|
1136
|
+
const forwardAbort = () => callAbort.abort(signal?.reason);
|
|
1137
|
+
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
1138
|
+
const inFlight = { controller: callAbort, label: `${serverName}:${name}` };
|
|
1139
|
+
connected.inFlight.add(inFlight);
|
|
1140
|
+
try {
|
|
1141
|
+
return await connected.client.callTool(
|
|
1142
|
+
{ name, arguments: args && typeof args === "object" && !Array.isArray(args) ? args : {} },
|
|
1143
|
+
undefined,
|
|
1144
|
+
{
|
|
1145
|
+
timeout: 120_000,
|
|
1146
|
+
maxTotalTimeout: 120_000,
|
|
1147
|
+
signal: callAbort.signal,
|
|
1148
|
+
},
|
|
1149
|
+
);
|
|
1150
|
+
} finally {
|
|
1151
|
+
connected.inFlight.delete(inFlight);
|
|
1152
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1083
1155
|
close: async () => {
|
|
1084
1156
|
connected.retainedByMcpApps = false;
|
|
1085
1157
|
await closeConnectedMcpClient(connected, 5_000);
|
|
@@ -1143,9 +1215,36 @@ function selectMcpAppResource(response, resourceUri) {
|
|
|
1143
1215
|
};
|
|
1144
1216
|
}
|
|
1145
1217
|
|
|
1218
|
+
/**
|
|
1219
|
+
* Cancel every call still on the wire, then close.
|
|
1220
|
+
*
|
|
1221
|
+
* Order matters. `Protocol._onclose` rejects pending requests LOCALLY with
|
|
1222
|
+
* ConnectionClosed and never notifies the server, and StdioClientTransport then
|
|
1223
|
+
* escalates stdin.end -> SIGTERM -> SIGKILL — so closing first kills a stdio
|
|
1224
|
+
* proxy mid-request with its upstream work still outstanding, and the caller
|
|
1225
|
+
* gets a bare "Connection closed" that cannot be told apart from a server that
|
|
1226
|
+
* died on its own. Aborting first makes the SDK emit `notifications/cancelled`
|
|
1227
|
+
* so the server can unwind, and rejects the caller with a sentence naming the
|
|
1228
|
+
* server and tool. See mono-agent#664.
|
|
1229
|
+
*/
|
|
1230
|
+
function cancelInFlightMcpCalls(connected) {
|
|
1231
|
+
const pending = connected?.inFlight;
|
|
1232
|
+
if (!pending || pending.size === 0) return;
|
|
1233
|
+
for (const entry of [...pending]) {
|
|
1234
|
+
try {
|
|
1235
|
+
entry.controller.abort(new McpError(
|
|
1236
|
+
ErrorCode.ConnectionClosed,
|
|
1237
|
+
`${entry.label} was cancelled because its MCP connection was torn down.`,
|
|
1238
|
+
));
|
|
1239
|
+
} catch { /* best-effort */ }
|
|
1240
|
+
}
|
|
1241
|
+
pending.clear();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1146
1244
|
async function closeConnectedMcpClient(connected, timeoutMs) {
|
|
1147
1245
|
if (!connected || connected.closed === true) return;
|
|
1148
1246
|
connected.closed = true;
|
|
1247
|
+
cancelInFlightMcpCalls(connected);
|
|
1149
1248
|
const { client, transport } = connected;
|
|
1150
1249
|
try { await closeWithTimeout(client?.close?.bind(client), timeoutMs); } catch { /* best-effort */ }
|
|
1151
1250
|
try { await closeWithTimeout(transport?.close?.bind(transport), timeoutMs); } catch { /* best-effort */ }
|
|
@@ -16,7 +16,7 @@ import { startPreparedProcess } from "./process-runner.js";
|
|
|
16
16
|
* timeoutMs?: number,
|
|
17
17
|
* maxOutputChars?: number,
|
|
18
18
|
* launch: (options?: {timeoutMs?: number, signal?: AbortSignal, maxBufferBytes?: number, onStdout?: (chunk: Buffer) => void, onStderr?: (chunk: Buffer) => void}) => ReturnType<typeof startPreparedProcess>,
|
|
19
|
-
* }) => Promise<{jobId: string, state: "queued"|"starting"|"running", startedAt: string|null}>} start
|
|
19
|
+
* }) => Promise<{jobId: string, state: "queued"|"starting"|"running", startedAt: string|null, maxRuntimeMs?: number}>} start
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -82,6 +82,7 @@ export async function handOffProcessJob({
|
|
|
82
82
|
job_id: result.jobId,
|
|
83
83
|
state: result.state,
|
|
84
84
|
started_at: result.startedAt,
|
|
85
|
+
...(result.maxRuntimeMs === undefined ? {} : { max_runtime_ms: result.maxRuntimeMs }),
|
|
85
86
|
};
|
|
86
87
|
return {
|
|
87
88
|
text: `${BACKGROUND_START_GUIDANCE}\n${JSON.stringify(payload)}`,
|
|
@@ -119,7 +120,7 @@ export async function handOffProcessJob({
|
|
|
119
120
|
* status command invites exactly the polling this forbids.
|
|
120
121
|
*/
|
|
121
122
|
const BACKGROUND_START_GUIDANCE =
|
|
122
|
-
"Background process job started (tool-authored guidance): this conversation is woken with a new turn when the job reaches a terminal state, and its output arrives with that turn. Do not poll, sleep, wait on it, or re-run the command to check progress, and do not report the work as finished yet.";
|
|
123
|
+
"Background process job started (tool-authored guidance): this conversation is woken with a new turn when the job reaches a terminal state, and its output arrives with that turn. Do not poll, sleep, wait on it, or re-run the command to check progress, and do not report the work as finished yet. `max_runtime_ms` is the budget the host granted; if it is below what you requested, the host capped it and the job will be killed at that limit, so plan the work around the granted budget rather than re-running the same command.";
|
|
123
124
|
|
|
124
125
|
const PUBLIC_BACKGROUND_START_FAILURES = Object.freeze({
|
|
125
126
|
background_unsupported: "Background process jobs are unsupported for this tool call.",
|
|
@@ -191,6 +192,8 @@ function validProcessJobStartResult(value) {
|
|
|
191
192
|
if (!value || typeof value !== "object") return false;
|
|
192
193
|
if (typeof value.jobId !== "string" || value.jobId.trim().length === 0 || value.jobId.length > 256) return false;
|
|
193
194
|
if (value.state !== "queued" && value.state !== "starting" && value.state !== "running") return false;
|
|
195
|
+
if (value.maxRuntimeMs !== undefined
|
|
196
|
+
&& (!Number.isSafeInteger(value.maxRuntimeMs) || value.maxRuntimeMs <= 0)) return false;
|
|
194
197
|
if (value.startedAt === null) return true;
|
|
195
198
|
if (typeof value.startedAt !== "string") return false;
|
|
196
199
|
const timestamp = Date.parse(value.startedAt);
|
package/src/ai/types.js
CHANGED
|
@@ -214,9 +214,10 @@
|
|
|
214
214
|
* @property {number} [toolPayloadMaxBytes] Hard cap on a single tool_result payload.
|
|
215
215
|
* @property {number} [mcpCallTimeoutMs] Per-MCP-call inactivity timeout.
|
|
216
216
|
* @property {number} [mcpCallMaxTotalTimeoutMs] Hard wall-clock cap for one MCP call.
|
|
217
|
-
* @property {number} [bashTimeoutMs]
|
|
218
|
-
*
|
|
219
|
-
*
|
|
217
|
+
* @property {number} [bashTimeoutMs] Foreground ceiling and default for
|
|
218
|
+
* Bash/Exec timeouts on the Pi bridge, applied by `normalizePiBuiltinToolParams`
|
|
219
|
+
* (defaults to 120_000). Background hand-offs deliberately ignore it: a process job is
|
|
220
|
+
* bounded by the host's `processJobs.maxRuntimeMs` instead.
|
|
220
221
|
*/
|
|
221
222
|
|
|
222
223
|
/**
|
|
@@ -7,6 +7,20 @@ export function normalizeBashTimeoutMs(value: any, fallback?: number): any;
|
|
|
7
7
|
* Exact millisecond timeout used by Bash.timeout_ms and Exec.timeout_ms.
|
|
8
8
|
*/
|
|
9
9
|
export function normalizeProcessTimeoutMs(value: any, fallback?: number): any;
|
|
10
|
+
/**
|
|
11
|
+
* Background process-job timeout: positive-integer milliseconds with no
|
|
12
|
+
* foreground ceiling. A background budget belongs to the host's `processJobs`
|
|
13
|
+
* settings (`maxRuntimeMs`), which clamp it on their own side; reusing the
|
|
14
|
+
* foreground default as a cap here silently discarded the long runtime a caller
|
|
15
|
+
* deliberately asked for. `undefined` means "no explicit request", leaving the
|
|
16
|
+
* host default in force.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeBackgroundTimeoutMs(value: any): number;
|
|
19
|
+
/**
|
|
20
|
+
* Legacy Bash `timeout` for a background job: the same seconds-vs-milliseconds
|
|
21
|
+
* heuristic as {@link normalizeBashTimeoutMs}, minus the foreground ceiling.
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeBackgroundBashTimeoutMs(value: any): number;
|
|
10
24
|
/**
|
|
11
25
|
* Compatibility wrapper retained for direct callers and tests.
|
|
12
26
|
*
|
|
@@ -5,7 +5,7 @@ export { globToolImpl } from "./glob.js";
|
|
|
5
5
|
export { grepToolImpl } from "./grep.js";
|
|
6
6
|
export { createWebToolController } from "./web-controller.js";
|
|
7
7
|
export { resolveRgPath } from "./shared/ripgrep.js";
|
|
8
|
-
export { bashToolImpl, bashToolRun, normalizeBashTimeoutMs, normalizeProcessTimeoutMs } from "./bash.js";
|
|
8
|
+
export { bashToolImpl, bashToolRun, normalizeBackgroundBashTimeoutMs, normalizeBackgroundTimeoutMs, normalizeBashTimeoutMs, normalizeProcessTimeoutMs } from "./bash.js";
|
|
9
9
|
export { execToolImpl, execToolRun } from "./exec.js";
|
|
10
10
|
export { webFetchToolImpl, performWebFetch } from "./web-fetch.js";
|
|
11
11
|
export { webSearchToolImpl, performWebSearch } from "./web-search.js";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* timeoutMs?: number,
|
|
11
11
|
* maxOutputChars?: number,
|
|
12
12
|
* launch: (options?: {timeoutMs?: number, signal?: AbortSignal, maxBufferBytes?: number, onStdout?: (chunk: Buffer) => void, onStderr?: (chunk: Buffer) => void}) => ReturnType<typeof startPreparedProcess>,
|
|
13
|
-
* }) => Promise<{jobId: string, state: "queued"|"starting"|"running", startedAt: string|null}>} start
|
|
13
|
+
* }) => Promise<{jobId: string, state: "queued"|"starting"|"running", startedAt: string|null, maxRuntimeMs?: number}>} start
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
16
|
* Transfer one prepared command to the injected host controller. From the
|
|
@@ -59,6 +59,7 @@ export type ProcessJobsController = {
|
|
|
59
59
|
jobId: string;
|
|
60
60
|
state: "queued" | "starting" | "running";
|
|
61
61
|
startedAt: string | null;
|
|
62
|
+
maxRuntimeMs?: number;
|
|
62
63
|
}>;
|
|
63
64
|
};
|
|
64
65
|
import { startPreparedProcess } from "./process-runner.js";
|
package/types/ai/types.d.ts
CHANGED
|
@@ -177,9 +177,10 @@
|
|
|
177
177
|
* @property {number} [toolPayloadMaxBytes] Hard cap on a single tool_result payload.
|
|
178
178
|
* @property {number} [mcpCallTimeoutMs] Per-MCP-call inactivity timeout.
|
|
179
179
|
* @property {number} [mcpCallMaxTotalTimeoutMs] Hard wall-clock cap for one MCP call.
|
|
180
|
-
* @property {number} [bashTimeoutMs]
|
|
181
|
-
*
|
|
182
|
-
*
|
|
180
|
+
* @property {number} [bashTimeoutMs] Foreground ceiling and default for
|
|
181
|
+
* Bash/Exec timeouts on the Pi bridge, applied by `normalizePiBuiltinToolParams`
|
|
182
|
+
* (defaults to 120_000). Background hand-offs deliberately ignore it: a process job is
|
|
183
|
+
* bounded by the host's `processJobs.maxRuntimeMs` instead.
|
|
183
184
|
*/
|
|
184
185
|
/**
|
|
185
186
|
* @typedef {Object} RuntimeCompactionPolicy
|
|
@@ -754,9 +755,10 @@ export type RuntimeToolLimits = {
|
|
|
754
755
|
*/
|
|
755
756
|
mcpCallMaxTotalTimeoutMs?: number;
|
|
756
757
|
/**
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
758
|
+
* Foreground ceiling and default for
|
|
759
|
+
* Bash/Exec timeouts on the Pi bridge, applied by `normalizePiBuiltinToolParams`
|
|
760
|
+
* (defaults to 120_000). Background hand-offs deliberately ignore it: a process job is
|
|
761
|
+
* bounded by the host's `processJobs.maxRuntimeMs` instead.
|
|
760
762
|
*/
|
|
761
763
|
bashTimeoutMs?: number;
|
|
762
764
|
};
|