@sema-agent/core 7.46.0 → 7.48.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 +106 -0
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +4 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +23 -6
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +7 -4
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +12 -6
- package/dist/core/permission-rule-model.js +10 -2
- package/dist/core/permission-rules.d.ts +4 -5
- package/dist/core/permission-rules.js +28 -15
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +5 -5
- package/dist/core/runner/prepare-policy-chain.js +2 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +8 -8
- package/dist/core/shell-wrapper-table.js +3 -3
- package/dist/core/tool-catalog-entries.js +3 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +10 -9
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/write-protect.d.ts +10 -3
- package/dist/core/write-protect.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.d.ts +15 -0
- package/dist/shell/lex.js +86 -15
- package/dist/tools/fs/bash-program-position.d.ts +16 -6
- package/dist/tools/fs/bash-program-position.js +18 -12
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +84 -12
- package/dist/tools/fs/program-read-model.js +105 -34
- package/dist/tools/fs/program-read-model.rows.js +26 -7
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +24 -5
- package/dist/tools/fs/shell-read-argv.d.ts +6 -8
- package/dist/tools/fs/shell-read-argv.js +5 -5
- package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
- package/dist/tools/fs/shell-read-bindings.js +190 -75
- package/dist/tools/fs/shell-read-establish.js +6 -7
- package/dist/tools/fs/shell-read-plan.d.ts +3 -15
- package/dist/tools/fs/shell-read-plan.js +126 -129
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +68 -8
- package/dist/tools/fs/shell-read-word.js +106 -12
- package/dist/tools/fs/shell-read-write-targets.js +4 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
- package/dist/tools/fs/shell-reentry-text.js +122 -4
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -3
- package/wire-changes/core@7.47.0.json +412 -0
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -69,6 +69,10 @@ export declare const NAME_READINGS: {
|
|
|
69
69
|
readonly kind: "wire";
|
|
70
70
|
readonly file: string;
|
|
71
71
|
};
|
|
72
|
+
readonly "gate.askFace": {
|
|
73
|
+
readonly kind: "wire";
|
|
74
|
+
readonly file: string;
|
|
75
|
+
};
|
|
72
76
|
readonly "fold.readStation": {
|
|
73
77
|
readonly kind: "seat";
|
|
74
78
|
readonly rank: "member";
|
|
@@ -251,6 +255,10 @@ export declare const NAME_READINGS: {
|
|
|
251
255
|
readonly kind: "wire";
|
|
252
256
|
readonly file: string;
|
|
253
257
|
};
|
|
258
|
+
readonly "inherited.askFace": {
|
|
259
|
+
readonly kind: "wire";
|
|
260
|
+
readonly file: string;
|
|
261
|
+
};
|
|
254
262
|
readonly "park.contentKindProjection": {
|
|
255
263
|
readonly kind: "wire";
|
|
256
264
|
readonly file: string;
|
|
@@ -19,6 +19,7 @@ export const NAME_READINGS = {
|
|
|
19
19
|
"gate.sandboxBoundary.egress": any(CHAIN),
|
|
20
20
|
"gate.sandboxBoundary.hand": wire(CHAIN),
|
|
21
21
|
"gate.preToolUse": wire("src/core/hooks.ts"),
|
|
22
|
+
"gate.askFace": wire("src/core/runner/gate-exit.ts"),
|
|
22
23
|
"fold.readStation": seat("member", "src/tools/fs/shell-read-check.ts"),
|
|
23
24
|
"lanes.contentQuestionClass": any(LANES),
|
|
24
25
|
"lanes.readOnlyShellAllow": wire(LANES),
|
|
@@ -63,6 +64,7 @@ export const NAME_READINGS = {
|
|
|
63
64
|
"activity.sink": spelling(HANDLERS),
|
|
64
65
|
"reconcile.orphanEffect": spelling("src/core/session-reconcile.ts"),
|
|
65
66
|
"inherited.questionFloat": wire(CHAIN),
|
|
67
|
+
"inherited.askFace": wire(CHAIN),
|
|
66
68
|
"park.contentKindProjection": wire("src/core/checkpoint-store.ts"),
|
|
67
69
|
"resume.contentAskRow": wire("src/core/runner/decide-continuation.ts"),
|
|
68
70
|
"resume.editRecheckQuestionSkip": wire("src/core/runner/resume-apply.ts"),
|
|
@@ -977,13 +977,14 @@ export declare function createApprovalPolicy(opts: {
|
|
|
977
977
|
*/
|
|
978
978
|
export declare function combinePolicies(...policies: ToolPolicy[]): ToolPolicy;
|
|
979
979
|
/**
|
|
980
|
-
* The shell tools whose `{ command }` arg a coarse command-name policy parses; other tools are out of scope.
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
980
|
+
* The shell tools whose `{ command }` arg a coarse command-name policy parses; other tools are out of scope. It is the
|
|
981
|
+
* catalog's shell face ({@link shellFaceToolNames} — every definition whose `command` the command grammar judges, `Monitor`
|
|
982
|
+
* among them: it runs an arbitrary shell `command` through the same env seam as Bash), spelled out for the wire; the
|
|
983
|
+
* catalog's shell face is the truth, and a lockstep pin keeps the two one (a catalog row added or dropped turns it red):
|
|
984
|
+
* the always-on parity gates keyed on it, the `Bash(...)` rules and the shell seat reach the same tools. EXPORTED: a
|
|
985
|
+
* downstream governance layer reads it so its per-command ask-list gates the same tools core does.
|
|
985
986
|
*/
|
|
986
|
-
export declare const COARSE_SHELL_TOOLS:
|
|
987
|
+
export declare const COARSE_SHELL_TOOLS: readonly ["Bash", "Monitor"];
|
|
987
988
|
/**
|
|
988
989
|
* design/center §10 — a COARSE allow/deny gate keyed on the LEADING command NAME (`argv[0]`) of a single
|
|
989
990
|
* simple shell command. It reuses the ONE shared simple-command parser
|
|
@@ -1033,7 +1034,7 @@ export declare function createCoarseCommandNamePolicy(opts: {
|
|
|
1033
1034
|
deny?: string[];
|
|
1034
1035
|
/** What to do for a command not matched by `allow` (or un-parseable): `"ask"` (default) or `"deny"`. */
|
|
1035
1036
|
defaultAction?: "ask" | "deny";
|
|
1036
|
-
/** Override the shell tool names this gate parses (default
|
|
1037
|
+
/** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
|
|
1037
1038
|
tools?: string[];
|
|
1038
1039
|
}): ToolPolicy;
|
|
1039
1040
|
export declare function findUnverifiableRecursiveDelete(command: string, safeVars?: ReadonlySet<string>): string | undefined;
|
|
@@ -1112,7 +1113,7 @@ export declare function createUnverifiableDeletePolicy(opts?: {
|
|
|
1112
1113
|
* the default was `TMPDIR` alone, reading STRICTER than the gate really is; `HOME`/`PWD` are a
|
|
1113
1114
|
* registered sema divergence from CC's temp-only carve-out, see the RB-340 note there). */
|
|
1114
1115
|
safeVars?: readonly string[];
|
|
1115
|
-
/** Override the shell tool names this gate parses (default
|
|
1116
|
+
/** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
|
|
1116
1117
|
tools?: string[];
|
|
1117
1118
|
removalSeat?: RemovalGuardSeat;
|
|
1118
1119
|
}): ToolPolicy;
|
|
@@ -1121,7 +1122,7 @@ export declare function createTranscriptIntegrityPolicy(opts?: {
|
|
|
1121
1122
|
sessionsDirs?: readonly string[];
|
|
1122
1123
|
/** argv[0] allowlist for read-only access to the transcript dir (default {@link BASH_READONLY_DEFAULT_ALLOW}). */
|
|
1123
1124
|
readAllow?: readonly string[];
|
|
1124
|
-
/** Override the shell tool names this gate parses (default
|
|
1125
|
+
/** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
|
|
1125
1126
|
tools?: string[];
|
|
1126
1127
|
}): ToolPolicy;
|
|
1127
1128
|
/**
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -10,7 +10,7 @@ export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
|
|
|
10
10
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
11
11
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
12
12
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
13
|
-
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
13
|
+
import { protectivePathTargetOf, shellFaceToolNames } from "./tool-registry.js";
|
|
14
14
|
import { heldBy } from "./tool-name-readings.js";
|
|
15
15
|
import { boundInputHashOf } from "./canonical-json.js";
|
|
16
16
|
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
|
|
@@ -642,7 +642,7 @@ export function combinePolicies(...policies) {
|
|
|
642
642
|
},
|
|
643
643
|
};
|
|
644
644
|
}
|
|
645
|
-
export const COARSE_SHELL_TOOLS = ["Bash", "Monitor"];
|
|
645
|
+
export const COARSE_SHELL_TOOLS = Object.freeze(["Bash", "Monitor"]);
|
|
646
646
|
function canonicalToolNameSet(tools) {
|
|
647
647
|
return new Set(tools ?? COARSE_SHELL_TOOLS);
|
|
648
648
|
}
|
|
@@ -725,7 +725,7 @@ function assignmentTokenSpan(tokens, i) {
|
|
|
725
725
|
return 1;
|
|
726
726
|
}
|
|
727
727
|
function valueIsOpaque(value, assigned, safeVars) {
|
|
728
|
-
const v = value.replace(/["']/g, "");
|
|
728
|
+
const v = value.replace(/["']/g, "").replace(/\\(.)/g, "$1");
|
|
729
729
|
if (v.includes("$(") || v.includes("`"))
|
|
730
730
|
return true;
|
|
731
731
|
if (/\$[@*#!?0-9-]/.test(v))
|
|
@@ -734,11 +734,12 @@ function valueIsOpaque(value, assigned, safeVars) {
|
|
|
734
734
|
return true;
|
|
735
735
|
for (const vm of v.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g)) {
|
|
736
736
|
const ref = vm[1];
|
|
737
|
-
if (!assigned.has(ref) && !safeVars.has(ref))
|
|
737
|
+
if (!assigned.has(ref) && !(safeVars.has(ref) && safeRootBelow(v.slice((vm.index ?? 0) + vm[0].length))))
|
|
738
738
|
return true;
|
|
739
739
|
}
|
|
740
740
|
return false;
|
|
741
741
|
}
|
|
742
|
+
const safeRootBelow = (after) => /^\/[^*?[\]]+$/.test(after) && !after.split("/").includes("..");
|
|
742
743
|
export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVERIFIABLE_DELETE_SAFE_VARS)) {
|
|
743
744
|
const assigned = new Set();
|
|
744
745
|
const assignedEscapesRoot = new Set();
|
|
@@ -887,9 +888,7 @@ export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVE
|
|
|
887
888
|
return `\`rm\` recursive+force target ${raw} resolves through ${name}, whose in-window value climbs above a known-safe root (\`..\`) — the deleted path cannot be verified from this command`;
|
|
888
889
|
}
|
|
889
890
|
if (safeVars.has(name)) {
|
|
890
|
-
|
|
891
|
-
const escapesRoot = after.split("/").includes("..");
|
|
892
|
-
if (/^\/[^*?[\]]+$/.test(after) && !escapesRoot)
|
|
891
|
+
if (safeRootBelow(targetNorm.slice((vm.index ?? 0) + vm[0].length)))
|
|
893
892
|
continue;
|
|
894
893
|
}
|
|
895
894
|
return `\`rm\` recursive+force target ${raw} references $${name}, which has no assignment in this command — an empty or unexpected value could make this a home-directory or filesystem-root wipe`;
|
|
@@ -25,6 +25,15 @@ export declare function catalogToolNames(): ReadonlySet<string>;
|
|
|
25
25
|
export declare function pathConfinableWriteToolNames(): ReadonlySet<string>;
|
|
26
26
|
/** A-8 — every definition that names a path target at all (read or write): the file-tool glob lane. */
|
|
27
27
|
export declare function pathTargetingToolNames(): ReadonlySet<string>;
|
|
28
|
+
/**
|
|
29
|
+
* The SHELL FACE: every definition whose `command` argument is a shell command line the command grammar judges (today the two
|
|
30
|
+
* `Bash` definitions and `Monitor`). A `Bash(<command>)` rule reaches a call that answers to any of these names; the coarse
|
|
31
|
+
* command-name gate's default list, the shell read station and the shell seat are this set. Derived from the rows' rule faces,
|
|
32
|
+
* never a list of its own.
|
|
33
|
+
*/
|
|
34
|
+
export declare function shellFaceToolNames(): ReadonlySet<string>;
|
|
35
|
+
/** {@link shellFaceToolNames}' membership for one name — any definition under the name on the shell face. */
|
|
36
|
+
export declare function isShellFaceToolName(name: string): boolean;
|
|
28
37
|
/** The tools whose declared path target is a READ (a file read, a search root): the file read station's membership. */
|
|
29
38
|
export declare function readTargetToolNames(): ReadonlySet<string>;
|
|
30
39
|
/** A-10 — the micro-compaction whitelist (`compactable: true`). */
|
|
@@ -29,6 +29,16 @@ export function pathConfinableWriteToolNames() {
|
|
|
29
29
|
export function pathTargetingToolNames() {
|
|
30
30
|
return names((e) => e.face.pathTarget !== undefined);
|
|
31
31
|
}
|
|
32
|
+
const onShellFace = (e) => e.face.ruleFace?.grammar === "command";
|
|
33
|
+
export function shellFaceToolNames() {
|
|
34
|
+
return names(onShellFace);
|
|
35
|
+
}
|
|
36
|
+
export function isShellFaceToolName(name) {
|
|
37
|
+
for (const e of TOOL_CATALOG_ENTRIES)
|
|
38
|
+
if (e.name === name && onShellFace(e))
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
32
42
|
export function readTargetToolNames() {
|
|
33
43
|
return names((e) => e.face.pathTarget?.access === "read");
|
|
34
44
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
2
|
import type { PermissionResult } from "./tool-policy.js";
|
|
3
|
+
import { type AskFace } from "./ask-origin.js";
|
|
3
4
|
import { type ToolCallLike } from "./tool-registry.js";
|
|
4
5
|
/**
|
|
5
6
|
* How one table row matches the judged path:
|
|
@@ -209,11 +210,17 @@ export declare function compileWriteProtection(entries?: readonly WriteProtected
|
|
|
209
210
|
* message (table names are deployment-validated vocabulary); the model-authored target path deliberately
|
|
210
211
|
* does not — `message` is the classifier's `askMessage`, and the approval card already shows the args. The
|
|
211
212
|
* spelling form's sentence is the historical one byte for byte; the target form says the target RESOLVED.
|
|
212
|
-
* · `unresolvable` ⇒
|
|
213
|
-
*
|
|
213
|
+
* · `unresolvable` ⇒ read by the call's FACE (`face`, {@link releasableAskClassesFor}): where no permission mode releases
|
|
214
|
+
* `write_protection_unresolved` (the file face) — `deny` naming the cause: an unjudged target is never vouched (the rule
|
|
215
|
+
* the canonicalizing deny set follows for the same reason), so there a standing allow is refused while a standing ask
|
|
216
|
+
* carrying the record stays a person's question in the modes that ask (dontAsk and bypassPermissions refuse it); where
|
|
217
|
+
* a mode may release the class (the shell face) — `ask` (`safety`)
|
|
218
|
+
* naming the cause, which the mode arbitration answers exactly as it answers that record over a standing ask
|
|
219
|
+
* (bypassPermissions releases it, default puts it to a person, dontAsk refuses): on that face alone a standing allow
|
|
220
|
+
* reads as a standing ask.
|
|
214
221
|
* · `clear` ⇒ `undefined`: the surviving decision stands.
|
|
215
222
|
*/
|
|
216
|
-
export declare function writeProtectionTightenDecision(toolName: string, verdict: WriteProtectionVerdict): PermissionResult | undefined;
|
|
223
|
+
export declare function writeProtectionTightenDecision(toolName: string, verdict: WriteProtectionVerdict, face: AskFace): PermissionResult | undefined;
|
|
217
224
|
/**
|
|
218
225
|
* @contract write_protection.verdict_is_the_mandate — the judge's two non-clear verdicts are the two facts
|
|
219
226
|
* a standing allow rule may not retire, and `clear` is neither: a MATCHED row is the mandate a live decider
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { releasableAskClassesFor } from "./ask-origin.js";
|
|
1
2
|
import { canonicalizeTarget, isWinFormPath, writeTargetPath, isUncPath, isAbsoluteForFamily, joinForFamily, pathFamilyOf } from "../tools/fs/safety.js";
|
|
2
3
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
3
4
|
import { pathTargetBaseOf } from "./effective-path-target.js";
|
|
@@ -346,7 +347,7 @@ export function compileWriteProtection(entries, opts) {
|
|
|
346
347
|
},
|
|
347
348
|
};
|
|
348
349
|
}
|
|
349
|
-
export function writeProtectionTightenDecision(toolName, verdict) {
|
|
350
|
+
export function writeProtectionTightenDecision(toolName, verdict, face) {
|
|
350
351
|
if (verdict.kind === "hit") {
|
|
351
352
|
return {
|
|
352
353
|
action: "ask",
|
|
@@ -359,6 +360,15 @@ export function writeProtectionTightenDecision(toolName, verdict) {
|
|
|
359
360
|
};
|
|
360
361
|
}
|
|
361
362
|
if (verdict.kind === "unresolvable") {
|
|
363
|
+
if (releasableAskClassesFor(face).includes("write_protection_unresolved")) {
|
|
364
|
+
return {
|
|
365
|
+
action: "ask",
|
|
366
|
+
message: verdict.unresolvedSymlink
|
|
367
|
+
? `tool "${toolName}" writes to a symlink whose real target could not be resolved, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause}) — explicit approval required`
|
|
368
|
+
: `tool "${toolName}" writes to a target that could not be resolved on this tree, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause}) — explicit approval required`,
|
|
369
|
+
decisionReason: "safety",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
362
372
|
return {
|
|
363
373
|
action: "deny",
|
|
364
374
|
message: verdict.unresolvedSymlink
|
package/dist/index.d.ts
CHANGED
|
@@ -197,7 +197,7 @@ export { createAllowDenyPolicy, createApprovalPolicy,
|
|
|
197
197
|
type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathRules, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
|
|
198
198
|
export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
|
|
199
199
|
export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
|
|
200
|
-
export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow,
|
|
200
|
+
export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, ASK_FACES, type AskFace, askFaceOf, releasableAskClassesFor, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
|
|
201
201
|
export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
|
|
202
202
|
export type { PermissionSeatInputs, PermissionSeatReading } from "./core/mode-policy.js";
|
|
203
203
|
export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantOrigin, SkillGrantRecord } from "./core/skill-grant.js";
|
package/dist/index.js
CHANGED
|
@@ -162,7 +162,7 @@ export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.
|
|
|
162
162
|
export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
|
|
163
163
|
export {} from "./core/read-root-grant-screen.js";
|
|
164
164
|
export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
|
|
165
|
-
export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION,
|
|
165
|
+
export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, ASK_FACES, askFaceOf, releasableAskClassesFor, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
|
|
166
166
|
export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
|
|
167
167
|
export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
|
|
168
168
|
export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, renderListEntry } from "./core/allow-entry.js";
|
|
@@ -16,7 +16,7 @@ import { isWorkflowRunActive, closeWorkflowChannel, markWorkflowActive, publishW
|
|
|
16
16
|
import { isDurablePause } from "../agents/suspend-guard.js";
|
|
17
17
|
import { RUNNING_AGENT_OBSERVE_EVERY_BEATS } from "../config/defaults.js";
|
|
18
18
|
import { boundInputHashOf } from "../core/canonical-json.js";
|
|
19
|
-
import { boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
19
|
+
import { boundedRedactedSummary, redactSecrets } from "../core/untrusted-egress.js";
|
|
20
20
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
21
21
|
import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
|
|
22
22
|
import { deniedToolNamesOf } from "../core/runner/sub-agent-deny-lists.js";
|
|
@@ -2420,7 +2420,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
2420
2420
|
run.endedAt = now();
|
|
2421
2421
|
if (parkedLeg !== undefined && !thrownIs(err, WorkflowAgentParkedError))
|
|
2422
2422
|
err = parkedError();
|
|
2423
|
-
run.error = thrownText(err);
|
|
2423
|
+
run.error = redactSecrets(thrownText(err));
|
|
2424
2424
|
closeAbandonedAgents(run.endedAt);
|
|
2425
2425
|
{
|
|
2426
2426
|
let thrownCode;
|
|
@@ -146,7 +146,7 @@ export interface PackSectionDeclaration {
|
|
|
146
146
|
/**
|
|
147
147
|
* design/147 epoch declaration axis (ruling a): `"provider"` marks a section whose text came
|
|
148
148
|
* from the typed `stableBlocks` lane (incl. the provider-assembled pass-through role) — the SELECTOR
|
|
149
|
-
* for the epoch artifactDigest's declaration input, so a
|
|
149
|
+
* for the epoch artifactDigest's declaration input, so a web-admin publish (declaration bytes
|
|
150
150
|
* change) re-pins sessions at the next legal boundary. NOT a digest-disclosure gate
|
|
151
151
|
* (that stays {@link declaredContentHash}-only): epoch digests are one-way hashes over the payload.
|
|
152
152
|
*/
|
package/dist/shell/lex.d.ts
CHANGED
|
@@ -103,6 +103,17 @@ export interface WordExpansion {
|
|
|
103
103
|
readonly body?: string;
|
|
104
104
|
/** The parameter name, where the expansion names one. */
|
|
105
105
|
readonly name?: string;
|
|
106
|
+
/** A glob's pattern — absent where an expansion stands inside it (its text is not the pattern): `folded`, what the
|
|
107
|
+
* program receives where it matches nothing — quotes removed, the spelling {@link LexedWord.folded} carries at its place
|
|
108
|
+
* (`[^.]`); `written`, as written, quoting kept (`[^\.]`, `["!"a]`) — the text a shell re-reading it matches by;
|
|
109
|
+
* `judged`, the spelling a reader judges the names it matches by — `folded`, save a class the judge reads as any one
|
|
110
|
+
* character: one holding a quoted or escaped character (its quote-removed text reads another class: `["!"a]` is not
|
|
111
|
+
* the negation `[!a]`) or a `[:…:]` / `[=…=]` / `[.….]` unit — spelled `?`, a superset of its members. */
|
|
112
|
+
readonly pattern?: {
|
|
113
|
+
readonly folded: string;
|
|
114
|
+
readonly written: string;
|
|
115
|
+
readonly judged: string;
|
|
116
|
+
};
|
|
106
117
|
}
|
|
107
118
|
export interface QuotedSpan {
|
|
108
119
|
/** Offsets into {@link LexedWord.raw}, `[start, end)`; the quote characters belong to their span. */
|
|
@@ -143,6 +154,10 @@ export interface LexedWord {
|
|
|
143
154
|
readonly kind: "word";
|
|
144
155
|
readonly raw: string;
|
|
145
156
|
readonly folded: string;
|
|
157
|
+
/** The spelling a reader judges the word's names by, where it is not `folded`: each bracket class holding a quoted or
|
|
158
|
+
* escaped character or a `[:…:]` / `[=…=]` / `[.….]` unit spelled `?` (`["!"a]*`, `[[:alpha:]]*` ⇒ `?*`,
|
|
159
|
+
* {@link WordExpansion.pattern}). */
|
|
160
|
+
readonly judged?: string;
|
|
146
161
|
readonly pieces: readonly WordPiece[];
|
|
147
162
|
readonly quotedSpans: readonly QuotedSpan[];
|
|
148
163
|
/** The pieces that carry an expansion, in order. */
|
package/dist/shell/lex.js
CHANGED
|
@@ -357,6 +357,7 @@ class WordBuilder {
|
|
|
357
357
|
marks = [];
|
|
358
358
|
subscript;
|
|
359
359
|
compound = false;
|
|
360
|
+
widenedClasses = [];
|
|
360
361
|
begin(src) { if (!this.started)
|
|
361
362
|
this.at = src; }
|
|
362
363
|
push(w, f, by, src) {
|
|
@@ -402,6 +403,17 @@ class WordBuilder {
|
|
|
402
403
|
mark(reason, rawAt) { this.marks.push({ at: rawAt, reason }); }
|
|
403
404
|
inSubscript() { return this.subscript !== undefined && this.subscript.rawEnd === undefined; }
|
|
404
405
|
foldedOf(r) { return r >= this.foldedAt.length ? this.folded.length : this.foldedAt[r]; }
|
|
406
|
+
classSpelled(from, to) {
|
|
407
|
+
let out = "";
|
|
408
|
+
let at = from;
|
|
409
|
+
for (const [a, b] of this.widenedClasses) {
|
|
410
|
+
if (a < at || b > to)
|
|
411
|
+
continue;
|
|
412
|
+
out += `${this.folded.slice(at, a)}?`;
|
|
413
|
+
at = b;
|
|
414
|
+
}
|
|
415
|
+
return out + this.folded.slice(at, to);
|
|
416
|
+
}
|
|
405
417
|
slice(from, to) {
|
|
406
418
|
const out = [];
|
|
407
419
|
for (const p of this.pieces) {
|
|
@@ -519,9 +531,37 @@ class WordBuilder {
|
|
|
519
531
|
else if (stack.length > 0 && (d === "," || (d === "." && this.raw[j + 1] === ".")))
|
|
520
532
|
stack[stack.length - 1].listed = true;
|
|
521
533
|
}
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
534
|
+
const text = this.folded;
|
|
535
|
+
const nf = text.length;
|
|
536
|
+
const readableAt = (f) => f < nf && readable(this.rawOfFolded[f]);
|
|
537
|
+
const unitClose = (d) => {
|
|
538
|
+
const at = new Array(nf + 2).fill(-1);
|
|
539
|
+
for (let f = nf - 2; f >= 0; f--)
|
|
540
|
+
at[f] = readableAt(f) && readableAt(f + 1) && text[f] === d && text[f + 1] === "]" ? f : at[f + 1];
|
|
541
|
+
return at;
|
|
542
|
+
};
|
|
543
|
+
const unitCloses = new Map([":", "=", "."].map((d) => [d, unitClose(d)]));
|
|
544
|
+
const lettersEnd = new Array(nf + 1).fill(nf);
|
|
545
|
+
for (let f = nf - 1; f >= 0; f--) {
|
|
546
|
+
const lower = text.charCodeAt(f) | 0x20;
|
|
547
|
+
lettersEnd[f] = lower >= 0x61 && lower <= 0x7a && readableAt(f) ? lettersEnd[f + 1] : f;
|
|
548
|
+
}
|
|
549
|
+
const unitEndAt = (f) => {
|
|
550
|
+
if (!(readableAt(f) && text[f] === "[" && readableAt(f + 1)))
|
|
551
|
+
return -1;
|
|
552
|
+
const d = text[f + 1];
|
|
553
|
+
const end = unitCloses.get(d)?.[f + 2] ?? -1;
|
|
554
|
+
if (end < 0)
|
|
555
|
+
return -1;
|
|
556
|
+
return d === ":" ? (end > f + 2 && lettersEnd[f + 2] === end ? end : -1) : end === f + 3 ? end : -1;
|
|
557
|
+
};
|
|
558
|
+
const nextBracketClose = new Array(nf + 2).fill(-1);
|
|
559
|
+
const nextSlash = new Array(nf + 1).fill(nf);
|
|
560
|
+
for (let f = nf - 1; f >= 0; f--) {
|
|
561
|
+
const unitEnd = unitEndAt(f);
|
|
562
|
+
nextBracketClose[f] = readableAt(f) && text[f] === "]" ? f : unitEnd >= 0 ? nextBracketClose[unitEnd + 2] : nextBracketClose[f + 1];
|
|
563
|
+
nextSlash[f] = text[f] === "/" ? f : nextSlash[f + 1];
|
|
564
|
+
}
|
|
525
565
|
const ranges = [];
|
|
526
566
|
for (let k = 0; k < n; k++) {
|
|
527
567
|
if (!readable(k))
|
|
@@ -536,19 +576,35 @@ class WordBuilder {
|
|
|
536
576
|
continue;
|
|
537
577
|
}
|
|
538
578
|
if (c === "*" || c === "?") {
|
|
539
|
-
ranges.push({ from: k, to: k + 1, exp: { kind: "glob", yields: "many" } });
|
|
579
|
+
ranges.push({ from: k, to: k + 1, exp: { kind: "glob", yields: "many", pattern: { folded: c, written: c, judged: c } } });
|
|
540
580
|
continue;
|
|
541
581
|
}
|
|
542
582
|
if (c === "[") {
|
|
543
|
-
|
|
544
|
-
|
|
583
|
+
if (this.rawOfFolded[this.foldedOf(k)] !== k)
|
|
584
|
+
continue;
|
|
585
|
+
let j = this.foldedOf(k) + 1;
|
|
586
|
+
if ((text[j] === "!" || text[j] === "^") && readableAt(j))
|
|
545
587
|
j++;
|
|
546
|
-
if (
|
|
588
|
+
if (text[j] === "]" && readableAt(j))
|
|
547
589
|
j++;
|
|
548
|
-
const
|
|
549
|
-
if (
|
|
590
|
+
const closeAt = j < nf ? nextBracketClose[j] : -1;
|
|
591
|
+
if (closeAt < 0 || nextSlash[j] < closeAt)
|
|
550
592
|
continue;
|
|
551
|
-
|
|
593
|
+
const close = this.rawOfFolded[closeAt];
|
|
594
|
+
let expands = false;
|
|
595
|
+
for (let m = k + 1; m < close && !expands; m++)
|
|
596
|
+
expands = inExp[m] || braceClose.get(m)?.listed === true;
|
|
597
|
+
let widened = false;
|
|
598
|
+
for (let m = k + 1; m < close && !widened; m++)
|
|
599
|
+
widened = this.rawQuoted[m] !== undefined;
|
|
600
|
+
for (let f = j; f < closeAt && !widened; f++)
|
|
601
|
+
widened = readableAt(f) && text[f] === "[" && readableAt(f + 1) && (text[f + 1] === ":" || text[f + 1] === "=" || text[f + 1] === ".");
|
|
602
|
+
const from = this.foldedOf(k);
|
|
603
|
+
const to = this.foldedOf(close + 1);
|
|
604
|
+
if (!expands && widened)
|
|
605
|
+
this.widenedClasses.push([from, to]);
|
|
606
|
+
const pattern = expands ? {} : { pattern: { folded: this.folded.slice(from, to), written: this.raw.slice(k, close + 1), judged: widened ? "?" : this.folded.slice(from, to) } };
|
|
607
|
+
ranges.push({ from: k, to: close + 1, exp: { kind: "glob", yields: "many", body: this.raw.slice(k + 1, close), ...pattern } });
|
|
552
608
|
k = close;
|
|
553
609
|
}
|
|
554
610
|
}
|
|
@@ -647,18 +703,20 @@ class WordBuilder {
|
|
|
647
703
|
}
|
|
648
704
|
finish(atCommandPosition, tildeSpelling = true) {
|
|
649
705
|
const rawText = this.raw;
|
|
650
|
-
let
|
|
706
|
+
let literalTilde = false;
|
|
651
707
|
let assignment;
|
|
652
708
|
const tildeEnd = this.tildePrefixEnd(0, false);
|
|
653
709
|
if (tildeEnd !== undefined)
|
|
654
710
|
this.carveTilde(0, tildeEnd);
|
|
655
711
|
else {
|
|
656
|
-
|
|
657
|
-
foldedText = literalTildeSpelling(foldedText);
|
|
712
|
+
literalTilde = tildeSpelling;
|
|
658
713
|
assignment = this.readAssignment();
|
|
659
714
|
}
|
|
660
715
|
if (!(assignment !== undefined && assignment.bare && atCommandPosition))
|
|
661
716
|
this.carveBraceAndGlob();
|
|
717
|
+
const spelled = (text) => (literalTilde ? literalTildeSpelling(text) : text);
|
|
718
|
+
const foldedText = spelled(this.folded);
|
|
719
|
+
const judged = this.widenedClasses.length > 0 ? spelled(this.classSpelled(0, this.folded.length)) : undefined;
|
|
662
720
|
if (assignment !== undefined && this.subscript?.text)
|
|
663
721
|
this.mark("subscript_text", this.subscript.rawStart);
|
|
664
722
|
const marks = this.marks.filter((m) => m.reason !== "subscript_text" || assignment?.subscript !== undefined).sort((a, b) => a.at - b.at);
|
|
@@ -679,6 +737,7 @@ class WordBuilder {
|
|
|
679
737
|
kind: "word",
|
|
680
738
|
raw: rawText,
|
|
681
739
|
folded: foldedText,
|
|
740
|
+
...(judged !== undefined ? { judged } : {}),
|
|
682
741
|
pieces,
|
|
683
742
|
quotedSpans: spans,
|
|
684
743
|
expansions: pieces.filter((p) => p.expansion !== undefined),
|
|
@@ -703,6 +762,7 @@ function scan(source, connectorsAreText) {
|
|
|
703
762
|
let acceptable = true;
|
|
704
763
|
let timeGrammar;
|
|
705
764
|
let definitionName = false;
|
|
765
|
+
let coprocWord;
|
|
706
766
|
let afterLeadingRedirect = false;
|
|
707
767
|
let assignmentSeen = false;
|
|
708
768
|
let subscriptsClosed = false;
|
|
@@ -716,7 +776,7 @@ function scan(source, connectorsAreText) {
|
|
|
716
776
|
w.opaque(source.slice(from, to), SUBSTITUTION_PLACEHOLDER, by, from, to, { kind, yields: kind === "command" && by === undefined ? "many" : "one", body });
|
|
717
777
|
current().substitutions.push(body);
|
|
718
778
|
};
|
|
719
|
-
const resetPosition = () => { acceptable = true; commandWord = undefined; afterLeadingRedirect = false; assignmentSeen = false; subscriptsClosed = false; timeGrammar = undefined; definitionName = false; };
|
|
779
|
+
const resetPosition = () => { acceptable = true; commandWord = undefined; afterLeadingRedirect = false; assignmentSeen = false; subscriptsClosed = false; timeGrammar = undefined; definitionName = false; coprocWord = undefined; };
|
|
720
780
|
const endWord = () => {
|
|
721
781
|
if (!w.started)
|
|
722
782
|
return undefined;
|
|
@@ -771,6 +831,11 @@ function scan(source, connectorsAreText) {
|
|
|
771
831
|
}
|
|
772
832
|
if (acceptable && bare && (SHELL_CONTROL_PREFIXES.has(word.folded) || GROUP_WORDS.has(word.folded))) {
|
|
773
833
|
timeGrammar = word.folded === "time" ? "opt" : undefined;
|
|
834
|
+
coprocWord = undefined;
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
if (acceptable && bare && word.folded === "coproc") {
|
|
838
|
+
coprocWord = "head";
|
|
774
839
|
return undefined;
|
|
775
840
|
}
|
|
776
841
|
if (acceptable && bare && timeGrammar === "opt" && word.folded === "-p") {
|
|
@@ -784,10 +849,16 @@ function scan(source, connectorsAreText) {
|
|
|
784
849
|
timeGrammar = undefined;
|
|
785
850
|
if (word.assignment !== undefined && word.assignment.bare && acceptable) {
|
|
786
851
|
assignmentSeen = true;
|
|
852
|
+
coprocWord = undefined;
|
|
787
853
|
return undefined;
|
|
788
854
|
}
|
|
789
|
-
if (acceptable)
|
|
855
|
+
if (acceptable && coprocWord !== "name")
|
|
790
856
|
commandWord = bare ? word.folded : undefined;
|
|
857
|
+
if (coprocWord === "head") {
|
|
858
|
+
coprocWord = "name";
|
|
859
|
+
return undefined;
|
|
860
|
+
}
|
|
861
|
+
coprocWord = undefined;
|
|
791
862
|
acceptable = false;
|
|
792
863
|
return undefined;
|
|
793
864
|
};
|
|
@@ -34,6 +34,13 @@ export { SHELL_CONTROL_PREFIXES };
|
|
|
34
34
|
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
|
|
35
35
|
* looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
|
|
36
36
|
export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean;
|
|
37
|
+
/** Does the shell read this word as syntax — a reserved word, a control word, `{`, `coproc` — of `set`? Only a word
|
|
38
|
+
* written BARE where bash reads a command's first word (the lexer's {@link LexedWord.atCommandPosition}): a quoted or
|
|
39
|
+
* escaped spelling is a command name, and a word an expansion produced (a value, a split field) is never syntax —
|
|
40
|
+
* bash parses the line before it expands it. The one reading every syntax reader of a command's words asks. */
|
|
41
|
+
export declare function isShellSyntaxWord(w: LexedWord | undefined, set: ReadonlySet<string>): boolean;
|
|
42
|
+
/** The words that head a loop over a list (`for NAME in …`, `select NAME in …`). */
|
|
43
|
+
export declare const LOOP_LIST_HEADS: ReadonlySet<string>;
|
|
37
44
|
/**
|
|
38
45
|
* A word bash strips as an ASSIGNMENT before it chooses the program: the lexer's assignment fact, written BARE
|
|
39
46
|
* (the name, the brackets and the operator unquoted — a quoted VALUE still assigns, a quoted operator is a
|
|
@@ -53,12 +60,15 @@ export type ProgramPositionRefusal = {
|
|
|
53
60
|
readonly kind: "control_syntax_at_program_position";
|
|
54
61
|
readonly owner: string;
|
|
55
62
|
readonly word: string;
|
|
56
|
-
/** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
readonly
|
|
63
|
+
/** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL — or a program the launcher
|
|
64
|
+
* names by an option of its own (`su -s PROGRAM`) — stands at the program position, run by the launcher at argv
|
|
65
|
+
* index `launcher` and handed the words from index `from` on (none where `from` is the argv's end: `sudo -s`,
|
|
66
|
+
* `chroot DIR`), as that launcher's row composes them (`launchedCommands`, shell-reentry-text.ts). Absent ⇒
|
|
67
|
+
* genuine control syntax, which IS one. */
|
|
68
|
+
readonly runsShell?: {
|
|
69
|
+
readonly launcher: number;
|
|
70
|
+
readonly from: number;
|
|
71
|
+
};
|
|
62
72
|
};
|
|
63
73
|
export interface ProgramPosition {
|
|
64
74
|
/** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
|