@sema-agent/core 7.49.4 → 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 +152 -0
- package/dist/agents/observer.js +3 -10
- package/dist/agents/peer-directory.d.ts +8 -3
- package/dist/agents/peer-directory.js +5 -3
- 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/posix-mode-face.d.ts +10 -0
- package/dist/core/posix-mode-face.js +3 -0
- 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 +127 -1
- 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/stores/file/permission-rule-store.d.ts +13 -1
- package/dist/stores/file/permission-rule-store.js +40 -21
- 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.49.5.json +104 -0
- 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/fs-read.js
CHANGED
|
@@ -4,17 +4,22 @@ import { sha256, resolveKey, violationText, violationDetails, binaryMagicFormat,
|
|
|
4
4
|
import { decodeTextBytes } from "./encoding.js";
|
|
5
5
|
import { isNotebookPath, parseNotebookCells, renderNotebookCells, stripNotebookImageData, NOTEBOOK_IMAGE_BASE64_BUDGET } from "./notebook.js";
|
|
6
6
|
import { MCP_IMAGE_MAX_BASE64, IMAGE_TARGET_RAW_SIZE } from "../../core/mcp.js";
|
|
7
|
-
import { PDF_MAX_PAGES_PER_READ, pdfMagicMatches } from "./pdf.js";
|
|
7
|
+
import { countPdfPagesFromBytes, PDF_MAX_PAGES_PER_READ, PDF_TARGET_RAW_SIZE, pdfMagicMatches } from "./pdf.js";
|
|
8
8
|
import { MAX_READ_BYTES, SLICED_READ_MAX_BYTES, NO_DOWNSAMPLER_IMAGE_CAP_HINT, resolveAutoDownsampler, MAX_READ_OUTPUT_CHARS, FILE_PATH_PARAMS, countLines, seededFileUnchangedReminder, enoentMessage, } from "./fs-shared.js";
|
|
9
9
|
import { readPdfFile } from "./fs-pdf.js";
|
|
10
|
-
import {
|
|
10
|
+
import { seatForCall, seatReady } from "./read-deny.js";
|
|
11
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
12
|
+
import { MARKED_BLOCK_AFTER_DATA, openSystemReminder } from "../../core/reminder-mint.js";
|
|
11
13
|
import { planReminderDisclosure, commitReminderDisclosureEffects, renderReminderDisclosureTrailer, } from "../../core/reminder-disclosure.js";
|
|
12
14
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
13
15
|
import { admitImageBytes, imageReadCap } from "./image-admission.js";
|
|
16
|
+
import { createEntryDenyJudge, enumerateDirectory } from "./dir-entries.js";
|
|
17
|
+
import { FileError } from "../../internal/harness.js";
|
|
14
18
|
export const READ_OUTCOMES = Object.freeze(["attached", "partial", "unchanged", "not_attached"]);
|
|
19
|
+
const UNBINDABLE = "this environment cannot bind a read to the file that was checked, so it was not read";
|
|
15
20
|
const NO_EFFECTS = () => ({ disclosure: [] });
|
|
16
21
|
export function createModelFileReader(opts) {
|
|
17
|
-
const { env, state, rootCanonical, cwdRef,
|
|
22
|
+
const { env, state, rootCanonical, cwdRef, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readFace, reminderMark, readDeny: readDenySeat, additionalRoots: rootsSeat } = opts;
|
|
18
23
|
const windows = opts.disclosureWindows ?? new Map();
|
|
19
24
|
const refuse = (envelope, kind) => ({
|
|
20
25
|
outcome: "not_attached",
|
|
@@ -29,7 +34,13 @@ export function createModelFileReader(opts) {
|
|
|
29
34
|
const verdict = plan.verdict;
|
|
30
35
|
return { ...(verdict.reminder !== undefined || verdict.envelopeTags !== undefined ? { note: { kind: "disclosure", verdict } } : {}), effects: plan.effects };
|
|
31
36
|
};
|
|
32
|
-
const
|
|
37
|
+
const resolve = async (path, ctx) => {
|
|
38
|
+
if (ctx.toolCallId === undefined)
|
|
39
|
+
await seatReady(rootsSeat, ctx.signal);
|
|
40
|
+
return resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, seatForCall(rootsSeat, ctx.toolCallId), bgOutputReadExemption === undefined ? undefined : (key) => bgOutputReadExemption(key, ctx), seatForCall(readDenySeat, ctx.toolCallId), readFace);
|
|
41
|
+
};
|
|
42
|
+
const readForModel = async (args, ctx, opts) => {
|
|
43
|
+
const readDeny = seatForCall(readDenySeat, ctx.toolCallId);
|
|
33
44
|
const { offset, limit, pages } = (args ?? {});
|
|
34
45
|
const path = fileArgPath(args);
|
|
35
46
|
if (path === undefined)
|
|
@@ -42,29 +53,35 @@ export function createModelFileReader(opts) {
|
|
|
42
53
|
details: { type: "file_unchanged", reason, file: { filePath: path, startLine, endLine, totalLines } },
|
|
43
54
|
effects: NO_EFFECTS(),
|
|
44
55
|
});
|
|
45
|
-
const r = await
|
|
56
|
+
const r = await resolve(path, ctx);
|
|
46
57
|
if (!r.ok)
|
|
47
58
|
return refuse(errorResult(violationText("Read", r.violation), violationDetails(r.violation)));
|
|
59
|
+
if (opts?.bound !== undefined && r.key !== opts.bound.key) {
|
|
60
|
+
return refuse(errorResult(`Error (Read): "${path.slice(0, 300)}" resolved to a different file while it was being checked, so it was not read.`));
|
|
61
|
+
}
|
|
62
|
+
const bound = opts?.bound;
|
|
63
|
+
const readBytes = (key, laneCap) => bound === undefined ? env.readBinaryFile(key, ctx.signal) : bound.object.readBytes(laneCap, ctx.signal);
|
|
64
|
+
const info = bound === undefined ? await env.fileInfo(r.key, ctx.signal) : await bound.object.size().then((s) => (s.ok ? { ok: true, value: { kind: bound.object.kind, size: s.value } } : s));
|
|
65
|
+
if (info.ok && info.value.kind === "directory") {
|
|
66
|
+
return refuse(errorResult(`Error (Read): "${path}" is a directory, not a file; use glob/grep or list it with the shell.`), "directory");
|
|
67
|
+
}
|
|
48
68
|
const isNb = isNotebookPath(r.key);
|
|
49
69
|
const imageMime = imageMimeForRead(r.key);
|
|
50
70
|
if (imageMime !== undefined) {
|
|
51
|
-
const meta =
|
|
71
|
+
const meta = info;
|
|
52
72
|
if (!meta.ok) {
|
|
53
73
|
const ex = await env.exists(r.key, ctx.signal);
|
|
54
74
|
if (ex.ok && !ex.value)
|
|
55
75
|
return refuse(errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal, readDeny)}`), "image");
|
|
56
76
|
return refuse(errorResult(`Error (Read): cannot stat image "${path}" to verify its size before reading: ${meta.error.message}`), "image");
|
|
57
77
|
}
|
|
58
|
-
if (meta.value.kind === "directory") {
|
|
59
|
-
return refuse(errorResult(`Error (Read): "${path}" is a directory, not a file; use glob/grep or list it with the shell.`), "image");
|
|
60
|
-
}
|
|
61
78
|
const downsampler = imageDownsampler === false ? undefined : (imageDownsampler ?? (await resolveAutoDownsampler()));
|
|
62
79
|
const readCap = imageReadCap(downsampler);
|
|
63
80
|
const tooLargeToRead = (bytes) => `Error (Read): image "${path}" is too large to read (${bytes} bytes > ${readCap}-byte cap).` +
|
|
64
81
|
(downsampler ? "" : ` ${NO_DOWNSAMPLER_IMAGE_CAP_HINT}`);
|
|
65
82
|
if (meta.value.size > readCap)
|
|
66
83
|
return refuse(errorResult(tooLargeToRead(meta.value.size)), "image");
|
|
67
|
-
const bin = await
|
|
84
|
+
const bin = await readBytes(r.key, readCap);
|
|
68
85
|
if (!bin.ok)
|
|
69
86
|
return refuse(errorResult(`Error (Read): cannot read image "${path}": ${bin.error.message}`), "image");
|
|
70
87
|
const admitted = await admitImageBytes(bin.value, imageMime, downsampler);
|
|
@@ -96,6 +113,25 @@ export function createModelFileReader(opts) {
|
|
|
96
113
|
};
|
|
97
114
|
}
|
|
98
115
|
const pdfOf = async (preRead) => {
|
|
116
|
+
if (bound !== undefined) {
|
|
117
|
+
if (!info.ok)
|
|
118
|
+
return refuse(errorResult(`Error (Read): cannot size PDF "${path.slice(0, 300)}": ${info.error.message.slice(0, 200)}`), "pdf");
|
|
119
|
+
let held = preRead;
|
|
120
|
+
if (held === undefined && info.value.size <= PDF_TARGET_RAW_SIZE) {
|
|
121
|
+
const got = await readBytes(r.key, PDF_TARGET_RAW_SIZE);
|
|
122
|
+
if (!got.ok)
|
|
123
|
+
return refuse(errorResult(`Error (Read): cannot read PDF "${path.slice(0, 300)}": ${got.error.message.slice(0, 200)}`), "pdf");
|
|
124
|
+
held = got.value;
|
|
125
|
+
}
|
|
126
|
+
const pageCount = held !== undefined ? countPdfPagesFromBytes(held) : undefined;
|
|
127
|
+
return {
|
|
128
|
+
outcome: "attached",
|
|
129
|
+
kind: "pdf",
|
|
130
|
+
data: [],
|
|
131
|
+
notes: [{ kind: "pdf_reference", bytes: info.value.size, ...(pageCount !== undefined ? { pages: pageCount } : {}) }],
|
|
132
|
+
effects: NO_EFFECTS(),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
99
135
|
const effects = [];
|
|
100
136
|
const out = await readPdfFile(env, path, r.key, pages, ctx.signal, imageDownsampler, cwdRef?.current ?? rootCanonical, preRead, pdfCapabilities, readDeny, {
|
|
101
137
|
mark: reminderMark,
|
|
@@ -113,11 +149,7 @@ export function createModelFileReader(opts) {
|
|
|
113
149
|
return pdfOf(undefined);
|
|
114
150
|
const binaryExt = hasBinaryExtension(r.key);
|
|
115
151
|
const binaryExtRefusal = () => refuse(errorResult(`Error (Read): "${path}" appears to be a binary file (by extension); this tool reads UTF-8 text only.`), "text");
|
|
116
|
-
const info = await env.fileInfo(r.key, ctx.signal);
|
|
117
152
|
if (info.ok) {
|
|
118
|
-
if (info.value.kind === "directory") {
|
|
119
|
-
return refuse(errorResult(`Error (Read): "${path}" is a directory, not a file; use glob/grep or list it with the shell.`), "text");
|
|
120
|
-
}
|
|
121
153
|
if (info.value.size > SLICED_READ_MAX_BYTES) {
|
|
122
154
|
if (binaryExt)
|
|
123
155
|
return binaryExtRefusal();
|
|
@@ -137,7 +169,7 @@ export function createModelFileReader(opts) {
|
|
|
137
169
|
return refuse(errorResult(`Error (Read): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal, readDeny)}`), "text");
|
|
138
170
|
return refuse(errorResult(`Error (Read): cannot determine the type of "${path.slice(0, 300)}" (stat failed: ${info.error.message.slice(0, 200)}); refusing to open it — if it is a FIFO/socket/device, reading it would hang. Retry if this was transient, or inspect it with Bash (e.g. \`file\`, \`ls -l\`).`), "text");
|
|
139
171
|
}
|
|
140
|
-
const readBin = await
|
|
172
|
+
const readBin = await readBytes(r.key, !isNb && limit === undefined ? MAX_READ_BYTES : SLICED_READ_MAX_BYTES);
|
|
141
173
|
if (!readBin.ok)
|
|
142
174
|
return refuse(errorResult(`Error (Read): cannot read "${path}": ${readBin.error.message}`), "text");
|
|
143
175
|
const readSize = readBin.value.byteLength;
|
|
@@ -302,6 +334,16 @@ export function createModelFileReader(opts) {
|
|
|
302
334
|
const committed = new WeakSet();
|
|
303
335
|
return {
|
|
304
336
|
readForModel,
|
|
337
|
+
async resolveTarget(path, ctx) {
|
|
338
|
+
const r = await resolve(path, ctx).catch((err) => ({ ok: false, violation: { code: "invalid", message: thrownText(err) } }));
|
|
339
|
+
return r.ok ? { ok: true, key: r.key } : { ok: false, refusal: errorResult(violationText("Read", r.violation), violationDetails(r.violation)) };
|
|
340
|
+
},
|
|
341
|
+
openBound: (key, signal) => (env.openBound !== undefined ? env.openBound(key, signal) : Promise.resolve({ ok: false, error: new FileError("not_supported", UNBINDABLE, key) })),
|
|
342
|
+
listDirectory: (bound, signal) => enumerateDirectory(env, bound.key, readDenySeat !== undefined ? createEntryDenyJudge(env, readDenySeat, signal) : undefined, signal, () => bound.object.list(signal)),
|
|
343
|
+
planDisclosure(segments, throttleKey) {
|
|
344
|
+
const d = disclosureOf(segments, "read", throttleKey);
|
|
345
|
+
return { ...(d.note !== undefined ? { note: d.note } : {}), effects: d.effects };
|
|
346
|
+
},
|
|
305
347
|
commit(result, at) {
|
|
306
348
|
if (committed.has(result))
|
|
307
349
|
throw new Error("ModelFileReader.commit: this read result was already committed — a delivery commits its read once");
|
|
@@ -348,6 +390,8 @@ export function renderReadNoteForTool(note, mark) {
|
|
|
348
390
|
}
|
|
349
391
|
case "disclosure":
|
|
350
392
|
return renderReminderDisclosureTrailer(note.verdict, mark) ?? "";
|
|
393
|
+
case "pdf_reference":
|
|
394
|
+
return `[PDF not read: ${note.bytes !== undefined ? `${note.bytes} bytes` : "size unknown"}, ${note.pages !== undefined ? `${note.pages} page${note.pages === 1 ? "" : "s"}` : "page count unknown"}. Read it with the pages parameter, at most ${PDF_MAX_PAGES_PER_READ} pages per request.]`;
|
|
351
395
|
}
|
|
352
396
|
}
|
|
353
397
|
export function renderReadToolResult(r, mark) {
|
|
@@ -373,7 +417,7 @@ export function renderReadToolResult(r, mark) {
|
|
|
373
417
|
return {
|
|
374
418
|
content: [
|
|
375
419
|
...(noCells !== undefined ? [{ type: "text", text: renderReadNoteForTool(noCells, mark) }] : r.data),
|
|
376
|
-
...(trailer !== undefined ? [{ type: "text", text: trailer }] : []),
|
|
420
|
+
...(trailer !== undefined ? [{ type: "text", text: `${MARKED_BLOCK_AFTER_DATA}${trailer}` }] : []),
|
|
377
421
|
],
|
|
378
422
|
details: r.details,
|
|
379
423
|
};
|
|
@@ -383,14 +427,14 @@ export function renderReadToolResult(r, mark) {
|
|
|
383
427
|
const header = noteOf("partial_page") ?? noteOf("partial_chars") ?? noteOf("range");
|
|
384
428
|
const body = r.data[0]?.text ?? "";
|
|
385
429
|
return {
|
|
386
|
-
content: `${fallback !== undefined ? renderReadNoteForTool(fallback, mark) : ""}${header !== undefined ? renderReadNoteForTool(header, mark) : ""}${body}${trailer !== undefined ?
|
|
430
|
+
content: `${fallback !== undefined ? renderReadNoteForTool(fallback, mark) : ""}${header !== undefined ? renderReadNoteForTool(header, mark) : ""}${body}${trailer !== undefined ? `${MARKED_BLOCK_AFTER_DATA}${trailer}` : ""}`,
|
|
387
431
|
details: r.details,
|
|
388
432
|
};
|
|
389
433
|
}
|
|
390
434
|
}
|
|
391
435
|
}
|
|
392
|
-
export function createReadFileTool(env, state, rootCanonical, cwdRef, additionalRoots, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readDeny, readFace, reminderMark, reminderDisclosureCounts) {
|
|
393
|
-
const reader = createModelFileReader({
|
|
436
|
+
export function createReadFileTool(env, state, rootCanonical, cwdRef, additionalRoots, imageDownsampler, pdfCapabilities, bgOutputReadExemption, readDeny, readFace, reminderMark, reminderDisclosureCounts, sharedReader) {
|
|
437
|
+
const reader = sharedReader ?? createModelFileReader({
|
|
394
438
|
env,
|
|
395
439
|
state,
|
|
396
440
|
rootCanonical,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import { type RootsSeat } from "./safety.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type ReadDenyMatcher } from "./read-deny.js";
|
|
4
4
|
import type { ContainmentFace } from "./containment-face.js";
|
|
5
5
|
import type { CwdRef } from "./fs-shared.js";
|
|
6
6
|
/**
|
|
@@ -12,5 +12,5 @@ import type { CwdRef } from "./fs-shared.js";
|
|
|
12
12
|
* honesty boundary.
|
|
13
13
|
*/
|
|
14
14
|
export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
|
|
15
|
-
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string,
|
|
16
|
-
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string,
|
|
15
|
+
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, rootsSeat?: RootsSeat, denySeat?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef, readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean): AgentTool;
|
|
16
|
+
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, rootsSeat?: RootsSeat, denySeat?: ReadDenyMatcher, readFace?: ContainmentFace, cwdRef?: CwdRef, readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean): AgentTool;
|
|
@@ -2,6 +2,7 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
3
3
|
import { clipSpelling, pathFamilyOf, resolveKey, violationText, violationDetails, WIN32_NAMESPACE_REFUSAL_TEXT } from "./safety.js";
|
|
4
4
|
import { runGrepDetailed, runGlobDetailed, invalidGlobTokens } from "./search.js";
|
|
5
|
+
import { seatForCall } from "./read-deny.js";
|
|
5
6
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
6
7
|
import { pathTargetBaseOf, splitAbsoluteGlobPattern } from "../../core/effective-path-target.js";
|
|
7
8
|
import { pathTargetValue } from "../../core/tool-registry.js";
|
|
@@ -74,7 +75,7 @@ export function grepDetailFields(text, mode, offset, structuredRows) {
|
|
|
74
75
|
}
|
|
75
76
|
return detailFields;
|
|
76
77
|
}
|
|
77
|
-
export function createGrepTool(env, rootCanonical,
|
|
78
|
+
export function createGrepTool(env, rootCanonical, rootsSeat, denySeat, readFace, cwdRef, readAdmission) {
|
|
78
79
|
const declaredTarget = toolFace("grep").pathTarget;
|
|
79
80
|
return defineTool({
|
|
80
81
|
name: "Grep",
|
|
@@ -112,6 +113,8 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
112
113
|
offset: Type.Optional(Type.Number({ description: "Skip the first N output lines (pagination partner of head_limit)." })),
|
|
113
114
|
}),
|
|
114
115
|
execute: async (args, ctx) => {
|
|
116
|
+
const readDeny = seatForCall(denySeat, ctx.toolCallId);
|
|
117
|
+
const additionalRoots = seatForCall(rootsSeat, ctx.toolCallId);
|
|
115
118
|
const retired = args;
|
|
116
119
|
for (const [name, repair] of [
|
|
117
120
|
["regex", "the `pattern` is ALWAYS evaluated as a regular expression (ripgrep semantics) — drop the key; to match text literally, escape the regex metacharacters in `pattern`"],
|
|
@@ -177,7 +180,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
177
180
|
},
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
|
-
export function createGlobTool(env, rootCanonical,
|
|
183
|
+
export function createGlobTool(env, rootCanonical, rootsSeat, denySeat, readFace, cwdRef, readAdmission) {
|
|
181
184
|
const declaredTarget = toolFace("glob").pathTarget;
|
|
182
185
|
return defineTool({
|
|
183
186
|
name: "Glob",
|
|
@@ -201,6 +204,8 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
201
204
|
max_results: Type.Optional(Type.Number({ description: "Cap results (default 500)." })),
|
|
202
205
|
}),
|
|
203
206
|
execute: async (args, ctx) => {
|
|
207
|
+
const readDeny = seatForCall(denySeat, ctx.toolCallId);
|
|
208
|
+
const additionalRoots = seatForCall(rootsSeat, ctx.toolCallId);
|
|
204
209
|
let pattern = args.pattern;
|
|
205
210
|
let path = declaredTarget === undefined ? undefined : pathTargetValue(args, declaredTarget);
|
|
206
211
|
const { max_results } = args;
|
|
@@ -2,7 +2,7 @@ import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
|
2
2
|
import type { BeforeWriteHook, FileEditedHook, TrackFileEditHook } from "../../core/types.js";
|
|
3
3
|
import { type ReadFileState } from "./safety.js";
|
|
4
4
|
import type { ContainmentFace } from "./containment-face.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type WriteDenyMatcher } from "./read-deny.js";
|
|
6
6
|
import { type CwdRef } from "./fs-shared.js";
|
|
7
7
|
/**
|
|
8
8
|
* The `beforeWrite` CONTENT hook, mounted here rather than in ToolPolicy because only this band sees
|
|
@@ -3,6 +3,7 @@ import { Type } from "typebox";
|
|
|
3
3
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
4
4
|
import { sha256, resolveKey, violationText, violationDetails, requireRead, checkStale, checkEditMatch, checkNoChange, fileArgPath, resolveQuoteMatch, adaptNewStringQuotes, resolveEscapeMatch, adaptNewStringEscapes, escapeMatchWasAttempted, ESCAPE_MATCH_MISS_NOTE, deletionOldString, countOccurrences, READ_REFUSED_ESCAPE_HINT, } from "./safety.js";
|
|
5
5
|
import { decodeTextBytes, encodeTextForFile, normalizeEditText, normalizeFileText } from "./encoding.js";
|
|
6
|
+
import { seatForCall } from "./read-deny.js";
|
|
6
7
|
import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, coerceWriteArguments, ipynbRedirect, countLines, applyRecordedEdit, } from "./fs-shared.js";
|
|
7
8
|
import { toolFace } from "../../core/tool-catalog-entries.js";
|
|
8
9
|
import { thrownText } from "../../core/thrown-value.js";
|
|
@@ -112,7 +113,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
112
113
|
const path = fileArgPath(args);
|
|
113
114
|
if (path === undefined)
|
|
114
115
|
return { ok: true };
|
|
115
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
116
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
116
117
|
if (!r.ok)
|
|
117
118
|
return { ok: true };
|
|
118
119
|
if (a.old_string === a.new_string)
|
|
@@ -150,7 +151,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
150
151
|
const path = fileArgPath(args);
|
|
151
152
|
if (path === undefined)
|
|
152
153
|
return errorResult(`Error (Edit): file_path is required.`);
|
|
153
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
154
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
154
155
|
if (!r.ok)
|
|
155
156
|
return errorResult(violationText("Edit", r.violation), violationDetails(r.violation));
|
|
156
157
|
if (!batch && a.old_string === a.new_string) {
|
|
@@ -344,7 +345,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
344
345
|
return { ok: true };
|
|
345
346
|
if (ipynbRedirect("Write", path))
|
|
346
347
|
return { ok: true };
|
|
347
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
348
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
348
349
|
if (!r.ok)
|
|
349
350
|
return { ok: true };
|
|
350
351
|
const exists = await env.exists(r.key, ctx.signal);
|
|
@@ -363,7 +364,7 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
|
|
|
363
364
|
const ipynb = ipynbRedirect("Write", path);
|
|
364
365
|
if (ipynb)
|
|
365
366
|
return errorResult(ipynb);
|
|
366
|
-
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
367
|
+
const r = await resolveKey(env, rootCanonical, path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
367
368
|
if (!r.ok)
|
|
368
369
|
return errorResult(violationText("Write", r.violation), violationDetails(r.violation));
|
|
369
370
|
const exists = await env.exists(r.key, ctx.signal);
|
|
@@ -460,7 +461,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
|
|
|
460
461
|
return { ok: false, code: "invalid", message: NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE };
|
|
461
462
|
if (mode !== "insert" && !a.cell_id)
|
|
462
463
|
return { ok: false, code: "invalid", message: NOTEBOOK_CELL_ID_REQUIRED_MESSAGE };
|
|
463
|
-
const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
464
|
+
const r = await resolveKey(env, rootCanonical, a.notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
464
465
|
if (!r.ok)
|
|
465
466
|
return { ok: true };
|
|
466
467
|
const notRead = requireRead(state, r.key);
|
|
@@ -480,7 +481,7 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
|
|
|
480
481
|
return errorResult(NOTEBOOK_CELL_TYPE_REQUIRED_MESSAGE);
|
|
481
482
|
if (mode !== "insert" && !cell_id)
|
|
482
483
|
return errorResult(NOTEBOOK_CELL_ID_REQUIRED_MESSAGE);
|
|
483
|
-
const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, writeDeny, writeFace?.());
|
|
484
|
+
const r = await resolveKey(env, rootCanonical, notebook_path, ctx.signal, cwdRef?.current, additionalRoots, undefined, seatForCall(writeDeny, ctx.toolCallId), writeFace?.());
|
|
484
485
|
if (!r.ok)
|
|
485
486
|
return errorResult(violationText("NotebookEdit", r.violation), violationDetails(r.violation));
|
|
486
487
|
const notRead = requireRead(state, r.key);
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface HandsToolkitOptions {
|
|
|
57
57
|
* (`createShellReadStation`). */
|
|
58
58
|
shellReadAdmission?: ShellReadAdmission;
|
|
59
59
|
/** The gate's file read station's ADMISSION (`FileReadStation.admitted`, file-read-check.ts), handed to Read / Grep /
|
|
60
|
-
* Glob: an out-of-root target the gate put to a question and then let through is read once, on the canonical key the
|
|
60
|
+
* Glob / RepoMap: an out-of-root target the gate put to a question and then let through is read once, on the canonical key the
|
|
61
61
|
* station judged. ABSENT ⇒ no station (a library-direct toolkit): the tools refuse every out-of-root target. */
|
|
62
62
|
readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean;
|
|
63
63
|
/** Commit-message attribution trailer for the `bash` git protocol. Default NONE — attribution is a
|
|
@@ -245,6 +245,12 @@ export interface HandsToolkitOptions {
|
|
|
245
245
|
* mount may pass its own object or omit it (counting off — and the whole disclosure pipeline
|
|
246
246
|
* is off anyway when `reminderMark` is absent). */
|
|
247
247
|
reminderDisclosureCounts?: import("../../core/reminder-disclosure.js").ReminderDisclosureCounts;
|
|
248
|
+
/** Where the band hands out the leg's ONE file reader — the reader its Read tool reads through, with the same read
|
|
249
|
+
* state, throttle windows and counters — for the run's other carriers of a file to the model (a user's attachment).
|
|
250
|
+
* Written once at mount; absent ⇒ the reader stays the Read tool's own. */
|
|
251
|
+
modelFileReaderSeat?: {
|
|
252
|
+
current?: import("./fs-read.js").ModelFileReader;
|
|
253
|
+
};
|
|
248
254
|
}
|
|
249
255
|
/** Whether the band's WRITE half (Edit / Write / NotebookEdit, and the full `bash`) mounts for these options — the ONE
|
|
250
256
|
* predicate {@link createHandsToolkit} branches on and prepare-task plans by (the memory phase must know, before the
|
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)
|