@sema-agent/core 7.41.0 → 7.42.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/agents/subagent.js +2 -0
  3. package/dist/core/agent-definition.d.ts +1 -1
  4. package/dist/core/allow-entry.d.ts +241 -0
  5. package/dist/core/allow-entry.js +340 -0
  6. package/dist/core/allow-rule-shadow.d.ts +18 -0
  7. package/dist/core/allow-rule-shadow.js +1 -0
  8. package/dist/core/ask-origin.d.ts +5 -5
  9. package/dist/core/ask-origin.js +1 -1
  10. package/dist/core/auto-mode.d.ts +3 -2
  11. package/dist/core/checkpoint-store.d.ts +11 -19
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/decision-receipt.d.ts +4 -2
  14. package/dist/core/decision-receipt.js +1 -1
  15. package/dist/core/engine-notice.d.ts +8 -5
  16. package/dist/core/gate-fold.js +1 -1
  17. package/dist/core/gate-lanes.d.ts +1 -49
  18. package/dist/core/gate-lanes.js +85 -223
  19. package/dist/core/governance-codes.d.ts +3 -3
  20. package/dist/core/governance-codes.js +3 -3
  21. package/dist/core/hooks.d.ts +13 -10
  22. package/dist/core/hooks.js +5 -3
  23. package/dist/core/mode-policy.d.ts +3 -3
  24. package/dist/core/mode-policy.js +2 -2
  25. package/dist/core/permission-rules.d.ts +24 -23
  26. package/dist/core/permission-rules.js +37 -33
  27. package/dist/core/reminder-disclosure.d.ts +53 -2
  28. package/dist/core/reminder-disclosure.js +49 -36
  29. package/dist/core/rule-lane-reads.d.ts +84 -0
  30. package/dist/core/rule-lane-reads.js +164 -0
  31. package/dist/core/runner/contracts.d.ts +4 -2
  32. package/dist/core/runner/gate-exit.d.ts +3 -3
  33. package/dist/core/runner/image.js +4 -3
  34. package/dist/core/runner/inline-image.d.ts +14 -0
  35. package/dist/core/runner/inline-image.js +26 -0
  36. package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
  37. package/dist/core/runner/permission-rule-lanes.js +1 -1
  38. package/dist/core/runner/prepare-ask-lane.d.ts +0 -19
  39. package/dist/core/runner/prepare-ask-lane.js +2 -2
  40. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  41. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  42. package/dist/core/runner/prepare-park-ask.js +3 -3
  43. package/dist/core/runner/prepare-policy-chain.d.ts +12 -0
  44. package/dist/core/runner/prepare-policy-chain.js +10 -9
  45. package/dist/core/runner/prepare-project-context.d.ts +13 -3
  46. package/dist/core/runner/prepare-project-context.js +5 -4
  47. package/dist/core/runner/prepare-task.js +8 -5
  48. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -3
  49. package/dist/core/runner/prepare-turn-wiring.js +19 -18
  50. package/dist/core/runner/run-leg.js +1 -1
  51. package/dist/core/runner/runtask.js +2 -1
  52. package/dist/core/runner-deps.d.ts +11 -0
  53. package/dist/core/skill-grant.d.ts +8 -47
  54. package/dist/core/skill-grant.js +15 -136
  55. package/dist/core/synthetic-read-verdict.d.ts +108 -0
  56. package/dist/core/synthetic-read-verdict.js +111 -0
  57. package/dist/core/task-spec.d.ts +18 -0
  58. package/dist/core/tool-name-readings.d.ts +1 -1
  59. package/dist/core/tool-name-readings.js +1 -1
  60. package/dist/core/tool-policy.d.ts +25 -24
  61. package/dist/core/tool-policy.js +26 -17
  62. package/dist/core/tool-spec.d.ts +7 -0
  63. package/dist/core/trace.d.ts +16 -0
  64. package/dist/index.d.ts +5 -1
  65. package/dist/index.js +4 -0
  66. package/dist/internal/harness-types.d.ts +1 -1
  67. package/dist/tools/fs/dir-entries.d.ts +69 -0
  68. package/dist/tools/fs/dir-entries.js +60 -0
  69. package/dist/tools/fs/fs-pdf.d.ts +7 -11
  70. package/dist/tools/fs/fs-pdf.js +5 -14
  71. package/dist/tools/fs/fs-read.d.ts +173 -2
  72. package/dist/tools/fs/fs-read.js +399 -286
  73. package/dist/tools/fs/image-admission.d.ts +51 -0
  74. package/dist/tools/fs/image-admission.js +36 -0
  75. package/dist/tools/fs/search.js +3 -7
  76. package/package.json +1 -1
  77. package/test/export-surface.snapshot.json +29 -1
  78. package/wire-changes/core@7.41.1.json +159 -0
  79. package/wire-changes/core@7.42.0.json +1107 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The synthetic-Read DENY judge — "would a `Read` of this file be refused outright?", asked without a call,
3
+ * without an approver and without a hook. One judge, one set of layers, for every consumer that needs the
4
+ * answer for a file the model did not itself ask to read (the changed-file notice today; a user's
5
+ * attachment next). It answers in THREE states and never picks a direction:
6
+ *
7
+ * deny — some layer refuses it;
8
+ * no_objection — every layer it consulted answered and none refused (an `ask` is not a refusal);
9
+ * unanswerable — no layer refused, and at least one could not answer (see the table below).
10
+ *
11
+ * The CONSUMER decides where `unanswerable` goes: a notice that merely tells the model a file changed
12
+ * reads it as no objection (the worst case is a notice about a file it then cannot re-read); a face that
13
+ * would put a file's contents in front of the model reads it as a refusal.
14
+ *
15
+ * THE LAYERS (every one that can refuse a model's own `Read`): the run's skill deny rows; the
16
+ * deny-narrowing fold (session rules, inherited ancestor rules, frozen constraint projections); the
17
+ * deployment's caller policy; each live inherited parent policy; the person's persisted-rule lane; the org
18
+ * lane. Not consulted, by design: PreToolUse hooks (the deployment's screening of a model's CALL), the
19
+ * permission mode's write gate and the integrity guards (neither refuses a read), and the read-deny table
20
+ * and containment roots (the path's own resolution judges those before this judge is asked). A deny
21
+ * anywhere wins over an unanswerable layer elsewhere.
22
+ *
23
+ * FAULT → STATE (the live gate's own readings, through the same functions — never a second copy):
24
+ * | layer | fault | state |
25
+ * | skill rows | throws | unanswerable `threw` |
26
+ * | narrowing fold | throws / exceeds the judgment's budget | unanswerable `threw`/`timed_out` |
27
+ * | caller / inherited policy | throws / times out | unanswerable `threw`/`timed_out` |
28
+ * | caller / inherited policy | its deny carries an engine settlement (an approval | unanswerable `approval_seat` |
29
+ * | | seat that ended unanswered; in a combinePolicies | |
30
+ * | | composition a rule deny anywhere is returned instead)| |
31
+ * | persisted lane | store unreadable, throws, times out (readPersistedRuleLane ⇒ `{unreadable}`) | its rows take no part |
32
+ * | persisted lane | the call cannot be read against the rows (`reason`) — applyPersistedTightening ⇒ `unreadable` | unanswerable `call_unreadable` |
33
+ * | org lane | unavailable: throws, garbage (readOrgRuleLane) | unanswerable `org_unavailable` |
34
+ * | any layer | the judgment's budget (one dependency timeout, or a | unanswerable `timed_out` |
35
+ * | | batch's shared deadline) is spent | (persisted: rows take no part)|
36
+ *
37
+ * NEVER AN APPROVER. A deployment policy (the caller's, an inherited parent's) is consulted with an
38
+ * ALREADY-ABORTED signal and no approver seat: the `ToolPolicy.check` contract is that an aborted signal
39
+ * releases a pending approval, and every engine policy factory checks it before reaching a seat (the
40
+ * approval factory answers a deny attested `task_aborted`, read here as `approval_seat`; the inherited
41
+ * PreToolUse constraint does the same before invoking its hook). A deny that carries no engine settlement
42
+ * is a rule's deny and stands. RESIDUALS (stated, not closed): a deployment closure that reaches an
43
+ * approver before it reads its signal can still be asked; a deployment wrapper that answers an aborted
44
+ * signal with a deny of its own (no settlement) is read as a deny — the attachment face then withholds,
45
+ * the notice face stays silent. The engine's own layers (narrowing, skill rows, the two lanes) get the
46
+ * run's real signal: they are pure matchers and store reads.
47
+ *
48
+ * THE PROBE'S IDENTITY. Every layer sees `toolName: "Read"`, the probed arguments, and the stable synthetic
49
+ * call id `<face>-visibility-probe` ({@link syntheticReadProbeId}), so a deployment's audit can tell a
50
+ * visibility probe from a model's call.
51
+ */
52
+ import type { ToolCallRequest, ToolCallFace, ToolPolicy } from "./tool-policy.js";
53
+ import { type SkillGrantLane } from "./skill-grant.js";
54
+ import type { OrgGateVerdict, PersistedRuleAnswer } from "./hooks.js";
55
+ /** The layers the judge consults, as named in its verdicts. */
56
+ export declare const SYNTHETIC_READ_LAYERS: readonly ["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"];
57
+ export type SyntheticReadLayer = (typeof SYNTHETIC_READ_LAYERS)[number];
58
+ /** Why a layer could not answer — the consumer's disclosure word. */
59
+ export declare const SYNTHETIC_READ_UNANSWERABLE_CAUSES: readonly ["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"];
60
+ export type SyntheticReadUnanswerableCause = (typeof SYNTHETIC_READ_UNANSWERABLE_CAUSES)[number];
61
+ export type SyntheticReadVerdict = {
62
+ verdict: "deny";
63
+ layer: SyntheticReadLayer;
64
+ message?: string;
65
+ } | {
66
+ verdict: "no_objection";
67
+ } | {
68
+ verdict: "unanswerable";
69
+ layer: SyntheticReadLayer;
70
+ cause: SyntheticReadUnanswerableCause;
71
+ reason: string;
72
+ };
73
+ export interface SyntheticReadLayers {
74
+ skillGrants?: Pick<SkillGrantLane, "denying">;
75
+ denyNarrowingPolicy?: ToolPolicy;
76
+ callerPolicy?: ToolPolicy;
77
+ inheritedPolicies?: readonly ToolPolicy[];
78
+ persistedRuleLane?: {
79
+ adjudicate: (req: ToolCallRequest) => PersistedRuleAnswer | Promise<PersistedRuleAnswer>;
80
+ };
81
+ orgRuleLane?: {
82
+ adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
83
+ };
84
+ /** The run's signal, for the engine's own layers. */
85
+ signal?: AbortSignal;
86
+ }
87
+ /** The arguments a synthetic `Read` carries, and the facts a request stamps. */
88
+ export interface SyntheticReadCall {
89
+ file_path: string;
90
+ offset?: number;
91
+ limit?: number;
92
+ /** The tracked working directory a relative `file_path` resolves against. */
93
+ cwd?: string;
94
+ /** The live roster's face for `Read`, when the consumer has one. */
95
+ face?: ToolCallFace;
96
+ }
97
+ /** The stable synthetic call id a probe carries: `<face>-visibility-probe`, `face` a lowercase word
98
+ * (letters and hyphens) naming the consumer. */
99
+ export declare function syntheticReadProbeId(face: string): string;
100
+ /** One judgment's wait budget. `deadlineAt` (epoch ms) bounds the WHOLE judgment — every layer waits at most
101
+ * what is left of it, and a layer reached after it has passed is not consulted (`timed_out`). Absent ⇒ the
102
+ * dependency timeout from the start of this judgment. A consumer judging a batch passes one shared deadline,
103
+ * so a hung layer costs the batch one bound, not one per file per layer. */
104
+ export interface SyntheticReadBudget {
105
+ deadlineAt?: number;
106
+ }
107
+ export type SyntheticReadJudge = (call: SyntheticReadCall, face: string, budget?: SyntheticReadBudget) => Promise<SyntheticReadVerdict>;
108
+ export declare function createSyntheticReadJudge(layers: SyntheticReadLayers): SyntheticReadJudge;
@@ -0,0 +1,111 @@
1
+ import { describeThrown, engineSettlementOf, refuseOutOfContractDecision } from "./tool-policy.js";
2
+ import { skillRowRefusal } from "./skill-grant.js";
3
+ import { applyPersistedTightening } from "./persisted-rule-arms.js";
4
+ import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
5
+ import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "./permission-rule-org.js";
6
+ export const SYNTHETIC_READ_LAYERS = Object.freeze(["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"]);
7
+ export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable"]);
8
+ export function syntheticReadProbeId(face) {
9
+ if (!/^[a-z][a-z-]*$/.test(face))
10
+ throw new Error(`syntheticReadProbeId: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`);
11
+ return `${face}-visibility-probe`;
12
+ }
13
+ const ABORTED = AbortSignal.abort();
14
+ export function createSyntheticReadJudge(layers) {
15
+ return async (call, face, budget) => {
16
+ const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS;
17
+ const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now()));
18
+ const req = {
19
+ toolName: "Read",
20
+ args: { file_path: call.file_path, ...(call.offset !== undefined ? { offset: call.offset } : {}), ...(call.limit !== undefined ? { limit: call.limit } : {}) },
21
+ toolCallId: syntheticReadProbeId(face),
22
+ ...(call.cwd !== undefined ? { cwd: call.cwd } : {}),
23
+ ...(call.face !== undefined ? { face: call.face } : {}),
24
+ };
25
+ let unanswered;
26
+ const cannot = (layer, cause, reason) => {
27
+ unanswered ??= { verdict: "unanswerable", layer, cause, reason };
28
+ };
29
+ const spent = (layer) => {
30
+ if (left() > 0)
31
+ return false;
32
+ cannot(layer, "timed_out", "the judgment's wait budget was spent before this layer was asked");
33
+ return true;
34
+ };
35
+ const policyLayer = async (layer, policy, signal) => {
36
+ if (spent(layer))
37
+ return undefined;
38
+ const waitMs = left();
39
+ let timedOut = false;
40
+ const TIMEOUT = Symbol("timeout");
41
+ let d;
42
+ try {
43
+ d = await settleOrgVerdictWithin(Promise.resolve().then(() => policy.check(req, signal)), TIMEOUT, { ...(layers.signal !== undefined ? { signal: layers.signal } : {}), timeoutMs: waitMs, onFallback: () => void (timedOut = true) });
44
+ }
45
+ catch (err) {
46
+ cannot(layer, "threw", describeThrown(err));
47
+ return undefined;
48
+ }
49
+ if (d === TIMEOUT || timedOut) {
50
+ cannot(layer, "timed_out", `no answer within ${waitMs}ms (or the run ended first)`);
51
+ return undefined;
52
+ }
53
+ let read;
54
+ try {
55
+ read = refuseOutOfContractDecision(d);
56
+ }
57
+ catch (err) {
58
+ cannot(layer, "threw", describeThrown(err));
59
+ return undefined;
60
+ }
61
+ if (read.action !== "deny")
62
+ return undefined;
63
+ if (engineSettlementOf(d, req) !== undefined) {
64
+ cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a visibility probe");
65
+ return undefined;
66
+ }
67
+ return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}) };
68
+ };
69
+ try {
70
+ const refusal = skillRowRefusal(layers.skillGrants, req);
71
+ if (refusal !== undefined)
72
+ return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}) };
73
+ }
74
+ catch (err) {
75
+ cannot("skill_rows", "threw", describeThrown(err));
76
+ }
77
+ if (layers.denyNarrowingPolicy !== undefined) {
78
+ const v = await policyLayer("narrowing", layers.denyNarrowingPolicy, layers.signal);
79
+ if (v !== undefined)
80
+ return v;
81
+ }
82
+ if (layers.callerPolicy !== undefined) {
83
+ const v = await policyLayer("caller_policy", layers.callerPolicy, ABORTED);
84
+ if (v !== undefined)
85
+ return v;
86
+ }
87
+ for (const parent of layers.inheritedPolicies ?? []) {
88
+ if (parent === layers.callerPolicy)
89
+ continue;
90
+ const v = await policyLayer("inherited_constraint", parent, ABORTED);
91
+ if (v !== undefined)
92
+ return v;
93
+ }
94
+ if (layers.persistedRuleLane !== undefined && !spent("persisted_rules")) {
95
+ const read = await readPersistedRuleLane(layers.persistedRuleLane, req, layers.signal, left());
96
+ const tightened = applyPersistedTightening({ action: "allow" }, read).tightened;
97
+ if (tightened === "deny")
98
+ return { verdict: "deny", layer: "persisted_rules" };
99
+ if (tightened === "unreadable")
100
+ cannot("persisted_rules", "call_unreadable", read.reason ?? "the call could not be read against the rules");
101
+ }
102
+ if (layers.orgRuleLane !== undefined && !spent("org_rules")) {
103
+ const org = await readOrgRuleLane(layers.orgRuleLane, req, layers.signal, left());
104
+ if (org.status === "unavailable")
105
+ cannot("org_rules", left() > 0 ? "org_unavailable" : "timed_out", org.disclosures.join("; ") || "organization governance could not be read");
106
+ else if (org.verdict?.behavior === "deny")
107
+ return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it` };
108
+ }
109
+ return unanswered ?? { verdict: "no_objection" };
110
+ };
111
+ }
@@ -975,6 +975,24 @@ export interface TaskSpec {
975
975
  * when the person actually invoked one; a mid-turn invocation (one riding a steer) grants nothing.
976
976
  */
977
977
  userInvokedSkills?: string[];
978
+ /**
979
+ * This run's ALLOW RULES — the reference implementation's `allowedTools` (its `--allowedTools` list): each entry, in the
980
+ * rule grammar a skill's `allowed-tools` reads ({@link SkillSpec.allowedTools}), is a standing yes for this run. A
981
+ * whole tool name (`Write`, `Bash`), a protocol namespace (`mcp__srv`, `mcp__srv__*`), a shell command rule
982
+ * (`Bash(git:*)`, `Bash(npm test)`) or a Read directory (`Read(//srv/data/**)`) clears the question the permission
983
+ * layers raise for a call it covers — read in the allow layer beside a person's stored rules, a skill's list and the
984
+ * deployment's {@link import("./runner-deps.js").RunnerDeps.allowedTools}, under the same predicate: deny and ask rules
985
+ * still win, and a question no allow rule clears (a mandated, governance, hook or plan question) is still put to a person,
986
+ * with the entry disclosed on the card. In `auto` mode an entry that hands over arbitrary code (the whole shell, an
987
+ * interpreter or launcher prefix) is left to the classifier.
988
+ *
989
+ * Lives for this run — the steers it takes in included; a delegated child holds the entries until it ends; a resumed leg
990
+ * holds them only when its spec supplies them again (they are not written into a checkpoint). An entry this grammar
991
+ * reads as nothing (`*`, `mcp__*`, a write-path or domain pattern) grants nothing and is announced at the start of the
992
+ * run (`permission.allowed_tool_not_granted`); a list holding `*` grants nothing at all. A value that is not a list of
993
+ * strings refuses the run before it starts (`config.invalid_permission_rules`, with its issues).
994
+ */
995
+ allowedTools?: readonly string[];
978
996
  /**
979
997
  * Fraction of the model's context window the `<skills>` listing may spend. Absent ⇒ `0.01` (1%).
980
998
  * Domain `(0, 1]`; anything else — including `0`, a string, `NaN` — REFUSES at the door with
@@ -130,7 +130,7 @@ export declare const NAME_READINGS: {
130
130
  readonly kind: "wire";
131
131
  readonly file: string;
132
132
  };
133
- readonly "skillGrant.names": {
133
+ readonly "allowEntry.names": {
134
134
  readonly kind: "any";
135
135
  readonly file: string;
136
136
  };
@@ -34,7 +34,7 @@ export const NAME_READINGS = {
34
34
  "policy.projectionReplay": any(POLICY),
35
35
  "policy.sessionRules": any("src/core/runner/session-rule-policy.ts"),
36
36
  "policy.sessionRules.effect": wire("src/core/runner/session-rule-policy.ts"),
37
- "skillGrant.names": any("src/core/skill-grant.ts"),
37
+ "allowEntry.names": any("src/core/allow-entry.ts"),
38
38
  "skillGrant.binding": wire("src/core/skill-grant.ts"),
39
39
  "gate.skillActivationSeat": wire(GATE),
40
40
  "policy.permissionRules": any("src/core/permission-rules.ts"),
@@ -159,6 +159,11 @@ export declare function advertisedWritableRootsSeat(seat: {
159
159
  /** Declared beside the receipt's list (decision-receipt.ts) — a new member added to either side is a compile error rather
160
160
  * than a silent drift between the type and the screen; re-exported here, the module every consumer of the decision reads. */
161
161
  export type { DecisionReason };
162
+ /** The `task_aborted`-attested deny an engine policy answers on an aborted signal instead of reaching a seat. */
163
+ export declare function abortedSeatDeny(req: {
164
+ toolCallId: string;
165
+ toolName: string;
166
+ }, message: string, origin: AskOrigin): PermissionResult;
162
167
  /** Carry every engine attestation from a decision onto a reconstruction of it — the
163
168
  * seam for the engine's OWN clone sites outside this module (the inherited chain's final-form re-check carries the
164
169
  * post-rewrite args on a spread of an ancestor's deny). Returns the reconstruction. Not re-exported from `src/index.ts`. */
@@ -298,19 +303,17 @@ export type PermissionResult = {
298
303
  * same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
299
304
  * route it would be a window for has not been chosen yet. */
300
305
  denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
301
- /** #144 disclosure (additive): a persisted allow rule MATCHED this call but could not clear the
302
- * ask, because the ask is MANDATED (operator shellGate:"always", or the tool's own
303
- * egress/irreversibility marks) rather than a classifier's hesitation — "allow rules silence
304
- * the classifier's questions, never a mandated one". Carries the matched rule text — a covering
305
- * SET (conjunction arm) joins up to five sanitized member texts with an "and N more" tail — so a
306
- * consumer (approval card, wire frame) can tell the person their rule is alive, just outranked.
307
- * Absent ⇒ no rule matched, or the ask was cleared normally. */
308
- persistedRuleShadowed?: string;
306
+ /** The allow layer's DISCLOSURE: the members that MATCHED this call and could not clear the ask (it is a class
307
+ * no allow rule clears — a mandated, governance, hook, ask-rule or mode question — or the call reached the layer
308
+ * marked), one record per member ({@link AllowRuleShadow}). ENGINE-STAMPED by the allow layer, overwriting a
309
+ * policy's own value; a DISPLAY member: the approval card and the park row render it, nothing reads it to
310
+ * decide, and the classifier never sees it (it is not part of `message`). Absent ⇒ no member matched. */
311
+ allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
309
312
  /** backlog #239 disclosure (additive): WHY the reversibility probe did not clear this call, in the
310
313
  * probe's own words. ENGINE-STAMPED at the irreversibility tighten (which constructs this decision
311
314
  * wholesale), already neutralized + capped there; a `"maybe"` tier whose probe supplied no cause,
312
315
  * and the `"always"` tier (which runs no probe), leave it absent. Same DISPLAY-ONLY posture as
313
- * {@link persistedRuleShadowed}: a policy that self-declares it can only put its own prose on its
316
+ * {@link allowRuleShadowed}: a policy that self-declares it can only put its own prose on its
314
317
  * own card — nothing reads this member to decide anything, so it is not an authority channel. */
315
318
  probeReason?: string;
316
319
  /** backlog #239 disclosure (additive): the STRUCTURED cause, for a probe whose verdict this engine
@@ -370,7 +373,7 @@ export type PermissionResult = {
370
373
  * stamp point whenever the persisted-rule lane reported one (overwriting a policy's claim);
371
374
  * consumed only by the offer factory, which puts on the batch offer exactly the segments this
372
375
  * table calls uncovered. Absent ⇒ all segments read as uncovered (over-offer, the safe way).
373
- * Same display-only posture as {@link persistedRuleShadowed}: never adjudication input, and a
376
+ * Same display-only posture as {@link allowRuleShadowed}: never adjudication input, and a
374
377
  * value nobody stamps over can only shrink or annotate the card's ADVISORY offers — every
375
378
  * offered rule still passes the validator at redemption. */
376
379
  segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
@@ -451,7 +454,7 @@ export declare const ASK_EVIDENCE_ABSENCE_VALUES: readonly AskEvidenceAbsence[];
451
454
  * snapshot's `revision`, the personal rule's add `dot`s — onto the surface a consumer can actually
452
455
  * read. It performs no new judgment and changes no verdict; removing it would leave every decision
453
456
  * byte-identical. The prose channels ({@link PermissionResult}'s ask `message`,
454
- * {@link AskRequest.persistedRuleShadowed}) say the same things to a PERSON; those are sanitized,
457
+ * {@link AskRequest.allowRuleShadowed}) say the same things to a PERSON; those are sanitized,
455
458
  * capped, display-shaped strings, and reconciling a decision against a published policy revision by
456
459
  * regexing them is not an audit trail. This member is the machine's copy.
457
460
  *
@@ -480,7 +483,7 @@ export interface AskRuleEvidence {
480
483
  * #144 shadowed arm) — the deduplicated UNION across the covering set when the conjunction arm
481
484
  * matched, so the array does not belong to one rule on those asks. The dots are the rule's durable
482
485
  * identity — unlike
483
- * {@link AskRequest.persistedRuleShadowed}, which is a sanitized, length-capped DISPLAY value and
486
+ * {@link AskRequest.allowRuleShadowed}, which is a sanitized, length-capped DISPLAY value and
484
487
  * deliberately not an identity channel. A rule is a set of adds (concurrent approvals on one text
485
488
  * each redeem their own dot), so this is an array by construction: render/reconcile the entries as
486
489
  * data, never re-derive one scalar id by joining them. Absent ⇒ {@link personalRuleDotsAbsent}. */
@@ -499,12 +502,12 @@ export declare function decisionText(d: PermissionResult): string | undefined;
499
502
  *
500
503
  * `check` may be async, which is also how **human-in-the-loop approval** works: a deployment can
501
504
  * hold the promise open until an operator approves/denies. `signal` fires when the task aborts
502
- * (timeout / max turns / cancel) and, since design/384, when the asking turn is interrupted (a bare
503
- * user halt / steer-now boundary cut) — honor it to release a pending approval instead of hanging (F4).
504
- * The Runner also races `check` against `signal` itself, so a policy that ignores it still cannot
505
- * hang the worker past the deadline; passing it through just lets you clean up the wait early.
505
+ * (timeout / max turns / cancel) or the asking turn is interrupted (design/384) — honor it to release a
506
+ * pending approval (F4; the Runner also races `check` against it). An already-aborted one: see `check`.
506
507
  */
507
508
  export interface ToolPolicy {
509
+ /** @contract policy.check.aborted_signal_reaches_nobody — handed an already-aborted signal, `check` reaches no person and
510
+ * no external approval (it answers at once, or with a `task_aborted` deny); visibility probes rely on it. */
508
511
  check(req: ToolCallRequest, signal?: AbortSignal): PermissionResult | Promise<PermissionResult>;
509
512
  /**
510
513
  * The policy's CONSTRAINT PROJECTION — its deterministic DENY-verdict half expressed as pure data
@@ -1117,13 +1120,11 @@ export interface AskDelegationProvenance {
1117
1120
  * (see {@link OnAsk} for the full detached-settlement contract). */
1118
1121
  export interface AskRequest {
1119
1122
  toolName: string;
1120
- /** A persisted allow rule MATCHED this call but could not clear the ask (mandated — see
1121
- * {@link PermissionResult}'s ask arm). The matched rule text — or, when a covering SET admitted the
1122
- * command segment-wise (the conjunction arm), a comma-joined display list of up to five sanitized
1123
- * rule texts with an "and N more" tail — so the approval card renders "your rule is alive, just
1124
- * outranked" instead of leaving the person to regex the message prose. A DISPLAY value, not an
1125
- * identity channel: per-rule identity travels on {@link AskRuleEvidence.personalRuleDots}. */
1126
- persistedRuleShadowed?: string;
1123
+ /** The allow-layer members that MATCHED this call and could not clear the ask (see {@link PermissionResult}'s ask arm),
1124
+ * one sanitized record per member — so the card renders "your rule is alive, just outranked" from data; the same
1125
+ * account is rendered once onto {@link message} at this request's mint. A DISPLAY value, not an identity channel:
1126
+ * a stored rule's identity travels on {@link AskRuleEvidence.personalRuleDots}. */
1127
+ allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
1127
1128
  /** WHY the reversibility probe did not clear this call — the account of the tighten the
1128
1129
  * card was showing WITHOUT until now (the gate knew the cause and dropped it at this seam, leaving the
1129
1130
  * approver the action but not the reason it is being asked about). Present only for a `"maybe"`-tier
@@ -1213,7 +1214,7 @@ export interface AskRequest {
1213
1214
  * (operator shellGate:"always", the tool's own egress/irreversibility marks, a
1214
1215
  * `requiresRealApproval` demand, or — #502, the one PER-CALL member of that family — a demotion the
1215
1216
  * call's own reversibility probe declared structural, which for the built-in shell probe means a read
1216
- * outside the session's allowed directories) and an ask carrying {@link persistedRuleShadowed} offer
1217
+ * outside the session's allowed directories) and an ask carrying {@link allowRuleShadowed} offer
1217
1218
  * none.
1218
1219
  *
1219
1220
  * CONTRACT — see {@link import("./permission-rule-model.js").RuleOffer} for the closed union, the
@@ -36,6 +36,9 @@ function attestEngineSettlement(d, record, call) {
36
36
  ENGINE_SETTLEMENTS.set(d, { settlement, origin: record.origin, toolCallId: call.toolCallId, toolName: call.toolName });
37
37
  return d;
38
38
  }
39
+ export function abortedSeatDeny(req, message, origin) {
40
+ return attestEngineSettlement({ action: "deny", message }, { settlement: settledByNobody("task_aborted"), origin }, req);
41
+ }
39
42
  function transferEngineAttestations(from, to) {
40
43
  for (const seat of ENGINE_ATTESTATION_SEATS) {
41
44
  const v = seat.get(from);
@@ -256,7 +259,7 @@ const ALLOW = Object.freeze({ action: "allow" });
256
259
  const ALLOW_BY_RULE = Object.freeze({ action: "allow", decisionReason: "rule" });
257
260
  const RETIRED_TEXT_FIELD = "reason";
258
261
  export const DECISION_MEMBERS = Object.keys({
259
- action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, persistedRuleShadowed: 0, probeReason: 0,
262
+ action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, allowRuleShadowed: 0, probeReason: 0,
260
263
  probeCause: 0, probeMandated: 0, mandated: 0, mandate: 0, probeReadRootCandidate: 0, matchedAskRule: 0, ruleEvidence: 0, segmentCoverage: 0,
261
264
  origin: 0, askClass: 0, classes: 0, guard: 0, ruleUnreadable: 0, authoredBy: 0, foldedAskClasses: 0,
262
265
  });
@@ -453,9 +456,8 @@ export function createApprovalPolicy(opts) {
453
456
  }
454
457
  if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, req)) {
455
458
  const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
456
- if (signal?.aborted) {
457
- return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
458
- }
459
+ if (signal?.aborted)
460
+ return abortedSeatDeny(req, `approval aborted for "${req.toolName}" (run or turn interrupted)`, "policy");
459
461
  let ok;
460
462
  try {
461
463
  ok = await withTimeout(Promise.resolve(opts.approve(req, signal)), opts.approvalTimeoutMs, () => DEADLINE_ELAPSED);
@@ -470,9 +472,8 @@ export function createApprovalPolicy(opts) {
470
472
  `(${opts.approvalTimeoutMs}ms) with no answer; denied fail-closed`,
471
473
  }, { kind: "approval_window_expired", who: { party: "engine", window: "approval_factory" }, when: Date.now() });
472
474
  }
473
- if (signal?.aborted) {
474
- return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
475
- }
475
+ if (signal?.aborted)
476
+ return abortedSeatDeny(req, `approval aborted for "${req.toolName}" (run or turn interrupted)`, "policy");
476
477
  if (typeof ok === "object" && ok !== null) {
477
478
  const reading = readApproverOutcomeObject(ok, req.toolName, "none");
478
479
  if (reading.kind === "refused")
@@ -552,19 +553,25 @@ export function combinePolicies(...policies) {
552
553
  let fallbackSeen;
553
554
  let authoredBySeen = false;
554
555
  const contributors = [];
556
+ const settleDeny = (d) => {
557
+ const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
558
+ if (out !== d)
559
+ transferEngineAttestations(d, out);
560
+ if (!anyRecord)
561
+ return out;
562
+ const owned = out === d ? { ...d } : out;
563
+ transferEngineAttestations(records, owned);
564
+ transferEngineAttestations(d, owned);
565
+ return owned;
566
+ };
567
+ let seatDeny;
555
568
  for (const p of policies) {
556
569
  const d = refuseOutOfContractDecision(await p.check(current, signal));
557
570
  if (d.action === "deny") {
558
- const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
559
- if (out !== d) {
560
- transferEngineAttestations(d, out);
561
- }
562
- if (!anyRecord)
563
- return out;
564
- const owned = out === d ? { ...d } : out;
565
- transferEngineAttestations(records, owned);
566
- transferEngineAttestations(d, owned);
567
- return owned;
571
+ if (engineSettlementOf(d, current)?.settlement.kind !== "task_aborted")
572
+ return settleDeny(d);
573
+ seatDeny ??= settleDeny(d);
574
+ continue;
568
575
  }
569
576
  if (d.updatedInput !== undefined) {
570
577
  current = { ...current, args: d.updatedInput };
@@ -595,6 +602,8 @@ export function combinePolicies(...policies) {
595
602
  if (d.action === "ask")
596
603
  contributors.push(d);
597
604
  }
605
+ if (seatDeny !== undefined)
606
+ return seatDeny;
598
607
  if (asked) {
599
608
  const merged = rewrite?.updatedInput;
600
609
  const withRuleAsk = ruleAskText !== undefined && asked.matchedAskRule === undefined ? { ...asked, matchedAskRule: ruleAskText } : asked;
@@ -1079,6 +1079,13 @@ export interface ToolExecuteContext {
1079
1079
  * tool runs outside a Runner task.
1080
1080
  */
1081
1081
  activeSkillGrants?: () => readonly import("./skill-grant.js").SkillGrantRecord[];
1082
+ /**
1083
+ * A read-only CAPTURE of this (parent) task's request allow entries for the run ({@link import("./task-spec.js").TaskSpec.allowedTools},
1084
+ * its own and any it inherited), filled by the Runner beside {@link activeSkillGrants}: a delegation tool reads it AT SPAWN
1085
+ * TIME and threads it into the child, which holds them as its own request rows until it ends. A copy; the model can
1086
+ * neither read nor set it. Undefined when the tool runs outside a Runner task.
1087
+ */
1088
+ activeRequestAllowedTools?: () => readonly string[];
1082
1089
  /**
1083
1090
  * Parent effective-policy inheritance (tighten-only) — the Runner-filled chain accessor a delegation tool
1084
1091
  * (`createSubagentTool` / the `run_workflow` spawn legs) calls AT SPAWN TIME and threads verbatim into the
@@ -456,6 +456,22 @@ export type TraceEvent = {
456
456
  skill: string;
457
457
  entries: string[];
458
458
  ts: number;
459
+ } | {
460
+ /**
461
+ * An entry of the deployment's `RunnerDeps.allowedTools` (`source: "deployment"`) or of this run's
462
+ * `TaskSpec.allowedTools` (`source: "request"`) cleared the call, so no person and no classifier was asked: the
463
+ * allow layer's attribution channel for its two entry-list members, beside `permission.skill_allowed`. `entries` are
464
+ * the entries the call stands on, as the list spells them (deployment or caller text; a consumer rendering it applies
465
+ * its own display sanitizer).
466
+ */
467
+ kind: "permission.allowed_tools_allowed";
468
+ version: 1;
469
+ taskId: string;
470
+ toolName: string;
471
+ toolCallId: string;
472
+ source: "deployment" | "request";
473
+ entries: string[];
474
+ ts: number;
459
475
  } | {
460
476
  /**
461
477
  * design/179 — the person's persisted rule store could not be read (at prepare, or for one call), so
package/dist/index.d.ts CHANGED
@@ -198,7 +198,11 @@ export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL,
198
198
  export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
199
199
  export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
200
200
  export type { PermissionSeatInputs, PermissionSeatReading } from "./core/mode-policy.js";
201
- export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantRecord } from "./core/skill-grant.js";
201
+ export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantOrigin, SkillGrantRecord } from "./core/skill-grant.js";
202
+ export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
203
+ export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, type AllowEntryInertReason, type CompiledAllowEntry, type AllowEntryGateSeats, type AllowEntryLane, type AllowEntryHit } from "./core/allow-entry.js";
204
+ export { ALLOW_RULE_MEMBERS, type AllowRuleMember, type AllowRuleShadow } from "./core/allow-rule-shadow.js";
205
+ export { mintNamespacePrefix } from "./core/protocol-naming.js";
202
206
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
203
207
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
204
208
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_PARTIES, type SettlementParty, isSettlementParty, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, type ModeRelease, type ModeRefusal, screenGateOutcome } from "./core/gate-outcome.js";
package/dist/index.js CHANGED
@@ -162,6 +162,10 @@ export {} from "./core/read-root-grant-screen.js";
162
162
  export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
163
163
  export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
164
164
  export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
165
+ export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
166
+ export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList } from "./core/allow-entry.js";
167
+ export { ALLOW_RULE_MEMBERS } from "./core/allow-rule-shadow.js";
168
+ export { mintNamespacePrefix } from "./core/protocol-naming.js";
165
169
  export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
166
170
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
167
171
  export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_PARTIES, isSettlementParty, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, screenGateOutcome } from "./core/gate-outcome.js";
@@ -8,7 +8,7 @@ export type { CompactionPreparation, SummarizationClampDryRun } from "../engine/
8
8
  export type { InvokedSkillRetention } from "../engine/compaction/utils.js";
9
9
  export type { AgentCoreRuntimeDeps } from "../engine/loop/runtime-deps.js";
10
10
  export type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel, ToolExecutionMode, ToolInputValidationContext, ToolInputVerdict, } from "../engine/loop/types.js";
11
- export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileError, FileErrorCode, FileInfo, Result, WriteExpectation, WriteReceipt, SymlinkChain, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
11
+ export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileError, FileKind, FileErrorCode, FileInfo, Result, WriteExpectation, WriteReceipt, SymlinkChain, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
12
12
  export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
13
13
  export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
14
14
  export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Directory ENTRIES for the model, and the one per-entry read-deny judge every traversal shares.
3
+ *
4
+ * The judge ({@link createEntryDenyJudge}) is the walk's (search.ts `walk`, the throat Glob, the Grep
5
+ * fallback and the repo map traverse through): an entry path is judged as spelled and at its home view
6
+ * (read-deny-home.ts), so a traversal that enters the home through an alias of it meets the
7
+ * home-anchored rows where the home is. It answers; it does not count — each caller counts what its own
8
+ * result has to disclose.
9
+ *
10
+ * The enumerator ({@link enumerateDirectory}) lists ONE level of a directory the way the upstream
11
+ * at-mention lists one (a single readdir, at most {@link DIRECTORY_LISTING_MAX_ENTRIES} names, then
12
+ * "… and N more entries"), with three differences that are this engine's facts:
13
+ * · entries are files, directories AND symbolic links (kinds from the environment's listing, which does
14
+ * not follow links); a link is listed by its own name and judged at its own path — its target is never
15
+ * resolved or read here;
16
+ * · every entry passes the per-entry read-deny judge; a withheld entry is neither named nor counted in
17
+ * the remainder, and the withheld count is EXACT (one level, so every entry is judged);
18
+ * · no ignore rules apply: the user named this directory, and ignore files are a search-noise filter, not
19
+ * a read boundary.
20
+ * Entries sort by code unit (a stable listing; the environment's own order is not one). Three counts stay
21
+ * apart: `omitted` (withheld by the deny judge), `more` (kept entries past the cap), `incomplete` (the
22
+ * judging was cut short by an abort — the listing is a prefix, not the directory).
23
+ */
24
+ import type { ExecutionEnv } from "../../internal/harness-types.js";
25
+ import type { FileError, FileKind, Result } from "../../internal/harness.js";
26
+ import { type HomeViewEnv, type HomeViewsCarrier } from "./read-deny-home.js";
27
+ /** The upstream at-mention's cap on listed entries. */
28
+ export declare const DIRECTORY_LISTING_MAX_ENTRIES = 1000;
29
+ /** The read-deny table as the traversals hold it. */
30
+ export type EntryDenyTable = {
31
+ matchPath(path: string): {
32
+ pattern: string;
33
+ } | null;
34
+ } & HomeViewsCarrier;
35
+ /** Judge one entry path: the row that withholds it, or null. `leaf: "file"` asks the home view for a file
36
+ * leaf (a directory entry is judged as a directory). */
37
+ export type EntryDenyJudge = (path: string, leaf?: "file") => Promise<{
38
+ pattern: string;
39
+ } | null>;
40
+ /** The per-entry judge over `deny` (literal spelling, then the home view). */
41
+ export declare function createEntryDenyJudge(env: HomeViewEnv, deny: EntryDenyTable, signal?: AbortSignal): EntryDenyJudge;
42
+ export interface DirectoryEntry {
43
+ name: string;
44
+ kind: FileKind;
45
+ }
46
+ export interface DirectoryListing {
47
+ /** At most {@link DIRECTORY_LISTING_MAX_ENTRIES}, sorted. */
48
+ entries: DirectoryEntry[];
49
+ /** Kept entries past the cap. */
50
+ more: number;
51
+ /** Entries withheld by the read-deny judge (exact). */
52
+ omitted: number;
53
+ /** The rows that withheld something, first-seen order. */
54
+ omittedPatterns: string[];
55
+ /** An abort cut the judging short: `entries` / `more` / `omitted` describe a prefix of the directory. */
56
+ incomplete: boolean;
57
+ }
58
+ /** List one level of `dir` (a canonical directory path). A listing the environment refuses is the error. */
59
+ export declare function enumerateDirectory(env: ExecutionEnv, dir: string, judge: EntryDenyJudge | undefined, signal?: AbortSignal): Promise<Result<DirectoryListing, FileError>>;
60
+ /**
61
+ * A data label (a path, a file name) as a JSON string literal with every C0/C1 control, format character
62
+ * and line / paragraph separator written as `\uXXXX` — lossless (JSON.parse gives the original back), and
63
+ * one line whatever the name holds. JSON's own escaping alone leaves U+2028 / U+2029 / U+0085 and the
64
+ * format characters raw.
65
+ */
66
+ export declare function quoteDataLabel(label: string): string;
67
+ /** The listing as text: one entry per line (a directory with a trailing "/"; a name that could break or
68
+ * hide in its line is written as {@link quoteDataLabel}), then "… and N more entries" when capped. */
69
+ export declare function renderDirectoryListing(l: DirectoryListing): string;