@sema-agent/core 7.44.0 → 7.46.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 +418 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +54 -2
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +41 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -37
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -3,7 +3,7 @@ import { snapshotActorAssertion } from "../../internal/llm.js";
|
|
|
3
3
|
import { MAX_STEER_INPUT_ID_CHARS } from "../checkpoint-store.js";
|
|
4
4
|
import { formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
5
5
|
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
6
|
-
import { isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
6
|
+
import { injectionLaneOf, isSystemInjectionPriority, steerPriorityOf, SYSTEM_INJECTION_PRIORITIES } from "../task-notification.js";
|
|
7
7
|
import { deliverEngineNotice } from "../types.js";
|
|
8
8
|
import { inlineUntrusted, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
9
9
|
import { announceAcceptance, armAcceptance, nextHumanInputSeq } from "./steer-admission.js";
|
|
@@ -133,7 +133,7 @@ export function streamSteerVerb(input) {
|
|
|
133
133
|
const noteOptions = { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) };
|
|
134
134
|
if (mintsAFrame)
|
|
135
135
|
armAcceptance(parkRecord, () => publishQueued(h));
|
|
136
|
-
if (priority === "
|
|
136
|
+
if (injectionLaneOf(priority) === "followUp") {
|
|
137
137
|
await h.harness.followUp(payload, noteOptions);
|
|
138
138
|
noteAccepted();
|
|
139
139
|
return;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sub-agent tool lists of one prepared task: the allow advisory for `TaskSpec.agents` and THE judgment of every denied-tools
|
|
3
|
+
* list a deployment writes — the task's own definitions (`TaskSpec.agents`), the definitions a mounted delegation tool carries
|
|
4
|
+
* (the `authored` faces — the engine's built-ins name tools of other vocabularies and are not judged) and its tool-level list.
|
|
5
|
+
* Universe = the task's assembled roster plus every mounted delegation tool's pool, names and aliases; a pool the engine cannot
|
|
6
|
+
* list (an `extraTools` factory with no declared roster) turns the refusals that need a roster into host notices. A tool's own lists are judged where
|
|
7
|
+
* the tool is mounted: only a tool a deployment mounted (depth 0) publishes them (`authored`, `agentToolLevelAllowTools`, `agentToolLevelDenyTools`), the
|
|
8
|
+
* engine's nested copy for a delegated child carries neither.
|
|
9
|
+
*
|
|
10
|
+
* Throws `config.agent.unknown_tool` (`refused`, the one site) — synchronous, like the phase that calls it.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentTool } from "../../internal/harness.js";
|
|
13
|
+
import type { TaskSpec } from "../types.js";
|
|
14
|
+
import type { AnnounceOnceSink } from "./contracts.js";
|
|
15
|
+
/**
|
|
16
|
+
* The names a definition's denied-tools entries stand for, for a lane that has neither a roster nor a notice sink at the point
|
|
17
|
+
* it builds its own policy (a workflow leg's agentType): each entry is read by the one reading, so `Tool(content)` denies the
|
|
18
|
+
* whole tool there too, and an entry that names no tool is refused by the same refusal. The stale-name and told dispositions
|
|
19
|
+
* need the leg's roster and a sink; the leg's own policy-name audit is what reads them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function deniedToolNamesOf(where: string, value: unknown): string[];
|
|
22
|
+
export interface SubAgentToolListsInput {
|
|
23
|
+
/** borrowed-readonly — the REBOUND spec: `agents` (the task's definitions) and `tools` (the mounted delegation tools). */
|
|
24
|
+
spec: Pick<TaskSpec, "agents" | "tools">;
|
|
25
|
+
/** borrowed-readonly — the FULLY ASSEMBLED mount array (names + aliases = the roster half of the universe). */
|
|
26
|
+
tools: ReadonlyArray<AgentTool>;
|
|
27
|
+
/** borrowed-readonly — the once-per-session ledger whose operator sink carries the notices. */
|
|
28
|
+
onceLedger: Pick<AnnounceOnceSink, "onError">;
|
|
29
|
+
/** borrowed-readonly — the acquired session's id (operator-line context). */
|
|
30
|
+
sessionId: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function auditSubAgentToolLists(input: SubAgentToolListsInput): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { notAListSentence, notAStringSentence, readDeniedEntry, readToolNameList, valueTypeLabel } from "../../agents/tool-filter.js";
|
|
2
|
+
import { compileAllowList, inertEntryPhrase, renderListEntry } from "../allow-entry.js";
|
|
3
|
+
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
|
+
const DENY_LIST_NAMES_ONLY = "To block by path or command, write a deny rule in the caller's permission policy: a denied-tools list only takes tool names (it narrows which tools the agent is handed).";
|
|
5
|
+
function refused(message) {
|
|
6
|
+
const e = new Error(message);
|
|
7
|
+
e.code = "config.agent.unknown_tool";
|
|
8
|
+
return e;
|
|
9
|
+
}
|
|
10
|
+
function refusal(where, flaw) {
|
|
11
|
+
return refused(`${where} declares tool ${inertEntryPhrase(flaw)} in its denied tools, but no tool of that spelling exists in this deployment — fix the entry or mount the tool.${flaw.reason === "grammar" ? ` ${DENY_LIST_NAMES_ONLY}` : ""}`);
|
|
12
|
+
}
|
|
13
|
+
function refusalNotAString(where, side, index, type) {
|
|
14
|
+
return refused(notAStringSentence(where, side, index, type));
|
|
15
|
+
}
|
|
16
|
+
function refusalNotAList(where, side, list) {
|
|
17
|
+
return refused(notAListSentence(where, side, list));
|
|
18
|
+
}
|
|
19
|
+
export function deniedToolNamesOf(where, value) {
|
|
20
|
+
const list = readToolNameList(value);
|
|
21
|
+
if (list.kind !== "entries")
|
|
22
|
+
throw refusalNotAList(where, "denied", list);
|
|
23
|
+
return list.entries.map((entry, index) => {
|
|
24
|
+
if (entry === "*")
|
|
25
|
+
return entry;
|
|
26
|
+
const read = readDeniedEntry(entry);
|
|
27
|
+
if (read.kind === "not_a_string")
|
|
28
|
+
throw refusalNotAString(where, "denied", index, read.type);
|
|
29
|
+
const flaw = compileAllowList([read.name], "deny").inert[0];
|
|
30
|
+
if (flaw !== undefined && flaw.reason === "grammar")
|
|
31
|
+
throw refusal(where, { ...flaw, entry: read.name });
|
|
32
|
+
return read.name;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function judgeDeniedToolNames(where, value, universe, advise) {
|
|
36
|
+
if (value === undefined || value === null)
|
|
37
|
+
return;
|
|
38
|
+
const list = readToolNameList(value);
|
|
39
|
+
if (list.kind !== "entries")
|
|
40
|
+
throw refusalNotAList(where, "denied", list);
|
|
41
|
+
let index = -1;
|
|
42
|
+
for (const entry of list.entries) {
|
|
43
|
+
index += 1;
|
|
44
|
+
if (entry === "*")
|
|
45
|
+
continue;
|
|
46
|
+
const read = readDeniedEntry(entry);
|
|
47
|
+
if (read.kind === "not_a_string")
|
|
48
|
+
throw refusalNotAString(where, "denied", index, read.type);
|
|
49
|
+
const { name, content } = read;
|
|
50
|
+
const raw = entry;
|
|
51
|
+
const { rows, inert } = compileAllowList([name], "deny", universe.known);
|
|
52
|
+
const flaw = inert[0] === undefined ? undefined : { ...inert[0], entry: raw };
|
|
53
|
+
const stale = flaw !== undefined && (flaw.reason === "tool_name" || flaw.reason === "near_miss");
|
|
54
|
+
if (flaw !== undefined && (flaw.reason === "grammar" || (stale && universe.complete)))
|
|
55
|
+
throw refusal(where, flaw);
|
|
56
|
+
if (flaw === undefined && universe.known.has(name)) {
|
|
57
|
+
if (content)
|
|
58
|
+
advise(new Error(`${where} declares ${renderListEntry(raw)} in its denied tools: this list reads tool names, not content, so the whole tool "${inlineUntrusted(name, 80)}" is denied to the agent. ${DENY_LIST_NAMES_ONLY}`));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
advise(new Error(`${where} declares tool ${flaw !== undefined ? inertEntryPhrase(flaw) : renderListEntry(raw)} in its denied tools, a name this task's roster and delegation pools do not carry — the entry hides nothing here. Advisory only: a list shared across deployments is not wrong because one lacks a tool, but a misspelling guards nothing.` +
|
|
62
|
+
(rows[0]?.compiled.kind === "namespace" ? " This list matches tool names exactly: a server-level entry hides none of that server's tools; name them, or write a deny rule in the caller's permission policy." : "") +
|
|
63
|
+
(universe.complete ? "" : " A delegation tool this task mounts injects tools the engine cannot enumerate (extraTools), so the name cannot be checked here.")));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function judgeAllowedToolList(where, value, known, advise) {
|
|
67
|
+
if (value === undefined || value === null)
|
|
68
|
+
return;
|
|
69
|
+
const list = readToolNameList(value);
|
|
70
|
+
if (list.kind !== "entries")
|
|
71
|
+
throw refusalNotAList(where, "allowed", list);
|
|
72
|
+
const unknownNames = [];
|
|
73
|
+
let index = -1;
|
|
74
|
+
for (const entry of list.entries) {
|
|
75
|
+
index += 1;
|
|
76
|
+
if (typeof entry !== "string")
|
|
77
|
+
throw refusalNotAString(where, "allowed", index, valueTypeLabel(entry));
|
|
78
|
+
if (known !== undefined && entry !== "*" && !known.has(entry))
|
|
79
|
+
unknownNames.push(entry);
|
|
80
|
+
}
|
|
81
|
+
if (unknownNames.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
advise(new Error(`${where} allows tool(s) ${unknownNames.map(renderListEntry).join(", ")} not present in this task's assembled roster — likely a typo (the entry would be item-filtered at spawn; the agent stays usable). Advisory only: the delegation pool can differ from this roster, so a tool mounted only on the delegation tool or produced by per-spawn extraTools makes this spurious.`));
|
|
84
|
+
}
|
|
85
|
+
export function auditSubAgentToolLists(input) {
|
|
86
|
+
const { spec, tools, onceLedger, sessionId } = input;
|
|
87
|
+
const advise = (err) => {
|
|
88
|
+
try {
|
|
89
|
+
onceLedger.onError(err, { phase: "config", sessionId });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const delegationTools = (spec.tools ?? []).filter((t) => t.agentToolFaces !== undefined);
|
|
95
|
+
const taskAgents = spec.agents ?? [];
|
|
96
|
+
if (taskAgents.length === 0 && delegationTools.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
99
|
+
const denyKnown = new Set(known);
|
|
100
|
+
for (const t of delegationTools)
|
|
101
|
+
for (const p of t.agentToolPool ?? [])
|
|
102
|
+
for (const n of [p.name, ...(p.aliases ?? [])])
|
|
103
|
+
denyKnown.add(n);
|
|
104
|
+
const universe = { known: denyKnown, complete: delegationTools.every((t) => t.agentToolPool !== undefined) };
|
|
105
|
+
for (const def of taskAgents) {
|
|
106
|
+
judgeAllowedToolList(`TaskSpec.agents: agent "${def.name}"`, def.allowTools, known, advise);
|
|
107
|
+
judgeDeniedToolNames(`TaskSpec.agents: agent "${def.name}"`, def.denyTools, universe, advise);
|
|
108
|
+
}
|
|
109
|
+
const replaced = new Set(taskAgents.map((d) => d.name));
|
|
110
|
+
for (const t of delegationTools) {
|
|
111
|
+
for (const face of t.agentToolFaces ?? []) {
|
|
112
|
+
if (face.authored === true && !replaced.has(face.name)) {
|
|
113
|
+
const faceWhere = `delegation tool "${t.name}": agent definition "${face.name}"`;
|
|
114
|
+
judgeAllowedToolList(faceWhere, face.allowTools, undefined, advise);
|
|
115
|
+
judgeDeniedToolNames(faceWhere, face.denyTools, universe, advise);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
judgeAllowedToolList(`delegation tool "${t.name}": the tool-level list`, t.agentToolLevelAllowTools, undefined, advise);
|
|
119
|
+
judgeDeniedToolNames(`delegation tool "${t.name}": the tool-level list`, t.agentToolLevelDenyTools, universe, advise);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -110,7 +110,7 @@ export interface SkillListingEntry {
|
|
|
110
110
|
* and the tool, so what the prompt promises is exactly what the tool serves. Each field is READ ONCE, BY NAME, and
|
|
111
111
|
* both consumers see the copy: the gate screens this list while the invoke substitutes from it, so a `get baseDir()`
|
|
112
112
|
* answering those two reads differently passed a screen that measured a value the model never received — and a rest
|
|
113
|
-
* copy would silently drop an INHERITED or non-enumerable list
|
|
113
|
+
* copy would silently drop an INHERITED or non-enumerable list. The two tables are read by {@link readSkillTable}. */
|
|
114
114
|
export declare function normalizeSkills(skills: SkillLike[] | undefined): SkillLike[];
|
|
115
115
|
/** [c209-C] The skills listing's lead line — single-sourced so the run loop's durable-resume
|
|
116
116
|
* transcript probe (MED-3② skills arm, runtask) matches EXACTLY what the initial frame carries
|
|
@@ -2,7 +2,7 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
3
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
4
|
import { SKILL_LISTING_DEFAULT_BUDGET, SKILL_LISTING_DESC_MAX_CHARS_DEFAULT } from "../skill-listing-budget.js";
|
|
5
|
-
import { skillNamedBy } from "../skill-grant.js";
|
|
5
|
+
import { readSkillTable, skillNamedBy } from "../skill-grant.js";
|
|
6
6
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
7
7
|
import { substituteSkillRoot, substituteSkillRootBounded } from "./skill-base-dir.js";
|
|
8
8
|
import { foldListingWithin } from "./listing-fold.js";
|
|
@@ -116,7 +116,8 @@ export function normalizeSkills(skills) {
|
|
|
116
116
|
seen.add(s.name);
|
|
117
117
|
const { name, description, content, allowedTools, disallowedTools, files, baseDir, ...rest } = s;
|
|
118
118
|
void rest;
|
|
119
|
-
|
|
119
|
+
const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)];
|
|
120
|
+
out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) });
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
return out.toSorted((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
@@ -33,8 +33,9 @@ export interface LegWriteProtection extends WriteProtectionMount {
|
|
|
33
33
|
export declare function mountWriteProtection(input: {
|
|
34
34
|
/** borrowed-readonly — the deployment seats: the table (`writeProtectedPaths`; absent ⇒ the default
|
|
35
35
|
* table, `[]` ⇒ opted out), the data root the memory library is materialized under
|
|
36
|
-
* (`memoryEngineDir`; absent ⇒ the resolver's env/default chain)
|
|
37
|
-
|
|
36
|
+
* (`memoryEngineDir`; absent ⇒ the resolver's env/default chain), the configuration root the host
|
|
37
|
+
* declared (`agentConfigDir`) and the operator sink. */
|
|
38
|
+
deps: Pick<RunnerDeps, "writeProtectedPaths" | "memoryEngineDir" | "agentConfigDir" | "onNotice">;
|
|
38
39
|
/** The HANDS' environment, or `undefined` with no hands — the driver's own predicate, not a second
|
|
39
40
|
* reading of the env seats, so the judge sees exactly the environment the write tools do. */
|
|
40
41
|
env: ExecutionEnv | undefined;
|
|
@@ -6,6 +6,7 @@ export function mountWriteProtection(input) {
|
|
|
6
6
|
const announced = new Set();
|
|
7
7
|
const matcher = compileWriteProtection(input.deps.writeProtectedPaths, {
|
|
8
8
|
...(input.deps.memoryEngineDir !== undefined ? { dataRoot: input.deps.memoryEngineDir } : {}),
|
|
9
|
+
...(input.deps.agentConfigDir !== undefined ? { agentConfigDir: input.deps.agentConfigDir } : {}),
|
|
9
10
|
...(input.env !== undefined ? { env: input.env } : {}),
|
|
10
11
|
rootPath: input.rootPath,
|
|
11
12
|
onViewAbsent: (absence) => {
|
|
@@ -227,6 +227,20 @@ export interface RunnerDeps {
|
|
|
227
227
|
* ignored). Same deployment-only seat, clamp argument and checkpoint posture as the tiers key.
|
|
228
228
|
*/
|
|
229
229
|
readDenyBuiltinExclude?: readonly string[];
|
|
230
|
+
/**
|
|
231
|
+
* Where the host keeps the agent's configuration directory when it is not `<home>/.sema` — an ABSOLUTE path of an
|
|
232
|
+
* existing directory, resolved by the host (the engine reads no environment variable for it). The rows of the
|
|
233
|
+
* `agent-config` tier that name that directory (`~/.sema/settings.json`, `~/.sema/settings.local.json`,
|
|
234
|
+
* `~/.sema/engine-data/.env`, `~/.sema/config-lkg-*.json`) are then ALSO placed at the same files below this root, so the model can neither read
|
|
235
|
+
* nor (through the write-protection table and a wired `createSensitivePathPolicy({ agentConfigDir })`) rewrite the
|
|
236
|
+
* relocated settings; the default `~/.sema` location stays refused. Rows of the home's dotted family (`~/.sema.*`)
|
|
237
|
+
* and the external tools' files are not part of the directory and stay anchored at the home. A symlink the root is
|
|
238
|
+
* reached through is followed (the target is guarded too). ABSENT = today's behaviour, the literal `~/.sema` under
|
|
239
|
+
* the declared and account homes. A value that is not a string, is empty or relative, or names no existing directory
|
|
240
|
+
* refuses `new Runner` with `config.agent_config_dir_invalid` — never read as absent. DEPLOYMENT seat only (no
|
|
241
|
+
* TaskSpec twin) and fixed at construction: `swapDeps` does not carry it.
|
|
242
|
+
*/
|
|
243
|
+
agentConfigDir?: string;
|
|
230
244
|
/**
|
|
231
245
|
* backlog #286 (#279 — CC 2.1.233 `DANGEROUS_FILES`/`DANGEROUS_DIRECTORIES`/
|
|
232
246
|
* `DANGEROUS_DIRECTORY_PATHS` parity): the WRITE-protection table. DEFAULT-ON: a path-confinable
|
|
@@ -155,6 +155,14 @@ export declare function createSensitivePathPolicy(opts: {
|
|
|
155
155
|
* gate and the transcript gate keep speaking about the same directory.
|
|
156
156
|
*/
|
|
157
157
|
dataRoot?: string;
|
|
158
|
+
/**
|
|
159
|
+
* The host's configuration root when it keeps the agent's configuration directory somewhere other than the home's
|
|
160
|
+
* `.sema` — the same absolute directory it hands the Runner (`RunnerDeps.agentConfigDir`). Every pattern that opens with
|
|
161
|
+
* the `.sema` segment (`.sema/settings.json`, `.sema/hooks`, …) then also guards the same tail below this root; the
|
|
162
|
+
* patterns themselves keep guarding every `.sema` anywhere. Absent ⇒ the list is judged as it stands. A value that is not
|
|
163
|
+
* the absolute path of an existing directory throws (`config.agent_config_dir_invalid`).
|
|
164
|
+
*/
|
|
165
|
+
agentConfigDir?: string;
|
|
158
166
|
/** Override the guarded tool set (default: `Write`, `Edit`, `MultiEdit`-legacy → Edit, `NotebookEdit`). */
|
|
159
167
|
tools?: string[];
|
|
160
168
|
}): ToolPolicy;
|
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { canonicalizeTarget, expandHomeTilde, isAbsoluteForFamily, isWinFormPath, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
|
|
4
4
|
import { compileSegmentPattern, matchSegmentPatterns, READ_FACE_BUILTIN_DENY_TABLE, readDenyPatternRun } from "../tools/fs/read-deny.js";
|
|
5
|
+
import { agentConfigDirSegments, agentConfigDirTail, checkedAgentConfigDir } from "./agent-config-dir.js";
|
|
5
6
|
import { isProtectedWrite, protectivePathTargetOf } from "./tool-registry.js";
|
|
6
7
|
import { pathTargetBaseOf } from "./effective-path-target.js";
|
|
7
8
|
import { symlinkChainHops } from "./path-identity.js";
|
|
@@ -32,6 +33,7 @@ export const RECOMMENDED_SENSITIVE_PATTERNS = [
|
|
|
32
33
|
".mcp.json",
|
|
33
34
|
".ai-agent/.env",
|
|
34
35
|
".sema/engine-data/.env",
|
|
36
|
+
".sema/config-lkg-*.json",
|
|
35
37
|
".codex/config.toml",
|
|
36
38
|
".cursor/mcp.json",
|
|
37
39
|
".continue/config.json",
|
|
@@ -115,6 +117,19 @@ function matchSensitive(canonicalKey, compiled, aliasResolved) {
|
|
|
115
117
|
}
|
|
116
118
|
export function createSensitivePathPolicy(opts) {
|
|
117
119
|
const compiled = compilePatterns(opts.patterns);
|
|
120
|
+
const configRoot = checkedAgentConfigDir(opts.agentConfigDir, "createSensitivePathPolicy agentConfigDir");
|
|
121
|
+
const configRows = configRoot === undefined ? [] : opts.patterns.flatMap((raw) => {
|
|
122
|
+
const tail = agentConfigDirTail(raw, !CASE_INSENSITIVE_FS);
|
|
123
|
+
return tail === undefined ? [] : [{ raw, tail }];
|
|
124
|
+
});
|
|
125
|
+
const configTwins = async (signal) => {
|
|
126
|
+
if (configRoot === undefined || configRows.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const fold = CASE_INSENSITIVE_FS ? "ascii" : "none";
|
|
129
|
+
const lower = (segs) => (CASE_INSENSITIVE_FS ? segs.map((seg) => seg.replace(/[A-Z]/g, (c) => c.toLowerCase())) : segs);
|
|
130
|
+
const anchors = [configRoot, ...(await canonicalDataRootKeys(opts.env, [configRoot], signal))].map((root) => lower(agentConfigDirSegments(root)));
|
|
131
|
+
return configRows.map(({ raw, tail }) => ({ ...(compileSegmentPattern(tail, fold) ?? { raw, segments: [], foldAscii: CASE_INSENSITIVE_FS }), raw, anchors, rootedOnly: true }));
|
|
132
|
+
};
|
|
118
133
|
const dataRootPaths = dataRootSpellings(opts.dataRoot);
|
|
119
134
|
const guardedByName = opts.tools !== undefined ? new Set(opts.tools) : undefined;
|
|
120
135
|
const family = (cwd) => pathFamilyOf({ root: opts.rootPath, cwd: cwd ?? opts.env.cwd });
|
|
@@ -130,12 +145,13 @@ export function createSensitivePathPolicy(opts) {
|
|
|
130
145
|
}
|
|
131
146
|
if (relative === "")
|
|
132
147
|
return { kind: "clear" };
|
|
133
|
-
const
|
|
148
|
+
const twins = await configTwins(signal);
|
|
149
|
+
const hit = matchSensitive(relative ?? canon.key, [...compiled, ...twins], canon.aliasResolved === true);
|
|
134
150
|
if (hit)
|
|
135
151
|
return { kind: "hit", pattern: hit, via: "target" };
|
|
136
152
|
if (relative === null) {
|
|
137
153
|
for (const hop of await symlinkChainHops(opts.env, path, signal, fam, base)) {
|
|
138
|
-
const viaHop = matchSensitive(hop, compiled, false);
|
|
154
|
+
const viaHop = matchSensitive(hop, [...compiled, ...twins], false);
|
|
139
155
|
if (viaHop)
|
|
140
156
|
return { kind: "hit", pattern: viaHop, via: "chain" };
|
|
141
157
|
}
|
|
@@ -52,12 +52,16 @@ export interface PutRulesOptions {
|
|
|
52
52
|
* this from a verified, role-checked principal (mirrors `steer.trusted`). */
|
|
53
53
|
operator?: boolean;
|
|
54
54
|
}
|
|
55
|
-
/** The {@link SessionPolicyError.detail} of a tool-name refusal: which bucket, which entry, the judgment's word,
|
|
56
|
-
* spelling to write instead when the judgment has one (a retired name's 1:1
|
|
55
|
+
/** The {@link SessionPolicyError.detail} of a tool-name refusal: which bucket, which entry, the judgment's word, the judgment's
|
|
56
|
+
* one-line sentence when it has one, and the spelling to write instead when the judgment has one (a retired name's 1:1
|
|
57
|
+
* successor, an unmintable segment's minted name). */
|
|
57
58
|
export interface SessionRuleNameRefusal {
|
|
58
59
|
field: SessionRuleToolNameField;
|
|
59
60
|
name: string;
|
|
60
61
|
verdict: Exclude<RuleToolNameVerdict["kind"], "current">;
|
|
62
|
+
/** The `guidance` of the judgment ({@link judgeRuleToolName}), byte for byte — the same string the refusal's sentence quotes. Present for
|
|
63
|
+
* `retired` and `unmintable_segment`, the two verdicts that carry one; absent for `unprefixed_namespace`, whose judgment has no sentence of its own. */
|
|
64
|
+
guidance?: string;
|
|
61
65
|
currentName?: string;
|
|
62
66
|
}
|
|
63
67
|
/** Typed error from {@link SessionPolicyStore.putRules}. `conflict` = stale `expectedRev`; `loosen_forbidden`
|
|
@@ -41,6 +41,7 @@ export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLA
|
|
|
41
41
|
continue;
|
|
42
42
|
const quoted = JSON.stringify(name);
|
|
43
43
|
const successor = verdict.kind === "unprefixed_namespace" ? undefined : verdict.currentName;
|
|
44
|
+
const guidance = verdict.kind === "unprefixed_namespace" ? undefined : verdict.guidance;
|
|
44
45
|
let message;
|
|
45
46
|
switch (verdict.kind) {
|
|
46
47
|
case "retired":
|
|
@@ -57,7 +58,7 @@ export function assertSessionRuleNamesCurrent(rules, mountedToolNames = NO_DECLA
|
|
|
57
58
|
return exhaustive;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(successor !== undefined ? { currentName: successor } : {}) });
|
|
61
|
+
throw new SessionPolicyError("legacy_tool_name", message, { field, name, verdict: verdict.kind, ...(guidance !== undefined ? { guidance } : {}), ...(successor !== undefined ? { currentName: successor } : {}) });
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ShellWrapperRow } from "./shell-wrapper-table.js";
|
|
2
|
+
/**
|
|
3
|
+
* The PROJECTION {@link import("./shell-wrapper-table.js").SHELL_WRAPPER_TABLE} is: the second lexer's view of the program read model's rows (`program-read-model.ts`
|
|
4
|
+
* — one table of program facts, read through the binary gate). A launcher row's options by arity (a value word, a
|
|
5
|
+
* switch or an attached-only value, a command string), its required operand (a duration, or the spelling it must have),
|
|
6
|
+
* its bare-dash switch and its `NAME=value` operands; a row that runs its operands, or a code body, as shell input
|
|
7
|
+
* (`viaShell`); an option that runs a command (`executes`). The table is this projection written out (its
|
|
8
|
+
* literal type is the export's), and `test/shell-wrapper-table-projection.test.ts` pins it equal — a launcher fact is
|
|
9
|
+
* edited in its row, never here.
|
|
10
|
+
*/
|
|
11
|
+
export declare function shellWrapperProjection(): Record<string, ShellWrapperRow>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { programRows } from "../tools/fs/program-read-model.js";
|
|
2
|
+
import { DURATION_SPELLING } from "../tools/fs/program-read-model.rows.js";
|
|
3
|
+
export function shellWrapperProjection() {
|
|
4
|
+
const out = {};
|
|
5
|
+
for (const row of programRows()) {
|
|
6
|
+
const projected = projectRow(row);
|
|
7
|
+
if (projected !== undefined)
|
|
8
|
+
out[row.key] = projected;
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
function spellings(row) {
|
|
13
|
+
const out = new Map();
|
|
14
|
+
for (const g of row.grammars) {
|
|
15
|
+
const add = (spelling, spec) => {
|
|
16
|
+
const e = out.get(spelling) ?? { spec, arities: new Set() };
|
|
17
|
+
e.arities.add(spec.arity);
|
|
18
|
+
out.set(spelling, e);
|
|
19
|
+
};
|
|
20
|
+
for (const [l, spec] of Object.entries(g.short))
|
|
21
|
+
add(`-${l}`, spec);
|
|
22
|
+
for (const [n, spec] of Object.entries(g.long))
|
|
23
|
+
add(`--${n}`, spec);
|
|
24
|
+
for (const [n, spec] of Object.entries(g.dashWords ?? {}))
|
|
25
|
+
add(`-${n}`, spec);
|
|
26
|
+
}
|
|
27
|
+
return [...out].map(([spelling, e]) => ({ spelling, spec: e.spec, arity: e.arities.size === 1 ? [...e.arities][0] : "disputed" }));
|
|
28
|
+
}
|
|
29
|
+
const runsCommand = (spec) => spec.effect === "executes" || spec.value?.kind === "shell-text" || spec.value?.kind === "command";
|
|
30
|
+
function projectRow(row) {
|
|
31
|
+
const sorted = (xs) => [...new Set(xs)].sort();
|
|
32
|
+
const all = spellings(row);
|
|
33
|
+
const viaShell = row.reentry?.kind === "shell" || row.launcher?.shellText === true || row.body === "first-operand" || row.body === "assignment-values" || (row.operands?.rest?.kind === "program-text" && row.operands.rest.language === "arithmetic");
|
|
34
|
+
if (row.launcher === undefined) {
|
|
35
|
+
if (viaShell)
|
|
36
|
+
return { valueFlags: [], flags: [], commandString: [], viaShell: true };
|
|
37
|
+
const executes = sorted(all.filter((o) => runsCommand(o.spec)).map((o) => o.spelling));
|
|
38
|
+
return executes.length > 0 ? { valueFlags: [], flags: [], commandString: [], executes } : undefined;
|
|
39
|
+
}
|
|
40
|
+
const commandString = sorted(all.filter((o) => o.spec.value?.kind === "shell-text").map((o) => o.spelling));
|
|
41
|
+
const values = all.filter((o) => o.spec.value?.kind !== "shell-text");
|
|
42
|
+
const attached = sorted(values.filter((o) => o.arity === "attached" && !o.spelling.startsWith("--")).map((o) => o.spelling));
|
|
43
|
+
const required = row.launcher.requiredOperands ?? 0;
|
|
44
|
+
const duration = required > 0 && row.launcher.requiredOperandSpelling === DURATION_SPELLING;
|
|
45
|
+
return {
|
|
46
|
+
valueFlags: sorted(values.filter((o) => o.arity === 1).map((o) => o.spelling)),
|
|
47
|
+
flags: sorted(values.filter((o) => o.arity === 0 || (o.arity === "attached" && o.spelling.startsWith("--"))).map((o) => o.spelling)),
|
|
48
|
+
...(attached.length > 0 ? { attached } : {}),
|
|
49
|
+
commandString,
|
|
50
|
+
...(required > 0 && !duration ? { positional: row.launcher.requiredOperandSpelling ?? /^/ } : {}),
|
|
51
|
+
...(duration ? { duration: true } : {}),
|
|
52
|
+
...(row.launcher.bareDash === true ? { bareDash: true } : {}),
|
|
53
|
+
...(row.launcher.assignmentOperands === true ? { assignments: true } : {}),
|
|
54
|
+
...(viaShell ? { viaShell: true } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|