@sema-agent/core 7.27.0 → 7.28.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/CHANGELOG.md +25 -0
- package/dist/agents/builtin-agents.d.ts +6 -15
- package/dist/agents/cascade.d.ts +29 -33
- package/dist/agents/observer.d.ts +18 -20
- package/dist/agents/subagent.d.ts +58 -70
- package/dist/agents/tool-filter.d.ts +10 -12
- package/dist/agents/verify.d.ts +44 -49
- package/dist/brain/input-too-long.d.ts +10 -12
- package/dist/brain/media-degrade.d.ts +4 -5
- package/dist/brain/reasoning.d.ts +10 -12
- package/dist/brain/repetition.d.ts +7 -9
- package/dist/brain/request-params.d.ts +20 -25
- package/dist/brain/route-adjudicator.d.ts +4 -4
- package/dist/brain/route-conformance.d.ts +2 -2
- package/dist/brain/status-sink.d.ts +12 -14
- package/dist/brain/terminal-cause.d.ts +12 -15
- package/dist/brain/timeout.d.ts +17 -19
- package/dist/core/memory-engine/consolidation.d.ts +11 -15
- package/dist/core/memory-engine/engine.d.ts +45 -54
- package/dist/core/memory-engine/tools.d.ts +17 -23
- package/dist/core/permission-rule-model.js +5 -3
- package/dist/shell/lex.d.ts +279 -0
- package/dist/shell/lex.js +1253 -0
- package/dist/tools/fs/bash-program-position.d.ts +2 -4
- package/dist/tools/fs/bash-program-position.js +2 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -2
- package/dist/tools/fs/bash-readonly-classifier.js +1 -1
- package/dist/tools/fs/shell-read-check.js +1 -1
- package/dist/tools/fs/shell-read-establish.js +11 -1
- package/dist/tools/fs/shell-read-plan.d.ts +7 -2
- package/dist/tools/fs/shell-read-plan.js +48 -10
- package/package.json +2 -2
- package/wire-changes/core@7.28.0.json +594 -0
- package/dist/tools/fs/bash-lexer.d.ts +0 -152
- package/dist/tools/fs/bash-lexer.js +0 -627
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader
|
|
9
9
|
* does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
|
|
10
10
|
*/
|
|
11
|
-
import type
|
|
11
|
+
import { SHELL_CONTROL_PREFIXES, type SegmentTokens } from "../../shell/lex.js";
|
|
12
12
|
/** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
|
|
13
13
|
* they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
|
|
14
14
|
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
|
|
@@ -35,9 +35,7 @@ export declare function writtenAssignmentPrefix(token: string): {
|
|
|
35
35
|
readonly appends: boolean;
|
|
36
36
|
readonly settlesWholeName: boolean;
|
|
37
37
|
} | undefined;
|
|
38
|
-
|
|
39
|
-
* judged). */
|
|
40
|
-
export declare const SHELL_CONTROL_PREFIXES: ReadonlySet<string>;
|
|
38
|
+
export { SHELL_CONTROL_PREFIXES };
|
|
41
39
|
/** Shell block words that name no command of their own (a loop head, a block close, a case head). */
|
|
42
40
|
export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
|
|
43
41
|
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SHELL_CONTROL_PREFIXES } from "../../shell/lex.js";
|
|
1
2
|
export const NOT_AUTO_ALLOWED = "— not auto-allowed";
|
|
2
3
|
export function programBasename(word) {
|
|
3
4
|
return word.includes("/") ? (word.split("/").pop() ?? word) : word;
|
|
@@ -12,7 +13,7 @@ export function writtenAssignmentPrefix(token) {
|
|
|
12
13
|
const bracket = written.indexOf("[");
|
|
13
14
|
return { prefix, name: bracket < 0 ? written : written.slice(0, bracket), appends, settlesWholeName: !appends && bracket < 0 };
|
|
14
15
|
}
|
|
15
|
-
export
|
|
16
|
+
export { SHELL_CONTROL_PREFIXES };
|
|
16
17
|
export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
|
|
17
18
|
export function isBareControlWord(toks, i, words) {
|
|
18
19
|
const word = toks.folded[i];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SegmentTokens, type ShellSegmentConnector } from "
|
|
1
|
+
import { type SegmentTokens, type ShellSegmentConnector } from "../../shell/lex.js";
|
|
2
2
|
/**
|
|
3
3
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
4
4
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -31,7 +31,7 @@ import { type SegmentTokens, type ShellSegmentConnector } from "./bash-lexer.js"
|
|
|
31
31
|
* value-taking options defeat the stdin-floor operand count without a per-verb option-value model
|
|
32
32
|
* — those are a follow-on slice, not a silent drop.
|
|
33
33
|
*/
|
|
34
|
-
export type { ShellSegmentConnector } from "
|
|
34
|
+
export type { ShellSegmentConnector } from "../../shell/lex.js";
|
|
35
35
|
export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
|
|
36
36
|
/**
|
|
37
37
|
* The verbs the CLASSIFY face admits ON TOP of the shared list ({@link bashClassifyDefaultAllow}) —
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf } from "./safety.js";
|
|
2
|
-
import { lexShellText, literalTildeSpelling, ansiCQuoteClose, dollarOpensQuote, quoteMask, wordsOf, } from "
|
|
2
|
+
import { lexShellText, literalTildeSpelling, ansiCQuoteClose, dollarOpensQuote, quoteMask, wordsOf, } from "../../shell/lex.js";
|
|
3
3
|
import { COMMAND_LAUNCHERS, NOT_AUTO_ALLOWED, SHELL_BLOCK_WORDS, argvReentryUnjudged, isBareControlWord, programPositionRefusalSentence, ASSIGNMENT_PREFIX, readProgramPosition, writtenAssignmentPrefix, } from "./bash-program-position.js";
|
|
4
4
|
export const BASH_READONLY_DEFAULT_ALLOW = [
|
|
5
5
|
"ls", "cat", "head", "tail", "wc", "pwd", "echo", "whoami", "uname",
|
|
@@ -8,7 +8,7 @@ import { planShellReads } from "./shell-read-plan.js";
|
|
|
8
8
|
import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
|
|
9
9
|
import { allFindings, decideShellRead, foldShellReadFindings, judgeEstablishedPlan, readRootGrantCandidateOf, } from "./shell-read-judge.js";
|
|
10
10
|
import { normalizeReadRootCandidate } from "../../core/read-root-candidate.js";
|
|
11
|
-
import { wordsOf, lexShellText } from "
|
|
11
|
+
import { wordsOf, lexShellText } from "../../shell/lex.js";
|
|
12
12
|
function containmentDetails(paths) {
|
|
13
13
|
return { type: "readonly_out_of_root", code: "readonly_out_of_root", paths };
|
|
14
14
|
}
|
|
@@ -98,7 +98,17 @@ function lexicalReadsOf(candidate, boundary) {
|
|
|
98
98
|
}
|
|
99
99
|
const bases = basesFor(candidate, boundary);
|
|
100
100
|
if (bases === "unmodelled") {
|
|
101
|
-
|
|
101
|
+
const floors = candidate.floorBases ?? [boundary.cwd ?? boundary.roots[0] ?? ""];
|
|
102
|
+
const spellings = [candidate.spelled];
|
|
103
|
+
for (const floorBase of floors) {
|
|
104
|
+
const viaFloor = floorBase === "" ? undefined : resolveOperandSpellings(floorBase, candidate.spelled, boundary.homeDir);
|
|
105
|
+
if (viaFloor === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
spellings.push(viaFloor.folded);
|
|
108
|
+
if (viaFloor.spelled !== undefined)
|
|
109
|
+
spellings.push(viaFloor.spelled);
|
|
110
|
+
}
|
|
111
|
+
return [{ candidate, established: "unestablishable", spellings, cause: `"${candidate.raw}" is relative to a working directory this command's text does not settle` }];
|
|
102
112
|
}
|
|
103
113
|
const out = [];
|
|
104
114
|
const seen = new Set();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The read boundary today re-derives "which words may be read" at several sites (the compound walk, the
|
|
5
5
|
* containment-only gate, the refused-command whitelist, the execute-time recheck), each with its own idea
|
|
6
6
|
* of launchers, option slots, redirections and re-entry. This module is the single extraction those sites
|
|
7
|
-
* are to consume: a pure, zero-I/O function over the lenient lexer (`
|
|
7
|
+
* are to consume: a pure, zero-I/O function over the lenient lexer (`src/shell/lex.ts`) and the program-position
|
|
8
8
|
* reader (`bash-program-position.ts`), returning every path word a command MAY read, each tagged with its
|
|
9
9
|
* PROVENANCE (why it is a candidate) and the working-directory candidates it is relative to. Establishment
|
|
10
10
|
* (`shell-read-establish.ts`) and judgment are downstream; nothing here decides allow/ask/deny.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* otherwise loses the base for what follows.
|
|
27
27
|
* Every ambiguity resolves toward MORE candidates, never fewer.
|
|
28
28
|
*/
|
|
29
|
-
import { type LenientConnector, type LexedRedirect, type LexedWord } from "
|
|
29
|
+
import { type LenientConnector, type LexedRedirect, type LexedWord } from "../../shell/lex.js";
|
|
30
30
|
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
31
31
|
/** WHY a word is a read candidate — a closed set, registered in `docs/CLOSED-SETS.md`. */
|
|
32
32
|
export declare const READ_CANDIDATE_PROVENANCES: readonly ["operand", "option_value", "stdin_redirect", "write_redirect_target", "env_assignment", "unlisted_word", "traversal_root", "cd_target", "program_path", "script_slot_file"];
|
|
@@ -55,6 +55,11 @@ export interface ReadCandidate {
|
|
|
55
55
|
* EMPTY for an absolute or home-rooted spelling; `"unmodelled"` for a relative word in a segment whose base the
|
|
56
56
|
* text does not settle (an unfollowable move before it, a loop this parser cannot count) — it cannot be placed. */
|
|
57
57
|
readonly bases: readonly string[] | "unmodelled";
|
|
58
|
+
/** Only beside `bases: "unmodelled"`: the working directories the text was certainly standing in when it LOST
|
|
59
|
+
* track (the reachable candidate set at the unprovable move; the declared cwd when nothing had moved yet).
|
|
60
|
+
* A deny row is a floor, so the judge resolves the spelling against these for deny rows ONLY — never to
|
|
61
|
+
* establish a read. `cd sub && cd $'\x2e' && cat <deny row under sub>` (bash: `cd .`) is refused through it. */
|
|
62
|
+
readonly floorBases?: readonly string[];
|
|
58
63
|
}
|
|
59
64
|
/** How the shell reached one segment — what the base-candidate walk needs from the connectors. */
|
|
60
65
|
export interface SegmentReach {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lexShell, PARSE_FAILURE_SENTENCE, UNRESOLVED_SENTENCE, UNRESOLVED_VIEW, substitutionsInHeredocBody, wordsOf, literalTildeSpelling, SUBSTITUTION_PLACEHOLDER, } from "../../shell/lex.js";
|
|
2
2
|
import { LAUNCHER_TABLE, SHELL_BLOCK_WORDS, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, writtenAssignmentPrefix, } from "./bash-program-position.js";
|
|
3
3
|
import { ASSIGNMENT_VALUE_NOT_PATHS, INTERPRETER_INLINE_PROGRAMS, NO_PATH_OPERAND_COMMANDS, POLL_LOOP_MAX_BEATS, RECURSIVE_READ_FORMS, SHELL_REENTRY_PROGRAM_RE, UNMODELLED_BASE_MOVERS, argvProgramOf, assignmentWordPayload, attachedOptionPayloads, foldHomeVariableSpelling, resolveOperandLexically, hasUnquotedExpansionMetachar, hasUnquotedGlobMetachar, inlineProgramClause, interpreterKey, isPathShapedToken, readVerbOptionSlot, sedScriptSlotFiles, segmentSelectsRecursiveRead, slotValueCandidates, suppliesFirstOperandSlot, verbFirstOperandSlot, verbOptionsArePositionIndependent, } from "./bash-readonly-classifier.js";
|
|
4
4
|
import { isAbsolutePathForm } from "./safety.js";
|
|
@@ -16,14 +16,25 @@ export const READ_CANDIDATE_PROVENANCES = [
|
|
|
16
16
|
];
|
|
17
17
|
const NESTING_MAX = 4;
|
|
18
18
|
const BASE_CANDIDATE_MAX = 256;
|
|
19
|
+
function firstRefusedUnresolvedSentence(lexed) {
|
|
20
|
+
for (const segment of lexed.segments) {
|
|
21
|
+
for (const token of segment.tokens) {
|
|
22
|
+
const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
|
|
23
|
+
if (word?.unresolved !== undefined && UNRESOLVED_VIEW[word.unresolved] === "refuse")
|
|
24
|
+
return UNRESOLVED_SENTENCE[word.unresolved];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
19
29
|
export function planShellReads(command, decl, depth = 0) {
|
|
20
30
|
const shape = { grouped: false, backgrounded: false, strayRedirection: false };
|
|
21
31
|
if (depth > NESTING_MAX)
|
|
22
32
|
return { segments: [], unreadable: `command text nested more than ${NESTING_MAX} levels deep`, shape };
|
|
23
33
|
const homeReassigned = /(^|[\s;&|(){}`])(?:export\s+)?HOME\+?=/.test(command);
|
|
24
|
-
const lexed =
|
|
25
|
-
if ("
|
|
26
|
-
return { segments: [], unreadable: lexed.
|
|
34
|
+
const lexed = lexShell(foldHomeVariableSpelling(command, homeReassigned ? undefined : decl.homeDir), { mode: "lenient" });
|
|
35
|
+
if ("parseFailure" in lexed)
|
|
36
|
+
return { segments: [], unreadable: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason], shape };
|
|
37
|
+
const unreadable = firstRefusedUnresolvedSentence(lexed);
|
|
27
38
|
const nestedPlan = (text) => planShellReads(text, decl, depth + 1);
|
|
28
39
|
const projected = lexed.segments.map((segment) => projectSegment(segment));
|
|
29
40
|
const facts = projected.map((p, si) => segmentFacts(lexed.segments[si], p, decl, nestedPlan));
|
|
@@ -34,6 +45,11 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
34
45
|
const unmodelled = facts.map(() => false);
|
|
35
46
|
let candidates = [{ base: decl.cwd, skippedCd: -1 }];
|
|
36
47
|
let baseLost = false;
|
|
48
|
+
let floorBases;
|
|
49
|
+
const loseBase = (reachable) => {
|
|
50
|
+
baseLost = true;
|
|
51
|
+
floorBases ??= [...new Set(reachable.map((c) => c.base))];
|
|
52
|
+
};
|
|
37
53
|
let andRunStart = 0;
|
|
38
54
|
let crossedAllAnd = true;
|
|
39
55
|
let crossedAnyOr = false;
|
|
@@ -68,7 +84,7 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
68
84
|
if (move === undefined || baseLost)
|
|
69
85
|
return;
|
|
70
86
|
if (typeof move.target === "string" || move.target.expands !== false) {
|
|
71
|
-
|
|
87
|
+
loseBase(reachable);
|
|
72
88
|
return;
|
|
73
89
|
}
|
|
74
90
|
const target = move.target.spelled;
|
|
@@ -78,7 +94,7 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
78
94
|
if (c.skippedCd < andRunStart) {
|
|
79
95
|
const moved = joinBase(c.base, target, decl.homeDir);
|
|
80
96
|
if (moved === undefined) {
|
|
81
|
-
|
|
97
|
+
loseBase(reachable);
|
|
82
98
|
return;
|
|
83
99
|
}
|
|
84
100
|
branched.push({ base: moved, skippedCd: c.skippedCd });
|
|
@@ -93,7 +109,7 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
93
109
|
}
|
|
94
110
|
const next = [...byBase.values()];
|
|
95
111
|
if (next.length > BASE_CANDIDATE_MAX)
|
|
96
|
-
|
|
112
|
+
loseBase(reachable);
|
|
97
113
|
else
|
|
98
114
|
candidates = next;
|
|
99
115
|
};
|
|
@@ -116,7 +132,7 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
116
132
|
if (frame.beats === undefined) {
|
|
117
133
|
for (let k = frame.bodyStart; k < si; k++)
|
|
118
134
|
unmodelled[k] = true;
|
|
119
|
-
|
|
135
|
+
loseBase(candidates.filter((c) => c.skippedCd < andRunStart));
|
|
120
136
|
continue;
|
|
121
137
|
}
|
|
122
138
|
for (let beat = 1; beat < frame.beats && !baseLost; beat++) {
|
|
@@ -133,7 +149,11 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
133
149
|
if (f.empty)
|
|
134
150
|
continue;
|
|
135
151
|
const bases = unmodelled[si] ? "unmodelled" : [...basesOf[si]];
|
|
136
|
-
const
|
|
152
|
+
const floor = unmodelled[si] && floorBases !== undefined && floorBases.length > 0 ? floorBases : undefined;
|
|
153
|
+
const withBases = (c) => {
|
|
154
|
+
const relative = relativeSpelling(c.spelled);
|
|
155
|
+
return { ...c, bases: relative ? bases : [], ...(relative && floor !== undefined ? { floorBases: floor } : {}) };
|
|
156
|
+
};
|
|
137
157
|
segments.push({
|
|
138
158
|
text: f.text,
|
|
139
159
|
connector: f.connector,
|
|
@@ -151,7 +171,7 @@ export function planShellReads(command, decl, depth = 0) {
|
|
|
151
171
|
reach: reaches[si] ?? { andJoined: false, orBroken: false, moveProvable: false },
|
|
152
172
|
});
|
|
153
173
|
}
|
|
154
|
-
return { segments, shape };
|
|
174
|
+
return { segments, shape, ...(unreadable !== undefined ? { unreadable } : {}) };
|
|
155
175
|
}
|
|
156
176
|
function projectSegment(segment) {
|
|
157
177
|
const tokens = [...segment.tokens];
|
|
@@ -178,6 +198,24 @@ function projectSegment(segment) {
|
|
|
178
198
|
};
|
|
179
199
|
}
|
|
180
200
|
function segmentFacts(segment, p, decl, nestedPlan) {
|
|
201
|
+
const facts = segmentFactsRead(segment, p, decl, nestedPlan);
|
|
202
|
+
const unresolvedRaw = new Set();
|
|
203
|
+
for (const t of segment.tokens) {
|
|
204
|
+
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
205
|
+
if (w?.unresolved !== undefined && UNRESOLVED_VIEW[w.unresolved] === "refuse")
|
|
206
|
+
unresolvedRaw.add(w.raw);
|
|
207
|
+
}
|
|
208
|
+
if (unresolvedRaw.size === 0)
|
|
209
|
+
return facts;
|
|
210
|
+
const unsettle = (c) => (unresolvedRaw.has(c.raw) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
|
|
211
|
+
const move = facts.baseMove;
|
|
212
|
+
return {
|
|
213
|
+
...facts,
|
|
214
|
+
reads: facts.reads.map(unsettle),
|
|
215
|
+
...(move !== undefined && typeof move.target !== "string" ? { baseMove: { target: unsettle(move.target) } } : {}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function segmentFactsRead(segment, p, decl, nestedPlan) {
|
|
181
219
|
const nested = [];
|
|
182
220
|
for (const body of segment.substitutions)
|
|
183
221
|
nested.push(nestedPlan(body));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.28.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"pretest": "npm run gate:debts",
|
|
53
53
|
"test": "vitest run --exclude \"**/*.local.test.ts\"",
|
|
54
54
|
"test:watch": "vitest",
|
|
55
|
-
"gate:skips": "vitest run --exclude \"**/node_modules/**\" --exclude \"**/*.local.test.ts\" --reporter=default --reporter=json --outputFile.json=.skip-report.json && node scripts/verify-skip-baseline.mjs --report .skip-report.json",
|
|
55
|
+
"gate:skips": "vitest run --exclude \"**/node_modules/**\" --exclude \"**/*.local.test.ts\" --reporter=default --reporter=json --outputFile.json=.skip-report.json && node scripts/verify-skip-baseline.mjs --report .skip-report.json --full",
|
|
56
56
|
"gate:live": "node scripts/run-live-gate.mjs",
|
|
57
57
|
"smoke": "tsx src/examples/smoke.ts",
|
|
58
58
|
"eval": "tsx eval/run.ts",
|