@sema-agent/core 7.49.5 → 7.50.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 +138 -0
- package/dist/agents/observer.js +3 -10
- package/dist/agents/subagent-steps.d.ts +6 -14
- package/dist/agents/subagent-steps.js +3 -17
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +1 -1
- package/dist/core/ask-origin.d.ts +15 -11
- package/dist/core/ask-origin.js +6 -14
- package/dist/core/auto-mode-prompt.js +6 -2
- package/dist/core/checkpoint-store.d.ts +6 -4
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/context-edit.d.ts +2 -2
- package/dist/core/decision-receipt.d.ts +2 -2
- package/dist/core/decision-receipt.js +30 -11
- package/dist/core/delegation-frames.d.ts +66 -18
- package/dist/core/delegation-frames.js +22 -0
- package/dist/core/engine-notice.d.ts +7 -5
- package/dist/core/frame-settlement.d.ts +6 -1
- package/dist/core/gate-fold.js +19 -13
- package/dist/core/gate-lanes.js +12 -11
- package/dist/core/gate-outcome.d.ts +2 -2
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +28 -20
- package/dist/core/human-input-projection.js +2 -0
- package/dist/core/lsp-diagnostics.d.ts +3 -0
- package/dist/core/lsp-diagnostics.js +6 -0
- package/dist/core/lsp-session.d.ts +39 -17
- package/dist/core/lsp-session.js +118 -58
- package/dist/core/lsp.d.ts +83 -3
- package/dist/core/lsp.js +146 -16
- package/dist/core/mcp.js +2 -1
- package/dist/core/media-byte-cap.d.ts +14 -0
- package/dist/core/media-byte-cap.js +15 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
- package/dist/core/memory-engine/consolidation-driver.js +54 -6
- package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
- package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
- package/dist/core/memory-engine/consolidation.d.ts +17 -0
- package/dist/core/memory-engine/consolidation.js +1 -0
- package/dist/core/memory-engine/distiller.d.ts +8 -2
- package/dist/core/memory-engine/distiller.js +8 -6
- package/dist/core/memory-engine/engine.d.ts +37 -4
- package/dist/core/memory-engine/engine.js +127 -38
- package/dist/core/memory-engine/file-backend.d.ts +8 -3
- package/dist/core/memory-engine/file-backend.js +44 -16
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/landing-admission.d.ts +65 -0
- package/dist/core/memory-engine/landing-admission.js +68 -0
- package/dist/core/memory-engine/types.d.ts +23 -0
- package/dist/core/mode-policy.d.ts +3 -2
- package/dist/core/mode-policy.js +2 -0
- package/dist/core/permission-rule-model.d.ts +19 -13
- package/dist/core/permission-rule-model.js +7 -4
- package/dist/core/permission-rule-provider.d.ts +11 -0
- package/dist/core/permission-rules.d.ts +1 -1
- package/dist/core/permission-rules.js +5 -2
- package/dist/core/persisted-rule-arms.d.ts +15 -7
- package/dist/core/persisted-rule-arms.js +13 -6
- package/dist/core/reminder-mint.d.ts +5 -0
- package/dist/core/reminder-mint.js +1 -0
- package/dist/core/retired-keys.d.ts +8 -0
- package/dist/core/retired-keys.js +4 -0
- package/dist/core/rule-lane-reads.d.ts +4 -4
- package/dist/core/rule-lane-reads.js +10 -1
- package/dist/core/runner/construction-doors.d.ts +10 -4
- package/dist/core/runner/construction-doors.js +25 -2
- package/dist/core/runner/contracts.d.ts +20 -3
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.js +2 -1
- package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
- package/dist/core/runner/memory-consolidation-driver.js +5 -2
- package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
- package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
- package/dist/core/runner/permission-rule-lanes.js +13 -43
- package/dist/core/runner/person-path-rows.d.ts +244 -0
- package/dist/core/runner/person-path-rows.js +387 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
- package/dist/core/runner/prepare-artifact.d.ts +5 -3
- package/dist/core/runner/prepare-artifact.js +2 -8
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-context-lane.js +2 -2
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
- package/dist/core/runner/prepare-hands-readface.js +57 -43
- package/dist/core/runner/prepare-lsp.d.ts +15 -13
- package/dist/core/runner/prepare-lsp.js +11 -11
- package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
- package/dist/core/runner/prepare-policy-chain.js +6 -7
- package/dist/core/runner/prepare-run-refs.js +1 -1
- package/dist/core/runner/prepare-task.js +24 -16
- package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
- package/dist/core/runner/prepare-turn-wiring.js +19 -8
- package/dist/core/runner/read-root-grant.d.ts +1 -25
- package/dist/core/runner/read-root-grant.js +1 -17
- package/dist/core/runner/retired-runner-deps.d.ts +2 -2
- package/dist/core/runner/run-brain-sinks.d.ts +1 -5
- package/dist/core/runner/run-brain-sinks.js +2 -4
- package/dist/core/runner/run-harness-handlers.js +5 -4
- package/dist/core/runner/run-identity-wiring.d.ts +4 -4
- package/dist/core/runner/run-identity-wiring.js +10 -1
- package/dist/core/runner/run-leg.d.ts +1 -1
- package/dist/core/runner/run-leg.js +22 -9
- package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
- package/dist/core/runner/run-settle-and-teardown.js +7 -4
- package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
- package/dist/core/runner/run-terminal-adoption.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +2 -2
- package/dist/core/runner/runtask.js +8 -8
- package/dist/core/runner/session-file-state-replay.d.ts +14 -4
- package/dist/core/runner/session-file-state-replay.js +8 -0
- package/dist/core/runner/stream-steer-verb.js +3 -0
- package/dist/core/runner/tool-end-body.d.ts +5 -4
- package/dist/core/runner/tool-end-body.js +4 -4
- package/dist/core/runner/tool-output-projection.d.ts +6 -4
- package/dist/core/runner/tool-output-projection.js +66 -47
- package/dist/core/runner-deps.d.ts +6 -3
- package/dist/core/shared-memory/types.d.ts +2 -2
- package/dist/core/shell-read-silence.d.ts +23 -0
- package/dist/core/shell-read-silence.js +18 -0
- package/dist/core/synthetic-read-verdict.d.ts +80 -21
- package/dist/core/synthetic-read-verdict.js +89 -18
- package/dist/core/task-event.d.ts +113 -25
- package/dist/core/task-spec.d.ts +44 -0
- package/dist/core/task-tool-shape.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +16 -2
- package/dist/core/tool-policy.d.ts +25 -15
- package/dist/core/tool-policy.js +9 -13
- package/dist/core/trace.d.ts +3 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +2 -2
- package/dist/core/user-attachment.d.ts +164 -0
- package/dist/core/user-attachment.js +437 -0
- package/dist/engine/compaction/compaction.js +10 -3
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
- package/dist/engine/execution-env/node-execution-env.js +125 -0
- package/dist/engine/harness/agent-harness.d.ts +12 -2
- package/dist/engine/harness/agent-harness.js +15 -3
- package/dist/engine/harness/types.d.ts +21 -0
- package/dist/engine/llm/content-block-render.d.ts +15 -0
- package/dist/engine/llm/content-block-render.js +10 -0
- package/dist/engine/llm/index.d.ts +1 -0
- package/dist/engine/llm/index.js +1 -0
- package/dist/engine/llm/types.d.ts +80 -4
- package/dist/engine/llm/types.js +43 -1
- package/dist/engine/session/import-validate.js +2 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow.d.ts +4 -5
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompts/default.d.ts +1 -1
- package/dist/tools/artifact/artifact-tool.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +4 -10
- package/dist/tools/fs/dir-entries.js +4 -11
- package/dist/tools/fs/file-read-check.js +3 -3
- package/dist/tools/fs/fs-pdf.js +2 -4
- package/dist/tools/fs/fs-read.d.ts +47 -4
- package/dist/tools/fs/fs-read.js +63 -19
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-search-tools.js +7 -2
- package/dist/tools/fs/fs-write.d.ts +1 -1
- package/dist/tools/fs/fs-write.js +7 -6
- package/dist/tools/fs/index.d.ts +7 -1
- package/dist/tools/fs/index.js +56 -34
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +32 -22
- package/dist/tools/fs/read-deny.d.ts +14 -0
- package/dist/tools/fs/read-deny.js +8 -0
- package/dist/tools/fs/repo-map.d.ts +3 -2
- package/dist/tools/fs/repo-map.js +11 -3
- package/dist/tools/fs/safety.d.ts +41 -2
- package/dist/tools/fs/safety.js +12 -3
- package/dist/tools/fs/shell-read-check.d.ts +12 -3
- package/dist/tools/fs/shell-read-check.js +28 -15
- package/dist/tools/fs/shell-read-device.d.ts +1 -1
- package/dist/tools/fs/shell-read-judge.d.ts +9 -4
- package/dist/tools/fs/shell-read-judge.js +1 -1
- package/dist/tools/loop-tick.d.ts +2 -2
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +39 -1
- package/wire-changes/core@7.50.0.json +1699 -0
- package/dist/core/runner/persisted-path-rows.d.ts +0 -88
- package/dist/core/runner/persisted-path-rows.js +0 -108
package/dist/tools/fs/index.js
CHANGED
|
@@ -16,11 +16,11 @@ import { BASH_READONLY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
|
|
|
16
16
|
export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
|
|
17
17
|
import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, isRootedReadDenyPattern, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
|
|
18
18
|
import { withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
19
|
-
import { carryHomeViews, homeViewsAt, placeReadDenyAtHome, placementViewsOf, readDenyPlacedLater, sayPlacementLost } from "./read-deny-home.js";
|
|
19
|
+
import { carriedReadFaceOf, carryHomeViews, homeViewsAt, placeReadDenyAtHome, placementViewsOf, readDenyPlacedLater, sayPlacementLost } from "./read-deny-home.js";
|
|
20
20
|
import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
|
|
21
21
|
export * from "./containment-face.js";
|
|
22
22
|
import {} from "./fs-shared.js";
|
|
23
|
-
import { createReadFileTool } from "./fs-read.js";
|
|
23
|
+
import { createModelFileReader, createReadFileTool } from "./fs-read.js";
|
|
24
24
|
import { createEditFileTool, createWriteFileTool, createNotebookEditTool } from "./fs-write.js";
|
|
25
25
|
import { createGrepTool, createGlobTool } from "./fs-search-tools.js";
|
|
26
26
|
import { createBashTool, createBashReadonlyTool, createEnvTaskOutputTool, createEnvTaskStopTool } from "./fs-bash.js";
|
|
@@ -34,42 +34,15 @@ export function backgroundOutputReadExemption(registry, seat, env) {
|
|
|
34
34
|
}
|
|
35
35
|
export function createHandsToolkit(env, readFileState, rootCanonical, opts = {}) {
|
|
36
36
|
const { includeShell = false, readOnly = false, bashReadonlyAllow, commitCoAuthor = false, mountBackgroundTaskTools = true, additionalRoots, additionalReadRoots, } = opts;
|
|
37
|
-
const
|
|
37
|
+
const carriedFace = opts.readDenyHomes !== undefined ? carriedReadFaceOf(opts.readDenyHomes) : undefined;
|
|
38
|
+
const readFaceRoots = carriedFace?.readRoots ?? (additionalReadRoots === undefined ? additionalRoots : () => (additionalReadRoots.length === 0 ? additionalRoots : [...(additionalRoots ?? []), ...additionalReadRoots]));
|
|
38
39
|
const cwdRef = opts.cwdRef ?? { current: rootCanonical };
|
|
39
40
|
const bgOutput = backgroundOutputReadExemption(opts.taskRegistry, opts, env);
|
|
40
41
|
const readAdmission = opts.readAdmission;
|
|
41
42
|
const bgOutputReadExemption = bgOutput === undefined && readAdmission === undefined
|
|
42
43
|
? undefined
|
|
43
44
|
: (canonicalKey, c) => readAdmission?.(c.toolCallId, canonicalKey) === true || (bgOutput?.(canonicalKey, c) ?? false);
|
|
44
|
-
const
|
|
45
|
-
const readDenyRules = opts.pathDenyRules === undefined ? undefined : [...opts.pathDenyRules];
|
|
46
|
-
const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: opts.readDenyBuiltinTiers, exclude: opts.readDenyBuiltinExclude });
|
|
47
|
-
const readDenyHomes = opts.readDenyHomes === undefined ? undefined : [...checkedReadDenyHomes(opts.readDenyHomes)];
|
|
48
|
-
const compileAt = (homes, linked) => compileReadDeny(readDenyAdditions, "HandsToolkitOptions.readDenyPatterns", readDenyBuiltinCfg, readDenyRules, homes, linked);
|
|
49
|
-
const unplaced = compileAt([]);
|
|
50
|
-
const carried = readDenyHomes !== undefined && opts.readDenyHomeLinks !== undefined;
|
|
51
|
-
const carriedViews = carried ? placementViewsOf(opts.readDenyHomes) : undefined;
|
|
52
|
-
if (carried && carriedViews === undefined)
|
|
53
|
-
sayPlacementLost();
|
|
54
|
-
const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored || isRootedReadDenyPattern(e.pattern));
|
|
55
|
-
const late = carried || placesNothing ? undefined : readDenyPlacedLater(carryHomeViews(unplaced, homeViewsAt(env, [])), () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
|
|
56
|
-
const readDeny = late?.matcher ??
|
|
57
|
-
(carried
|
|
58
|
-
? carriedViews !== undefined
|
|
59
|
-
? carryHomeViews(compileAt(readDenyHomes, opts.readDenyHomeLinks), carriedViews)
|
|
60
|
-
: carryHomeViews(unplaced, homeViewsAt(env, []))
|
|
61
|
-
: carryHomeViews(unplaced, homeViewsAt(env, readDenyHomes ?? [])));
|
|
62
|
-
const afterPlacement = (tool) => late === undefined
|
|
63
|
-
? tool
|
|
64
|
-
: {
|
|
65
|
-
...tool,
|
|
66
|
-
execute: async (...args) => {
|
|
67
|
-
await withGateTimeout(late.ready(), undefined, args[2], "readDenyPlacement").catch(() => undefined);
|
|
68
|
-
return await tool.execute(...args);
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
const writeDenyCompiled = compileWriteDeny(readDenyRules);
|
|
72
|
-
const writeDeny = writeDenyCompiled.ruleRows.length > 0 ? carryHomeViews(writeDenyCompiled, readDeny.homeViews) : undefined;
|
|
45
|
+
const { readDeny, writeDeny, afterPlacement } = carriedFace !== undefined ? { readDeny: carriedFace.readDeny, writeDeny: carriedFace.writeDeny, afterPlacement: (tool) => tool } : bandOwnTables(env, opts);
|
|
73
46
|
const readFace = resolveContainmentFace({
|
|
74
47
|
kind: "read",
|
|
75
48
|
depsFace: opts.readFace,
|
|
@@ -87,13 +60,30 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
87
60
|
onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("write")),
|
|
88
61
|
});
|
|
89
62
|
const writeFaceOf = opts.writeFaceLive ?? (() => writeFace);
|
|
63
|
+
const readCwd = readOnly ? undefined : cwdRef;
|
|
64
|
+
const reader = createModelFileReader({
|
|
65
|
+
env,
|
|
66
|
+
state: readFileState,
|
|
67
|
+
rootCanonical,
|
|
68
|
+
...(readCwd !== undefined ? { cwdRef: readCwd } : {}),
|
|
69
|
+
...(readFaceRoots !== undefined ? { additionalRoots: readFaceRoots } : {}),
|
|
70
|
+
imageDownsampler: opts.readImageDownsampler,
|
|
71
|
+
...(opts.pdfModelCapabilities !== undefined ? { pdfCapabilities: opts.pdfModelCapabilities } : {}),
|
|
72
|
+
...(bgOutputReadExemption !== undefined ? { bgOutputReadExemption } : {}),
|
|
73
|
+
...(readDeny !== undefined ? { readDeny } : {}),
|
|
74
|
+
...(readFace !== undefined ? { readFace } : {}),
|
|
75
|
+
...(opts.reminderMark !== undefined ? { reminderMark: opts.reminderMark } : {}),
|
|
76
|
+
...(opts.reminderDisclosureCounts !== undefined ? { reminderDisclosureCounts: opts.reminderDisclosureCounts } : {}),
|
|
77
|
+
});
|
|
78
|
+
if (opts.modelFileReaderSeat !== undefined)
|
|
79
|
+
opts.modelFileReaderSeat.current = reader;
|
|
90
80
|
const tools = [
|
|
91
|
-
afterPlacement(createReadFileTool(env, readFileState, rootCanonical,
|
|
81
|
+
afterPlacement(createReadFileTool(env, readFileState, rootCanonical, readCwd, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts, reader)),
|
|
92
82
|
];
|
|
93
83
|
if (handsWriteHalfMounts({ readOnly })) {
|
|
94
84
|
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createWriteFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny), createNotebookEditTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited, writeFaceOf, writeDeny));
|
|
95
85
|
}
|
|
96
|
-
tools.push(afterPlacement(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace)));
|
|
86
|
+
tools.push(afterPlacement(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)), afterPlacement(createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef, readAdmission)));
|
|
97
87
|
if (includeShell) {
|
|
98
88
|
tools.push(readOnly
|
|
99
89
|
? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
|
|
@@ -141,3 +131,35 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
141
131
|
}
|
|
142
132
|
return tools;
|
|
143
133
|
}
|
|
134
|
+
function bandOwnTables(env, opts) {
|
|
135
|
+
const readDenyAdditions = (opts.readDenyPatterns ?? []).map((e) => (typeof e === "object" && e !== null ? { ...e } : e));
|
|
136
|
+
const readDenyRules = opts.pathDenyRules === undefined ? undefined : [...opts.pathDenyRules];
|
|
137
|
+
const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: opts.readDenyBuiltinTiers, exclude: opts.readDenyBuiltinExclude });
|
|
138
|
+
const readDenyHomes = opts.readDenyHomes === undefined ? undefined : [...checkedReadDenyHomes(opts.readDenyHomes)];
|
|
139
|
+
const compileAt = (homes, linked) => compileReadDeny(readDenyAdditions, "HandsToolkitOptions.readDenyPatterns", readDenyBuiltinCfg, readDenyRules, homes, linked);
|
|
140
|
+
const unplaced = compileAt([]);
|
|
141
|
+
const carried = readDenyHomes !== undefined && opts.readDenyHomeLinks !== undefined;
|
|
142
|
+
const carriedViews = carried ? placementViewsOf(opts.readDenyHomes) : undefined;
|
|
143
|
+
if (carried && carriedViews === undefined)
|
|
144
|
+
sayPlacementLost();
|
|
145
|
+
const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored || isRootedReadDenyPattern(e.pattern));
|
|
146
|
+
const late = carried || placesNothing ? undefined : readDenyPlacedLater(carryHomeViews(unplaced, homeViewsAt(env, [])), () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
|
|
147
|
+
const readDeny = late?.matcher ??
|
|
148
|
+
(carried
|
|
149
|
+
? carriedViews !== undefined
|
|
150
|
+
? carryHomeViews(compileAt(readDenyHomes, opts.readDenyHomeLinks), carriedViews)
|
|
151
|
+
: carryHomeViews(unplaced, homeViewsAt(env, []))
|
|
152
|
+
: carryHomeViews(unplaced, homeViewsAt(env, readDenyHomes ?? [])));
|
|
153
|
+
const afterPlacement = (tool) => late === undefined
|
|
154
|
+
? tool
|
|
155
|
+
: {
|
|
156
|
+
...tool,
|
|
157
|
+
execute: async (...args) => {
|
|
158
|
+
await withGateTimeout(late.ready(), undefined, args[2], "readDenyPlacement").catch(() => undefined);
|
|
159
|
+
return await tool.execute(...args);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const writeDenyCompiled = compileWriteDeny(readDenyRules);
|
|
163
|
+
const writeDeny = writeDenyCompiled.ruleRows.length > 0 ? carryHomeViews(writeDenyCompiled, readDeny.homeViews) : undefined;
|
|
164
|
+
return { readDeny, writeDeny, afterPlacement };
|
|
165
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import { type PathFamily } from "./safety.js";
|
|
3
|
-
import { type ReadDenyMatcher } from "./read-deny.js";
|
|
3
|
+
import { type ReadDenyMatcher, type WriteDenyMatcher } from "./read-deny.js";
|
|
4
4
|
/** The one bound every probe of an AUXILIARY home shares within one placement (see the module head): long enough for a
|
|
5
5
|
* local directory's canonical spelling, its chain of identities and its rows' heads, short enough that a hung home
|
|
6
6
|
* costs a placement seconds, not the gate's bound. */
|
|
@@ -116,11 +116,29 @@ export interface HomeViewsCarrier {
|
|
|
116
116
|
export type PlacedReadDenyMatcher = ReadDenyMatcher & {
|
|
117
117
|
readonly homeViews: HomeViewSource;
|
|
118
118
|
};
|
|
119
|
+
/** The Runner's read face as its tool band takes it: the run's read and write deny tables — each judgment reads the run's
|
|
120
|
+
* latest, `forCall` hands a call its own — and the read face's root set, read per judgment (a call's own through
|
|
121
|
+
* `forCall`). A band handed one judges with it and compiles no table of its own. */
|
|
122
|
+
export interface CarriedReadFace {
|
|
123
|
+
readonly readDeny: PlacedReadDenyMatcher;
|
|
124
|
+
readonly writeDeny: WriteDenyMatcher & {
|
|
125
|
+
readonly homeViews: HomeViewSource;
|
|
126
|
+
};
|
|
127
|
+
readonly readRoots: () => readonly string[];
|
|
128
|
+
}
|
|
119
129
|
/** The views of the placement that returned `homes` — the very list object, as the engine hands it on (the Runner's
|
|
120
130
|
* tool band). `undefined` for any other list: a copy, or a host's own. A mount that was handed the engine's placement
|
|
121
131
|
* and cannot find its views has lost it: an engine defect, which the mount names ({@link sayPlacementLost}) and answers
|
|
122
132
|
* by holding its home-anchored rows at every location — never by reading the homes a second time. */
|
|
123
133
|
export declare function placementViewsOf(homes: readonly string[]): HomeViewSource | undefined;
|
|
134
|
+
/** The run's read face a home list carries ({@link carryReadFace}); `undefined` for any other list. */
|
|
135
|
+
export declare function carriedReadFaceOf(homes: readonly string[]): CarriedReadFace | undefined;
|
|
136
|
+
/** A home list (a fresh frozen copy of `homes`) that hands the Runner's read face to its band: the band judges with the
|
|
137
|
+
* face's tables and roots instead of compiling its own from the rows it is handed. */
|
|
138
|
+
export declare function carryReadFace(homes: readonly string[], face: CarriedReadFace): readonly string[];
|
|
139
|
+
/** A judge that reads `current` at every judgment — the table a placement in flight will replace, or the run's tables of
|
|
140
|
+
* which each judgment takes the latest — with the home views `homeViews` (default: the current judge's). */
|
|
141
|
+
export declare function readDenyOver(current: () => PlacedReadDenyMatcher, homeViews?: HomeViewSource): PlacedReadDenyMatcher;
|
|
124
142
|
/** The line a mount says when the engine handed it a home list that is not its placement's (an engine defect, said every
|
|
125
143
|
* time: no latch). */
|
|
126
144
|
export declare function sayPlacementLost(): void;
|
|
@@ -348,11 +348,40 @@ function homeViewSourceOver(env, auxiliary) {
|
|
|
348
348
|
const PLACEMENT_VIEWS = new WeakMap();
|
|
349
349
|
function placementOf(matcher, homes, linked, whole) {
|
|
350
350
|
const list = Object.freeze([...homes]);
|
|
351
|
-
PLACEMENT_VIEWS.set(list, matcher.homeViews);
|
|
351
|
+
PLACEMENT_VIEWS.set(list, { views: matcher.homeViews });
|
|
352
352
|
return { matcher, homes: list, linked, whole };
|
|
353
353
|
}
|
|
354
354
|
export function placementViewsOf(homes) {
|
|
355
|
-
return PLACEMENT_VIEWS.get(homes);
|
|
355
|
+
return PLACEMENT_VIEWS.get(homes)?.views;
|
|
356
|
+
}
|
|
357
|
+
export function carriedReadFaceOf(homes) {
|
|
358
|
+
return PLACEMENT_VIEWS.get(homes)?.face;
|
|
359
|
+
}
|
|
360
|
+
export function carryReadFace(homes, face) {
|
|
361
|
+
const list = Object.freeze([...homes]);
|
|
362
|
+
PLACEMENT_VIEWS.set(list, { views: face.readDeny.homeViews, face });
|
|
363
|
+
return list;
|
|
364
|
+
}
|
|
365
|
+
export function readDenyOver(current, homeViews) {
|
|
366
|
+
return {
|
|
367
|
+
get entries() {
|
|
368
|
+
return current().entries;
|
|
369
|
+
},
|
|
370
|
+
get ruleRows() {
|
|
371
|
+
return current().ruleRows;
|
|
372
|
+
},
|
|
373
|
+
matchPath: (path, opts) => current().matchPath(path, opts),
|
|
374
|
+
matchTarget: (canonicalKey, lexicalView, opts) => current().matchTarget(canonicalKey, lexicalView, opts),
|
|
375
|
+
get rgExclusionGlobs() {
|
|
376
|
+
return current().rgExclusionGlobs;
|
|
377
|
+
},
|
|
378
|
+
get rgProbeGlobs() {
|
|
379
|
+
return current().rgProbeGlobs;
|
|
380
|
+
},
|
|
381
|
+
unexcludableUnder: (scope) => current().unexcludableUnder?.(scope) ?? false,
|
|
382
|
+
rowAtOrBelow: (path) => current().rowAtOrBelow?.(path) ?? null,
|
|
383
|
+
homeViews: homeViews ?? { views: (signal) => current().homeViews.views(signal) },
|
|
384
|
+
};
|
|
356
385
|
}
|
|
357
386
|
export function sayPlacementLost() {
|
|
358
387
|
try {
|
|
@@ -447,24 +476,5 @@ export function readDenyPlacedLater(unplaced, place) {
|
|
|
447
476
|
};
|
|
448
477
|
},
|
|
449
478
|
};
|
|
450
|
-
|
|
451
|
-
get entries() {
|
|
452
|
-
return current.entries;
|
|
453
|
-
},
|
|
454
|
-
get ruleRows() {
|
|
455
|
-
return current.ruleRows;
|
|
456
|
-
},
|
|
457
|
-
matchPath: (path, opts) => current.matchPath(path, opts),
|
|
458
|
-
matchTarget: (canonicalKey, lexicalView, opts) => current.matchTarget(canonicalKey, lexicalView, opts),
|
|
459
|
-
get rgExclusionGlobs() {
|
|
460
|
-
return current.rgExclusionGlobs;
|
|
461
|
-
},
|
|
462
|
-
get rgProbeGlobs() {
|
|
463
|
-
return current.rgProbeGlobs;
|
|
464
|
-
},
|
|
465
|
-
unexcludableUnder: (scope) => current.unexcludableUnder?.(scope) ?? false,
|
|
466
|
-
rowAtOrBelow: (path) => current.rowAtOrBelow?.(path) ?? null,
|
|
467
|
-
homeViews,
|
|
468
|
-
};
|
|
469
|
-
return { matcher, ready };
|
|
479
|
+
return { matcher: readDenyOver(() => current, homeViews), ready };
|
|
470
480
|
}
|
|
@@ -142,6 +142,8 @@ export interface WriteDenyMatcher {
|
|
|
142
142
|
readonly aliasResolved?: boolean;
|
|
143
143
|
readonly hops?: readonly string[];
|
|
144
144
|
}): ReadDenyHit | null;
|
|
145
|
+
/** The table ONE call judges with ({@link ReadDenyMatcher.forCall}). */
|
|
146
|
+
forCall?(toolCallId: string | undefined): WriteDenyMatcher;
|
|
145
147
|
}
|
|
146
148
|
/** Compile the write face over the rule rows (the rows not on it are dropped here, once). */
|
|
147
149
|
export declare function compileWriteDeny(rules?: readonly PathRuleDenyRow[]): WriteDenyMatcher;
|
|
@@ -197,7 +199,19 @@ export interface ReadDenyMatcher {
|
|
|
197
199
|
* path from its own segments ({@link rowMeetsPath}) — so moving, copying or removing the path changes what the row
|
|
198
200
|
* covers. A rule row's reach below a path is not enumerable: any rule row answers. */
|
|
199
201
|
rowAtOrBelow?(path: string): ReadDenyHit | null;
|
|
202
|
+
/** The table ONE call judges with — the Runner's tables are one per read of the person's rules, and a call judges with
|
|
203
|
+
* the read it was pinned to (an unknown or released call: the run's latest). Absent on a table compiled once: it is
|
|
204
|
+
* every call's ({@link seatForCall}). */
|
|
205
|
+
forCall?(toolCallId: string | undefined): ReadDenyMatcher;
|
|
200
206
|
}
|
|
207
|
+
/** A seat the run's tables back — a deny table, a read-root set — as ONE call takes it: the call's own where the seat
|
|
208
|
+
* carries `forCall`, the seat itself otherwise (a table compiled once, a host's own list) and for a reader that is no
|
|
209
|
+
* call (no id: the seat's own reading of the run's latest, with every refusal it carries). Taken once, where the call's
|
|
210
|
+
* judgment starts, so every judgment of the call reads one table. */
|
|
211
|
+
export declare function seatForCall<T>(seat: T, toolCallId: string | undefined): T;
|
|
212
|
+
/** Settles once the tables a seat the run's tables back stands on are ready for a reader that is no call (the seat's
|
|
213
|
+
* `ready`); any other seat is ready. Never rejects. */
|
|
214
|
+
export declare function seatReady(seat: unknown, signal?: AbortSignal): Promise<void>;
|
|
201
215
|
/** The DENY HALF of a shell read boundary, in the shape `BashReadonlyRootBoundary` declares it (structural:
|
|
202
216
|
* this module is the leaf every face depends on). `denyMatch` judges one LEXICALLY RESOLVED operand,
|
|
203
217
|
* returning the matched pattern or null; PRESENT-IFF there is something to judge. */
|
|
@@ -153,6 +153,14 @@ export function compileWriteDeny(rules = []) {
|
|
|
153
153
|
};
|
|
154
154
|
return { face: "write", ruleRows, matchPath, matchTarget: (canonicalKey, lexicalView, opts) => denyTableHit(rows, denyTargetViews(canonicalKey, lexicalView, opts)) };
|
|
155
155
|
}
|
|
156
|
+
export function seatForCall(seat, toolCallId) {
|
|
157
|
+
const forCall = seat?.forCall;
|
|
158
|
+
return typeof forCall === "function" && toolCallId !== undefined ? forCall.call(seat, toolCallId) : seat;
|
|
159
|
+
}
|
|
160
|
+
export function seatReady(seat, signal) {
|
|
161
|
+
const ready = seat?.ready;
|
|
162
|
+
return typeof ready === "function" ? Promise.resolve(ready.call(seat, signal)) : Promise.resolve();
|
|
163
|
+
}
|
|
156
164
|
export function readDenySeat(matcher) {
|
|
157
165
|
const entries = [...(matcher?.entries ?? []).map((e) => e.pattern), ...(matcher?.ruleRows ?? []).map((r) => r.rule)];
|
|
158
166
|
if (matcher === undefined || entries.length === 0)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AgentTool } from "../../internal/harness-types.js";
|
|
2
2
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
3
3
|
import { type RootsSeat } from "./safety.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type ReadDenyMatcher } from "./read-deny.js";
|
|
5
5
|
import type { ContainmentFace } from "./containment-face.js";
|
|
6
|
+
import type { CwdRef } from "./fs-shared.js";
|
|
6
7
|
/** Extract top-level symbol names (deduped, in first-seen order) from source text. A lightweight
|
|
7
8
|
* comment/docstring state machine skips column-0 declaration-like lines that are really inside a
|
|
8
9
|
* `/* … */` block comment or a Python `"""`/`'''` docstring — otherwise e.g. a module docstring
|
|
@@ -14,4 +15,4 @@ import type { ContainmentFace } from "./containment-face.js";
|
|
|
14
15
|
* (src/core/auto-promote.ts) consumes it in ESCALATE-ONLY mode — a clean scan contributes nothing;
|
|
15
16
|
* it can only ever raise an escalation, never clear one. */
|
|
16
17
|
export declare function extractSymbols(text: string): string[];
|
|
17
|
-
export declare function createRepoMapTool(env: ExecutionEnv, rootCanonical: string,
|
|
18
|
+
export declare function createRepoMapTool(env: ExecutionEnv, rootCanonical: string, rootsSeat?: RootsSeat, denySeat?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef, readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean): AgentTool;
|
|
@@ -2,7 +2,10 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
3
3
|
import { resolveKey, violationText, violationDetails } from "./safety.js";
|
|
4
4
|
import { buildIgnore, denyWithheldNote, walk, walkIsPartial } from "./search.js";
|
|
5
|
+
import { seatForCall } from "./read-deny.js";
|
|
5
6
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
7
|
+
import { pathTargetBaseOf } from "../../core/effective-path-target.js";
|
|
8
|
+
import { pathTargetValue } from "../../core/tool-registry.js";
|
|
6
9
|
const DEFAULT_MAX_CHARS = 16_000;
|
|
7
10
|
const DEFAULT_MAX_FILES = 400;
|
|
8
11
|
const FILE_SCAN_MAX_BYTES = 256 * 1024;
|
|
@@ -80,7 +83,8 @@ function rankEntries(a, b) {
|
|
|
80
83
|
return b.symbols.length - a.symbols.length;
|
|
81
84
|
return a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0;
|
|
82
85
|
}
|
|
83
|
-
export function createRepoMapTool(env, rootCanonical,
|
|
86
|
+
export function createRepoMapTool(env, rootCanonical, rootsSeat, denySeat, readFace, cwdRef, readAdmission) {
|
|
87
|
+
const declaredTarget = toolFace("repo-map").pathTarget;
|
|
84
88
|
return defineTool({
|
|
85
89
|
name: "RepoMap",
|
|
86
90
|
...toolFace("repo-map"),
|
|
@@ -94,12 +98,16 @@ export function createRepoMapTool(env, rootCanonical, additionalRoots, readDeny,
|
|
|
94
98
|
max_files: Type.Optional(Type.Number({ description: `Max files to scan (default ${DEFAULT_MAX_FILES}).` })),
|
|
95
99
|
}),
|
|
96
100
|
execute: async (args, ctx) => {
|
|
101
|
+
const readDeny = seatForCall(denySeat, ctx.toolCallId);
|
|
102
|
+
const additionalRoots = seatForCall(rootsSeat, ctx.toolCallId);
|
|
97
103
|
const a = args;
|
|
98
104
|
const signal = ctx.signal;
|
|
99
105
|
const rootPrefix = rootCanonical.replace(/[\\/]+$/, "") + (rootCanonical.includes("\\") ? "\\" : "/");
|
|
100
106
|
let start = rootCanonical;
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
const scopeSpelling = declaredTarget === undefined ? undefined : pathTargetValue(a, declaredTarget);
|
|
108
|
+
if (scopeSpelling !== undefined) {
|
|
109
|
+
const baseCwd = pathTargetBaseOf(declaredTarget, { root: rootCanonical, cwd: cwdRef?.current });
|
|
110
|
+
const r = await resolveKey(env, rootCanonical, scopeSpelling, signal, baseCwd, additionalRoots, readAdmission && ((key) => readAdmission(ctx.toolCallId, key)), readDeny, readFace);
|
|
103
111
|
if (!r.ok)
|
|
104
112
|
return errorResult(violationText("RepoMap", r.violation), violationDetails(r.violation));
|
|
105
113
|
start = r.key;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DenyFace } from "./read-deny.js";
|
|
1
2
|
import { type HomeViewSource } from "./read-deny-home.js";
|
|
2
3
|
import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
|
|
3
4
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
@@ -520,12 +521,36 @@ export declare function violationDetails(v: FsViolation): {
|
|
|
520
521
|
code: "path_not_in_root";
|
|
521
522
|
target: string;
|
|
522
523
|
roots: readonly string[];
|
|
523
|
-
} |
|
|
524
|
+
} | ReadPathDeniedCard | undefined;
|
|
525
|
+
/** The read face's refusal card: which target, which deny pattern. */
|
|
526
|
+
export type ReadPathDeniedCard = {
|
|
524
527
|
type: "read_path_denied";
|
|
525
528
|
code: "read_path_denied";
|
|
526
529
|
target: string;
|
|
527
530
|
pattern: string;
|
|
528
|
-
}
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* The ONE mint of the `read_path_denied` card. `type` is the CC_DETAIL_TYPES discriminator, so the refusal's
|
|
534
|
+
* coordinates reach `tool_end.structured`, not just prose; `code` carries the same value for in-process readers.
|
|
535
|
+
* Every site that refuses a read on a deny row — the tools' own resolution, the shell station, the gate's rule lanes —
|
|
536
|
+
* mints it here.
|
|
537
|
+
*/
|
|
538
|
+
export declare function readPathDeniedCard(target: string, pattern: string): ReadPathDeniedCard;
|
|
539
|
+
/**
|
|
540
|
+
* Where one deny row refused a call: the row's text, the view of the target it met, and the face the call is on.
|
|
541
|
+
* Minted once, by the judge that found the row; every exit of the gate reads it and judges nothing again.
|
|
542
|
+
*/
|
|
543
|
+
export interface PathDenialEvidence {
|
|
544
|
+
readonly rule: string;
|
|
545
|
+
readonly view: string;
|
|
546
|
+
readonly face: DenyFace;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* The card a gate-side path refusal carries: the read face's own ({@link readPathDeniedCard}); the write face has none
|
|
550
|
+
* (as in the tools' own resolution, where a write refusal's `write_path_denied` code carries no card). The card's
|
|
551
|
+
* presence follows the FACE, never the station that refused.
|
|
552
|
+
*/
|
|
553
|
+
export declare function pathDenialCardOf(evidence: PathDenialEvidence | undefined): ReadPathDeniedCard | undefined;
|
|
529
554
|
/**
|
|
530
555
|
* Does `p` sit inside ANY of the canonical containment roots? Exposed for the Bash
|
|
531
556
|
* tool's post-`cd` disclosure note (fs-bash.ts): the note reuses the SAME segment-aware `within`
|
|
@@ -534,6 +559,20 @@ export declare function violationDetails(v: FsViolation): {
|
|
|
534
559
|
* design; fs-bash canonical-confirms before claiming "outside").
|
|
535
560
|
*/
|
|
536
561
|
export declare function withinAnyRoot(rootsCanonical: readonly string[], p: string): boolean;
|
|
562
|
+
/**
|
|
563
|
+
* THE store-isolation rule, one judgment for every root admission point (the Artifact mount over the prepare-time
|
|
564
|
+
* roots, an in-flight read-root grant over the granted root, a person's read directory where a run's read roots are
|
|
565
|
+
* read): a root that CONTAINS the local artifact store, lies INSIDE it, or IS it hands the file tools the store's ledger
|
|
566
|
+
* and stored files. Returns the first such root, or undefined.
|
|
567
|
+
*
|
|
568
|
+
* `storePhysical` is the store's physical path on THIS process's filesystem (`physicalPathOfExisting`, resolved
|
|
569
|
+
* once per run by the Artifact phase); a root is the execution env's canonical key. The judgment touches no
|
|
570
|
+
* filesystem — the grant runs after the approver's wait under the call's cut, and a synchronous lookup on a stalled
|
|
571
|
+
* store mount would block the event loop past that cut. The two are compared as identities only when both spell the
|
|
572
|
+
* same family (`within` reads both under the root's family); a root of the other family names a filesystem the store
|
|
573
|
+
* cannot be on, and no claim is made against it.
|
|
574
|
+
*/
|
|
575
|
+
export declare function rootAdmittingArtifactStore(storePhysical: string, rootsCanonical: readonly string[]): string | undefined;
|
|
537
576
|
/**
|
|
538
577
|
* The escape-hatch tail for the `path_not_in_root` refusal: like its three sibling hints below, it
|
|
539
578
|
* names a sanctioned next step, so the model does not oscillate between "the boundary is hard" and
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -659,16 +659,25 @@ export function violationText(toolName, v) {
|
|
|
659
659
|
return `Error (${toolName}): ${v.message}`;
|
|
660
660
|
}
|
|
661
661
|
export function violationDetails(v) {
|
|
662
|
-
if (v.code === "read_path_denied" && v.target !== undefined && v.pattern !== undefined)
|
|
663
|
-
return
|
|
664
|
-
}
|
|
662
|
+
if (v.code === "read_path_denied" && v.target !== undefined && v.pattern !== undefined)
|
|
663
|
+
return readPathDeniedCard(v.target, v.pattern);
|
|
665
664
|
return v.code === "path_not_in_root" && v.target !== undefined
|
|
666
665
|
? { type: "path_not_in_root", code: "path_not_in_root", target: v.target, roots: v.roots ?? [] }
|
|
667
666
|
: undefined;
|
|
668
667
|
}
|
|
668
|
+
export function readPathDeniedCard(target, pattern) {
|
|
669
|
+
return { type: "read_path_denied", code: "read_path_denied", target, pattern };
|
|
670
|
+
}
|
|
671
|
+
export function pathDenialCardOf(evidence) {
|
|
672
|
+
return evidence?.face === "read" ? readPathDeniedCard(evidence.view, evidence.rule) : undefined;
|
|
673
|
+
}
|
|
669
674
|
export function withinAnyRoot(rootsCanonical, p) {
|
|
670
675
|
return rootsCanonical.some((r) => within(r, p));
|
|
671
676
|
}
|
|
677
|
+
export function rootAdmittingArtifactStore(storePhysical, rootsCanonical) {
|
|
678
|
+
const storeFamily = pathFamilyOf({ root: storePhysical });
|
|
679
|
+
return rootsCanonical.find((root) => pathFamilyOf({ root }) === storeFamily && (withinAnyRoot([root], storePhysical) || withinAnyRoot([storePhysical], root)));
|
|
680
|
+
}
|
|
672
681
|
export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access; a deployment that wants reads or writes open everywhere can declare readFace / writeFace: \"open\" instead of listing directories.)";
|
|
673
682
|
export function requireRead(state, key) {
|
|
674
683
|
const entry = state.get(key);
|
|
@@ -44,6 +44,14 @@ export type ShellReadRefusalDetails = NonNullable<ReturnType<typeof violationDet
|
|
|
44
44
|
export interface ShellReadVerdict {
|
|
45
45
|
readonly disposition: ShellReadDisposition;
|
|
46
46
|
readonly finding: ShellReadFinding;
|
|
47
|
+
/** The environment did not answer the judgment — a resolver, a listing or an identity lookup gave no answer, a bound was
|
|
48
|
+
* reached, the judge itself did not answer: present on the `unanswered` finding the fold or the fallback gives. The gate
|
|
49
|
+
* classes the question as shell text the engine cannot read. */
|
|
50
|
+
readonly environmentSilent?: true;
|
|
51
|
+
/** Rows the station's boundary stands on could not be read (`ShellReadBoundary.unreadPartitions`, or a boundary the seat
|
|
52
|
+
* could not hand over): present on ANY verdict judged beside them, whatever its finding. The gate classes the question as
|
|
53
|
+
* a rule that could not be read. */
|
|
54
|
+
readonly rowsUnread?: true;
|
|
47
55
|
/** `write` ⇔ the finding is about where the command WRITES — its witness is a write target (a redirection's, or a word
|
|
48
56
|
* the program's row writes), judged against the write roots. Absent ⇒ a read's finding (or `vouch`). */
|
|
49
57
|
readonly face?: "write";
|
|
@@ -146,9 +154,10 @@ export interface ShellReadStationOptions {
|
|
|
146
154
|
readonly tools: ReadonlySet<string>;
|
|
147
155
|
/** The trust-policy seat a tool is judged on. */
|
|
148
156
|
readonly seatOf: (toolName: string) => ShellReadStationSeat;
|
|
149
|
-
/** The boundary for a call, read per call (the tracked working directory moves
|
|
150
|
-
* boundary declared, nothing to be outside of — the station
|
|
151
|
-
|
|
157
|
+
/** The boundary for a call, read per call (the tracked working directory moves; the call's own tables, by its id —
|
|
158
|
+
* `undefined` where no call is judged). `undefined` ⇒ no boundary declared, nothing to be outside of — the station
|
|
159
|
+
* vouches. */
|
|
160
|
+
readonly boundaryOf: (toolName: string, cwd: string | undefined, toolCallId: string | undefined) => ShellReadBoundary | undefined;
|
|
152
161
|
/** The listed read verbs (decides whether a segment is read by verb slots or word by word). */
|
|
153
162
|
readonly readers?: ReadonlySet<string>;
|
|
154
163
|
/** `true` ⇔ every root `boundaryOf` returns is already a canonical identity (the Runner's): the station never
|
|
@@ -286,15 +286,17 @@ function verdictOf(tool, seat, fold, judged, boundary) {
|
|
|
286
286
|
const paths = outsidePathsOf(judged);
|
|
287
287
|
const unestablished = fold.unestablished === true ? { unestablished: true } : {};
|
|
288
288
|
const writes = fold.witness?.read.candidate.face === "write" ? { face: "write" } : {};
|
|
289
|
+
const by = fold.finding === "unanswered" ? { environmentSilent: true } : {};
|
|
289
290
|
if (disposition === "refuse") {
|
|
290
291
|
const r = refusalOf(tool, fold, boundary, paths);
|
|
291
|
-
return { disposition, finding: fold.finding, ...writes, sentence: r.sentence, ...(r.details !== undefined ? { details: r.details } : {}) };
|
|
292
|
+
return { disposition, finding: fold.finding, ...by, ...writes, sentence: r.sentence, ...(r.details !== undefined ? { details: r.details } : {}) };
|
|
292
293
|
}
|
|
293
294
|
const grant = fold.finding === "outside_roots" ? readRootGrantCandidateOf(judged, boundary) : undefined;
|
|
294
295
|
const containment = fold.finding === "outside_roots" || fold.finding === "unestablishable" || fold.finding === "unanswered";
|
|
295
296
|
return {
|
|
296
297
|
disposition,
|
|
297
298
|
finding: fold.finding,
|
|
299
|
+
...by,
|
|
298
300
|
...writes,
|
|
299
301
|
...unestablished,
|
|
300
302
|
sentence: findingAccount(fold, boundary),
|
|
@@ -404,6 +406,16 @@ export function createShellReadStation(opts) {
|
|
|
404
406
|
admissions.set(toolCallId, a);
|
|
405
407
|
};
|
|
406
408
|
const memberOf = (call) => readStationMemberOf(call, opts.tools)?.name;
|
|
409
|
+
const unansweredOver = (toolName, seat, cause, boundary, silence) => {
|
|
410
|
+
if (boundary === undefined || (boundary !== "unread" && !(anyFaceGuards(boundary) || boundary.writeTargetJudge !== undefined)))
|
|
411
|
+
return { disposition: "vouch", finding: "none" };
|
|
412
|
+
const disposition = decideShellRead("unanswered", seat);
|
|
413
|
+
const account = `${cause}, so where this command reads could not be established ${NOT_AUTO_ALLOWED}`;
|
|
414
|
+
const details = unestablishedDetails();
|
|
415
|
+
if (disposition === "refuse")
|
|
416
|
+
return { disposition, finding: "unanswered", ...silence, sentence: `Error (${toolName}): ${account}. ${BASH_READONLY_CONFINEMENT_NOTE}`, details };
|
|
417
|
+
return { disposition, finding: "unanswered", ...silence, sentence: account, details };
|
|
418
|
+
};
|
|
407
419
|
const check = {
|
|
408
420
|
judge(call, args, cwd, signal, callOpts) {
|
|
409
421
|
const member = memberOf(call);
|
|
@@ -412,7 +424,7 @@ export function createShellReadStation(opts) {
|
|
|
412
424
|
const command = shellCommandOf(args);
|
|
413
425
|
if (command === undefined)
|
|
414
426
|
return null;
|
|
415
|
-
const boundary = opts.boundaryOf(member, cwd);
|
|
427
|
+
const boundary = opts.boundaryOf(member, cwd, callOpts.toolCallId);
|
|
416
428
|
if (boundary === undefined)
|
|
417
429
|
return null;
|
|
418
430
|
const seat = callOpts.seat ?? opts.seatOf(member);
|
|
@@ -420,22 +432,23 @@ export function createShellReadStation(opts) {
|
|
|
420
432
|
const run = await runShellReadStation(call.toolName, command, boundary, seat, readers, establisherFor(boundary.roots), opts.env, signal);
|
|
421
433
|
if (run.verdict.disposition !== "refuse" && callOpts.toolCallId !== undefined)
|
|
422
434
|
remember(callOpts.toolCallId, { toolName: member, boundary: run.boundary, established: run.established, command });
|
|
423
|
-
|
|
435
|
+
if (boundary.unreadPartitions !== true)
|
|
436
|
+
return run.verdict;
|
|
437
|
+
return run.verdict.disposition === "vouch" ? unansweredOver(call.toolName, seat, "the rules this check stands on could not all be read", boundary, { rowsUnread: true }) : { ...run.verdict, rowsUnread: true };
|
|
424
438
|
})();
|
|
425
439
|
},
|
|
426
440
|
unanswered(call, cause) {
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return { disposition, finding: "unanswered", sentence: account, details };
|
|
441
|
+
const member = memberOf(call) ?? call.toolName;
|
|
442
|
+
const boundary = (() => {
|
|
443
|
+
try {
|
|
444
|
+
return opts.boundaryOf(member, undefined, undefined);
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return "unread";
|
|
448
|
+
}
|
|
449
|
+
})();
|
|
450
|
+
const rowsUnread = boundary === "unread" || boundary?.unreadPartitions === true ? { rowsUnread: true } : {};
|
|
451
|
+
return unansweredOver(call.toolName, opts.seatOf(member), cause, boundary, { environmentSilent: true, ...rowsUnread });
|
|
439
452
|
},
|
|
440
453
|
};
|
|
441
454
|
const admit = async (toolName, toolCallId, command, signal) => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* operand, the content a program writes through an operand, a `git diff` operand) and the shape tier's two readings of "does
|
|
6
6
|
* this redirection write" (the read-only reader, the classify faces' data-free redirections).
|
|
7
7
|
*
|
|
8
|
-
* The table is the reference implementation's own device predicate (CC 2.1.296 `wwe
|
|
8
|
+
* The table is the reference implementation's own device predicate (CC 2.1.296 `wwe` (cli296.js:249645) @18511591): the null
|
|
9
9
|
* device, the three standard streams, the terminal, any `/dev/fd/<digits>`, and `/proc/self/fd/{0,1,2}`. A word is such a
|
|
10
10
|
* spelling AS WRITTEN (quotes removed, nothing folded: `/dev/./null` is a path, not the null device).
|
|
11
11
|
*
|
|
@@ -74,7 +74,7 @@ export declare function dispositionOfCell(cell: ShellReadCell, narrowedReadFace:
|
|
|
74
74
|
* ask is MANDATED and no automatic lane answers; on the OPEN face (where the station asks only because a deny list has
|
|
75
75
|
* rows), and for a written target alone (a narrowed WRITE face fences what is written; it is no request to be asked about
|
|
76
76
|
* every word the floor cannot place), it is the ORDINARY ask — the reference implementation's plain permission ask (CC
|
|
77
|
-
* 2.1.296 `sR
|
|
77
|
+
* 2.1.296 `sR` (cli296.js:242691) @13116941, reason `other`), which a whole-tool allow (`N9` (cli296.js:249675) @18717070), bypass (`$pn` (cli296.js:249675) @18716980)
|
|
78
78
|
* and the auto-mode classifier release and a prefix allow does not.
|
|
79
79
|
* · `declined` — the one deliberate fail-open of this table: the scan could not read the program bash
|
|
80
80
|
* will run, or the operands arrive on a stream. On the full shell it is an ORDINARY ask — an answerer
|
|
@@ -159,6 +159,10 @@ export interface ShellReadCellContext {
|
|
|
159
159
|
*/
|
|
160
160
|
export interface ShellReadBoundary extends BashReadonlyRootBoundary {
|
|
161
161
|
readonly alsoInside?: (canonicalTarget: string) => Promise<boolean>;
|
|
162
|
+
/** A partition of the rules the deny seats are compiled from could not be read: a guard of unknown reach. The station
|
|
163
|
+
* guards ({@link stationGuards}) and vouches for nothing against this boundary — what it would vouch for is answered as
|
|
164
|
+
* a judgment it could not make. */
|
|
165
|
+
readonly unreadPartitions?: true;
|
|
162
166
|
/** The deny table's WRITE face (`compileWriteDeny`, read-deny.ts): judges a command's write targets (candidates marked
|
|
163
167
|
* `face: "write"`) and returns the matched row's rule, or null. PRESENT-IFF there is something to judge. */
|
|
164
168
|
readonly writeDenyMatch?: (resolvedPath: string) => string | null;
|
|
@@ -303,12 +307,13 @@ export interface JudgedPlan {
|
|
|
303
307
|
}
|
|
304
308
|
/**
|
|
305
309
|
* Does the station GUARD anything for this boundary — a closed face (roots: containment is what it guards; an absent
|
|
306
|
-
* face reads as roots, as {@link containmentOf} does), or a deny table with rows (`denyMatch` is present exactly then)
|
|
310
|
+
* face reads as roots, as {@link containmentOf} does), or a deny table with rows (`denyMatch` is present exactly then),
|
|
311
|
+
* or rows it could not read (`unreadPartitions`: a guard of unknown reach)?
|
|
307
312
|
* Where it guards nothing (an open face, no deny row), a read it could not establish has nothing to be confirmed
|
|
308
313
|
* against: no finding. The deny judgment itself is untouched (with no row there is nothing to hit). The READ face's
|
|
309
314
|
* question only: a write target folds by {@link writeFaceGuards}.
|
|
310
315
|
*/
|
|
311
|
-
export declare function stationGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch">): boolean;
|
|
316
|
+
export declare function stationGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch" | "unreadPartitions">): boolean;
|
|
312
317
|
/**
|
|
313
318
|
* Does the station guard anything on the WRITE face — the same shape for the write seats: the write face in force runs
|
|
314
319
|
* the roots-containment step ({@link skipsRootsContainment}, the structured write tools' reading; absent ⇒ roots), or
|
|
@@ -319,7 +324,7 @@ export declare function writeFaceGuards(boundary: Pick<ShellReadBoundary, "write
|
|
|
319
324
|
/** Does the station guard anything on EITHER face? What a finding about the text's own structure folds by — a text the
|
|
320
325
|
* lexer could not read, a program it cannot name, program text it does not read, a working directory it lost, a text the
|
|
321
326
|
* shell re-enters — since what such a text reads AND what it writes are both unknown. */
|
|
322
|
-
export declare function anyFaceGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch" | "writeFace" | "writeDenyMatch">): boolean;
|
|
327
|
+
export declare function anyFaceGuards(boundary: Pick<ShellReadBoundary, "face" | "denyMatch" | "unreadPartitions" | "writeFace" | "writeDenyMatch">): boolean;
|
|
323
328
|
/** The command-level fold and the finding that produced it. `witness` is absent for `none`; for `declined`
|
|
324
329
|
* the segment that declined stands in for a candidate. */
|
|
325
330
|
export interface ShellReadFold {
|
|
@@ -104,7 +104,7 @@ function judgeWrite(read, boundary) {
|
|
|
104
104
|
return { read, kind: containment === "outside" && !skipsRootsContainment(boundary.writeFace) ? "outside_roots" : "none", containment };
|
|
105
105
|
}
|
|
106
106
|
export function stationGuards(boundary) {
|
|
107
|
-
return boundary.face !== "open" || boundary.denyMatch !== undefined;
|
|
107
|
+
return boundary.face !== "open" || boundary.denyMatch !== undefined || boundary.unreadPartitions === true;
|
|
108
108
|
}
|
|
109
109
|
export function writeFaceGuards(boundary) {
|
|
110
110
|
return !skipsRootsContainment(boundary.writeFace) || boundary.writeDenyMatch !== undefined;
|