@sema-agent/core 1.434.0 → 1.435.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.
- package/dist/core/task-registry.js +2 -20
- package/dist/core/tool-errors.d.ts +1 -0
- package/dist/core/tool-errors.js +21 -0
- package/dist/tools/fs/index.js +50 -20
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { summarizeWorkflowRun } from "./workflow-run-store.js";
|
|
|
7
7
|
import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-agent-store.js";
|
|
8
8
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
9
9
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
10
|
+
import { clipWithFilePointer } from "./tool-errors.js";
|
|
10
11
|
const defaultMonitorTimers = {
|
|
11
12
|
setInterval: (fn, ms) => {
|
|
12
13
|
const h = setInterval(() => void fn(), ms);
|
|
@@ -52,8 +53,6 @@ const TASK_OUTPUT_DEFAULT_CHARS = 32_000;
|
|
|
52
53
|
const TASK_OUTPUT_MAX_CHARS = 160_000;
|
|
53
54
|
const TASK_OUTPUT_ALIASES = ["BashOutput", "AgentOutputTool", "BashOutputTool", "AgentOutput", "WorkflowStatus"];
|
|
54
55
|
const TASK_STOP_ALIASES = ["KillShell", "KillBash"];
|
|
55
|
-
const isHighSurrogate = (c) => c >= 0xd800 && c <= 0xdbff;
|
|
56
|
-
const isLowSurrogate = (c) => c >= 0xdc00 && c <= 0xdfff;
|
|
57
56
|
const TASK_OUTPUT_MIN_CHARS = 512;
|
|
58
57
|
function taskMaxOutputChars() {
|
|
59
58
|
const raw = process.env.TASK_MAX_OUTPUT_LENGTH;
|
|
@@ -67,24 +66,7 @@ function taskMaxOutputChars() {
|
|
|
67
66
|
return Math.min(Math.max(n, TASK_OUTPUT_MIN_CHARS), TASK_OUTPUT_MAX_CHARS);
|
|
68
67
|
}
|
|
69
68
|
export function clipTaskOutput(s, fullOutputPath) {
|
|
70
|
-
|
|
71
|
-
if (s.length <= limit)
|
|
72
|
-
return s;
|
|
73
|
-
if (fullOutputPath !== undefined) {
|
|
74
|
-
const header = `[Truncated. Full output: ${fullOutputPath}]\n\n`;
|
|
75
|
-
const room = limit - header.length;
|
|
76
|
-
return room > 0 ? header + s.slice(-room) : header.trimEnd().slice(0, Math.min(limit, s.length));
|
|
77
|
-
}
|
|
78
|
-
const omittedCount = (kept) => s.length - kept;
|
|
79
|
-
const markerFor = (kept) => `\n…[${s.length} chars total, ${omittedCount(kept)} omitted from the middle]…\n`;
|
|
80
|
-
const budget = limit - markerFor(0).length;
|
|
81
|
-
if (budget < 2)
|
|
82
|
-
return s.slice(0, limit);
|
|
83
|
-
const half = Math.floor(budget / 2);
|
|
84
|
-
const headEnd = half > 0 && isHighSurrogate(s.charCodeAt(half - 1)) ? half - 1 : half;
|
|
85
|
-
const tailStart = half > 0 && isLowSurrogate(s.charCodeAt(s.length - half)) ? s.length - half + 1 : s.length - half;
|
|
86
|
-
const kept = headEnd + (s.length - tailStart);
|
|
87
|
-
return `${s.slice(0, headEnd)}${markerFor(kept)}${s.slice(tailStart)}`;
|
|
69
|
+
return clipWithFilePointer(s, taskMaxOutputChars(), fullOutputPath);
|
|
88
70
|
}
|
|
89
71
|
function firstString(...values) {
|
|
90
72
|
for (const value of values) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TSchema } from "typebox";
|
|
2
2
|
export declare function truncateError(s: string): string;
|
|
3
|
+
export declare function clipWithFilePointer(s: string, limit: number, fullOutputPath?: string): string;
|
|
3
4
|
export declare function formatToolError(error: unknown): string;
|
|
4
5
|
export type WorkerErrorClass = "budget" | "limit" | "output" | "suspend" | "review" | "unexpected" | "oracle" | "resume" | "config" | "conflict" | "brain" | "aborted" | "unknown";
|
|
5
6
|
export declare function errorClassOf(errorCode: string | undefined): WorkerErrorClass;
|
package/dist/core/tool-errors.js
CHANGED
|
@@ -7,6 +7,27 @@ export function truncateError(s) {
|
|
|
7
7
|
const removed = s.length - MAX_ERROR_CHARS;
|
|
8
8
|
return `${s.slice(0, HALF)}\n\n... [${removed} characters truncated] ...\n\n${s.slice(s.length - HALF)}`;
|
|
9
9
|
}
|
|
10
|
+
const isHighSurrogate = (c) => c >= 0xd800 && c <= 0xdbff;
|
|
11
|
+
const isLowSurrogate = (c) => c >= 0xdc00 && c <= 0xdfff;
|
|
12
|
+
export function clipWithFilePointer(s, limit, fullOutputPath) {
|
|
13
|
+
if (s.length <= limit)
|
|
14
|
+
return s;
|
|
15
|
+
if (fullOutputPath !== undefined) {
|
|
16
|
+
const header = `[Truncated. Full output: ${fullOutputPath}]\n\n`;
|
|
17
|
+
const room = limit - header.length;
|
|
18
|
+
return room > 0 ? header + s.slice(-room) : header.trimEnd().slice(0, Math.min(limit, s.length));
|
|
19
|
+
}
|
|
20
|
+
const omittedCount = (kept) => s.length - kept;
|
|
21
|
+
const markerFor = (kept) => `\n…[${s.length} chars total, ${omittedCount(kept)} omitted from the middle]…\n`;
|
|
22
|
+
const budget = limit - markerFor(0).length;
|
|
23
|
+
if (budget < 2)
|
|
24
|
+
return s.slice(0, limit);
|
|
25
|
+
const half = Math.floor(budget / 2);
|
|
26
|
+
const headEnd = half > 0 && isHighSurrogate(s.charCodeAt(half - 1)) ? half - 1 : half;
|
|
27
|
+
const tailStart = half > 0 && isLowSurrogate(s.charCodeAt(s.length - half)) ? s.length - half + 1 : s.length - half;
|
|
28
|
+
const kept = headEnd + (s.length - tailStart);
|
|
29
|
+
return `${s.slice(0, headEnd)}${markerFor(kept)}${s.slice(tailStart)}`;
|
|
30
|
+
}
|
|
10
31
|
function isShellLike(e) {
|
|
11
32
|
return typeof e === "object" && e !== null && ("exitCode" in e || "stderr" in e || "stdout" in e);
|
|
12
33
|
}
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -4,9 +4,10 @@ import { defineTool } from "../../core/tools.js";
|
|
|
4
4
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../../core/task-registry.js";
|
|
5
5
|
import { hasBackgroundShell } from "../../core/background-shell.js";
|
|
6
6
|
import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
7
|
+
import { clipWithFilePointer } from "../../core/tool-errors.js";
|
|
7
8
|
import { sha256, resolveKey, violationText, requireRead, checkStale, checkEditMatch, checkNoChange, hasBinaryExtension, isBinaryContent, fileArgPath, imageMimeForRead, imageMagicMatches, similarNameSuggestion, resolveQuoteMatch, adaptNewStringQuotes, deletionOldString, countOccurrences, OVERSIZE_READ_ESCAPE_HINT, isBlockedDevicePath, normalizeAbsPathLexically, } from "./safety.js";
|
|
8
9
|
import { decodeTextBytes, encodeTextForFile, normalizeEditText } from "./encoding.js";
|
|
9
|
-
import { runGrepDetailed, runGlobDetailed, splitAbsoluteGlobPattern } from "./search.js";
|
|
10
|
+
import { runGrepDetailed, runGlobDetailed, splitAbsoluteGlobPattern, shellQuote } from "./search.js";
|
|
10
11
|
import { makeRepoMapTool } from "./repo-map.js";
|
|
11
12
|
import { ghRateLimitHint } from "./gh-rate-limit.js";
|
|
12
13
|
import { MCP_IMAGE_MAX_BASE64, IMAGE_TARGET_RAW_SIZE, sharpImageDownsampler } from "../../core/mcp.js";
|
|
@@ -61,12 +62,23 @@ const FILE_PATH_PARAMS = {
|
|
|
61
62
|
path: Type.Optional(Type.String({ description: "Deprecated alias for `file_path` (back-compat; prefer file_path)." })),
|
|
62
63
|
};
|
|
63
64
|
function clipShellOutput(s) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
return clipWithFilePointer(s, bashMaxOutputChars());
|
|
66
|
+
}
|
|
67
|
+
async function writeShellOverflowFile(env, stdout, stderr) {
|
|
68
|
+
const tf = await env.createTempFile({ prefix: "bash-output-", suffix: ".log" });
|
|
69
|
+
if (!tf.ok)
|
|
70
|
+
return undefined;
|
|
71
|
+
const body = stderr.length > 0 ? `${stdout}${stdout.length > 0 && !stdout.endsWith("\n") ? "\n" : ""}--- stderr ---\n${stderr}` : stdout;
|
|
72
|
+
const w = await env.writeFile(tf.value, body);
|
|
73
|
+
if (!w.ok)
|
|
74
|
+
return undefined;
|
|
75
|
+
const canon = await env.canonicalPath(tf.value);
|
|
76
|
+
return canon.ok ? canon.value : tf.value;
|
|
77
|
+
}
|
|
78
|
+
function shellRecoveryHint(path, readOnly) {
|
|
79
|
+
const quoted = shellQuote(path);
|
|
80
|
+
const example = readOnly ? `tail -c 50000 ${quoted}` : `sed -n 'START,ENDp' ${quoted}`;
|
|
81
|
+
return `\n(captured output preserved at ${path} — inspect with bash, e.g. \`${example}\`)`;
|
|
70
82
|
}
|
|
71
83
|
const FILE_STATE_TRAILER = " (file state is current in your context — no need to Read it back)";
|
|
72
84
|
const CWD_SENTINEL = "__cc_cwd_9f2c1b__";
|
|
@@ -1446,7 +1458,7 @@ export function canAutoBackground(command) {
|
|
|
1446
1458
|
return false;
|
|
1447
1459
|
return true;
|
|
1448
1460
|
}
|
|
1449
|
-
async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef, detach, execClamp, toolCallId) {
|
|
1461
|
+
async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef, detach, execClamp, toolCallId, readOnly) {
|
|
1450
1462
|
let timeout = Math.min(BASH_MAX_TIMEOUT_SEC, Math.max(1, Math.floor(timeoutSec ?? BASH_DEFAULT_TIMEOUT_SEC)));
|
|
1451
1463
|
const requestedSec = timeout;
|
|
1452
1464
|
let clampedByDeadline = false;
|
|
@@ -1509,9 +1521,13 @@ async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef,
|
|
|
1509
1521
|
return `Error (${toolName}): connection to the execution environment was lost — the command's outcome is unknown and it may still be running. Verify its effects before retrying; retry only if the command is idempotent. (${res.error.message})`;
|
|
1510
1522
|
}
|
|
1511
1523
|
if (res.error.code === "timeout" || res.error.code === "aborted" || res.error.code === "callback_error") {
|
|
1512
|
-
const
|
|
1513
|
-
const
|
|
1514
|
-
const
|
|
1524
|
+
const rawStdout = res.error.partialStdout ?? "";
|
|
1525
|
+
const rawStderr = res.error.partialStderr ?? "";
|
|
1526
|
+
const cutOverflowFile = rawStdout.length > bashMaxOutputChars() || rawStderr.length > bashMaxOutputChars()
|
|
1527
|
+
? await writeShellOverflowFile(env, rawStdout, rawStderr)
|
|
1528
|
+
: undefined;
|
|
1529
|
+
const pStdout = clipShellOutput(rawStdout);
|
|
1530
|
+
const pStderr = clipShellOutput(rawStderr);
|
|
1515
1531
|
const headline = res.error.code === "timeout"
|
|
1516
1532
|
? clampedByDeadline
|
|
1517
1533
|
? deadlineExhausted
|
|
@@ -1526,13 +1542,15 @@ async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef,
|
|
|
1526
1542
|
const body = captured.length > 0
|
|
1527
1543
|
? `\n${delimitUntrusted("partial command output", captured)}`
|
|
1528
1544
|
: `\n(no output was produced before the cutoff)`;
|
|
1545
|
+
const overflowNote = cutOverflowFile !== undefined ? shellRecoveryHint(cutOverflowFile, readOnly) : "";
|
|
1529
1546
|
return {
|
|
1530
|
-
content: `Error (${toolName}): ${headline}${body}`,
|
|
1547
|
+
content: `Error (${toolName}): ${headline}${body}${overflowNote}`,
|
|
1531
1548
|
details: {
|
|
1532
1549
|
type: "bash",
|
|
1533
1550
|
stdout: pStdout,
|
|
1534
1551
|
stderr: pStderr,
|
|
1535
1552
|
exitCode: null,
|
|
1553
|
+
...(cutOverflowFile !== undefined ? { output_file: cutOverflowFile } : {}),
|
|
1536
1554
|
...(res.error.code === "timeout"
|
|
1537
1555
|
? {
|
|
1538
1556
|
timedOut: true,
|
|
@@ -1567,26 +1585,38 @@ async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef,
|
|
|
1567
1585
|
cwdRef.current = captured;
|
|
1568
1586
|
}
|
|
1569
1587
|
}
|
|
1570
|
-
const
|
|
1571
|
-
const
|
|
1572
|
-
const clippedStderr = clip(stderr);
|
|
1588
|
+
const clippedStdout = clipShellOutput(stdout);
|
|
1589
|
+
const clippedStderr = clipShellOutput(stderr);
|
|
1573
1590
|
const stdoutImage = dataUriImageFromStdout(stdout);
|
|
1574
1591
|
if (stdoutImage) {
|
|
1592
|
+
const imgOverflowFile = stderr.length > bashMaxOutputChars() ? await writeShellOverflowFile(env, "", stderr) : undefined;
|
|
1593
|
+
const imgOverflowNote = imgOverflowFile !== undefined ? shellRecoveryHint(imgOverflowFile, readOnly) : "";
|
|
1575
1594
|
return {
|
|
1576
1595
|
content: [
|
|
1577
1596
|
{ type: "image", data: stdoutImage.data, mimeType: stdoutImage.mime },
|
|
1578
|
-
{ type: "text", text: `exit code: ${exitCode}\n[Image data detected in stdout and shown above]${stderr.trim() !== "" ? `\n--- stderr ---\n${clippedStderr}` : ""}` },
|
|
1597
|
+
{ type: "text", text: `exit code: ${exitCode}\n[Image data detected in stdout and shown above]${stderr.trim() !== "" ? `\n--- stderr ---\n${clippedStderr}` : ""}${imgOverflowNote}` },
|
|
1579
1598
|
],
|
|
1580
|
-
details: { type: "bash", stdout: "[image data]", stderr: clippedStderr, exitCode, isImage: true },
|
|
1599
|
+
details: { type: "bash", stdout: "[image data]", stderr: clippedStderr, exitCode, isImage: true, ...(imgOverflowFile !== undefined ? { output_file: imgOverflowFile } : {}) },
|
|
1581
1600
|
};
|
|
1582
1601
|
}
|
|
1602
|
+
const cleanOverflowFile = stdout.length > bashMaxOutputChars() || stderr.length > bashMaxOutputChars() ? await writeShellOverflowFile(env, stdout, stderr) : undefined;
|
|
1603
|
+
const overflowNote = cleanOverflowFile !== undefined ? shellRecoveryHint(cleanOverflowFile, readOnly) : "";
|
|
1583
1604
|
const exit1Note = exitCode === 1 ? bashExitOneInterpretation(command) : undefined;
|
|
1584
1605
|
const parts = [`exit code: ${exitCode}${exit1Note ? ` (${exit1Note} — exit 1 from this command is not an error)` : ""}`, `--- stdout ---\n${clippedStdout || "(empty)"}`];
|
|
1585
1606
|
if (stderr.trim() !== "")
|
|
1586
1607
|
parts.push(`--- stderr ---\n${clippedStderr}`);
|
|
1608
|
+
if (overflowNote)
|
|
1609
|
+
parts.push(overflowNote.trim());
|
|
1587
1610
|
return {
|
|
1588
1611
|
content: parts.join("\n"),
|
|
1589
|
-
details: {
|
|
1612
|
+
details: {
|
|
1613
|
+
type: "bash",
|
|
1614
|
+
stdout: clippedStdout,
|
|
1615
|
+
stderr: clippedStderr,
|
|
1616
|
+
exitCode,
|
|
1617
|
+
...(exit1Note ? { returnCodeInterpretation: exit1Note } : {}),
|
|
1618
|
+
...(cleanOverflowFile !== undefined ? { output_file: cleanOverflowFile } : {}),
|
|
1619
|
+
},
|
|
1590
1620
|
};
|
|
1591
1621
|
}
|
|
1592
1622
|
function bashDescription(coAuthor, bgNotifies = false, bgRetained = false) {
|
|
@@ -1828,7 +1858,7 @@ export function makeBashTool(env, rootCanonical, coAuthor = false, cwdRef = { cu
|
|
|
1828
1858
|
: undefined;
|
|
1829
1859
|
let res;
|
|
1830
1860
|
try {
|
|
1831
|
-
res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToSec(timeout), ctx.signal, cwdRef, detachChain, taskOpts.execClamp, ctx.toolCallId);
|
|
1861
|
+
res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToSec(timeout), ctx.signal, cwdRef, detachChain, taskOpts.execClamp, ctx.toolCallId, false);
|
|
1832
1862
|
}
|
|
1833
1863
|
finally {
|
|
1834
1864
|
taskOpts.detachHub?.gc(ctx.toolCallId);
|
|
@@ -1863,7 +1893,7 @@ export function makeBashReadonlyTool(env, rootCanonical, allow, execClamp) {
|
|
|
1863
1893
|
const reason = coarseReadonlyCheck(command, allow);
|
|
1864
1894
|
if (reason)
|
|
1865
1895
|
return `Error (Bash): ${reason}`;
|
|
1866
|
-
return runShell(env, rootCanonical, "Bash", command, msTimeoutToSec(timeout), ctx.signal, undefined, undefined, execClamp, ctx.toolCallId);
|
|
1896
|
+
return runShell(env, rootCanonical, "Bash", command, msTimeoutToSec(timeout), ctx.signal, undefined, undefined, execClamp, ctx.toolCallId, true);
|
|
1867
1897
|
},
|
|
1868
1898
|
});
|
|
1869
1899
|
}
|