@sema-agent/core 7.23.0 → 7.23.2
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/CHANGELOG.md +64 -0
- package/dist/agents/agent-transcript-tool.js +2 -1
- package/dist/agents/cascade.js +2 -1
- package/dist/agents/list-agents-tool.js +2 -1
- package/dist/agents/observer.js +3 -2
- package/dist/agents/peer-session-drain.js +2 -1
- package/dist/agents/retain-ledger.js +2 -1
- package/dist/agents/send-message-tool.js +11 -10
- package/dist/agents/subagent.js +18 -17
- package/dist/agents/teacher.js +5 -4
- package/dist/brain/circuit-breaker.js +2 -1
- package/dist/brain/degrading.js +2 -1
- package/dist/brain/errors.js +24 -15
- package/dist/brain/failover.js +2 -1
- package/dist/brain/stream-engine.js +2 -1
- package/dist/core/a2a.js +4 -3
- package/dist/core/artifact-host.js +2 -1
- package/dist/core/ask-question.js +3 -2
- package/dist/core/auto-promote.js +3 -2
- package/dist/core/gate-bounded-await.d.ts +1 -1
- package/dist/core/gate-bounded-await.js +3 -2
- package/dist/core/gate-crash-trace.d.ts +7 -0
- package/dist/core/gate-crash-trace.js +3 -0
- package/dist/core/gate-fold.js +6 -2
- package/dist/core/gate-lanes.js +6 -3
- package/dist/core/git-worktree-env.js +6 -5
- package/dist/core/hooks.js +6 -5
- package/dist/core/lsp-session.js +3 -2
- package/dist/core/mcp.js +4 -3
- package/dist/core/memory-admission.js +2 -1
- package/dist/core/memory-engine/consolidation-driver.js +5 -4
- package/dist/core/memory-engine/consolidation.js +2 -1
- package/dist/core/memory-engine/delegation-settlement.js +4 -3
- package/dist/core/memory-engine/distiller.js +8 -4
- package/dist/core/memory-engine/engine.js +63 -55
- package/dist/core/memory-engine/file-backend.js +11 -10
- package/dist/core/memory-engine/layout.js +5 -4
- package/dist/core/memory-engine/session-incarnation.js +2 -1
- package/dist/core/memory.js +2 -1
- package/dist/core/oracle-isolation.js +3 -2
- package/dist/core/permission-rule-store.js +2 -1
- package/dist/core/runner/announce-once-ledger.js +2 -1
- package/dist/core/runner/compaction-seams.js +3 -2
- package/dist/core/runner/gate-exit.d.ts +5 -5
- package/dist/core/runner/gate-exit.js +0 -3
- package/dist/core/runner/git-leg-delivery.js +4 -3
- package/dist/core/runner/git-status-frame.js +2 -1
- package/dist/core/runner/mcp-redial.js +2 -1
- package/dist/core/runner/memory-consolidation-driver.js +2 -1
- package/dist/core/runner/permission-rule-lanes.js +3 -2
- package/dist/core/runner/prepare-artifact.js +2 -1
- package/dist/core/runner/prepare-ask-lane.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.js +4 -3
- package/dist/core/runner/prepare-inherited-gate.js +2 -1
- package/dist/core/runner/prepare-memory-engine-session.js +2 -1
- package/dist/core/runner/prepare-memory.js +5 -4
- package/dist/core/runner/prepare-policy-chain.js +3 -2
- package/dist/core/runner/prepare-project-context.js +3 -2
- package/dist/core/runner/prepare-protocol-tools.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.js +2 -1
- package/dist/core/runner/prepare-task.js +6 -5
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/resume-apply.js +5 -4
- package/dist/core/runner/resume-preflight.js +3 -2
- package/dist/core/runner/run-git-lane.js +4 -3
- package/dist/core/runner/run-leg.js +4 -3
- package/dist/core/runner/run-recovery-lanes.js +3 -2
- package/dist/core/runner/run-settle-and-teardown.js +2 -1
- package/dist/core/runner/run-stop-and-final-verify.js +2 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.js +2 -1
- package/dist/core/runner/run-terminal-adoption.js +6 -5
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/stream-settle-backstop.js +5 -4
- package/dist/core/runner/stream-steer-verb.js +2 -1
- package/dist/core/runner/strict-output-schema.js +2 -1
- package/dist/core/runner/teardown-bounded.js +2 -1
- package/dist/core/safe-notify.js +2 -1
- package/dist/core/skills-directory.js +2 -1
- package/dist/core/strategy-store.js +2 -1
- package/dist/core/task-registry-agent.js +2 -1
- package/dist/core/task-registry-monitor.js +2 -1
- package/dist/core/task-registry-workflow.js +2 -1
- package/dist/core/task-registry.js +3 -2
- package/dist/core/thrown-value.d.ts +79 -0
- package/dist/core/thrown-value.js +56 -0
- package/dist/core/tool-errors.d.ts +0 -6
- package/dist/core/tool-errors.js +36 -14
- package/dist/core/tool-model-gate.js +2 -1
- package/dist/core/tool-policy.js +6 -13
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/tool-spec.d.ts +12 -0
- package/dist/core/write-protect.js +4 -4
- package/dist/engine/compaction/compaction.js +3 -4
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.js +8 -7
- package/dist/engine/loop/agent-loop.js +7 -6
- package/dist/engine/lsp/node-lsp-manager.js +5 -4
- package/dist/engine/lsp/stdio-lsp-transport.js +12 -5
- package/dist/orchestration/dev-vm-script-runner.js +2 -1
- package/dist/orchestration/run-workflow-tool.js +7 -6
- package/dist/orchestration/workflow-governance.js +2 -1
- package/dist/orchestration/workflow-sandbox-conformance.js +3 -2
- package/dist/orchestration/workflow-script-store.js +2 -1
- package/dist/orchestration/workflow.js +15 -14
- package/dist/server/http.js +3 -2
- package/dist/stores/file/adoption/adopt.js +3 -2
- package/dist/stores/file/fs-atomic.js +2 -1
- package/dist/stores/file/permission-rule-adopt.js +2 -1
- package/dist/stores/file/permission-rule-store.js +2 -1
- package/dist/stores/file/session-store.js +4 -3
- package/dist/stores/file/strategy-store.js +7 -6
- package/dist/tools/artifact/artifact-tool.js +3 -2
- package/dist/tools/artifact/conformance.js +4 -3
- package/dist/tools/fs/bash-lexer.d.ts +12 -5
- package/dist/tools/fs/bash-lexer.js +5 -2
- package/dist/tools/fs/bash-program-position.d.ts +18 -0
- package/dist/tools/fs/bash-program-position.js +13 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +23 -7
- package/dist/tools/fs/bash-readonly-classifier.js +74 -41
- package/dist/tools/fs/fs-bash.d.ts +28 -0
- package/dist/tools/fs/fs-bash.js +31 -13
- package/dist/tools/fs/fs-write.js +3 -2
- package/dist/tools/fs/safety.js +2 -1
- package/dist/tools/fs/search.js +3 -2
- package/dist/tools/monitor.js +3 -2
- package/dist/tools/sql.js +2 -1
- package/dist/tools/web.js +5 -15
- package/dist/tools/worktree.js +12 -9
- package/package.json +2 -1
- package/wire-changes/core@7.23.1.json +205 -0
- package/wire-changes/core@7.23.2.json +42 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { hasWin32AliasedComponent, isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf, withinAnyRoot } from "./safety.js";
|
|
2
2
|
import { lexShellText, literalTildeSpelling, quoteMask, substitutionsInHeredocBody, SUBSTITUTION_PLACEHOLDER, wordsOf, } from "./bash-lexer.js";
|
|
3
|
-
import { ASSIGNMENT_WORD, COMMAND_LAUNCHERS, NOT_AUTO_ALLOWED, SHELL_BLOCK_WORDS, argvReentryUnjudged, isBareControlWord, programBasename, programPositionRefusalSentence, readProgramPosition, } from "./bash-program-position.js";
|
|
4
|
-
export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
|
|
3
|
+
import { ASSIGNMENT_WORD, COMMAND_LAUNCHERS, NOT_AUTO_ALLOWED, SHELL_BLOCK_WORDS, argvReentryUnjudged, isBareControlWord, programPositionRunsShell, shellScanDeclineOf, programBasename, programPositionRefusalSentence, readProgramPosition, } from "./bash-program-position.js";
|
|
5
4
|
export const BASH_READONLY_DEFAULT_ALLOW = [
|
|
6
5
|
"ls", "cat", "head", "tail", "wc", "pwd", "echo", "whoami", "uname",
|
|
7
6
|
"grep", "cut", "tr", "basename", "dirname", "stat", "du", "df", "which",
|
|
@@ -52,7 +51,8 @@ export function coarseReadonlyCheck(command, allow, options) {
|
|
|
52
51
|
return `command "${parsed.name}" is not in the read-only allowlist`;
|
|
53
52
|
return undefined;
|
|
54
53
|
}
|
|
55
|
-
const
|
|
54
|
+
const SHELL_SEGMENT_QUOTE_BLIND_REJECT = /[<>()`\n\r\\]/;
|
|
55
|
+
const SHELL_EXPANSION_CHARS = /[$]/;
|
|
56
56
|
export function carriesShellRedirection(text) {
|
|
57
57
|
return SHELL_REDIRECTION_OPERATORS.test(text);
|
|
58
58
|
}
|
|
@@ -78,10 +78,25 @@ function rejectedSansRedirection(source) {
|
|
|
78
78
|
if (redirectionPrecedesConnector(source)) {
|
|
79
79
|
return "a redirection followed by `|`, `&` or `;` is not allowed — the operator's own characters cannot be told apart from a command boundary";
|
|
80
80
|
}
|
|
81
|
+
return hardRejectReason(source, true);
|
|
82
|
+
}
|
|
83
|
+
function hardRejectReason(source, tolerateRedirection) {
|
|
84
|
+
const reason = tolerateRedirection
|
|
85
|
+
? "command/variable substitution, subshells, escapes, and line breaks are not allowed"
|
|
86
|
+
: "redirection, command/variable substitution, subshells, escapes, and line breaks are not allowed";
|
|
81
87
|
for (const ch of source) {
|
|
82
|
-
if (
|
|
83
|
-
return
|
|
84
|
-
|
|
88
|
+
if (SHELL_SEGMENT_QUOTE_BLIND_REJECT.test(ch) && !(tolerateRedirection && SHELL_REDIRECTION_OPERATORS.test(ch)))
|
|
89
|
+
return reason;
|
|
90
|
+
}
|
|
91
|
+
if (!SHELL_EXPANSION_CHARS.test(source))
|
|
92
|
+
return undefined;
|
|
93
|
+
const mask = quoteMask(source);
|
|
94
|
+
for (let i = 0; i < source.length; i++) {
|
|
95
|
+
if (!SHELL_EXPANSION_CHARS.test(source[i]))
|
|
96
|
+
continue;
|
|
97
|
+
if (mask.balanced && mask.openedBy[i] === "'")
|
|
98
|
+
continue;
|
|
99
|
+
return reason;
|
|
85
100
|
}
|
|
86
101
|
return undefined;
|
|
87
102
|
}
|
|
@@ -91,8 +106,10 @@ export function splitShellCompoundSegments(source, options) {
|
|
|
91
106
|
if (refused !== undefined)
|
|
92
107
|
return { reject: refused };
|
|
93
108
|
}
|
|
94
|
-
else
|
|
95
|
-
|
|
109
|
+
else {
|
|
110
|
+
const refused = hardRejectReason(source, false);
|
|
111
|
+
if (refused !== undefined)
|
|
112
|
+
return { reject: refused };
|
|
96
113
|
}
|
|
97
114
|
const scanned = options?.trailingTerminator !== "keep" ? source.replace(/;\s*$/, "") : source;
|
|
98
115
|
const lexed = lexShellText(scanned, { mode: "strict" });
|
|
@@ -239,8 +256,10 @@ function tokensMoveBase(toks) {
|
|
|
239
256
|
return pos.refusal !== undefined || pos.program?.basename === "cd";
|
|
240
257
|
}
|
|
241
258
|
const SHELL_REENTRY_PROGRAM_RE = /^(?:(?:ba|z|da|k|fi|a|mk|ya|pdk|c|tc)?sh|eval|source|\.|parallel)$/;
|
|
242
|
-
export function refusedCommandMayRead(command, allow, boundary) {
|
|
243
|
-
|
|
259
|
+
export function refusedCommandMayRead(command, allow, boundary, declinesAreEvidence) {
|
|
260
|
+
const scan = { declined: false };
|
|
261
|
+
const exempt = refusedCommandExempt(command, allow, boundary, declinesAreEvidence, scan, 0);
|
|
262
|
+
return { mayRead: !exempt, declined: scan.declined };
|
|
244
263
|
}
|
|
245
264
|
const STDIN_ONLY_READER_FLAGS = new Map([
|
|
246
265
|
["cat", /^-[AbeEnstTuv]+$/],
|
|
@@ -262,7 +281,7 @@ function whitelistSegment(segment) {
|
|
|
262
281
|
redirects: tokens.filter((t) => t.kind === "redirect"),
|
|
263
282
|
};
|
|
264
283
|
}
|
|
265
|
-
function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside = false) {
|
|
284
|
+
function refusedCommandExempt(command, allow, boundary, declinesAreEvidence, scan, depth, baseMovedOutside = false) {
|
|
266
285
|
if (depth > 4)
|
|
267
286
|
return false;
|
|
268
287
|
const lexed = lexShellText(command, { mode: "lenient" });
|
|
@@ -282,12 +301,23 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
282
301
|
}
|
|
283
302
|
segments.push(whitelistSegment(segment));
|
|
284
303
|
}
|
|
304
|
+
let declined;
|
|
305
|
+
for (const { words } of segments) {
|
|
306
|
+
const pos = readProgramPosition(words);
|
|
307
|
+
if (programPositionRunsShell(pos, SHELL_REENTRY_PROGRAM_RE))
|
|
308
|
+
return false;
|
|
309
|
+
const decline = shellScanDeclineOf(pos, XARGS_TARGETS_ACCEPTED_UNJUDGED);
|
|
310
|
+
if (decline !== undefined)
|
|
311
|
+
scan.declined = true;
|
|
312
|
+
if (decline !== undefined && declinesAreEvidence.has(decline))
|
|
313
|
+
return false;
|
|
314
|
+
declined ??= decline;
|
|
315
|
+
}
|
|
316
|
+
if (declined !== undefined)
|
|
317
|
+
return true;
|
|
285
318
|
const baseMoved = baseMovedOutside ||
|
|
286
319
|
segments.some(({ words }) => {
|
|
287
|
-
const
|
|
288
|
-
if (pos.refusal !== undefined)
|
|
289
|
-
return true;
|
|
290
|
-
const runs = pos.program?.basename;
|
|
320
|
+
const runs = readProgramPosition(words).program?.basename;
|
|
291
321
|
return runs === "cd" || (runs !== undefined && UNMODELLED_BASE_MOVERS.has(runs));
|
|
292
322
|
});
|
|
293
323
|
for (const body of bodies) {
|
|
@@ -296,7 +326,7 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
296
326
|
(walked.undecidedPaths?.length ?? 0) === 0 && (walked.unresolvedOperands?.length ?? 0) === 0 && (walked.recursiveReadPaths?.length ?? 0) === 0;
|
|
297
327
|
if (fullyGreen)
|
|
298
328
|
continue;
|
|
299
|
-
if (!refusedCommandExempt(body, allow, boundary, depth + 1, baseMoved))
|
|
329
|
+
if (!refusedCommandExempt(body, allow, boundary, declinesAreEvidence, scan, depth + 1, baseMoved))
|
|
300
330
|
return false;
|
|
301
331
|
}
|
|
302
332
|
const stdinFileJudgedInside = (word) => {
|
|
@@ -353,8 +383,6 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
353
383
|
if (r.op === "<" && !stdinFileJudgedInside(r.target?.folded))
|
|
354
384
|
return false;
|
|
355
385
|
const pos = readProgramPosition(words);
|
|
356
|
-
if (pos.refusal !== undefined)
|
|
357
|
-
return false;
|
|
358
386
|
for (const k of words.folded.keys()) {
|
|
359
387
|
const word = words.folded[k];
|
|
360
388
|
if (ASSIGNMENT_WORD.test(word) && /\$\(|`/.test(word) && (pos.assignments.includes(k) || pos.launchers.some((l) => l.index < k)))
|
|
@@ -365,24 +393,16 @@ function refusedCommandExempt(command, allow, boundary, depth, baseMovedOutside
|
|
|
365
393
|
if (program === undefined) {
|
|
366
394
|
if (pos.launchers.length === 0)
|
|
367
395
|
continue;
|
|
368
|
-
if (argvReentryUnjudged(pos, XARGS_TARGETS_ACCEPTED_UNJUDGED))
|
|
369
|
-
return false;
|
|
370
396
|
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index, pos.assignments))
|
|
371
397
|
return false;
|
|
372
398
|
continue;
|
|
373
399
|
}
|
|
374
400
|
if (program.name.includes(SUBSTITUTION_PLACEHOLDER) || /[$`]/.test(program.name))
|
|
375
401
|
return false;
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
if (pos.launchers.length > 0) {
|
|
379
|
-
if (argvReentryUnjudged(pos, XARGS_TARGETS_ACCEPTED_UNJUDGED))
|
|
402
|
+
if (pos.launchers.length > 0 && !listed(program.basename)) {
|
|
403
|
+
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index, pos.assignments))
|
|
380
404
|
return false;
|
|
381
|
-
|
|
382
|
-
if (!unlistedSegmentJudgedInside(words, pos.launchers[0].index, pos.assignments))
|
|
383
|
-
return false;
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
405
|
+
continue;
|
|
386
406
|
}
|
|
387
407
|
const head = program.basename;
|
|
388
408
|
if (head === "cd" || UNMODELLED_BASE_MOVERS.has(head))
|
|
@@ -455,12 +475,12 @@ function longOptionNameOf(tok) {
|
|
|
455
475
|
function isLongOptionAbbrevOf(name, full) {
|
|
456
476
|
return full.startsWith(name);
|
|
457
477
|
}
|
|
458
|
-
function
|
|
478
|
+
function suppliesFirstOperandSlot(slot, tok) {
|
|
459
479
|
if (tok.startsWith("--")) {
|
|
460
480
|
const long = longOptionNameOf(tok);
|
|
461
|
-
return long !== undefined && (
|
|
481
|
+
return long !== undefined && slot.suppliedByLongNames.some((full) => isLongOptionAbbrevOf(long, full));
|
|
462
482
|
}
|
|
463
|
-
return
|
|
483
|
+
return new RegExp(`^-[A-Za-z0-9]*[${slot.suppliedByLetters}]`).test(tok);
|
|
464
484
|
}
|
|
465
485
|
function isGrepClusterFileStdin(tok) {
|
|
466
486
|
if (tok.startsWith("--") || !/^-[A-Za-z0-9]/.test(tok))
|
|
@@ -534,6 +554,7 @@ const VERB_OPTION_SLOTS = {
|
|
|
534
554
|
regexp: "pattern",
|
|
535
555
|
},
|
|
536
556
|
longOptionalValue: { color: "label", colour: "label", context: "count" },
|
|
557
|
+
firstOperand: { kind: "pattern", suppliedByLetters: "ef", suppliedByLongNames: ["regexp", "file"] },
|
|
537
558
|
longPlain: [
|
|
538
559
|
"basic-regexp", "byte-offset", "count", "dereference-recursive", "extended-regexp",
|
|
539
560
|
"files-with-matches", "files-without-match", "fixed-strings", "help", "ignore-case", "initial-tab",
|
|
@@ -579,6 +600,7 @@ const VERB_OPTION_SLOTS = {
|
|
|
579
600
|
shortPlain: "nrEszu",
|
|
580
601
|
longValue: { expression: "script", file: "path", "line-length": "count" },
|
|
581
602
|
longOptionalValue: { "in-place": "label" },
|
|
603
|
+
firstOperand: { kind: "script", suppliedByLetters: "ef", suppliedByLongNames: ["expression", "file"] },
|
|
582
604
|
longPlain: [
|
|
583
605
|
"debug", "follow-symlinks", "help", "null-data", "posix", "quiet", "regexp-extended", "sandbox",
|
|
584
606
|
"separate", "silent", "unbuffered", "version", "zero-terminated",
|
|
@@ -1670,7 +1692,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1670
1692
|
}
|
|
1671
1693
|
else {
|
|
1672
1694
|
const eoo = args.indexOf("--");
|
|
1673
|
-
const
|
|
1695
|
+
const firstOperandSlot = VERB_OPTION_SLOTS[name]?.firstOperand;
|
|
1696
|
+
const firstOperandSuppliedByFlag = firstOperandSlot !== undefined && (eoo === -1 ? args : args.slice(0, eoo)).some((t) => suppliesFirstOperandSlot(firstOperandSlot, t));
|
|
1674
1697
|
let sawOperand = false;
|
|
1675
1698
|
let sawArgvOperand = false;
|
|
1676
1699
|
let endOfOptions = false;
|
|
@@ -1718,12 +1741,12 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1718
1741
|
continue;
|
|
1719
1742
|
}
|
|
1720
1743
|
}
|
|
1721
|
-
const
|
|
1744
|
+
const inFirstOperandSlot = firstOperandSlot !== undefined && !firstOperandSuppliedByFlag && !sawOperand;
|
|
1722
1745
|
sawOperand = true;
|
|
1723
1746
|
if (k >= argvOperandsStartAt)
|
|
1724
1747
|
sawArgvOperand = true;
|
|
1725
1748
|
if (t === "-") {
|
|
1726
|
-
if (
|
|
1749
|
+
if (inFirstOperandSlot)
|
|
1727
1750
|
continue;
|
|
1728
1751
|
const m = RECURSIVE_READ_FORMS[slotVerb];
|
|
1729
1752
|
if (m === undefined || m.dashIsStdin === true) {
|
|
@@ -1734,8 +1757,14 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1734
1757
|
positionalOperands.push(t);
|
|
1735
1758
|
continue;
|
|
1736
1759
|
}
|
|
1737
|
-
if (
|
|
1760
|
+
if (inFirstOperandSlot) {
|
|
1761
|
+
const named = slotValueCandidates(t, firstOperandSlot.kind);
|
|
1762
|
+
for (const c of named)
|
|
1763
|
+
judgeOperand(c, argGlobs(k));
|
|
1764
|
+
if (named.length > 0 && k >= argvOperandsStartAt)
|
|
1765
|
+
positionalOperands.push(t);
|
|
1738
1766
|
continue;
|
|
1767
|
+
}
|
|
1739
1768
|
if (k >= argvOperandsStartAt)
|
|
1740
1769
|
positionalOperands.push(t);
|
|
1741
1770
|
judgeOperand(t, argGlobs(k));
|
|
@@ -1987,7 +2016,8 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
|
|
|
1987
2016
|
if (!(pipeFed[si] ?? false)) {
|
|
1988
2017
|
const floor = STDIN_FILE_FLOOR[name];
|
|
1989
2018
|
const restArgs = toks.slice(1);
|
|
1990
|
-
const
|
|
2019
|
+
const floorFirstOperandSlot = VERB_OPTION_SLOTS[name]?.firstOperand;
|
|
2020
|
+
const floorSlotSuppliedByFlag = floorFirstOperandSlot !== undefined && restArgs.some((t) => suppliesFirstOperandSlot(floorFirstOperandSlot, t));
|
|
1991
2021
|
let sawNonFlagOperand = false;
|
|
1992
2022
|
let hasStdinDash = false;
|
|
1993
2023
|
let operandCount = 0;
|
|
@@ -2013,10 +2043,10 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
|
|
|
2013
2043
|
if (t.startsWith("-") && t !== "-")
|
|
2014
2044
|
continue;
|
|
2015
2045
|
}
|
|
2016
|
-
const
|
|
2046
|
+
const inFirstOperandSlot = floorFirstOperandSlot !== undefined && !floorSlotSuppliedByFlag && !sawNonFlagOperand;
|
|
2017
2047
|
sawNonFlagOperand = true;
|
|
2018
2048
|
operandCount++;
|
|
2019
|
-
if (
|
|
2049
|
+
if (inFirstOperandSlot)
|
|
2020
2050
|
continue;
|
|
2021
2051
|
if (t === "-") {
|
|
2022
2052
|
hasStdinDash = true;
|
|
@@ -2329,10 +2359,13 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
|
|
|
2329
2359
|
if (baseMoved)
|
|
2330
2360
|
return { gated: true, paths: [] };
|
|
2331
2361
|
const verdict = evaluateReadBoundary(readers, containmentOnly, false, readerReach);
|
|
2332
|
-
|
|
2362
|
+
const denied = boundary.denyMatch === undefined ? false : evaluateReadBoundary(readers, boundary, false, readerReach).readDenied === true;
|
|
2363
|
+
if (verdict.outOfRootRead === true)
|
|
2364
|
+
return { gated: true, paths: verdict.outOfRootPaths ?? [], ...(denied ? { denied: true } : {}) };
|
|
2365
|
+
return denied ? { gated: false, paths: [], denied: true } : NOT_GATED;
|
|
2333
2366
|
};
|
|
2334
2367
|
const direct = judge(command);
|
|
2335
|
-
if (direct.gated)
|
|
2368
|
+
if (direct.gated || direct.denied === true)
|
|
2336
2369
|
return direct;
|
|
2337
2370
|
const shape = POLL_LOOP_SHAPE.exec(command.trim());
|
|
2338
2371
|
return shape !== null ? judge(shape[3]) : NOT_GATED;
|
|
@@ -3,6 +3,7 @@ import { type TaskRegistry } from "../../core/task-registry.js";
|
|
|
3
3
|
import { type CwdRef } from "./fs-shared.js";
|
|
4
4
|
import type { ReversibilityVerdict } from "../../core/types.js";
|
|
5
5
|
import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
6
|
+
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
6
7
|
/**
|
|
7
8
|
* A parsed-command classifier for the `bash` tool, exposed as a
|
|
8
9
|
* `ToolSpec.reversibilityProbe`. A full shell is treated as egress+irreversible by DEFAULT; this probe is the
|
|
@@ -60,6 +61,33 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
|
60
61
|
* shape classifier here changes what happens to the residual shell risk, never that reading.
|
|
61
62
|
*/
|
|
62
63
|
export declare function bashReversibilityProbe(allow?: readonly string[], boundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => ReversibilityVerdict;
|
|
64
|
+
/**
|
|
65
|
+
* ONE rule, TWO TABLES: which {@link ShellScanDecline} a SEAT reads as read evidence. A decline names no word,
|
|
66
|
+
* so it is never the boundary DECLARING anything about a path.
|
|
67
|
+
*
|
|
68
|
+
* The READ-BOUNDARY seat is what the default/off doctrine and a bypass mode mount ALONE: there "not mandated"
|
|
69
|
+
* is VOUCHED, so a decline must count as evidence or a read nobody judged runs silently (`<out-of-root> |
|
|
70
|
+
* xargs cat` at zero asks — a closed P0). It keeps both. The CLASSIFY seat composes the same boundary with the
|
|
71
|
+
* SHAPE classifier, which refuses every one of these shapes: what survives is a CARD, and the only question
|
|
72
|
+
* this table answers is who may retire it. It keeps NEITHER — an ANSWERER may (a person, the auto-mode
|
|
73
|
+
* classifier, a person's stored allow rule) — empty by the argument, not by omission.
|
|
74
|
+
*
|
|
75
|
+
* WHAT AN EMPTY TABLE DOES NOT BUY, corrected where it was once claimed: "the shape classifier refuses these
|
|
76
|
+
* shapes anyway, so the only question left is the stored answer" was true of the PROBE and false of the OUTCOME
|
|
77
|
+
* — the allow layer's other members are not that classifier, and both cleared exactly these cards at ZERO asks. An
|
|
78
|
+
* empty table says only "no MANDATE HERE"; that the boundary DECLINED is a separate fact, carried on every ask this
|
|
79
|
+
* seat raises ({@link BoundaryAnswer}'s weaker rung), standing every ANSWERLESS arm down while every answerer's lane stays open.
|
|
80
|
+
* THE COST, stated rather than mitigated: once that ask is retired BY AN ANSWERER, an out-of-root or deny-listed
|
|
81
|
+
* read HIDDEN inside such a command is not asked again. A command's words are not the argv its programs receive (a
|
|
82
|
+
* stream is re-split by its consumer, an unread option may move the working directory first, stdin text is a
|
|
83
|
+
* command line nobody parsed), so a scan of them cannot be the guarantee it was claimed to be. "A read the boundary
|
|
84
|
+
* objects to is never retired by a stored rule" does NOT hold on the classify seat for this class, and holds
|
|
85
|
+
* unchanged elsewhere. One word-level check survives on both seats and is not part of the trade: a token the scan
|
|
86
|
+
* DID read naming a path outside the roots still gates the call ({@link classifyOutOfRootReadGate}).
|
|
87
|
+
*/
|
|
88
|
+
export declare const BOUNDARY_SEAT_DECLINE_EVIDENCE: ReadonlySet<ShellScanDecline>;
|
|
89
|
+
/** The classify doctrine's table — see above; empty by the argument. */
|
|
90
|
+
export declare const CLASSIFY_SEAT_DECLINE_EVIDENCE: ReadonlySet<ShellScanDecline>;
|
|
63
91
|
/**
|
|
64
92
|
* The READ-BOUNDARY seat of the full shell: a `ToolSpec.reversibilityProbe` that answers exactly ONE
|
|
65
93
|
* question — does the deployment's read boundary object to where this command reads? — and nothing about
|
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -13,8 +13,10 @@ import { boundaryIsTheSandbox, canonicalPathIsAuthoritative, hasDestroy, isIsola
|
|
|
13
13
|
import { ghRateLimitHint } from "./gh-rate-limit.js";
|
|
14
14
|
import { resolveBashTimeoutCaps, bashTimeoutCapsSec, bashTimeoutArgRefusal, bashTimeoutParamDescription, envErrorDetail, bashMaxOutputChars, clipShellOutput, writeShellOverflowFile, createShellOverflowSpoolFence, shellRecoveryHint, CWD_SENTINEL, stripCwdSentinel, BASH_READONLY_CONFINEMENT_NOTE, } from "./fs-shared.js";
|
|
15
15
|
import { PROBE_CAUSE_PATH_MAX, inlineUntrusted } from "../../core/untrusted-text.js";
|
|
16
|
-
import { bashClassifyDefaultAllow, BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadonlyPollLoopDetailed, classifyCompoundReadonlyDetailed, classifyOutOfRootReadGate, refusedCommandMayRead, classifySimpleCommandReadBoundary,
|
|
16
|
+
import { bashClassifyDefaultAllow, BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadonlyPollLoopDetailed, classifyCompoundReadonlyDetailed, classifyOutOfRootReadGate, refusedCommandMayRead, classifySimpleCommandReadBoundary, } from "./bash-readonly-classifier.js";
|
|
17
|
+
import { NOT_AUTO_ALLOWED, SHELL_SCAN_DECLINES } from "./bash-program-position.js";
|
|
17
18
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
19
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
18
20
|
const RECURSIVE_CAUSE_MAX_PATHS = 3;
|
|
19
21
|
const RECURSIVE_READ_CAUSE_CODE = "shell.recursive_read_unbounded";
|
|
20
22
|
function operandFamily(paths) {
|
|
@@ -27,8 +29,8 @@ function operandFamily(paths) {
|
|
|
27
29
|
export function bashReversibilityProbe(allow, boundary) {
|
|
28
30
|
const allowSet = new Set(allow ?? bashClassifyDefaultAllow());
|
|
29
31
|
const judge = (args, command, resolved, offers) => {
|
|
30
|
-
const boundaryGate = (verdict) => readBoundaryMandate(command, allowSet, resolved, verdict);
|
|
31
|
-
const outOfRootGate = () => readBoundaryMandate(command, allowSet, resolved, {});
|
|
32
|
+
const boundaryGate = (verdict) => readBoundaryMandate(command, allowSet, resolved, verdict, CLASSIFY_SEAT_DECLINE_EVIDENCE);
|
|
33
|
+
const outOfRootGate = () => readBoundaryMandate(command, allowSet, resolved, {}, CLASSIFY_SEAT_DECLINE_EVIDENCE);
|
|
32
34
|
const backgrounded = isBackgroundShellCall(args);
|
|
33
35
|
const detailed = classifyCompoundReadonlyDetailed(command, allowSet, resolved);
|
|
34
36
|
if (backgrounded)
|
|
@@ -46,7 +48,8 @@ export function bashReversibilityProbe(allow, boundary) {
|
|
|
46
48
|
if (loop.reason === undefined)
|
|
47
49
|
return { reversible: true };
|
|
48
50
|
const seatOffer = (verdict) => (offers ? readRootCandidateOf(verdict) : {});
|
|
49
|
-
|
|
51
|
+
const loopScan = refusedCommandScan(command, allowSet, resolved, loop, CLASSIFY_SEAT_DECLINE_EVIDENCE);
|
|
52
|
+
if (boundaryDeclared(loop) || unjudgedRead(command, allowSet, resolved, loop, loopScan))
|
|
50
53
|
return { reversible: false, mandated: true, ...seatOffer(loop) };
|
|
51
54
|
const refusedCause = recursiveReadCause(detailed);
|
|
52
55
|
return { reversible: false, ...boundaryGate(detailed), ...seatOffer(detailed), ...(refusedCause !== undefined ? { cause: refusedCause } : {}) };
|
|
@@ -86,24 +89,39 @@ function shellCommandOf(args) {
|
|
|
86
89
|
function isBackgroundShellCall(args) {
|
|
87
90
|
return args?.run_in_background === true;
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
export const BOUNDARY_SEAT_DECLINE_EVIDENCE = new Set(SHELL_SCAN_DECLINES);
|
|
93
|
+
export const CLASSIFY_SEAT_DECLINE_EVIDENCE = new Set();
|
|
94
|
+
function readBoundaryMandate(command, allowSet, resolved, verdict, declineEvidence) {
|
|
90
95
|
if (resolved === undefined)
|
|
91
96
|
return {};
|
|
92
|
-
|
|
97
|
+
const scan = refusedCommandScan(command, allowSet, resolved, verdict, declineEvidence);
|
|
98
|
+
if (denyJudgeSpoke(verdict) || unjudgedRead(command, allowSet, resolved, verdict, scan))
|
|
93
99
|
return { mandated: true };
|
|
94
|
-
|
|
100
|
+
const named = classifyOutOfRootReadGate(command, allowSet, resolved);
|
|
101
|
+
if (named.gated || named.denied === true)
|
|
102
|
+
return { mandated: true };
|
|
103
|
+
return declinedRung(scan);
|
|
104
|
+
}
|
|
105
|
+
function declinedRung(scan) {
|
|
106
|
+
return scan.declined ? { boundaryDeclined: true } : {};
|
|
107
|
+
}
|
|
108
|
+
function refusedCommandScan(command, allowSet, resolved, verdict, declineEvidence) {
|
|
109
|
+
return verdict.refusedWhole === true ? refusedCommandMayRead(command, allowSet, resolved, declineEvidence) : { mayRead: false, declined: false };
|
|
95
110
|
}
|
|
96
111
|
function boundaryUnjudged(verdict) {
|
|
97
112
|
return verdict.refusedWhole === true || (verdict.unresolvedOperands !== undefined && verdict.unresolvedOperands.length > 0);
|
|
98
113
|
}
|
|
99
|
-
function unjudgedRead(command, allowSet, resolved, verdict) {
|
|
114
|
+
function unjudgedRead(command, allowSet, resolved, verdict, scan) {
|
|
100
115
|
if (verdict.unresolvedOperands !== undefined && verdict.unresolvedOperands.length > 0)
|
|
101
116
|
return true;
|
|
102
117
|
if (verdict.refusedWhole !== true)
|
|
103
118
|
return false;
|
|
104
|
-
if (
|
|
119
|
+
if (scan.mayRead)
|
|
105
120
|
return true;
|
|
106
|
-
|
|
121
|
+
if (resolved === undefined)
|
|
122
|
+
return false;
|
|
123
|
+
const named = classifyOutOfRootReadGate(command, allowSet, resolved);
|
|
124
|
+
return named.gated || named.denied === true;
|
|
107
125
|
}
|
|
108
126
|
function recursiveReadCause(detailed) {
|
|
109
127
|
const recursive = detailed.recursiveReadPaths;
|
|
@@ -121,7 +139,7 @@ export function bashReadBoundaryProbe(boundary) {
|
|
|
121
139
|
const allowSet = new Set(bashClassifyDefaultAllow());
|
|
122
140
|
const judge = (command, resolved, offers) => {
|
|
123
141
|
const seatVerdict = (verdict) => {
|
|
124
|
-
if (boundaryDeclared(verdict) || unjudgedRead(command, allowSet, resolved, verdict)) {
|
|
142
|
+
if (boundaryDeclared(verdict) || unjudgedRead(command, allowSet, resolved, verdict, refusedCommandScan(command, allowSet, resolved, verdict, BOUNDARY_SEAT_DECLINE_EVIDENCE))) {
|
|
125
143
|
const cause = recursiveReadCause(verdict);
|
|
126
144
|
return { reversible: false, mandated: true, ...(cause !== undefined ? { cause } : {}) };
|
|
127
145
|
}
|
|
@@ -145,7 +163,7 @@ export function bashReadBoundaryProbe(boundary) {
|
|
|
145
163
|
const offerFrom = (verdict) => (offers ? readRootCandidateOf(verdict) : {});
|
|
146
164
|
if (mandated !== undefined)
|
|
147
165
|
return { ...mandated, ...offerFrom(detailed), ...offerFrom(loop) };
|
|
148
|
-
if (readBoundaryMandate(command, allowSet, resolved, detailed).mandated === true)
|
|
166
|
+
if (readBoundaryMandate(command, allowSet, resolved, detailed, BOUNDARY_SEAT_DECLINE_EVIDENCE).mandated === true)
|
|
149
167
|
return { reversible: false, mandated: true };
|
|
150
168
|
return compoundVerdict ?? loopVerdict ?? { reversible: true };
|
|
151
169
|
};
|
|
@@ -719,7 +737,7 @@ async function adoptBackgroundShell(req) {
|
|
|
719
737
|
return { ok: true, taskId, outputFile };
|
|
720
738
|
}
|
|
721
739
|
catch (e) {
|
|
722
|
-
const reason =
|
|
740
|
+
const reason = thrownText(e);
|
|
723
741
|
let kill;
|
|
724
742
|
try {
|
|
725
743
|
kill = await env.killBackground(shellId);
|
|
@@ -5,6 +5,7 @@ import { sha256, resolveKey, violationText, violationDetails, requireRead, check
|
|
|
5
5
|
import { decodeTextBytes, encodeTextForFile, normalizeEditText, normalizeFileText } from "./encoding.js";
|
|
6
6
|
import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, applyRecordedEdit, } from "./fs-shared.js";
|
|
7
7
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
8
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
8
9
|
async function envFinalWrite(env, key, content, signal, opts) {
|
|
9
10
|
const fireEdited = () => {
|
|
10
11
|
if (opts?.edited?.hook === undefined)
|
|
@@ -66,7 +67,7 @@ async function trackToolEdit(hook, tool, path, key, signal) {
|
|
|
66
67
|
res = await hook({ tool, path, key, ...(signal !== undefined ? { signal } : {}) });
|
|
67
68
|
}
|
|
68
69
|
catch (err) {
|
|
69
|
-
return { refusal: `Error (${tool}): the file-history first-touch backup failed (${clipHookText(
|
|
70
|
+
return { refusal: `Error (${tool}): the file-history first-touch backup failed (${clipHookText(thrownText(err))}) — nothing was written.` };
|
|
70
71
|
}
|
|
71
72
|
if (res.ok === false)
|
|
72
73
|
return { refusal: clipHookText(res.refusal) };
|
|
@@ -80,7 +81,7 @@ async function gateToolWrite(hook, tool, path, key, content) {
|
|
|
80
81
|
res = await hook({ tool, path, key, content });
|
|
81
82
|
}
|
|
82
83
|
catch (err) {
|
|
83
|
-
return `Error (${tool}): the write gate failed (${clipHookText(
|
|
84
|
+
return `Error (${tool}): the write gate failed (${clipHookText(thrownText(err))}) — nothing was written.`;
|
|
84
85
|
}
|
|
85
86
|
if (res !== undefined && res.ok === false)
|
|
86
87
|
return `Error (${tool}): write rejected by the write gate (${res.code}): ${clipHookText(res.reason)}`;
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
|
|
3
3
|
import { pathTargetValue, protectivePathTargetOf } from "../../core/tool-registry.js";
|
|
4
4
|
import { withGateTimeout, DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../../core/gate-bounded-await.js";
|
|
5
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
5
6
|
export function fileArgPath(args) {
|
|
6
7
|
const a = args;
|
|
7
8
|
const fp = a?.file_path;
|
|
@@ -524,7 +525,7 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
|
524
525
|
catch (err) {
|
|
525
526
|
if (seenThrow && err === thrownByProbe)
|
|
526
527
|
throw err;
|
|
527
|
-
return { ok: false, message: `cannot resolve path "${path}": ${
|
|
528
|
+
return { ok: false, message: `cannot resolve path "${path}": ${thrownText(err)}` };
|
|
528
529
|
}
|
|
529
530
|
}
|
|
530
531
|
async function probeRealTarget(env, path, target, family, signal) {
|
package/dist/tools/fs/search.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { delimitUntrusted } from "../../core/untrusted-text.js";
|
|
2
2
|
import { MAX_EXEC_OUTPUT_BYTES } from "../../core/exec-output-tail.js";
|
|
3
3
|
import { hasBinaryExtension, isAbsoluteForFamily, isWinFormPath, joinForFamily, pathFamilyOf } from "./safety.js";
|
|
4
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
4
5
|
const WALK_MAX_FILES = 5000;
|
|
5
6
|
const WALK_MAX_DEPTH = 32;
|
|
6
7
|
const GREP_DEFAULT_CAP = 250;
|
|
@@ -772,7 +773,7 @@ export async function jsGrepDetailed(env, root, p, signal, guards, deny, denyOut
|
|
|
772
773
|
test = compilePattern(p);
|
|
773
774
|
}
|
|
774
775
|
catch (e) {
|
|
775
|
-
return { text: `Error (grep): invalid regex: ${
|
|
776
|
+
return { text: `Error (grep): invalid regex: ${thrownText(e)}` };
|
|
776
777
|
}
|
|
777
778
|
const ignore = await buildIgnore(env, root, signal);
|
|
778
779
|
const rootPrefix = root.replace(/[\\/]+$/, "") + (root.includes("\\") ? "\\" : "/");
|
|
@@ -912,7 +913,7 @@ export async function jsGrepDetailed(env, root, p, signal, guards, deny, denyOut
|
|
|
912
913
|
catch (e) {
|
|
913
914
|
if (e instanceof GrepBudgetExceeded)
|
|
914
915
|
return { text: budgetError(shownPath, monotonicNow() - fileMatchStart) };
|
|
915
|
-
return { text: `Error (grep): invalid regex: ${
|
|
916
|
+
return { text: `Error (grep): invalid regex: ${thrownText(e)}` };
|
|
916
917
|
}
|
|
917
918
|
fileCount = spans.length;
|
|
918
919
|
if (mode === "content") {
|
package/dist/tools/monitor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { defineTool, errorResult } from "../core/tools.js";
|
|
|
3
3
|
import { hasBackgroundShell } from "../core/background-shell.js";
|
|
4
4
|
import { MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, } from "../core/task-registry.js";
|
|
5
5
|
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
6
|
+
import { thrownText } from "../core/thrown-value.js";
|
|
6
7
|
const MONITOR_DESCRIPTION = "Run a shell command in the background and watch its stdout as a stream of events.\n" +
|
|
7
8
|
"\n" +
|
|
8
9
|
"- Each line the command writes to stdout becomes a notification event delivered to you between turns; " +
|
|
@@ -98,7 +99,7 @@ export function createMonitorTool(env, opts) {
|
|
|
98
99
|
});
|
|
99
100
|
}
|
|
100
101
|
catch (e) {
|
|
101
|
-
const why =
|
|
102
|
+
const why = thrownText(e);
|
|
102
103
|
let killDetail;
|
|
103
104
|
try {
|
|
104
105
|
const kill = await env.killBackground(spawn.value.shellId);
|
|
@@ -106,7 +107,7 @@ export function createMonitorTool(env, opts) {
|
|
|
106
107
|
killDetail = `${kill.error.code}: ${kill.error.message}`;
|
|
107
108
|
}
|
|
108
109
|
catch (killErr) {
|
|
109
|
-
killDetail =
|
|
110
|
+
killDetail = thrownText(killErr);
|
|
110
111
|
}
|
|
111
112
|
return errorResult(killDetail === undefined
|
|
112
113
|
? `Error (Monitor): the watch process was started but could not be registered (${why}); it has been terminated.`
|
package/dist/tools/sql.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { thrownText } from "../core/thrown-value.js";
|
|
2
3
|
const FORBIDDEN = /\b(insert|update|delete|drop|alter|create|truncate|grant|revoke|merge|replace|attach|detach|pragma|vacuum|exec|execute|call|copy|lock|reindex)\b/i;
|
|
3
4
|
class SqlValidationError extends Error {
|
|
4
5
|
}
|
|
@@ -82,7 +83,7 @@ export function createSqlTool(opts) {
|
|
|
82
83
|
validateReadOnlySql(sql, opts.allowTables);
|
|
83
84
|
}
|
|
84
85
|
catch (e) {
|
|
85
|
-
const msg =
|
|
86
|
+
const msg = thrownText(e);
|
|
86
87
|
return { content: `SQL rejected: ${msg}`, details: { error: msg }, terminate: false };
|
|
87
88
|
}
|
|
88
89
|
const rows = await opts.query(stripTrailingSemicolon(sql.trim()));
|
package/dist/tools/web.js
CHANGED
|
@@ -8,6 +8,7 @@ import { DEFAULT_CHARS_PER_TOKEN } from "../internal/harness.js";
|
|
|
8
8
|
import { binaryMagicFormat } from "./fs/safety.js";
|
|
9
9
|
import { pdfMagicMatches } from "./fs/pdf.js";
|
|
10
10
|
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
11
|
+
import { thrownText } from "../core/thrown-value.js";
|
|
11
12
|
const MAX_REDIRECTS = 10;
|
|
12
13
|
const MAX_URL_LENGTH = 2048;
|
|
13
14
|
const WEBFETCH_ACCEPT = "text/markdown, text/html, */*";
|
|
@@ -447,7 +448,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
447
448
|
};
|
|
448
449
|
}
|
|
449
450
|
return {
|
|
450
|
-
content: `Error (WebFetch): request failed: ${
|
|
451
|
+
content: `Error (WebFetch): request failed: ${thrownText(e)}`,
|
|
451
452
|
details: errDetails,
|
|
452
453
|
isError: true,
|
|
453
454
|
};
|
|
@@ -594,7 +595,7 @@ export function webFetchToolSpec(config = {}) {
|
|
|
594
595
|
catch (e) {
|
|
595
596
|
out = text;
|
|
596
597
|
note =
|
|
597
|
-
`[note: summarization failed (${inlineUntrusted(
|
|
598
|
+
`[note: summarization failed (${inlineUntrusted(thrownText(e), 160)}) — ` +
|
|
598
599
|
`raw page content follows; the requested analysis ("${inlineUntrusted(prompt, 120)}") was NOT applied]`;
|
|
599
600
|
}
|
|
600
601
|
}
|
|
@@ -910,7 +911,7 @@ export function createWebSearchTool(config) {
|
|
|
910
911
|
return abortedFrame();
|
|
911
912
|
if (ac.signal.aborted)
|
|
912
913
|
return timedOutFrame();
|
|
913
|
-
const msg = redactSecrets(
|
|
914
|
+
const msg = redactSecrets(thrownText(e)).trim();
|
|
914
915
|
const headline = "Error (WebSearch): the search backend failed.";
|
|
915
916
|
const verdict = classifySearchFailure(msg);
|
|
916
917
|
const composed = (msg ? `${headline} The backend's error text follows:\n\n${delimitUntrusted("WebSearch backend error", msg, SEARCH_ERROR_EXCERPT_CHARS)}` : headline) + `\n\n${verdict.hint}`;
|
|
@@ -1029,18 +1030,7 @@ export async function probeSearchBackend(search, options) {
|
|
|
1029
1030
|
return { ok: true, results: results.length };
|
|
1030
1031
|
}
|
|
1031
1032
|
catch (e) {
|
|
1032
|
-
|
|
1033
|
-
if (e instanceof Error)
|
|
1034
|
-
text = e.message;
|
|
1035
|
-
else {
|
|
1036
|
-
try {
|
|
1037
|
-
text = String(e);
|
|
1038
|
-
}
|
|
1039
|
-
catch {
|
|
1040
|
-
text = Object.prototype.toString.call(e);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
return { ok: false, error: text };
|
|
1033
|
+
return { ok: false, error: thrownText(e) };
|
|
1044
1034
|
}
|
|
1045
1035
|
finally {
|
|
1046
1036
|
clearTimeout(timer);
|
package/dist/tools/worktree.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Type } from "typebox";
|
|
|
3
3
|
import { defineTool, errorResult } from "../core/tools.js";
|
|
4
4
|
import { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "../core/git-worktree-env.js";
|
|
5
5
|
import { toolFace } from "../core/tool-catalog-entries.js";
|
|
6
|
+
import { thrownText } from "../core/thrown-value.js";
|
|
6
7
|
const ENTER_DESCRIPTION = "Use this tool ONLY when explicitly instructed to work in a worktree — either by the user directly, or by " +
|
|
7
8
|
"project instructions. This tool creates an isolated git worktree and switches the current session into it.\n" +
|
|
8
9
|
"\n" +
|
|
@@ -67,14 +68,16 @@ const EXIT_DESCRIPTION = "Exit a worktree session created by EnterWorktree and r
|
|
|
67
68
|
"- Once exited, EnterWorktree can be called again to create a fresh worktree.";
|
|
68
69
|
const shq = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
69
70
|
function reasonOf(e) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
try {
|
|
72
|
+
if (typeof e === "object" && e !== null && !(e instanceof Error)) {
|
|
73
|
+
const m = e.message;
|
|
74
|
+
if (typeof m === "string" && m !== "")
|
|
75
|
+
return m;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
76
79
|
}
|
|
77
|
-
return
|
|
80
|
+
return thrownText(e);
|
|
78
81
|
}
|
|
79
82
|
async function resolveBaseCommit(env, repoRoot, baseRef) {
|
|
80
83
|
const rev = async (ref) => {
|
|
@@ -147,7 +150,7 @@ export function createWorktreeTools(env, opts) {
|
|
|
147
150
|
try {
|
|
148
151
|
const probe = await env.exec("git rev-parse --is-inside-work-tree", { cwd: repoRoot });
|
|
149
152
|
if (!probe.ok || probe.value.exitCode !== 0) {
|
|
150
|
-
const detail = probe.ok ? (probe.value.stderr || probe.value.stdout).trim().split("\n")[0] :
|
|
153
|
+
const detail = probe.ok ? (probe.value.stderr || probe.value.stdout).trim().split("\n")[0] : thrownText(probe.error);
|
|
151
154
|
return errorResult(`Error (EnterWorktree): the working directory ${repoRoot} is not a git repository` +
|
|
152
155
|
`${detail ? ` (${detail})` : ""}. This deployment has no VCS-agnostic worktree extension point, ` +
|
|
153
156
|
"so worktree isolation is unavailable here.");
|
|
@@ -214,7 +217,7 @@ export function createWorktreeTools(env, opts) {
|
|
|
214
217
|
createdDir = worktreeDir;
|
|
215
218
|
}
|
|
216
219
|
catch (e) {
|
|
217
|
-
return errorResult(`Error (EnterWorktree): ${
|
|
220
|
+
return errorResult(`Error (EnterWorktree): ${thrownText(e)}`);
|
|
218
221
|
}
|
|
219
222
|
session.current = {
|
|
220
223
|
worktreeDir: createdDir,
|