@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.
- package/CHANGELOG.md +90 -0
- package/dist/agents/subagent.js +2 -0
- package/dist/core/agent-definition.d.ts +1 -1
- package/dist/core/allow-entry.d.ts +241 -0
- package/dist/core/allow-entry.js +340 -0
- package/dist/core/allow-rule-shadow.d.ts +18 -0
- package/dist/core/allow-rule-shadow.js +1 -0
- package/dist/core/ask-origin.d.ts +5 -5
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/auto-mode.d.ts +3 -2
- package/dist/core/checkpoint-store.d.ts +11 -19
- package/dist/core/checkpoint-store.js +1 -1
- package/dist/core/decision-receipt.d.ts +4 -2
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/engine-notice.d.ts +8 -5
- package/dist/core/gate-fold.js +1 -1
- package/dist/core/gate-lanes.d.ts +1 -49
- package/dist/core/gate-lanes.js +85 -223
- package/dist/core/governance-codes.d.ts +3 -3
- package/dist/core/governance-codes.js +3 -3
- package/dist/core/hooks.d.ts +13 -10
- package/dist/core/hooks.js +5 -3
- package/dist/core/mode-policy.d.ts +3 -3
- package/dist/core/mode-policy.js +2 -2
- package/dist/core/permission-rules.d.ts +24 -23
- package/dist/core/permission-rules.js +37 -33
- package/dist/core/reminder-disclosure.d.ts +53 -2
- package/dist/core/reminder-disclosure.js +49 -36
- package/dist/core/rule-lane-reads.d.ts +84 -0
- package/dist/core/rule-lane-reads.js +164 -0
- package/dist/core/runner/contracts.d.ts +4 -2
- package/dist/core/runner/gate-exit.d.ts +3 -3
- package/dist/core/runner/image.js +4 -3
- package/dist/core/runner/inline-image.d.ts +14 -0
- package/dist/core/runner/inline-image.js +26 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +3 -1
- package/dist/core/runner/permission-rule-lanes.js +1 -1
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -19
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-park-ask.js +3 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +12 -0
- package/dist/core/runner/prepare-policy-chain.js +10 -9
- package/dist/core/runner/prepare-project-context.d.ts +13 -3
- package/dist/core/runner/prepare-project-context.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -5
- package/dist/core/runner/prepare-turn-wiring.d.ts +16 -3
- package/dist/core/runner/prepare-turn-wiring.js +19 -18
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner-deps.d.ts +11 -0
- package/dist/core/skill-grant.d.ts +8 -47
- package/dist/core/skill-grant.js +15 -136
- package/dist/core/synthetic-read-verdict.d.ts +108 -0
- package/dist/core/synthetic-read-verdict.js +111 -0
- package/dist/core/task-spec.d.ts +18 -0
- package/dist/core/tool-name-readings.d.ts +1 -1
- package/dist/core/tool-name-readings.js +1 -1
- package/dist/core/tool-policy.d.ts +25 -24
- package/dist/core/tool-policy.js +26 -17
- package/dist/core/tool-spec.d.ts +7 -0
- package/dist/core/trace.d.ts +16 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -0
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/tools/fs/dir-entries.d.ts +69 -0
- package/dist/tools/fs/dir-entries.js +60 -0
- package/dist/tools/fs/fs-pdf.d.ts +7 -11
- package/dist/tools/fs/fs-pdf.js +5 -14
- package/dist/tools/fs/fs-read.d.ts +173 -2
- package/dist/tools/fs/fs-read.js +399 -286
- package/dist/tools/fs/image-admission.d.ts +51 -0
- package/dist/tools/fs/image-admission.js +36 -0
- package/dist/tools/fs/search.js +3 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +29 -1
- package/wire-changes/core@7.41.1.json +159 -0
- 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
|
+
}
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -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
|
|
@@ -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
|
-
"
|
|
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
|
-
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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)
|
|
503
|
-
*
|
|
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
|
-
/**
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
|
|
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
|
|
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
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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;
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -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
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -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;
|