@sema-agent/core 7.33.8 → 7.34.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 +339 -0
- package/dist/agents/cross-session-judge.d.ts +16 -7
- package/dist/agents/cross-session-judge.js +18 -15
- package/dist/agents/peer-session-drain.d.ts +4 -0
- package/dist/agents/peer-session-drain.js +5 -2
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +12 -3
- package/dist/brain/anthropic.js +1 -0
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +351 -154
- package/dist/core/ask-origin.js +223 -45
- package/dist/core/ask-unresolvable-notice.d.ts +5 -2
- package/dist/core/ask-unresolvable-notice.js +4 -3
- package/dist/core/auto-mode-arming.d.ts +17 -4
- package/dist/core/auto-mode-arming.js +31 -6
- package/dist/core/auto-mode-defaults.d.ts +16 -7
- package/dist/core/auto-mode-defaults.js +2 -1
- package/dist/core/auto-mode-directive.d.ts +18 -30
- package/dist/core/auto-mode-directive.js +9 -18
- package/dist/core/auto-mode-prompt.d.ts +62 -1
- package/dist/core/auto-mode-prompt.js +42 -4
- package/dist/core/auto-mode-rebuild.d.ts +2 -1
- package/dist/core/auto-mode-rebuild.js +1 -0
- package/dist/core/auto-mode.d.ts +32 -3
- package/dist/core/auto-mode.js +80 -41
- package/dist/core/checkpoint-store.d.ts +50 -35
- package/dist/core/checkpoint-store.js +58 -3
- package/dist/core/child-clamp-seats.js +1 -0
- package/dist/core/classifier-unavailable-arm.d.ts +57 -0
- package/dist/core/classifier-unavailable-arm.js +17 -0
- package/dist/core/effective-path-target.d.ts +12 -0
- package/dist/core/effective-path-target.js +18 -7
- package/dist/core/engine-notice.d.ts +53 -2
- package/dist/core/fs-write-gate-policy.d.ts +5 -1
- package/dist/core/fs-write-gate-policy.js +4 -1
- package/dist/core/gate-fold.js +17 -1
- package/dist/core/gate-lanes.js +111 -46
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +12 -0
- package/dist/core/hooks.d.ts +50 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/mode-policy.d.ts +182 -0
- package/dist/core/mode-policy.js +179 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +16 -3
- package/dist/core/permission-rule-model.js +36 -3
- package/dist/core/permission-rule-provider.d.ts +22 -11
- package/dist/core/permission-rule-provider.js +17 -7
- package/dist/core/permission-rules.d.ts +23 -1
- package/dist/core/permission-rules.js +35 -8
- package/dist/core/persisted-rule-arms.d.ts +10 -9
- package/dist/core/persisted-rule-arms.js +9 -6
- package/dist/core/present-plan-tool.d.ts +1 -1
- package/dist/core/present-plan-tool.js +3 -3
- package/dist/core/read-only-shell.d.ts +19 -19
- package/dist/core/read-only-shell.js +38 -12
- package/dist/core/retired-keys.d.ts +9 -3
- package/dist/core/retired-keys.js +5 -1
- package/dist/core/runner/classifier-ladder.d.ts +43 -1
- package/dist/core/runner/classifier-ladder.js +29 -0
- package/dist/core/runner/contracts.d.ts +37 -35
- package/dist/core/runner/denial-limit-arms.d.ts +30 -7
- package/dist/core/runner/denial-limit-arms.js +15 -9
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/gate-exit.d.ts +30 -4
- package/dist/core/runner/gate-exit.js +28 -7
- package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
- package/dist/core/runner/permission-rule-lanes.js +44 -40
- package/dist/core/runner/persisted-path-rows.d.ts +64 -0
- package/dist/core/runner/persisted-path-rows.js +89 -0
- package/dist/core/runner/prepare-artifact.d.ts +5 -1
- package/dist/core/runner/prepare-artifact.js +9 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
- package/dist/core/runner/prepare-ask-lane.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
- package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
- package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
- package/dist/core/runner/prepare-gate-stations.js +32 -19
- package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
- package/dist/core/runner/prepare-hands-readface.js +64 -53
- package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
- package/dist/core/runner/prepare-inherited-gate.js +37 -23
- package/dist/core/runner/prepare-park-ask.d.ts +2 -2
- package/dist/core/runner/prepare-park-ask.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
- package/dist/core/runner/prepare-policy-chain.js +39 -12
- package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
- package/dist/core/runner/prepare-protocol-tools.js +13 -3
- package/dist/core/runner/prepare-run-refs.d.ts +6 -6
- package/dist/core/runner/prepare-run-refs.js +9 -4
- package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
- package/dist/core/runner/prepare-safety-scan.js +19 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
- package/dist/core/runner/prepare-suspend-saga.js +6 -6
- package/dist/core/runner/prepare-task.js +51 -22
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
- package/dist/core/runner/prepare-wiring-manifest.js +5 -3
- package/dist/core/runner/read-root-grant.d.ts +9 -0
- package/dist/core/runner/read-root-grant.js +12 -0
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +24 -15
- package/dist/core/runner/run-notification-lane.js +1 -0
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/runtask.js +3 -0
- package/dist/core/runner/stream-mode-verb.d.ts +27 -0
- package/dist/core/runner/stream-mode-verb.js +19 -0
- package/dist/core/runner/turn-attachments.d.ts +3 -3
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -1
- package/dist/core/runner/write-protection-station.js +6 -3
- package/dist/core/runner-deps.d.ts +31 -34
- package/dist/core/runtime-caps.d.ts +12 -3
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +1 -1
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +39 -30
- package/dist/core/task-event.d.ts +1 -1
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +41 -89
- package/dist/core/task-stream.d.ts +16 -0
- package/dist/core/terminal-cause.js +1 -0
- package/dist/core/tighten-task-spec.d.ts +2 -2
- package/dist/core/tighten-task-spec.js +12 -14
- package/dist/core/tool-catalog-entries.js +4 -3
- package/dist/core/tool-policy.d.ts +54 -25
- package/dist/core/tool-policy.js +43 -2
- package/dist/core/tool-registry.d.ts +2 -0
- package/dist/core/tool-registry.js +3 -0
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +7 -6
- package/dist/core/types.d.ts +2 -1
- package/dist/core/wiring-manifest.d.ts +31 -5
- package/dist/core/wiring-manifest.js +28 -1
- package/dist/core/write-protect.d.ts +11 -1
- package/dist/core/write-protect.js +26 -15
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
- package/dist/engine/execution-env/node-execution-env.js +11 -1
- package/dist/engine/harness/types.d.ts +6 -7
- package/dist/engine/llm/types.d.ts +2 -4
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.js +12 -3
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-size-guideline.js +2 -2
- package/dist/tools/artifact/artifact-tool.d.ts +17 -0
- package/dist/tools/artifact/artifact-tool.js +16 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
- package/dist/tools/fs/bash-readonly-classifier.js +88 -0
- package/dist/tools/fs/containment-face.d.ts +9 -3
- package/dist/tools/fs/containment-face.js +2 -0
- package/dist/tools/fs/file-read-check.d.ts +49 -0
- package/dist/tools/fs/file-read-check.js +71 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-read.d.ts +1 -0
- package/dist/tools/fs/fs-search-tools.d.ts +2 -2
- package/dist/tools/fs/fs-search-tools.js +4 -4
- package/dist/tools/fs/fs-shared.js +20 -6
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +39 -3
- package/dist/tools/fs/index.js +38 -15
- package/dist/tools/fs/read-deny-home.d.ts +93 -0
- package/dist/tools/fs/read-deny-home.js +225 -0
- package/dist/tools/fs/read-deny.d.ts +176 -34
- package/dist/tools/fs/read-deny.js +268 -61
- package/dist/tools/fs/safety.d.ts +6 -1
- package/dist/tools/fs/safety.js +18 -5
- package/dist/tools/fs/search.d.ts +3 -0
- package/dist/tools/fs/search.js +47 -9
- package/dist/tools/fs/shell-read-check.d.ts +42 -11
- package/dist/tools/fs/shell-read-check.js +188 -13
- package/dist/tools/fs/shell-read-establish.d.ts +7 -3
- package/dist/tools/fs/shell-read-establish.js +134 -21
- package/dist/tools/fs/shell-read-identity.d.ts +7 -0
- package/dist/tools/fs/shell-read-identity.js +21 -0
- package/dist/tools/fs/shell-read-judge.d.ts +92 -32
- package/dist/tools/fs/shell-read-judge.js +43 -18
- package/dist/tools/fs/shell-read-plan.d.ts +4 -0
- package/dist/tools/fs/shell-read-plan.js +26 -4
- package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
- package/dist/tools/fs/shell-read-write-targets.js +173 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +103 -11
- package/wire-changes/core@7.34.0.json +2621 -0
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from "./fs-search-tools.js";
|
|
|
11
11
|
export * from "./bash-readonly-classifier.js";
|
|
12
12
|
export * from "./fs-bash.js";
|
|
13
13
|
export * from "./read-deny.js";
|
|
14
|
+
export { readDenyHomesOf } from "./read-deny-home.js";
|
|
14
15
|
import type { ShellReadAdmission } from "./shell-read-check.js";
|
|
15
16
|
export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
|
|
16
17
|
import { type ReadDenyEntry } from "./read-deny.js";
|
|
@@ -55,6 +56,10 @@ export interface HandsToolkitOptions {
|
|
|
55
56
|
* consumer mounting the read-only band outside the Runner must run `runToolGate` with a station of its own
|
|
56
57
|
* (`createShellReadStation`). */
|
|
57
58
|
shellReadAdmission?: ShellReadAdmission;
|
|
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
|
|
61
|
+
* station judged. ABSENT ⇒ no station (a library-direct toolkit): the tools refuse every out-of-root target. */
|
|
62
|
+
readAdmission?: (toolCallId: string | undefined, canonicalKey: string) => boolean;
|
|
58
63
|
/** Commit-message attribution trailer for the `bash` git protocol. Default NONE — attribution is a
|
|
59
64
|
* deployment identity asset, so the branded deployment sets it; pass `"Name <email>"` to advertise
|
|
60
65
|
* one, `false` (≡ unset) for no trailer section. */
|
|
@@ -167,11 +172,10 @@ export interface HandsToolkitOptions {
|
|
|
167
172
|
* time. The write faces are untouched (their guard is createSensitivePathPolicy). */
|
|
168
173
|
readDenyPatterns?: readonly ReadDenyEntry[];
|
|
169
174
|
/** The deployment's built-in-table TIER selection ({@link import("./read-deny.js").ReadDenyBuiltinConfig}
|
|
170
|
-
* `tiers` key) — the
|
|
175
|
+
* `tiers` key) — the switch for the built-in table:
|
|
171
176
|
* EXACTLY the listed tiers of {@link import("./read-deny.js").READ_FACE_BUILTIN_DENY_TABLE}
|
|
172
177
|
* are active (`[]` = none — explicit and legal); absent = the default selection
|
|
173
|
-
* ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}
|
|
174
|
-
* mount activates no built-in row.
|
|
178
|
+
* ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}: `credentials` and the home-anchored `agent-config`).
|
|
175
179
|
* Composes with {@link readDenyBuiltinExclude} (tier selection first, then row removal) and never
|
|
176
180
|
* touches the additions seats. Unknown tier names throw at wiring time. Deployment seat ONLY —
|
|
177
181
|
* deliberately not a TaskSpec key, so no task can widen below its deployment. */
|
|
@@ -181,6 +185,22 @@ export interface HandsToolkitOptions {
|
|
|
181
185
|
* of an inactive tier is a satisfied intent (no-op); a name matching NO table row throws at wiring
|
|
182
186
|
* time (never silently ignored). Deployment seat ONLY, same clamp argument as the tiers key. */
|
|
183
187
|
readDenyBuiltinExclude?: readonly string[];
|
|
188
|
+
/** The spellings of the execution environment's home a home-anchored deny row (`~/…`) is placed under
|
|
189
|
+
* ({@link import("./read-deny-home.js").readDenyHomesOf}: the declared home and its canonical spelling). Absolute path
|
|
190
|
+
* strings only — any other shape throws at wiring time, naming the seat. The Runner passes the spellings its own judge
|
|
191
|
+
* was compiled with. */
|
|
192
|
+
readDenyHomes?: readonly string[];
|
|
193
|
+
/** Where each home-anchored row's literal head leads beyond the home's spellings
|
|
194
|
+
* ({@link import("./read-deny-home.js").readDenyHomeLinksOf}, keyed by the row's pattern) — the Runner passes the
|
|
195
|
+
* locations its own judge was compiled with, so the band and the gate judge one placement.
|
|
196
|
+
* Unless a mount carries both, the band resolves the placement itself the way the Runner does (the homes it carries
|
|
197
|
+
* are used as given; {@link import("./read-deny-home.js").placeReadDenyAtHome}), and its read tools (Read / Grep /
|
|
198
|
+
* Glob / RepoMap) wait for that placement before they judge anything. A placement that fails leaves the home-anchored
|
|
199
|
+
* rows held anywhere. */
|
|
200
|
+
readDenyHomeLinks?: ReadonlyMap<string, readonly string[]>;
|
|
201
|
+
/** The permission-rule rows of the deny table ({@link import("./read-deny.js").PathRuleDenyRow}, `ToolPolicy.pathDenyRules`):
|
|
202
|
+
* the read-face rows join the structured read faces' judge beside the segment rows. The Runner passes the leg's own. */
|
|
203
|
+
pathDenyRules?: readonly import("./read-deny.js").PathRuleDenyRow[];
|
|
184
204
|
/** The READ-face containment state for the structured read faces
|
|
185
205
|
* ({@link import("./containment-face.js").ContainmentFace}; resolved through the SAME
|
|
186
206
|
* {@link import("./containment-face.js").resolveContainmentFace} order prepare-task uses). Absent ⇒ the
|
|
@@ -202,6 +222,10 @@ export interface HandsToolkitOptions {
|
|
|
202
222
|
* wins the same way the read seat's does (that mount carries no write faces at all), announced as
|
|
203
223
|
* `config.write_face_deployment_clamped`. Never affects the read faces. */
|
|
204
224
|
writeFace?: ContainmentFace;
|
|
225
|
+
/** A runner's LIVE write face — its resolution, which reads the permission mode's face on every call. Present ⇒
|
|
226
|
+
* the write tools read it per call and {@link writeFace} is not consulted (the runner resolved and screened the
|
|
227
|
+
* seats itself); absent ⇒ the toolkit's own resolution of {@link writeFace}, fixed at creation. */
|
|
228
|
+
writeFaceLive?: () => ContainmentFace;
|
|
205
229
|
/** The session's reminder provenance mark: the band's engine-minted
|
|
206
230
|
* `<system-reminder>` open tags (the Read cyber/dedup/offset/empty reminders and the gh
|
|
207
231
|
* rate-limit hint) carry it as `mark="…"`, matching the system-prompt declaration the Runner
|
|
@@ -224,6 +248,18 @@ export interface HandsToolkitOptions {
|
|
|
224
248
|
* predicate {@link createHandsToolkit} branches on and prepare-task plans by (the memory phase must know, before the
|
|
225
249
|
* band exists, whether the instruction-taught write channel will be there). `readOnly` is the verifier boundary. */
|
|
226
250
|
export declare function handsWriteHalfMounts(opts: Pick<HandsToolkitOptions, "readOnly">): boolean;
|
|
251
|
+
/** The engine's background-output read exemption over one toolkit's seats: the exact registered output file of a
|
|
252
|
+
* background command, visible to the caller's own owner / scope / session, on THIS execution env — the file the
|
|
253
|
+
* launch receipt tells the model to Read. Shared by the Read tool's fence and the file read station, so the station
|
|
254
|
+
* puts no question where the fence would admit. */
|
|
255
|
+
export declare function backgroundOutputReadExemption(registry: TaskRegistry | undefined, seat: {
|
|
256
|
+
readonly taskOwner?: string;
|
|
257
|
+
readonly taskScope?: string;
|
|
258
|
+
readonly sessionId?: string;
|
|
259
|
+
}, env: ExecutionEnv): ((canonicalKey: string, c?: {
|
|
260
|
+
taskId?: string;
|
|
261
|
+
principal?: string;
|
|
262
|
+
}) => boolean) | undefined;
|
|
227
263
|
/**
|
|
228
264
|
* Build the per-task hand tool band over an injected env + fresh per-task read state.
|
|
229
265
|
* `rootCanonical` is the already-canonicalized containment root (prepare-task resolves it once). Normal
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -11,9 +11,12 @@ export * from "./fs-search-tools.js";
|
|
|
11
11
|
export * from "./bash-readonly-classifier.js";
|
|
12
12
|
export * from "./fs-bash.js";
|
|
13
13
|
export * from "./read-deny.js";
|
|
14
|
+
export { readDenyHomesOf } from "./read-deny-home.js";
|
|
14
15
|
import { BASH_READONLY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
|
|
15
16
|
export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
|
|
16
|
-
import { compileReadDeny } from "./read-deny.js";
|
|
17
|
+
import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
|
|
18
|
+
import { withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
19
|
+
import { placeReadDenyAtHome, readDenyPlacedLater } from "./read-deny-home.js";
|
|
17
20
|
import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
|
|
18
21
|
export * from "./containment-face.js";
|
|
19
22
|
import {} from "./fs-shared.js";
|
|
@@ -24,22 +27,41 @@ import { createBashTool, createBashReadonlyTool, createEnvTaskOutputTool, create
|
|
|
24
27
|
export function handsWriteHalfMounts(opts) {
|
|
25
28
|
return opts.readOnly !== true;
|
|
26
29
|
}
|
|
30
|
+
export function backgroundOutputReadExemption(registry, seat, env) {
|
|
31
|
+
if (registry === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
return (canonicalKey, c) => registry.isBackgroundOutputFile(canonicalKey, { owner: c?.taskId ?? seat.taskOwner, scope: c?.principal ?? seat.taskScope, ...(seat.sessionId !== undefined ? { sessionId: seat.sessionId } : {}) }, env);
|
|
34
|
+
}
|
|
27
35
|
export function createHandsToolkit(env, readFileState, rootCanonical, opts = {}) {
|
|
28
36
|
const { includeShell = false, readOnly = false, bashReadonlyAllow, commitCoAuthor = false, mountBackgroundTaskTools = true, additionalRoots, additionalReadRoots, } = opts;
|
|
29
37
|
const readFaceRoots = additionalReadRoots === undefined ? additionalRoots : () => (additionalReadRoots.length === 0 ? additionalRoots : [...(additionalRoots ?? []), ...additionalReadRoots]);
|
|
30
38
|
const cwdRef = opts.cwdRef ?? { current: rootCanonical };
|
|
31
|
-
const
|
|
32
|
-
const
|
|
39
|
+
const bgOutput = backgroundOutputReadExemption(opts.taskRegistry, opts, env);
|
|
40
|
+
const readAdmission = opts.readAdmission;
|
|
41
|
+
const bgOutputReadExemption = bgOutput === undefined && readAdmission === undefined
|
|
33
42
|
? undefined
|
|
34
|
-
: (canonicalKey, c) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
: (canonicalKey, c) => readAdmission?.(c.toolCallId, canonicalKey) === true || (bgOutput?.(canonicalKey, c) ?? false);
|
|
44
|
+
const readDenyAdditions = (opts.readDenyPatterns ?? []).map((e) => (typeof e === "object" && e !== null ? { ...e } : e));
|
|
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 placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored);
|
|
52
|
+
const late = carried || placesNothing ? undefined : readDenyPlacedLater(unplaced, () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
|
|
53
|
+
const readDeny = late?.matcher ?? (carried ? compileAt(readDenyHomes, opts.readDenyHomeLinks) : unplaced);
|
|
54
|
+
const afterPlacement = (tool) => late === undefined
|
|
55
|
+
? tool
|
|
56
|
+
: {
|
|
57
|
+
...tool,
|
|
58
|
+
execute: async (...args) => {
|
|
59
|
+
await withGateTimeout(late.ready(), undefined, args[2], "readDenyPlacement").catch(() => undefined);
|
|
60
|
+
return await tool.execute(...args);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const writeDenyCompiled = compileWriteDeny(readDenyRules);
|
|
64
|
+
const writeDeny = writeDenyCompiled.ruleRows.length > 0 ? writeDenyCompiled : undefined;
|
|
43
65
|
const readFace = resolveContainmentFace({
|
|
44
66
|
kind: "read",
|
|
45
67
|
depsFace: opts.readFace,
|
|
@@ -56,13 +78,14 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
56
78
|
fullShellReachable: includeShell && !readOnly,
|
|
57
79
|
onDeploymentClamp: () => deliverEngineNotice(opts.onNotice, deploymentFaceClampNotice("write")),
|
|
58
80
|
});
|
|
81
|
+
const writeFaceOf = opts.writeFaceLive ?? (() => writeFace);
|
|
59
82
|
const tools = [
|
|
60
|
-
createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts),
|
|
83
|
+
afterPlacement(createReadFileTool(env, readFileState, rootCanonical, readOnly ? undefined : cwdRef, readFaceRoots, opts.readImageDownsampler, opts.pdfModelCapabilities, bgOutputReadExemption, opts.readCyberReminder, readDeny, readFace, opts.reminderMark, opts.reminderDisclosureCounts)),
|
|
61
84
|
];
|
|
62
85
|
if (handsWriteHalfMounts({ readOnly })) {
|
|
63
|
-
tools.push(createEditFileTool(env, readFileState, rootCanonical, cwdRef, additionalRoots, opts.beforeWrite, opts.trackFileEdit, opts.onFileEdited,
|
|
86
|
+
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));
|
|
64
87
|
}
|
|
65
|
-
tools.push(createGrepTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createGlobTool(env, rootCanonical, readFaceRoots, readDeny, readFace, cwdRef), createRepoMapTool(env, rootCanonical, readFaceRoots, readDeny, readFace));
|
|
88
|
+
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)));
|
|
66
89
|
if (includeShell) {
|
|
67
90
|
tools.push(readOnly
|
|
68
91
|
? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
+
import { type PathFamily } from "./safety.js";
|
|
3
|
+
import { type ReadDenyMatcher } from "./read-deny.js";
|
|
4
|
+
/** The environment's DECLARED home as an anchor list: `[homeDir]` when it declares an absolute one, else `[]` (no
|
|
5
|
+
* home — a home-anchored row then holds its run anywhere, see `compileReadDeny`). */
|
|
6
|
+
export declare function declaredReadDenyHomes(env: Pick<ExecutionEnv, "homeDir">): string[];
|
|
7
|
+
/** The declared home and, where the environment's canonical spelling of it differs (a home reached through a
|
|
8
|
+
* symlink), that spelling too: the faces judge canonical keys beside the spelled views, so a row placed only at
|
|
9
|
+
* the spelled home would never meet a canonical key. A home the environment cannot resolve keeps its spelling. */
|
|
10
|
+
export declare function readDenyHomesOf(env: Pick<ExecutionEnv, "homeDir" | "canonicalPath">, signal?: AbortSignal): Promise<string[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Where the home-anchored rows of a table LEAD beyond the home's own spellings, keyed by the row's pattern — the
|
|
13
|
+
* `linked` argument of `compileReadDeny`. For each row, its LITERAL HEAD (`readDenyRunHead`: the run's segments before
|
|
14
|
+
* the first one carrying `*`) is spelled under each home and resolved the way the read faces mint their keys
|
|
15
|
+
* (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined); a location that differs from every
|
|
16
|
+
* home's own spelling of the head is where one of the head's segments is a link, and the row is placed there too.
|
|
17
|
+
* · a head that resolves to its own spelling under a home adds nothing (the row is already placed there);
|
|
18
|
+
* · a head that cannot be resolved (an unanswered or refused probe, a probe that throws) adds nothing, and the answer
|
|
19
|
+
* is not WHOLE (`whole: false`): the row stays placed at the home's spellings until the mount places again;
|
|
20
|
+
* · a row whose head is empty (`~/.sema.*`) has no location to resolve and adds nothing;
|
|
21
|
+
* · the heads are resolved together, so one placement waits at most one probe bound;
|
|
22
|
+
* · read per placement — a link created afterwards is followed from the next placement on.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readDenyHomeLinksOf(env: ExecutionEnv, entries: readonly {
|
|
25
|
+
readonly pattern: string;
|
|
26
|
+
readonly caseSensitive?: boolean;
|
|
27
|
+
}[], homes: readonly string[], signal?: AbortSignal): Promise<{
|
|
28
|
+
readonly linked: Map<string, string[]>;
|
|
29
|
+
readonly whole: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
/** Where an absolute location LEADS in one environment — resolved the way the read and write faces mint their keys
|
|
32
|
+
* (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined). `undefined` when the environment
|
|
33
|
+
* does not answer (a refused or failed probe, one that throws). The one resolution a deny row's placement reads: a
|
|
34
|
+
* home-anchored row's head ({@link readDenyHomeLinksOf}) and a rule row's head ({@link whereItsAnsweredPrefixLeads}). */
|
|
35
|
+
export declare function whereItLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>;
|
|
36
|
+
/** {@link whereItLeads} with a floor: where the environment does not answer for `at` (a probe that fails), the longest
|
|
37
|
+
* prefix it does answer for is resolved and the rest rejoined — a rule row's head below a base that resolves is never
|
|
38
|
+
* placed narrower than that base's own canonical spelling. The whole walk waits at most one bounded-await deadline.
|
|
39
|
+
* `undefined` when no prefix answers in time. */
|
|
40
|
+
export declare function whereItsAnsweredPrefixLeads(env: ExecutionEnv, at: string, family?: PathFamily, signal?: AbortSignal): Promise<string | undefined>;
|
|
41
|
+
/** One mount's read-deny judge with its home-anchored rows placed: at the home's spellings (`homes` when the mount
|
|
42
|
+
* carries them, else {@link readDenyHomesOf}, under the tree's bounded-await deadline — an environment that cannot
|
|
43
|
+
* answer rejects the placement instead of holding it open) and where their heads lead ({@link readDenyHomeLinksOf}).
|
|
44
|
+
* `whole` says whether the home and every head answered. A home the environment did not answer for places nothing:
|
|
45
|
+
* the rows are held anywhere (`compile([])`, the rule for a row that cannot be placed) rather than at a spelling that
|
|
46
|
+
* may miss the home's canonical one. `compile` is the mount's own compile over the two; the rows' patterns are read
|
|
47
|
+
* off its compile at the homes alone. */
|
|
48
|
+
export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal): Promise<{
|
|
49
|
+
readonly matcher: ReadDenyMatcher;
|
|
50
|
+
readonly homes: readonly string[];
|
|
51
|
+
readonly linked: ReadonlyMap<string, readonly string[]>;
|
|
52
|
+
readonly whole: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* A judge for a mount that is built before its placement settles (the hands band is built synchronously): the table as
|
|
56
|
+
* `unplaced` compiles it — home-anchored rows held anywhere, the rule for a row that cannot be placed — until a
|
|
57
|
+
* placement settles, then the placed table. `place` starts at once; `ready()` settles when the latest placement has
|
|
58
|
+
* (either way) and, while no placement has come back WHOLE (a failed one, or one with a head that did not answer),
|
|
59
|
+
* places again first — an unanswered lookup is never kept as the answer. A failed placement leaves the table it
|
|
60
|
+
* replaced (the rows held anywhere, or an earlier placement), never unplaced-and-narrow. The mount holds its read faces
|
|
61
|
+
* until `ready()`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readDenyPlacedLater(unplaced: ReadDenyMatcher, place: () => Promise<{
|
|
64
|
+
readonly matcher: ReadDenyMatcher;
|
|
65
|
+
readonly whole: boolean;
|
|
66
|
+
}>): {
|
|
67
|
+
readonly matcher: ReadDenyMatcher;
|
|
68
|
+
readonly ready: () => Promise<void>;
|
|
69
|
+
};
|
|
70
|
+
/** The environment surface a home view reads. */
|
|
71
|
+
export type HomeViewEnv = Pick<ExecutionEnv, "homeDir" | "canonicalPath" | "fileIdentity">;
|
|
72
|
+
/**
|
|
73
|
+
* A path's HOME VIEW — the one reader every face that judges a deny row by location uses (the structured read and
|
|
74
|
+
* write tools' target judgment, the shell station's establishment, the search walker, the ripgrep leg's output check
|
|
75
|
+
* and its scope choice). The view of a path is the path itself when it IS a directory on the home's chain (the
|
|
76
|
+
* canonical home and each of its ancestors, matched by {@link ExecutionEnv.fileIdentity}) — that directory's
|
|
77
|
+
* canonical spelling — and otherwise its parent's view with its own name rejoined. `of` answers the view only where
|
|
78
|
+
* it differs from the path as spelled; `undefined` otherwise, and wherever it cannot be read:
|
|
79
|
+
* · the environment declares no home, no `fileIdentity`, or its home does not resolve ⇒ no view (the spelling form);
|
|
80
|
+
* a home, or a level of its chain, that could not be read is asked again by the next judgment — an unanswered
|
|
81
|
+
* lookup is never kept as the answer;
|
|
82
|
+
* · a level that does not exist (a write's missing tail) has no identity of its own and takes its parent's view;
|
|
83
|
+
* · any other failure at a level stops the view there: the path is judged as spelled, never wider.
|
|
84
|
+
* `leaf: "file"` skips the path's own identity (a file is never a directory on the chain).
|
|
85
|
+
*/
|
|
86
|
+
export interface HomeViews {
|
|
87
|
+
of(path: string, opts?: {
|
|
88
|
+
readonly leaf?: "file";
|
|
89
|
+
}): Promise<string | undefined>;
|
|
90
|
+
}
|
|
91
|
+
/** The home views of one judgment (a tool call, a command's establishment, one search): a view per directory is read
|
|
92
|
+
* once and shared by every path below it. */
|
|
93
|
+
export declare function homeViewsOf(env: HomeViewEnv, signal?: AbortSignal): HomeViews;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { canonicalizeTarget, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
2
|
+
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
3
|
+
import { readDenyPatternRun, readDenyRunHead } from "./read-deny.js";
|
|
4
|
+
export function declaredReadDenyHomes(env) {
|
|
5
|
+
const home = env.homeDir;
|
|
6
|
+
return home !== undefined && isAbsolutePathForm(home) ? [home] : [];
|
|
7
|
+
}
|
|
8
|
+
export async function readDenyHomesOf(env, signal) {
|
|
9
|
+
return (await readDenyHomesAnswered(env, signal)).homes;
|
|
10
|
+
}
|
|
11
|
+
async function readDenyHomesAnswered(env, signal) {
|
|
12
|
+
const home = declaredReadDenyHomes(env)[0];
|
|
13
|
+
if (home === undefined)
|
|
14
|
+
return { homes: [], whole: true };
|
|
15
|
+
const canonical = await env.canonicalPath(home, signal);
|
|
16
|
+
if (canonical.ok)
|
|
17
|
+
return { homes: canonical.value !== home ? [home, canonical.value] : [home], whole: true };
|
|
18
|
+
return { homes: [home], whole: canonical.error.code === "not_found" || canonical.error.code === "not_directory" };
|
|
19
|
+
}
|
|
20
|
+
export async function readDenyHomeLinksOf(env, entries, homes, signal) {
|
|
21
|
+
const rows = [];
|
|
22
|
+
const familyOf = new Map();
|
|
23
|
+
for (const { pattern, caseSensitive } of entries) {
|
|
24
|
+
const { homeAnchored, run } = readDenyPatternRun(pattern);
|
|
25
|
+
if (!homeAnchored)
|
|
26
|
+
continue;
|
|
27
|
+
const head = run.split("/").filter(Boolean).slice(0, readDenyRunHead(run)).join("/");
|
|
28
|
+
if (head.length === 0)
|
|
29
|
+
continue;
|
|
30
|
+
const spelled = homes.map((home) => {
|
|
31
|
+
const family = pathFamilyOf({ root: home });
|
|
32
|
+
const at = normalizeAbsPathLexically(joinForFamily(family, home, head));
|
|
33
|
+
familyOf.set(at, family);
|
|
34
|
+
return at;
|
|
35
|
+
});
|
|
36
|
+
rows.push({ pattern, caseSensitive: caseSensitive === true, spelled });
|
|
37
|
+
}
|
|
38
|
+
const resolved = new Map(await Promise.all([...familyOf].map(async ([at, family]) => [at, await whereItLeads(env, at, family, signal)])));
|
|
39
|
+
const out = new Map();
|
|
40
|
+
const whole = [...resolved.values()].every((location) => location !== undefined);
|
|
41
|
+
for (const { pattern, caseSensitive, spelled } of rows) {
|
|
42
|
+
const fold = (p) => (caseSensitive ? p : p.replace(/[A-Z]/g, (c) => c.toLowerCase()));
|
|
43
|
+
const own = new Set(spelled.map(fold));
|
|
44
|
+
const locations = [];
|
|
45
|
+
for (const at of spelled) {
|
|
46
|
+
const location = resolved.get(at);
|
|
47
|
+
if (location !== undefined && !own.has(fold(location)) && !locations.includes(location))
|
|
48
|
+
locations.push(location);
|
|
49
|
+
}
|
|
50
|
+
if (locations.length > 0)
|
|
51
|
+
out.set(pattern, locations);
|
|
52
|
+
}
|
|
53
|
+
return { linked: out, whole };
|
|
54
|
+
}
|
|
55
|
+
export async function whereItLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
|
|
56
|
+
try {
|
|
57
|
+
const key = await canonicalizeTarget(env, at, signal, family);
|
|
58
|
+
return key.ok ? normalizeAbsPathLexically(key.key) : undefined;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export async function whereItsAnsweredPrefixLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
|
|
65
|
+
const walk = async () => {
|
|
66
|
+
const rest = [];
|
|
67
|
+
for (let here = normalizeAbsPathLexically(at);;) {
|
|
68
|
+
const led = await whereItLeads(env, here, family, signal);
|
|
69
|
+
if (led !== undefined)
|
|
70
|
+
return rest.reduce((p, name) => joinForFamily(family, p, name), led);
|
|
71
|
+
const parent = normalizeAbsPathLexically(`${here}/..`);
|
|
72
|
+
if (parent === here)
|
|
73
|
+
return undefined;
|
|
74
|
+
rest.unshift(here.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
75
|
+
here = parent;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return await withGateTimeout(walk(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "ruleRowHead").catch(() => undefined);
|
|
79
|
+
}
|
|
80
|
+
export async function placeReadDenyAtHome(env, compile, homes, signal) {
|
|
81
|
+
let at = homes;
|
|
82
|
+
if (at === undefined) {
|
|
83
|
+
const answer = await withGateTimeout(readDenyHomesAnswered(env, signal), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHomes");
|
|
84
|
+
if (!answer.whole)
|
|
85
|
+
return { matcher: compile([]), homes: [], linked: new Map(), whole: false };
|
|
86
|
+
at = answer.homes;
|
|
87
|
+
}
|
|
88
|
+
const atHomes = compile(at);
|
|
89
|
+
const { linked, whole } = await readDenyHomeLinksOf(env, atHomes.entries, at, signal);
|
|
90
|
+
return { matcher: linked.size === 0 ? atHomes : compile(at, linked), homes: at, linked, whole };
|
|
91
|
+
}
|
|
92
|
+
export function readDenyPlacedLater(unplaced, place) {
|
|
93
|
+
let current = unplaced;
|
|
94
|
+
let whole = false;
|
|
95
|
+
let pending;
|
|
96
|
+
const again = () => (pending ??= place()
|
|
97
|
+
.then((placed) => {
|
|
98
|
+
current = placed.matcher;
|
|
99
|
+
whole = placed.whole;
|
|
100
|
+
}, () => undefined)
|
|
101
|
+
.finally(() => {
|
|
102
|
+
pending = undefined;
|
|
103
|
+
}));
|
|
104
|
+
void again();
|
|
105
|
+
const ready = async () => {
|
|
106
|
+
if (pending !== undefined)
|
|
107
|
+
await pending;
|
|
108
|
+
if (!whole)
|
|
109
|
+
await again();
|
|
110
|
+
};
|
|
111
|
+
const matcher = {
|
|
112
|
+
get entries() {
|
|
113
|
+
return current.entries;
|
|
114
|
+
},
|
|
115
|
+
get ruleRows() {
|
|
116
|
+
return current.ruleRows;
|
|
117
|
+
},
|
|
118
|
+
matchPath: (path, opts) => current.matchPath(path, opts),
|
|
119
|
+
matchTarget: (canonicalKey, lexicalView, opts) => current.matchTarget(canonicalKey, lexicalView, opts),
|
|
120
|
+
get rgExclusionGlobs() {
|
|
121
|
+
return current.rgExclusionGlobs;
|
|
122
|
+
},
|
|
123
|
+
get rgProbeGlobs() {
|
|
124
|
+
return current.rgProbeGlobs;
|
|
125
|
+
},
|
|
126
|
+
unexcludableUnder: (scope) => current.unexcludableUnder?.(scope) ?? false,
|
|
127
|
+
rowAtOrBelow: (path) => current.rowAtOrBelow?.(path) ?? null,
|
|
128
|
+
};
|
|
129
|
+
return { matcher, ready };
|
|
130
|
+
}
|
|
131
|
+
const HOME_CHAINS = new WeakMap();
|
|
132
|
+
function homeChainOf(env) {
|
|
133
|
+
const identity = env.fileIdentity;
|
|
134
|
+
const home = declaredReadDenyHomes(env)[0];
|
|
135
|
+
if (typeof identity !== "function" || home === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
let perEnv = HOME_CHAINS.get(env);
|
|
138
|
+
if (perEnv === undefined)
|
|
139
|
+
HOME_CHAINS.set(env, (perEnv = new Map()));
|
|
140
|
+
const known = perEnv;
|
|
141
|
+
let chain = known.get(home);
|
|
142
|
+
if (chain === undefined) {
|
|
143
|
+
const reading = withGateTimeout((async () => {
|
|
144
|
+
const canonical = await env.canonicalPath(home);
|
|
145
|
+
if (!canonical.ok)
|
|
146
|
+
return { index: undefined, whole: false };
|
|
147
|
+
const out = new Map();
|
|
148
|
+
let whole = true;
|
|
149
|
+
for (let dir = normalizeAbsPathLexically(canonical.value);;) {
|
|
150
|
+
const id = await identityOf(env, identity, dir);
|
|
151
|
+
if (id?.ok === true) {
|
|
152
|
+
if (!out.has(id.value))
|
|
153
|
+
out.set(id.value, dir);
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
whole = false;
|
|
157
|
+
const parent = normalizeAbsPathLexically(`${dir}/..`);
|
|
158
|
+
if (parent === dir)
|
|
159
|
+
break;
|
|
160
|
+
dir = parent;
|
|
161
|
+
}
|
|
162
|
+
return { index: out.size > 0 ? out : undefined, whole };
|
|
163
|
+
})(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, undefined, "homeChain").catch(() => ({ index: undefined, whole: false }));
|
|
164
|
+
chain = reading;
|
|
165
|
+
known.set(home, reading);
|
|
166
|
+
const drop = () => {
|
|
167
|
+
if (known.get(home) === reading)
|
|
168
|
+
known.delete(home);
|
|
169
|
+
};
|
|
170
|
+
void reading.then((c) => (c.whole ? undefined : drop()));
|
|
171
|
+
}
|
|
172
|
+
return chain.then((c) => c.index);
|
|
173
|
+
}
|
|
174
|
+
async function identityOf(env, identity, path, signal) {
|
|
175
|
+
try {
|
|
176
|
+
return await identity.call(env, path, signal);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function homeViewsOf(env, signal) {
|
|
183
|
+
const chain = homeChainOf(env);
|
|
184
|
+
const identity = env.fileIdentity;
|
|
185
|
+
const views = new Map();
|
|
186
|
+
const viewOf = (path, leafIsFile) => {
|
|
187
|
+
const key = `${leafIsFile ? "f" : "d"}:${path}`;
|
|
188
|
+
let v = views.get(key);
|
|
189
|
+
if (v === undefined) {
|
|
190
|
+
v = (async () => {
|
|
191
|
+
const index = await chain;
|
|
192
|
+
if (index === undefined || typeof identity !== "function")
|
|
193
|
+
return undefined;
|
|
194
|
+
if (!leafIsFile) {
|
|
195
|
+
const id = await identityOf(env, identity, path, signal);
|
|
196
|
+
if (id === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
if (id.ok) {
|
|
199
|
+
const at = index.get(id.value);
|
|
200
|
+
if (at !== undefined)
|
|
201
|
+
return at;
|
|
202
|
+
}
|
|
203
|
+
else if (id.error.code !== "not_found" && id.error.code !== "not_directory")
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const parent = normalizeAbsPathLexically(`${path}/..`);
|
|
207
|
+
if (parent === path)
|
|
208
|
+
return undefined;
|
|
209
|
+
const up = await viewOf(parent, false);
|
|
210
|
+
return up === undefined ? undefined : joinForFamily(pathFamilyOf({ root: path }), up, path.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
211
|
+
})();
|
|
212
|
+
views.set(key, v);
|
|
213
|
+
}
|
|
214
|
+
return v;
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
async of(path, opts) {
|
|
218
|
+
if (chain === undefined || !isAbsolutePathForm(path))
|
|
219
|
+
return undefined;
|
|
220
|
+
const spelled = normalizeAbsPathLexically(path);
|
|
221
|
+
const view = await viewOf(spelled, opts?.leaf === "file");
|
|
222
|
+
return view === undefined || view === spelled ? undefined : view;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|