@sema-agent/core 7.45.0 → 7.47.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 +298 -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 +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +6 -4
- package/dist/core/allow-entry.js +6 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +24 -7
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +12 -4
- package/dist/core/gate-fold.js +5 -2
- package/dist/core/gate-lanes.js +11 -4
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -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 +59 -24
- package/dist/core/permission-rule-model.js +48 -44
- 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 +26 -45
- package/dist/core/permission-rules.js +77 -57
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -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 +46 -0
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- 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 +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +11 -11
- 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-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +13 -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 +37 -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/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- 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/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/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +12 -2
- package/dist/core/tool-face.d.ts +10 -1
- 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 +40 -17
- package/dist/core/tool-policy.js +11 -9
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +14 -3
- package/dist/core/write-protect.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +22 -0
- package/dist/shell/lex.js +75 -13
- package/dist/tools/fs/bash-program-position.d.ts +14 -90
- package/dist/tools/fs/bash-program-position.js +41 -74
- 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 +711 -0
- package/dist/tools/fs/program-read-model.js +1259 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +624 -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 +7 -6
- package/dist/tools/fs/removal-targets.js +34 -24
- 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 +86 -0
- package/dist/tools/fs/shell-read-argv.js +388 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
- package/dist/tools/fs/shell-read-bindings.js +755 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +82 -8
- 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 +33 -8
- package/dist/tools/fs/shell-read-plan.js +309 -407
- package/dist/tools/fs/shell-read-word.d.ts +60 -26
- package/dist/tools/fs/shell-read-word.js +47 -37
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +85 -137
- package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
- package/dist/tools/fs/shell-reentry-text.js +203 -53
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +25 -5
- package/wire-changes/core@7.46.0.json +1522 -0
- package/wire-changes/core@7.47.0.json +412 -0
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
29
29
|
import { persistedRuleMandateOf, type RuleClearanceFacts } from "../ask-origin.js";
|
|
30
30
|
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
31
|
-
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
32
|
-
import { type
|
|
31
|
+
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, ScopeRootIdentity, SegmentCoverage } from "../permission-rule-model.js";
|
|
32
|
+
import { type PathRuleBases } from "../permission-rule-model.js";
|
|
33
|
+
import { type HeadLeads } from "../path-rule-binding.js";
|
|
34
|
+
import type { ToolCallLike } from "../tool-registry.js";
|
|
33
35
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
34
36
|
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
35
37
|
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for it and the
|
|
@@ -87,28 +89,25 @@ export declare function persistedRuleHitOf(verdict: PersistedRuleVerdict | undef
|
|
|
87
89
|
* question nobody can answer. Every other verdict is {@link persistedRuleHitOf}'s hit. */
|
|
88
90
|
export declare function persistedRuleAnswerOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | PersistedRuleUnreadable | undefined;
|
|
89
91
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
92
|
+
* The persisted-rule lane's PATH arm, for every path-targeting tool (and the entry lists' path rows): the call's target
|
|
93
|
+
* read through the ONE reader every lane shares ({@link callTargetViewsOf} — the reading's views, else the call's own
|
|
94
|
+
* lexical view), its rows anchored by `ctx.anchorsOf` (a stored row: a project row at its scope root, the rest at the
|
|
95
|
+
* chain's top leg; a list's row: where the list was installed) and judged where their heads lead too (`ctx.leads`). The
|
|
96
|
+
* precedence walk, the admission predicates and the Read-only allow are the model's (`adjudicatePersistedPathRules`); which
|
|
97
|
+
* project rows apply is judged against the leg's own root (`ctx.root`). An ALLOW here clears ASKS only — the read fences
|
|
98
|
+
* judge inside the tool exactly as they would with no rule anywhere — and an unresolvable path is no verdict on the allow
|
|
99
|
+
* side and the fail-closed ask on the tightening side.
|
|
96
100
|
*
|
|
97
|
-
* TWO readings of the call's path slot, one per direction: the tightening arms judge
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* target (the face's OWN declaration only — a same-name tool that declares no path target is unconfirmable
|
|
101
|
-
* and no grant admits it). The precedence walk, the admission predicates and the Read-only allow are the
|
|
102
|
-
* model's (`adjudicatePersistedPathRules`). An ALLOW here clears ASKS only — the read fences (workspace
|
|
103
|
-
* containment, the sensitive-read deny face) judge inside the tool exactly as they would with no rule
|
|
104
|
-
* anywhere — and an unresolvable path is simply no verdict (fail toward asking on the allow side, no
|
|
105
|
-
* refusal on the tightening side: a rule about a path speaks about no other shape).
|
|
101
|
+
* TWO readings of the call's path slot, one per direction: the tightening arms judge the PROTECTIVE reading (the object
|
|
102
|
+
* face's declaration, the catalog's for the name as the floor); the allow arm the DECLARED one (the face's OWN
|
|
103
|
+
* declaration only — a same-name tool that declares no path target is unconfirmable and no grant admits it).
|
|
106
104
|
*/
|
|
107
|
-
export declare function pathRuleLaneAnswer(table: readonly PersistedRule[], req: Pick<ToolCallRequest, "toolName" | "args" | "face">, ctx: {
|
|
105
|
+
export declare function pathRuleLaneAnswer(table: readonly PersistedRule[], req: Pick<ToolCallRequest, "toolName" | "args" | "face" | "targetReading" | "root" | "cwd">, ctx: {
|
|
108
106
|
root: string | undefined;
|
|
109
107
|
sessionId: string | undefined;
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
anchorsOf: (rule: PersistedRule) => PathRuleBases;
|
|
109
|
+
leads: ReadonlyMap<string, string | undefined>;
|
|
110
|
+
scopeIdentity?: ScopeRootIdentity;
|
|
112
111
|
}): PersistedRuleHit | PersistedRuleUnreadable | undefined;
|
|
113
112
|
/** The two lanes prepare hands the gate. Each is `undefined` when its partition is not configured, so
|
|
114
113
|
* a deployment without one keeps a decision path byte-identical to a build without the feature. */
|
|
@@ -152,15 +151,23 @@ export declare function createPermissionRuleLanes(cfg: {
|
|
|
152
151
|
principal: string | undefined;
|
|
153
152
|
sessionId: string;
|
|
154
153
|
/** The isolation-aware task root — the same value the file fence, the prompt cwd and the session rules
|
|
155
|
-
* use, so a project-scoped rule agrees with every other place that says "this project". */
|
|
154
|
+
* use, so a project-scoped rule agrees with every other place that says "this project" (which project rows apply). */
|
|
156
155
|
root: string;
|
|
156
|
+
/** The same root's canonical spelling (`undefined`: none could be read): a project row applies when its scope root covers
|
|
157
|
+
* either spelling of it — the row's root resolved where it leads ({@link projectScopeIdentity}). */
|
|
158
|
+
rootCanonical: string | undefined;
|
|
159
|
+
/** The anchors of the person's global and session rows and of every `~/` row: the root and home of the chain's top leg
|
|
160
|
+
* (a project row anchors at its own scope root). */
|
|
161
|
+
chainTop: PathRuleBases;
|
|
162
|
+
/** The leg's resolution of the locations rule heads pass through (the rows' twins). */
|
|
163
|
+
leads: HeadLeads;
|
|
157
164
|
/** The LIVE tracked working directory: the base a relative cd resolves against
|
|
158
165
|
* after an earlier observable `cd`; read per call. */
|
|
159
166
|
liveCwd: () => string | undefined;
|
|
160
|
-
/** The EXECUTION ENVIRONMENT's home directory (`ExecutionEnv.homeDir`, adapter-declared),
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
167
|
+
/** The EXECUTION ENVIRONMENT's home directory (`ExecutionEnv.homeDir`, adapter-declared), screened here: a declared
|
|
168
|
+
* value that is not an absolute path is refused, loudly (a bad declaration is a wiring defect, not an absence). The
|
|
169
|
+
* `~/` rows anchor at the chain's top leg's home (`chainTop.home`); `undefined` there = no home was declared: a `~/`
|
|
170
|
+
* deny/ask row then reads UNREADABLE for every call (a fail-closed ask), never the engine process's home. */
|
|
164
171
|
home: string | undefined;
|
|
165
172
|
/** True iff the reserved question-tool NAME resolves to the ENGINE's own content-ask tool on this leg. */
|
|
166
173
|
questionToolMounted: boolean;
|
|
@@ -173,6 +180,8 @@ export declare function createPermissionRuleLanes(cfg: {
|
|
|
173
180
|
/** The person is told their store (or the named sources of it) was skipped for a call (the run's one notice,
|
|
174
181
|
* `ruleStoreUnreadableNotice`). */
|
|
175
182
|
onStoreUnreadable: (at: "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
183
|
+
/** The person is told the store served rows that were left out (the run's one `permission.rule_row_dropped`). */
|
|
184
|
+
onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
|
|
176
185
|
/** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
|
|
177
186
|
signal?: AbortSignal;
|
|
178
187
|
/** The allow layer's auto strip over a stored ALLOW row (true ⇒ out of this call's table BEFORE the coverage is chosen). */
|
|
@@ -2,8 +2,8 @@ import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
3
|
import { adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { callTargetViewsOf } from "../effective-path-target.js";
|
|
6
|
+
import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
|
|
7
7
|
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
|
|
8
8
|
import { orgRuleVerdictFor, settleOrgVerdictWithin } from "../permission-rule-org.js";
|
|
9
9
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
|
|
@@ -13,6 +13,7 @@ import { strongestAnswer } from "../persisted-rule-arms.js";
|
|
|
13
13
|
import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
14
14
|
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
15
15
|
export const PERSISTED_RULE_TOOL = "Bash";
|
|
16
|
+
const NO_LEADS = new Map();
|
|
16
17
|
const HANDOFF_BOUND = 256;
|
|
17
18
|
export function inheritedAskRuleEvidence(provider) {
|
|
18
19
|
const org = provider?.partitions.org === true ? "not_adjudicated" : "not_wired";
|
|
@@ -40,12 +41,11 @@ export function persistedRuleAnswerOf(verdict) {
|
|
|
40
41
|
return persistedRuleHitOf(verdict);
|
|
41
42
|
}
|
|
42
43
|
export function pathRuleLaneAnswer(table, req, ctx) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const allow = effectivePathTargetOf(req, declaredPathTargetOf(req), bases);
|
|
44
|
+
const tighten = callTargetViewsOf(req, "tighten");
|
|
45
|
+
const allow = callTargetViewsOf(req, "allow");
|
|
46
46
|
if (tighten === undefined && allow === undefined)
|
|
47
47
|
return undefined;
|
|
48
|
-
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId, face: callPathFaceOf(req) }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) },
|
|
48
|
+
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId, face: callPathFaceOf(req), ...(ctx.scopeIdentity !== undefined ? { scopeIdentity: ctx.scopeIdentity } : {}) }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, ctx.anchorsOf, ctx.leads));
|
|
49
49
|
}
|
|
50
50
|
export function orgRuleLane(cfg) {
|
|
51
51
|
return {
|
|
@@ -141,8 +141,12 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
141
141
|
}
|
|
142
142
|
if (view.unreadable !== undefined)
|
|
143
143
|
skipped(view.unreadable, view.unreadableSources);
|
|
144
|
+
cfg.onRowsDropped(view.droppedRows);
|
|
144
145
|
try {
|
|
145
|
-
const
|
|
146
|
+
const table = view.rules.filter((r) => r.behavior !== "allow" || cfg.stripsAllow?.(r, req) !== true);
|
|
147
|
+
const leads = judged.some(([, g]) => g === "path") ? await cfg.leads.resolve(bindPathRules(table.filter((r) => r.match === "path" || r.match === "subpath").map((r) => ({ parsed: r, ruleText: r.rule, rule: r })), (spec) => anchorsOf(spec.rule))) : NO_LEADS;
|
|
148
|
+
const scopeIdentity = await projectScopeIdentity(table, cfg.rootCanonical, cfg.leads);
|
|
149
|
+
const answer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)));
|
|
146
150
|
return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
|
|
147
151
|
}
|
|
148
152
|
catch (err) {
|
|
@@ -151,20 +155,22 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
151
155
|
return { unreadable: true, reason: why };
|
|
152
156
|
}
|
|
153
157
|
};
|
|
154
|
-
const
|
|
158
|
+
const anchorsOf = (rule) => persistedRowAnchors(rule.scope, cfg.chainTop);
|
|
159
|
+
const judgeAgainst = (table, req, grammar, leads, scopeIdentity) => {
|
|
155
160
|
const liveCwd = cfg.liveCwd();
|
|
161
|
+
const scope = scopeIdentity !== undefined ? { scopeIdentity } : {};
|
|
156
162
|
if (grammar === "path")
|
|
157
|
-
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId,
|
|
163
|
+
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, anchorsOf, leads, ...scope });
|
|
158
164
|
if (grammar === "name")
|
|
159
|
-
return persistedRuleAnswerOf(adjudicatePersistedNameRules(table, { tool: req.toolName, name: nameRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId }));
|
|
165
|
+
return persistedRuleAnswerOf(adjudicatePersistedNameRules(table, { tool: req.toolName, name: nameRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId, ...scope }));
|
|
160
166
|
const command = req.args?.command;
|
|
161
167
|
if (typeof command !== "string")
|
|
162
168
|
return undefined;
|
|
163
169
|
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
|
|
164
|
-
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
170
|
+
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
|
|
165
171
|
if (verdict !== undefined)
|
|
166
172
|
return persistedRuleAnswerOf(verdict);
|
|
167
|
-
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
173
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
|
|
168
174
|
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
169
175
|
};
|
|
170
176
|
const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
* per call).
|
|
4
4
|
*/
|
|
5
5
|
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
6
|
-
import type
|
|
6
|
+
import { type PathRuleBases } from "../permission-rule-model.js";
|
|
7
|
+
import { type BoundPathRule, type HeadLeads } from "../path-rule-binding.js";
|
|
7
8
|
import type { RunnerDeps } from "../runner-deps.js";
|
|
8
9
|
/** The person's persisted PATH rows a leg's read face takes at prepare ({@link persistedPathRowsOf}). */
|
|
9
10
|
export interface PersistedPathRows {
|
|
10
11
|
/** Every live `Read(//dir/**)` allow row's directory (lexical, absolute): a standing yes to reads under it. */
|
|
11
12
|
readonly readDirectories: readonly string[];
|
|
12
|
-
/** Every live face-keyed path DENY row (`Read(...)`, `Edit(...)`),
|
|
13
|
-
|
|
13
|
+
/** Every live face-keyed path DENY row (`Read(...)`, `Edit(...)`), bound at its anchors (a project row at its scope root,
|
|
14
|
+
* the rest at the chain's top leg). */
|
|
15
|
+
readonly denyRows: readonly BoundPathRule[];
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* The person's persisted PATH rows, read ONCE at prepare from the same store query the gate's lanes read per call: a
|
|
@@ -22,19 +24,28 @@ export interface PersistedPathRows {
|
|
|
22
24
|
* rows. A read that failed (or did not answer in time) ⇒ no rows, and a partition the read could not read ⇒ none of ITS
|
|
23
25
|
* rows (the partition that was read contributes its own): what could not be read is skipped for this run's tables — its
|
|
24
26
|
* directories widen nothing and its deny rows judge nothing here — disclosed on the trace and announced to the person
|
|
25
|
-
* (`onUnread`); the next prepare reads again, as it would for a row the store gained mid-run. A deny row
|
|
26
|
-
*
|
|
27
|
+
* (`onUnread`); the next prepare reads again, as it would for a row the store gained mid-run. A deny row is anchored by
|
|
28
|
+
* its scope (`persistedRowAnchors`: a project row at its own scope root, a global / session row at the chain's top leg,
|
|
29
|
+
* `~/` at the top leg's home); one whose pattern needs an anchor the chain does not supply (`~/` with no declared home) is
|
|
30
|
+
* left to the per-call lane and disclosed.
|
|
27
31
|
*/
|
|
28
32
|
export declare function persistedPathRowsOf(cfg: {
|
|
29
33
|
provider: PermissionRuleStoreProvider | undefined;
|
|
30
34
|
localOwnerDeclared: boolean;
|
|
31
35
|
principal: string | undefined;
|
|
32
36
|
sessionId: string;
|
|
37
|
+
/** The leg's own root: which project rows apply to this leg (either spelling of it — {@link projectScopeIdentity}). */
|
|
33
38
|
root: string;
|
|
34
|
-
|
|
39
|
+
rootCanonical: string | undefined;
|
|
40
|
+
/** The leg's resolution of locations (a project row's scope root, where it leads). */
|
|
41
|
+
leads: HeadLeads;
|
|
42
|
+
/** The anchors of the global / session rows and of `~/`: the chain's top leg's root and home. */
|
|
43
|
+
chainTop: PathRuleBases;
|
|
35
44
|
onDisclosure: (message: string) => void;
|
|
36
45
|
/** The person is told the store (or the named sources of it) was skipped ({@link ruleStoreUnreadableNotice}). */
|
|
37
46
|
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
47
|
+
/** The person is told rows were left out ({@link ruleRowsDroppedNotice}). */
|
|
48
|
+
onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
|
|
38
49
|
/** The run's cut: a read that has not answered by then (or by the gate's await bound) reads as unreadable. */
|
|
39
50
|
signal?: AbortSignal;
|
|
40
51
|
}): Promise<PersistedPathRows>;
|
|
@@ -48,6 +59,16 @@ export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice
|
|
|
48
59
|
readonly sessionId: string;
|
|
49
60
|
readonly runId: string;
|
|
50
61
|
}): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
62
|
+
/**
|
|
63
|
+
* The run's ONE `permission.rule_row_dropped` notice (audience user): the person's store served project-scoped rows whose
|
|
64
|
+
* scope root is not a complete absolute path, left out of the checks; minted by whichever read met them first (the
|
|
65
|
+
* prepare read of the path rows, or a call's own read on the rule lane), once per run. The sentence names no path; the
|
|
66
|
+
* rows (bounded) ride the detail. The trace keeps every read's line.
|
|
67
|
+
*/
|
|
68
|
+
export declare function ruleRowsDroppedNotice(onNotice: RunnerDeps["onNotice"], ids: {
|
|
69
|
+
readonly sessionId: string;
|
|
70
|
+
readonly runId: string;
|
|
71
|
+
}): (rows: EffectivePermissionRules["droppedRows"]) => void;
|
|
51
72
|
/** {@link persistedPathRowsOf} for one task's prepare: only where a read face mounts (hands), disclosed on the run's trace,
|
|
52
73
|
* cut by the run's own signals. `undefined` = nothing to read, returned synchronously: a run with no person's store (or
|
|
53
74
|
* no hands) gains no suspension point on its prepare. */
|
|
@@ -56,9 +77,12 @@ export declare function persistedPathRowsOfTask(t: {
|
|
|
56
77
|
principal: string | undefined;
|
|
57
78
|
sessionId: string;
|
|
58
79
|
root: string;
|
|
59
|
-
|
|
80
|
+
rootCanonical: string | undefined;
|
|
81
|
+
leads: HeadLeads;
|
|
82
|
+
chainTop: PathRuleBases;
|
|
60
83
|
taskId: string;
|
|
61
84
|
handsEnabled: boolean;
|
|
62
85
|
signals: readonly AbortSignal[];
|
|
63
86
|
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
87
|
+
onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
|
|
64
88
|
}): Promise<PersistedPathRows> | undefined;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
2
|
-
import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted
|
|
3
|
-
import {
|
|
4
|
-
import { pathRuleDenyRowOf } from "../permission-rules.js";
|
|
2
|
+
import { PATH_RULE_FACES, READ_RULE_TOOL, eligiblePersisted } from "../permission-rule-model.js";
|
|
3
|
+
import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
|
|
5
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
5
|
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
7
6
|
import { thrownText } from "../thrown-value.js";
|
|
@@ -21,11 +20,11 @@ export async function persistedPathRowsOf(cfg) {
|
|
|
21
20
|
}
|
|
22
21
|
if (view.unreadable !== undefined)
|
|
23
22
|
skipped(cfg, view.unreadable, view.unreadableSources);
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
23
|
+
cfg.onRowsDropped(view.droppedRows);
|
|
24
|
+
const scopeIdentity = await projectScopeIdentity(view.rules, cfg.rootCanonical, cfg.leads);
|
|
25
|
+
const call = { tool: READ_RULE_TOOL, cwd: cfg.root, sessionId: cfg.sessionId, ...(scopeIdentity !== undefined ? { scopeIdentity } : {}) };
|
|
27
26
|
const readDirectories = [];
|
|
28
|
-
const
|
|
27
|
+
const denySpecs = [];
|
|
29
28
|
for (const rule of view.rules) {
|
|
30
29
|
if (rule.status !== "live")
|
|
31
30
|
continue;
|
|
@@ -37,12 +36,15 @@ export async function persistedPathRowsOf(cfg) {
|
|
|
37
36
|
const faces = PATH_RULE_FACES[rule.tool]?.deny;
|
|
38
37
|
if (rule.behavior !== "deny" || faces === undefined || (rule.match !== "path" && rule.match !== "subpath") || !eligiblePersisted(rule, { ...call, tool: rule.tool }, "deny"))
|
|
39
38
|
continue;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
denySpecs.push({ parsed: rule, ruleText: rule.rule, faces, rule });
|
|
40
|
+
}
|
|
41
|
+
const denyRows = [];
|
|
42
|
+
for (const row of bindPathRules(denySpecs, (spec) => persistedRowAnchors(spec.rule.scope, cfg.chainTop))) {
|
|
43
|
+
if (row.unanchored !== undefined) {
|
|
44
|
+
cfg.onDisclosure(`the persisted deny rule ${inlineUntrusted(row.rule, 200)} needs a ${row.unanchored.join(" / ")} base this task does not declare — the shell's file commands and the search tools do not judge it on this leg`);
|
|
43
45
|
continue;
|
|
44
46
|
}
|
|
45
|
-
denyRows.push(
|
|
47
|
+
denyRows.push(row);
|
|
46
48
|
}
|
|
47
49
|
return { readDirectories, denyRows };
|
|
48
50
|
}
|
|
@@ -68,6 +70,20 @@ export function ruleStoreUnreadableNotice(onNotice, ids) {
|
|
|
68
70
|
deliverEngineNotice(onNotice, { code: "permission.rule_store_unavailable", message: SKIPPED_STORE_SENTENCE[at](which), detail: { at, sessionId: ids.sessionId, runId: ids.runId } });
|
|
69
71
|
};
|
|
70
72
|
}
|
|
73
|
+
export function ruleRowsDroppedNotice(onNotice, ids) {
|
|
74
|
+
let sent = false;
|
|
75
|
+
return (rows) => {
|
|
76
|
+
if (sent || rows === undefined || rows.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
sent = true;
|
|
79
|
+
const one = rows.length === 1;
|
|
80
|
+
deliverEngineNotice(onNotice, {
|
|
81
|
+
code: "permission.rule_row_dropped",
|
|
82
|
+
message: `${rows.length} of your persisted permission rule rows ${one ? "is" : "are"} scoped to a project directory that is not written as a complete absolute path, so ${one ? "it takes" : "they take"} no part in this run's checks — a deny or ask row among them protects nothing. Rewrite ${one ? "it" : "them"} with the project's full path, or remove ${one ? "it" : "them"} (this notice's detail names ${one ? "it" : "them"}).`,
|
|
83
|
+
detail: { rows: rows.slice(0, 8).map((r) => ({ rule: inlineUntrusted(r.rule, 200), scopeRoot: inlineUntrusted(r.scopeRoot, 200) })), count: rows.length, sessionId: ids.sessionId, runId: ids.runId },
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
}
|
|
71
87
|
function readsPersistedPathRows(provider, principal, localOwnerDeclared) {
|
|
72
88
|
return provider.partitions.durable && ((principal !== undefined && principal !== "") || localOwnerDeclared);
|
|
73
89
|
}
|
|
@@ -81,9 +97,12 @@ export function persistedPathRowsOfTask(t) {
|
|
|
81
97
|
principal: t.principal,
|
|
82
98
|
sessionId: t.sessionId,
|
|
83
99
|
root: t.root,
|
|
84
|
-
|
|
100
|
+
rootCanonical: t.rootCanonical,
|
|
101
|
+
leads: t.leads,
|
|
102
|
+
chainTop: t.chainTop,
|
|
85
103
|
onDisclosure: (message) => emitTrace(t.deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: t.taskId, message, ts: Date.now() })),
|
|
86
104
|
onUnread: t.onUnread,
|
|
105
|
+
onRowsDropped: t.onRowsDropped,
|
|
87
106
|
signal: AbortSignal.any([...t.signals]),
|
|
88
107
|
});
|
|
89
108
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CallTargetStamp } from "../effective-path-target.js";
|
|
1
2
|
import type { AgentTool } from "../../internal/harness.js";
|
|
2
3
|
import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
|
|
3
4
|
import type { AutoModeDenialTracker } from "../auto-mode.js";
|
|
@@ -30,6 +31,9 @@ export interface PrepareAskLaneInput {
|
|
|
30
31
|
handsCwdRef: CwdRef | undefined;
|
|
31
32
|
/** borrowed-readonly — the root the hand tools resolve root-relative spellings against, or undefined (hands-less). */
|
|
32
33
|
handsRoot: string | undefined;
|
|
34
|
+
/** borrowed-readonly — the leg's target-reading stamp ({@link CallTargetStamp}), or undefined (hands-less): the request a
|
|
35
|
+
* policy judges carries a reading of its own args. Called at RUN time; a seat with nothing to stamp answers undefined. */
|
|
36
|
+
stampCallTarget: CallTargetStamp | undefined;
|
|
33
37
|
/** borrowed-readonly — the run's advertised-writable-directory seat, or undefined (hands-less / nothing advertised). `roots` is read at RUN time, never captured: it is the fence's own admitted list and the write gate's exemption domain must be that same list. Not written here. */
|
|
34
38
|
advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
|
|
35
39
|
/** borrowed-readonly — the shared mount roster; read at RUN time by the approval-preview projection (alias-aware
|
|
@@ -59,14 +59,16 @@ function resolveApprovalPreview(tools, toolName, args) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
export function prepareAskLane(input) {
|
|
62
|
-
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, handsRoot, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
62
|
+
const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, handsRoot, stampCallTarget, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
|
|
63
63
|
if (!gateMachineryActive)
|
|
64
64
|
return { askLane: undefined };
|
|
65
65
|
const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
|
|
66
66
|
const adjudicate = effectivePolicy
|
|
67
67
|
? (req, callSignal) => {
|
|
68
68
|
const signal = composedCallSignal(callSignal);
|
|
69
|
-
|
|
69
|
+
const stampedReq = () => ({ ...req, budget: budgetSnapshot, ...(handsCwdRef !== undefined ? { cwd: handsCwdRef.current } : {}), ...(handsRoot !== undefined ? { root: handsRoot } : {}), ...advertisedWritableRootsSeat(advertisedWritableDirs) });
|
|
70
|
+
const withReading = stampCallTarget?.(stampedReq(), signal);
|
|
71
|
+
return raceAbort(withReading === undefined ? Promise.resolve(effectivePolicy.check(stampedReq(), signal)) : withReading.then((r) => effectivePolicy.check(r, signal)), signal, () => ({
|
|
70
72
|
action: "deny",
|
|
71
73
|
message: "policy check aborted (task timed out or cancelled)",
|
|
72
74
|
}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
2
2
|
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
3
|
-
import type { PrepareResume, RollbackStack, RunInternals } from "./contracts.js";
|
|
3
|
+
import type { ChildPlacement, PrepareResume, RollbackStack, RunInternals } from "./contracts.js";
|
|
4
4
|
/**
|
|
5
5
|
* The ONE reference to a factory-minted env, shared by the driver's detach guard and the rollback entry
|
|
6
6
|
* this phase registers. `current` is the minted env, or `undefined` when the run has none / after the
|
|
@@ -19,7 +19,7 @@ export interface PrepareExecutionEnvInput {
|
|
|
19
19
|
spec: Pick<TaskSpec, "taskId">;
|
|
20
20
|
/** borrowed-readonly — the trusted spawn-side requests: the explicit cwd (a contract), the isolation
|
|
21
21
|
* request and the parent's cwd (both threaded to the factory ctx). */
|
|
22
|
-
internals: Pick<RunInternals, "requestedCwd" | "isolation" | "parentCwd"> | undefined;
|
|
22
|
+
internals: Pick<RunInternals, "requestedCwd" | "isolation" | "parentCwd" | "callerRoot" | "worktreeOffered"> | undefined;
|
|
23
23
|
/** borrowed-readonly — the resume leg, read only to refuse an explicit cwd that a checkpoint workspace
|
|
24
24
|
* restore would overrule. */
|
|
25
25
|
resume: Pick<PrepareResume, "workspaceHandle"> | undefined;
|
|
@@ -50,5 +50,8 @@ export interface PrepareExecutionEnvResult {
|
|
|
50
50
|
/** borrowed-readonly — the INITIAL task root (the workspace-restore phase may re-root it and returns the
|
|
51
51
|
* settled root under the NEW name `taskRootFinal`). */
|
|
52
52
|
taskRootInitial: string;
|
|
53
|
+
/** borrowed-readonly — where a CHILD landed against where its caller asked ({@link childPlacementOnLeg}); absent on a leg no
|
|
54
|
+
* delegation caller placed (no `callerRoot`). */
|
|
55
|
+
placement: ChildPlacement | undefined;
|
|
53
56
|
}
|
|
54
57
|
export declare function prepareExecutionEnv(input: PrepareExecutionEnvInput): Promise<PrepareExecutionEnvResult>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
2
2
|
import { hasDestroy } from "../remote-env.js";
|
|
3
3
|
import { announceMalformedCapabilities } from "./env-capability-census.js";
|
|
4
|
-
import { deploymentCanRerootChild } from "./child-tree.js";
|
|
4
|
+
import { childPlacementOnLeg, deploymentCanRerootChild, worktreeNotHonored } from "./child-tree.js";
|
|
5
5
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
6
6
|
export async function prepareExecutionEnv(input) {
|
|
7
7
|
const { deps, spec, internals, resume, placementRootResolved, sessionId, forgetOnThrow, rollback } = input;
|
|
@@ -69,8 +69,15 @@ export async function prepareExecutionEnv(input) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
const handsEnabled = ownedEnv !== undefined || deps.executionEnv !== undefined;
|
|
72
|
-
const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
|
|
73
|
-
|
|
72
|
+
const taskRootInitial = internals?.isolation === "worktree" || internals?.requestedCwd !== undefined || internals?.worktreeOffered === true || !deps.rootPath ? executionEnv.cwd : deps.rootPath;
|
|
73
|
+
const placement = internals?.callerRoot === undefined ? undefined : await childPlacementOnLeg(executionEnv, { ...internals, callerRoot: internals.callerRoot }, taskRootInitial);
|
|
74
|
+
if (internals?.worktreeOffered === true && placement === "caller_tree") {
|
|
75
|
+
await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (worktree-not-honored leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
|
|
76
|
+
await settleTeardownLeg(() => (ownedEnv && hasDestroy(ownedEnv) ? ownedEnv.destroy() : undefined), "ownedEnv.destroy (worktree-not-honored leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
|
|
77
|
+
ownedEnvSeat.current = undefined;
|
|
78
|
+
throw worktreeNotHonored(executionEnv.cwd);
|
|
79
|
+
}
|
|
80
|
+
return { executionEnv, ownedEnvSeat, ownedEnvObservedAt, handsEnabled, taskRootInitial, placement };
|
|
74
81
|
}
|
|
75
82
|
function cwdConflictsRestoreError(requestedCwd) {
|
|
76
83
|
const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* gate outcome; nothing here changes a word, a source or an order — the stations are moved, not re-adjudicated.
|
|
25
25
|
*/
|
|
26
26
|
import type { AgentHarness, AgentTool } from "../../internal/harness.js";
|
|
27
|
+
import type { CallTargetStamp } from "../effective-path-target.js";
|
|
27
28
|
import type { AskClass } from "../ask-class.js";
|
|
28
29
|
import type { Arbitration, ToolMarkSource } from "../ask-origin.js";
|
|
29
30
|
import { type PermissionModeController } from "../mode-policy.js";
|
|
@@ -152,6 +153,8 @@ export interface PrepareGateStationsInput {
|
|
|
152
153
|
/** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per screening / per post seat. Not
|
|
153
154
|
* written here. */
|
|
154
155
|
handsCwdRef: CwdRef | undefined;
|
|
156
|
+
/** borrowed-readonly — the leg's target-reading stamp, or undefined (hands-less): the gate stamps its request with it. */
|
|
157
|
+
stampCallTarget: CallTargetStamp | undefined;
|
|
155
158
|
/** borrowed-readonly — the leg's read-deny table in force, read at a release: the active built-in tiers and the rows
|
|
156
159
|
* beside them (the deployment's and the task's patterns, the read-face rule rows). Empty ⇒ no table. */
|
|
157
160
|
readDenyInForce: () => {
|
|
@@ -46,7 +46,7 @@ export function maybeHumanRejectionHalt(input) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
export function prepareGateStations(input) {
|
|
49
|
-
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, shellReadCheck, readFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
|
|
49
|
+
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, stampCallTarget, hookEnvFace, writeProtectionCheck, shellReadCheck, readFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
|
|
50
50
|
const gatePreToolUse = ownGatePreToolUse !== undefined ? (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) : undefined;
|
|
51
51
|
const releasesAnnounced = new Map();
|
|
52
52
|
const onModeReleased = (info) => {
|
|
@@ -120,6 +120,7 @@ export function prepareGateStations(input) {
|
|
|
120
120
|
...(gatePreToolUse !== undefined ? { preToolUse: gatePreToolUse } : {}),
|
|
121
121
|
hookTimeoutMs,
|
|
122
122
|
...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
|
|
123
|
+
...(stampCallTarget !== undefined ? { stampCallTarget } : {}),
|
|
123
124
|
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
|
|
124
125
|
toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name === e.toolName ? called.toolName : name),
|
|
125
126
|
adjudicate,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BeforeWriteHook, type CwdRef, type PathRuleDenyRow, type ContainmentFace } from "../../tools/fs/index.js";
|
|
2
2
|
import { type ReadFileState } from "../../tools/fs/safety.js";
|
|
3
3
|
import { type PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
|
|
4
|
+
import type { HeadLeads } from "../path-rule-binding.js";
|
|
4
5
|
import { type ShellReadAdmission, type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
|
|
5
6
|
import type { ShellReadBoundary } from "../../tools/fs/shell-read-judge.js";
|
|
6
7
|
import type { RunnerDeps, ShellGateDoctrine, TaskSpec } from "../types.js";
|
|
@@ -54,9 +55,12 @@ export interface PrepareHandsReadFaceInput {
|
|
|
54
55
|
/** borrowed-readonly — the registry scope (`internals?.registryScope ?? spec.principal ??
|
|
55
56
|
* "default"`, computed in the driver); the band's task-registry scope. */
|
|
56
57
|
taskScope: string;
|
|
57
|
-
/** borrowed-readonly — the deny tables' permission-rule rows: the path deny rules of the leg's caller policy and
|
|
58
|
-
* ancestor's (`
|
|
59
|
-
|
|
58
|
+
/** borrowed-readonly — the deny tables' permission-rule rows: the face-keyed path deny rules of the leg's caller policy and
|
|
59
|
+
* of every ancestor's, each bound where it was installed (`toolPolicyPathRules`), and the person's deny rows; joined to the
|
|
60
|
+
* read face's matcher and to the shell station's write face. */
|
|
61
|
+
denyRuleRows: readonly PathRuleDenyRow[];
|
|
62
|
+
/** borrowed-readonly — the leg's one resolution of where rule heads lead (the rows' twins, the same the gate's lanes read). */
|
|
63
|
+
headLeads: HeadLeads;
|
|
60
64
|
/** borrowed-readonly — the two judges a shell command's write targets meet for the mode's file-command placement: the
|
|
61
65
|
* leg's write-protection judge (read per judgment — it is compiled at the manifest phase, before any gate is mounted)
|
|
62
66
|
* and the chain's write-target guard (`toolPolicyWriteTargetGuard`). Absent ⇒ neither (a standalone phase drive). */
|
|
@@ -93,7 +97,7 @@ export interface PrepareHandsReadFaceInput {
|
|
|
93
97
|
* object (R-4: `deps.onError?.()` keeps its receiver; a new deps read must widen this Pick).
|
|
94
98
|
* `readDenyBuiltinTiers`/`readDenyBuiltinExclude` are SNAPSHOTTED before the first await that
|
|
95
99
|
* precedes the toolkit compile (the caller retains these arrays by reference). */
|
|
96
|
-
deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "readFace" | "writeFace" | "permissionRuleStore" | "hands">;
|
|
100
|
+
deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "agentConfigDir" | "readFace" | "writeFace" | "permissionRuleStore" | "hands">;
|
|
97
101
|
/** borrowed-readonly — the trusted spawn-side seats the band forwards: the finished-command
|
|
98
102
|
* notification lane, the mid-flight detach hub, and the Runner's per-session
|
|
99
103
|
* read-state seats (absent on a standalone prepare ⇒ the transcript-replay rung covers alone). */
|
|
@@ -2,13 +2,13 @@ import { emitTrace } from "../trace.js";
|
|
|
2
2
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
3
3
|
import { hasBackgroundShell } from "../background-shell.js";
|
|
4
4
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
5
|
-
import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, compileWriteDeny,
|
|
5
|
+
import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, compileWriteDeny, rowsWhereTheyLead, readDenySeat, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, backgroundOutputReadExemption, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
|
|
6
6
|
import { resolveKey, sha256 } from "../../tools/fs/safety.js";
|
|
7
|
-
import { placeReadDenyAtHome
|
|
7
|
+
import { placeReadDenyAtHome } from "../../tools/fs/read-deny-home.js";
|
|
8
8
|
import { createShellOverflowSpoolFence } from "../../tools/fs/fs-shared.js";
|
|
9
9
|
import { createShellReadStation } from "../../tools/fs/shell-read-check.js";
|
|
10
10
|
import { composeReadChecks, createFileReadStation } from "../../tools/fs/file-read-check.js";
|
|
11
|
-
import { readTargetToolNames } from "../tool-registry.js";
|
|
11
|
+
import { isShellFaceToolName, predictMountedToolNames, readTargetToolNames } from "../tool-registry.js";
|
|
12
12
|
import { normalizeFileText } from "../../tools/fs/encoding.js";
|
|
13
13
|
import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
|
|
14
14
|
import { deliverEngineNotice } from "../types.js";
|
|
@@ -203,6 +203,7 @@ export async function prepareHandsMount(input) {
|
|
|
203
203
|
const handsIncludeShell = !(executionEnv instanceof StubExecutionEnv);
|
|
204
204
|
const handsReadOnly = spec.handsReadOnly === true;
|
|
205
205
|
backgroundTaskToolsActive = handsIncludeShell && !handsReadOnly && hasBackgroundShell(executionEnv);
|
|
206
|
+
const shellFaceHere = [...predictMountedToolNames(new Set(backgroundTaskToolsActive ? ["hands", "backgroundTasks"] : ["hands"]))].filter(isShellFaceToolName);
|
|
206
207
|
wsSnapshot = resume !== undefined || !(handsIncludeShell && !handsReadOnly) ? undefined : await session.getWorkspaceState().catch(() => undefined);
|
|
207
208
|
rebaseWsPath = (p) => (wsSnapshot === undefined || wsSnapshot.taskRoot === rootCanonical ? p : rebaseWorkspacePath(p, wsSnapshot.taskRoot, rootCanonical));
|
|
208
209
|
if (handsIncludeShell && !handsReadOnly) {
|
|
@@ -220,8 +221,8 @@ export async function prepareHandsMount(input) {
|
|
|
220
221
|
const seedReadFaceSection = resume?.seed.readFace;
|
|
221
222
|
const readDenyAdditions = [...(deps.readDenyPatterns ?? []), ...(spec.readDenyPatterns ?? []), ...(seedReadFaceSection?.denyEntries ?? [])];
|
|
222
223
|
const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: deps.readDenyBuiltinTiers, exclude: deps.readDenyBuiltinExclude });
|
|
223
|
-
const
|
|
224
|
-
const readDenyPlacement = await placeReadDenyAtHome(executionEnv, (homes, linked) => compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg,
|
|
224
|
+
const denyRuleRows = rowsWhereTheyLead(input.denyRuleRows, await input.headLeads.resolve(input.denyRuleRows));
|
|
225
|
+
const readDenyPlacement = await placeReadDenyAtHome(executionEnv, (homes, linked) => compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg, denyRuleRows, homes, linked), undefined, undefined, deps.agentConfigDir);
|
|
225
226
|
readDenyMatcher = readDenyPlacement.matcher;
|
|
226
227
|
readDenyBuiltinTiersActive = activeReadDenyBuiltinTiers(readDenyBuiltinCfg);
|
|
227
228
|
readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
|
|
@@ -291,7 +292,7 @@ export async function prepareHandsMount(input) {
|
|
|
291
292
|
}
|
|
292
293
|
: undefined;
|
|
293
294
|
const denySeat = readDenySeat(readDenyMatcher);
|
|
294
|
-
const writeDeny = compileWriteDeny(
|
|
295
|
+
const writeDeny = compileWriteDeny(denyRuleRows);
|
|
295
296
|
const writeDenySeat = writeDeny.ruleRows.length > 0 ? { writeDenyMatch: (p) => writeDeny.matchPath(p)?.pattern ?? null } : {};
|
|
296
297
|
const shellReadBoundary = () => ({
|
|
297
298
|
roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
|
|
@@ -307,7 +308,7 @@ export async function prepareHandsMount(input) {
|
|
|
307
308
|
if (handsIncludeShell) {
|
|
308
309
|
const spoolFence = handsReadOnly ? createShellOverflowSpoolFence(executionEnv) : undefined;
|
|
309
310
|
const readonlyBoundary = () => spoolFence !== undefined ? { roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical], cwd: rootCanonical, ...denySeat, ...writeDenySeat, alsoInside: spoolFence } : undefined;
|
|
310
|
-
shellReadTools = new Set(
|
|
311
|
+
shellReadTools = new Set(shellFaceHere);
|
|
311
312
|
shellReadStation = createShellReadStation({
|
|
312
313
|
env: executionEnv,
|
|
313
314
|
tools: shellReadTools,
|
|
@@ -334,7 +335,7 @@ export async function prepareHandsMount(input) {
|
|
|
334
335
|
...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
|
|
335
336
|
additionalReadRoots: additionalReadRootsCanonical,
|
|
336
337
|
...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
|
|
337
|
-
...(
|
|
338
|
+
...(denyRuleRows.length > 0 ? { pathDenyRules: denyRuleRows } : {}),
|
|
338
339
|
readDenyHomes: readDenyPlacement.homes,
|
|
339
340
|
readDenyHomeLinks: readDenyPlacement.linked,
|
|
340
341
|
...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
|
|
@@ -400,9 +401,8 @@ export async function prepareHandsMount(input) {
|
|
|
400
401
|
if (seatOwned)
|
|
401
402
|
shellGatedTools.add(name);
|
|
402
403
|
};
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
installShellSeat("Monitor");
|
|
404
|
+
for (const name of shellFaceHere)
|
|
405
|
+
installShellSeat(name);
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
return {
|