@sema-agent/core 7.12.0 → 7.14.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 +124 -0
- package/dist/agents/subagent.d.ts +4 -9
- package/dist/agents/subagent.js +16 -12
- package/dist/agents/teacher.js +1 -1
- package/dist/brain/reasoning.d.ts +23 -0
- package/dist/brain/reasoning.js +35 -4
- package/dist/brain/stream-engine.js +35 -17
- package/dist/brain/timeout.d.ts +25 -4
- package/dist/brain/timeout.js +1 -1
- package/dist/core/ask-origin.d.ts +83 -9
- package/dist/core/ask-origin.js +31 -5
- package/dist/core/auto-mode.d.ts +3 -3
- package/dist/core/checkpoint-seat.d.ts +18 -0
- package/dist/core/checkpoint-seat.js +6 -0
- package/dist/core/checkpoint-store.d.ts +9 -16
- package/dist/core/checkpoint-store.js +7 -4
- package/dist/core/engine-notice.d.ts +21 -1
- package/dist/core/env-knob-announce.d.ts +16 -0
- package/dist/core/env-knob-announce.js +14 -0
- package/dist/core/gate-fold.js +2 -0
- package/dist/core/gate-lanes.d.ts +0 -40
- package/dist/core/gate-lanes.js +24 -29
- package/dist/core/gate-outcome.d.ts +42 -3
- package/dist/core/gate-outcome.js +22 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +3 -1
- package/dist/core/hooks.d.ts +6 -17
- package/dist/core/hooks.js +7 -5
- package/dist/core/mcp.d.ts +3 -2
- package/dist/core/mcp.js +5 -11
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/runner/advertised-writable-dirs.d.ts +74 -0
- package/dist/core/runner/advertised-writable-dirs.js +48 -0
- package/dist/core/runner/assemble-result.d.ts +50 -1
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/compaction-seams.d.ts +42 -0
- package/dist/core/runner/compaction-seams.js +80 -0
- package/dist/core/runner/contracts.d.ts +44 -6
- package/dist/core/runner/denial-limit-arms.d.ts +7 -9
- package/dist/core/runner/denial-limit-arms.js +11 -12
- package/dist/core/runner/gate-exit.d.ts +15 -3
- package/dist/core/runner/gate-exit.js +6 -4
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +2 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +34 -4
- package/dist/core/runner/prepare-caps-and-workflow.js +47 -12
- package/dist/core/runner/prepare-gate-stations.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +10 -4
- package/dist/core/runner/prepare-hands-readface.js +5 -14
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -3
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +31 -34
- package/dist/core/runner/prepare-memory.js +73 -51
- package/dist/core/runner/prepare-policy-chain.js +8 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -2
- package/dist/core/runner/prepare-protocol-tools.js +5 -0
- package/dist/core/runner/prepare-question-face.d.ts +10 -8
- package/dist/core/runner/prepare-question-face.js +1 -3
- package/dist/core/runner/prepare-safety-scan.js +11 -16
- package/dist/core/runner/prepare-task.d.ts +9 -1
- package/dist/core/runner/prepare-task.js +63 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
- package/dist/core/runner/prepare-wiring-manifest.js +1 -1
- package/dist/core/runner/resume-claim.d.ts +2 -2
- package/dist/core/runner/resume-preflight.d.ts +2 -2
- package/dist/core/runner/run-attachment-seats.d.ts +2 -2
- package/dist/core/runner/run-compaction-machinery.js +2 -0
- package/dist/core/runner/run-git-lane.d.ts +1 -1
- package/dist/core/runner/run-harness-handlers.js +5 -1
- package/dist/core/runner/run-identity-wiring.d.ts +7 -0
- package/dist/core/runner/run-identity-wiring.js +3 -1
- package/dist/core/runner/run-leg.d.ts +7 -5
- package/dist/core/runner/run-leg.js +256 -5
- package/dist/core/runner/run-notification-lane.d.ts +4 -3
- package/dist/core/runner/run-recovery-lanes.d.ts +4 -17
- package/dist/core/runner/run-recovery-lanes.js +5 -4
- package/dist/core/runner/run-settle-and-teardown.d.ts +14 -10
- package/dist/core/runner/run-settle-and-teardown.js +112 -4
- package/dist/core/runner/run-stop-and-final-verify.d.ts +2 -2
- package/dist/core/runner/run-terminal-adoption.d.ts +16 -16
- package/dist/core/runner/run-terminal-adoption.js +87 -7
- package/dist/core/runner/run-turn-boundary.js +5 -4
- package/dist/core/runner/runtask.d.ts +10 -90
- package/dist/core/runner/runtask.js +77 -596
- package/dist/core/runner/stream-lifecycle-verbs.js +10 -1
- package/dist/core/runner/stream-settle-backstop.js +1 -1
- package/dist/core/runtime-caps.d.ts +21 -0
- package/dist/core/runtime-caps.js +5 -1
- package/dist/core/task-event.d.ts +11 -2
- package/dist/core/task-registry-shared.js +8 -6
- package/dist/core/task-result.d.ts +15 -0
- package/dist/core/task-stream.d.ts +10 -1
- package/dist/core/terminal-cause.d.ts +6 -2
- package/dist/core/tool-face.d.ts +8 -0
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +39 -31
- package/dist/core/tool-policy.js +29 -5
- package/dist/core/tool-registry.d.ts +11 -3
- package/dist/core/tool-registry.js +7 -1
- package/dist/core/tool-roster.d.ts +26 -0
- package/dist/core/tool-roster.js +40 -7
- package/dist/core/tool-spec.d.ts +13 -12
- package/dist/core/types.d.ts +2 -1
- package/dist/core/types.js +1 -0
- package/dist/core/wiring-manifest.d.ts +16 -9
- package/dist/core/wiring-manifest.js +8 -3
- package/dist/index.d.ts +8 -7
- package/dist/index.js +7 -6
- package/dist/orchestration/run-workflow-tool.d.ts +8 -8
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +4 -3
- package/dist/orchestration/workflow-primitives.js +3 -3
- package/dist/orchestration/workflow-types.d.ts +14 -0
- package/dist/orchestration/workflow.d.ts +41 -4
- package/dist/orchestration/workflow.js +23 -5
- package/dist/server/http.js +2 -1
- package/dist/tools/fs/fs-shared.js +9 -3
- package/dist/tools/fs/read-deny.d.ts +24 -8
- package/dist/tools/fs/read-deny.js +20 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +35 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class AdvertisedWritableDirs {
|
|
2
|
+
env;
|
|
3
|
+
onSkipped;
|
|
4
|
+
admitted = [];
|
|
5
|
+
verdicts = new Map();
|
|
6
|
+
constructor(env, onSkipped) {
|
|
7
|
+
this.env = env;
|
|
8
|
+
this.onSkipped = onSkipped;
|
|
9
|
+
}
|
|
10
|
+
get roots() {
|
|
11
|
+
return this.admitted;
|
|
12
|
+
}
|
|
13
|
+
verdictOf(advertiser) {
|
|
14
|
+
return this.verdicts.get(advertiser) ?? "not-asked";
|
|
15
|
+
}
|
|
16
|
+
async admit(dir) {
|
|
17
|
+
const ok = await this.judge(dir);
|
|
18
|
+
this.verdicts.set(dir.advertiser, ok ? "admitted" : "skipped");
|
|
19
|
+
return ok;
|
|
20
|
+
}
|
|
21
|
+
async judge(dir) {
|
|
22
|
+
if (!dir.path || !dir.path.trim()) {
|
|
23
|
+
this.onSkipped(dir, "empty path");
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
let c = await this.env.canonicalPath(dir.path);
|
|
27
|
+
if (!c.ok && c.error.code === "not_found" && dir.materializeMarker !== undefined) {
|
|
28
|
+
const marker = await this.env.joinPath([dir.path, dir.materializeMarker]);
|
|
29
|
+
const exclusiveCreate = this.env.writeFileExclusive?.bind(this.env);
|
|
30
|
+
if (marker.ok && exclusiveCreate) {
|
|
31
|
+
const w = await exclusiveCreate(marker.value, "");
|
|
32
|
+
if (w.ok || w.error.code === "already_exists")
|
|
33
|
+
c = await this.env.canonicalPath(dir.path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!c.ok) {
|
|
37
|
+
this.onSkipped(dir, `${c.error.code}: ${c.error.message}`);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (dir.expectCanonical !== undefined && c.value !== dir.expectCanonical) {
|
|
41
|
+
this.onSkipped(dir, `the execution environment resolves it to ${c.value} while the advertiser's gates key on ${dir.expectCanonical} — a fence hole those gates cannot see is not opened`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (!this.admitted.includes(c.value))
|
|
45
|
+
this.admitted.push(c.value);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -18,6 +18,11 @@ export interface Stats {
|
|
|
18
18
|
* it fails the type check instead of silently reporting a total short by its own leg. */
|
|
19
19
|
totalInputTokens: number;
|
|
20
20
|
costMicroUsd: number;
|
|
21
|
+
/** #692 C-b — present (`true`) when at least one model round of this leg reported NO usage frame (the brain's
|
|
22
|
+
* own `usageMissing` mark on the assistant message: an aborted / errored / usage-less provider reply). The
|
|
23
|
+
* numeric fields stay required and stay what was COUNTED — with this key present they are a LOWER BOUND,
|
|
24
|
+
* not a measurement ("0 is unknown, not free"). Absent ⇔ every round reported usage. */
|
|
25
|
+
usageMissing?: true;
|
|
21
26
|
nested?: NestedUsage;
|
|
22
27
|
/** Post-task memory-consolidation usage (design/41); set after the result is assembled. Kept out of
|
|
23
28
|
* the budget gate (`overBudget` reads `costMicroUsd`/`tokens`, never this). */
|
|
@@ -178,6 +183,10 @@ export interface ResultFlags {
|
|
|
178
183
|
* abort (no synthetic stopReason:"aborted" assistant is appended), `final` is the last REAL assistant
|
|
179
184
|
* message — this flag is what keeps an interrupted run from being mistaken for a completed one. */
|
|
180
185
|
abortedLive?: boolean;
|
|
186
|
+
/** The leg's ONE evaluation of {@link answerSettled} (a clean `"stop"` final with no queued continuation
|
|
187
|
+
* lost): a live abort that found this true landed on the run's tail, not on work — slot 9 stands down
|
|
188
|
+
* and the run takes the completed floor. Absent/false = the abort's ordinary reading. */
|
|
189
|
+
answerSettled?: boolean;
|
|
181
190
|
budgetHit?: "exceeded" | "precall";
|
|
182
191
|
/** design/164 — which budget ceiling the hit belongs to. Set together with {@link budgetHit} at every
|
|
183
192
|
* site that records one; it picks the terminal code (`limits.max_tokens_exceeded` vs
|
|
@@ -225,6 +234,43 @@ export declare function errorCodeOf(err: unknown): string | undefined;
|
|
|
225
234
|
* in-presence rule the usage-window wait hint states for itself), and it is not part of the cause's
|
|
226
235
|
* public projection.
|
|
227
236
|
*/
|
|
237
|
+
/**
|
|
238
|
+
* Has the run's ANSWER settled — is `final` the model's own clean end of its work? A run whose last turn
|
|
239
|
+
* ended `stopReason:"stop"` has said everything it was going to say; whatever ran after that turn (the
|
|
240
|
+
* final boundary's compaction, a stop hook, the teardown) is the run's tail, not its work. A hard stop
|
|
241
|
+
* that lands in that tail — `TaskStream.interrupt()`, the task's `signal`, the walltime or turns limit —
|
|
242
|
+
* cuts nothing the person or the caller wanted, so the run is NOT cancellable there: it ends `completed`
|
|
243
|
+
* with the answer as `result` (slot 9 yields to the completed floor), no interruption marker is written
|
|
244
|
+
* for it, and the end-of-task hook seats keep their full deadline. One predicate, read by every site that
|
|
245
|
+
* used to decide the question for itself; there is no second spelling.
|
|
246
|
+
*
|
|
247
|
+
* What does NOT count as settled — each is "the model was still working" and stays cancellable:
|
|
248
|
+
* - `stopReason:"length"` — the output was truncated; the auto-continue chain owns what follows;
|
|
249
|
+
* - `stopReason:"toolUse"` — a batch was in flight (the orphan reconcile owns the abort);
|
|
250
|
+
* - `stopReason:"aborted"` / `"error"` — the turn itself was cut or failed;
|
|
251
|
+
* - a `partialFinalized` `"stop"` — a mid-stream loss promoted to a final; the loop's bounded
|
|
252
|
+
* continue nudge owns it, so the stop the caller sees is a salvage, not the model's own end;
|
|
253
|
+
* - no `final` at all;
|
|
254
|
+
* - `continuationCut` — the run was cut with a continuation still owed: a steer / follow-up the person
|
|
255
|
+
* had already queued was still pending (queued, or drained at the final boundary and never served —
|
|
256
|
+
* the harness's agent_end account, which also mints `task.user_steer_undrained` /
|
|
257
|
+
* `task.user_followup_undrained`), or the loop itself had decided to run another turn (a drained
|
|
258
|
+
* frame, a truncated-output / thinking-only / degenerate-output nudge) and the abort caught it before
|
|
259
|
+
* the dispatch (`Prepared.workCutRef.continuation`, from the loop's own trace steps) — in both, a
|
|
260
|
+
* stop landing between the decision and the next stream returns the PREVIOUS clean answer as
|
|
261
|
+
* `final` (the loop exits at its pre-dispatch guard), so `final` alone cannot tell, and a
|
|
262
|
+
* thinking-only "stop" the loop was about to nudge into a visible answer is an empty result, not
|
|
263
|
+
* an answer; or the abort was already in force when the last turn ENDED
|
|
264
|
+
* (`Prepared.workCutRef.abortedBeforeTurnEnd`) — a brain that ignored its signal and delivered a
|
|
265
|
+
* full "stop" through the abort still answered a run somebody had stopped mid-work.
|
|
266
|
+
* Three axes, one law: the `abortedLive` flag alone decides that a stop landed; this decides whether it
|
|
267
|
+
* landed on work or on the tail. Evaluated ONCE per leg (the terminal-adoption lane, where both facts
|
|
268
|
+
* are in hand) and handed on as a value — {@link ResultFlags.answerSettled} — so the arbitration, the
|
|
269
|
+
* marker gate, the hook-seat grace, the teardown attribution and the notice read the same verdict.
|
|
270
|
+
* (A limit that is NOT a stop — a budget ceiling crossed by the last turn's own spend — is arbitrated
|
|
271
|
+
* above slot 9 and is unaffected.)
|
|
272
|
+
*/
|
|
273
|
+
export declare function answerSettled(final: AssistantMessage | undefined, continuationCut?: boolean): boolean;
|
|
228
274
|
export declare function terminalCauseOf(final: AssistantMessage | undefined, stats: Pick<Stats, "humanReview">, flags: ResultFlags): {
|
|
229
275
|
terminal: TerminalCause;
|
|
230
276
|
apiFailure: TaskResult["apiFailure"];
|
|
@@ -252,7 +298,10 @@ export declare function terminalCauseOf(final: AssistantMessage | undefined, sta
|
|
|
252
298
|
* 9. `abortedLive` | stopReason aborted → `"limits.max_walltime_exceeded"` | `"limits.max_turns_exceeded"` | undefined
|
|
253
299
|
* (audit A-1: hoisted ABOVE no-final — the loop exits cleanly on abort, so `final` may be a
|
|
254
300
|
* normal message or absent; a plain user interrupt carries NO errorCode — switch on `status`),
|
|
255
|
-
* EXCEPT when the aborted `final` is the bare halt's OWN cut artifact (`haltOwnsAbortedFinal`)
|
|
301
|
+
* EXCEPT when the aborted `final` is the bare halt's OWN cut artifact (`haltOwnsAbortedFinal`),
|
|
302
|
+
* and EXCEPT when the answer had already settled (`answerSettled`: a clean `"stop"` final, not
|
|
303
|
+
* partial) — a stop landing on the run's tail cuts no work, so the run falls through to 12
|
|
304
|
+
* (`completed`, the answer as `result`, no `limits.*` code for the axis; three axes, one law)
|
|
256
305
|
* 9.7 `userHalted` + (no `final` | the halt's own aborted `final`) → status `"completed"` (#504: a
|
|
257
306
|
* bare user halt — the verb working, not a defect; below the aborts on purpose, a real
|
|
258
307
|
* abort/limit that also fired owns the terminal and the halt rides as the pass-through seat)
|
|
@@ -41,6 +41,9 @@ function assistantText(msg) {
|
|
|
41
41
|
.join("");
|
|
42
42
|
return stripThinkTags(text);
|
|
43
43
|
}
|
|
44
|
+
export function answerSettled(final, continuationCut = false) {
|
|
45
|
+
return final?.stopReason === "stop" && final.partialFinalized !== true && !continuationCut;
|
|
46
|
+
}
|
|
44
47
|
export function terminalCauseOf(final, stats, flags) {
|
|
45
48
|
let terminal;
|
|
46
49
|
let apiFailure;
|
|
@@ -96,7 +99,7 @@ export function terminalCauseOf(final, stats, flags) {
|
|
|
96
99
|
else if (flags.paused) {
|
|
97
100
|
terminal = flags.paused;
|
|
98
101
|
}
|
|
99
|
-
else if ((flags.abortedLive || final?.stopReason === "aborted") && !haltOwnsAbortedFinal) {
|
|
102
|
+
else if ((flags.abortedLive || final?.stopReason === "aborted") && !haltOwnsAbortedFinal && flags.answerSettled !== true) {
|
|
100
103
|
const code = flags.abortedForTimeout ? "limits.max_walltime_exceeded" : flags.abortedForTurns ? "limits.max_turns_exceeded" : undefined;
|
|
101
104
|
terminal = { kind: "failed", ...(code !== undefined ? { code } : {}), message: final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted") };
|
|
102
105
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MaybeCompactOptions } from "../auto-compaction.js";
|
|
2
|
+
import type { TaskSpec } from "../types.js";
|
|
3
|
+
import type { Prepared, RunnerDepsSeat } from "./contracts.js";
|
|
4
|
+
/**
|
|
5
|
+
* design/84 Seam C — the cost-optimization compaction options threaded into BOTH `maybeCompact` call
|
|
6
|
+
* sites (within-task turn boundary + `finish()`). All three fields come from `RunnerDeps` (a trusted
|
|
7
|
+
* FUNCTION seam — never `TaskSpec`, which is serializable/durable-resumable/untrusted-caller). The
|
|
8
|
+
* Runner OWNS the consecutive-reuse counter (`prepared.compactionReuseRef`) so the
|
|
9
|
+
* `maxConsecutiveProviderReuse` drift guard spans the whole task across both sites: it FEEDS the current
|
|
10
|
+
* count in as `consecutiveProviderReuse`, and {@link recordCompactionReuse} updates it from the result.
|
|
11
|
+
* Returns `undefined` when no provider is wired (so the call site spreads nothing → byte-identical to
|
|
12
|
+
* the pre-design/84 behavior).
|
|
13
|
+
*/
|
|
14
|
+
export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared: Prepared): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
|
|
15
|
+
/** design/134 §3.2 — resolve the pre/postCompact lifecycle callbacks (whole-slot `spec.hooks ??
|
|
16
|
+
* deps.hooks`, same resolution as the stop hook) and wrap each in a SWALLOW+TRACE shell before
|
|
17
|
+
* threading them into maybeCompact. The wrapper owns the observability half of the R3 MED contract
|
|
18
|
+
* (maybeCompact swallows defensively too, but has no sink): a throwing callback is reported via
|
|
19
|
+
* `onError(phase:"hook")` and treated as absent; a `block` returned under a "forced" trigger is
|
|
20
|
+
* reported as ignored (maybeCompact enforces the ignore — blocking a compaction the provider/trim
|
|
21
|
+
* layer already demanded would kill the run). */
|
|
22
|
+
export declare function compactionHookOptions(runner: RunnerDepsSeat, spec: TaskSpec, sessionId: string, trigger: "auto" | "manual" | "forced",
|
|
23
|
+
/** #281 件A — the leg's identity envelope, spread onto both compaction contexts by the wrappers
|
|
24
|
+
* below (the compaction module builds its contexts below the runner and cannot know the leg;
|
|
25
|
+
* the wrapper is the runner's seam). Every runner call site supplies `prepared.hookIdentity`;
|
|
26
|
+
* optional only so a context built without a leg stays honestly identity-less. */
|
|
27
|
+
identity?: import("../hooks.js").HookInvocationIdentity,
|
|
28
|
+
/** The leg's already-validated seat bound (`Prepared.hookTimeoutMs`) and its abort signal. Optional
|
|
29
|
+
* for the same reason `identity` is — a wrapper built without a prepared leg still works, on the
|
|
30
|
+
* default bound and with no signal. The signal is what keeps a hung compaction hook from holding a
|
|
31
|
+
* CANCELLED run's turn boundary for the whole deadline (the seat's post-abort grace). */
|
|
32
|
+
seatBound?: {
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
|
|
36
|
+
/** design/84 Seam C — fold a finished compaction's `reused` flag into the run-scoped consecutive-reuse
|
|
37
|
+
* counter: a reused (provider) summary increments it, a real (LLM) summary resets it to 0. A no-op
|
|
38
|
+
* compaction (`compacted:false`) leaves the counter untouched. No-op when no provider is wired. */
|
|
39
|
+
export declare function recordCompactionReuse(runner: RunnerDepsSeat, prepared: Prepared, comp: {
|
|
40
|
+
compacted: boolean;
|
|
41
|
+
reused?: boolean;
|
|
42
|
+
}): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
2
|
+
export function seamCCompactionOptions(runner, prepared) {
|
|
3
|
+
if (!runner.deps.summaryProvider)
|
|
4
|
+
return undefined;
|
|
5
|
+
return {
|
|
6
|
+
summaryProvider: runner.deps.summaryProvider,
|
|
7
|
+
onCompaction: runner.deps.onCompaction,
|
|
8
|
+
maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
|
|
9
|
+
consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function compactionHookOptions(runner, spec, sessionId, trigger, identity, seatBound) {
|
|
13
|
+
const hooks = spec.hooks ?? runner.deps.hooks;
|
|
14
|
+
const pre = hooks?.preCompact;
|
|
15
|
+
const post = hooks?.postCompact;
|
|
16
|
+
const seatMs = seatBound?.timeoutMs ?? resolveHookTimeoutMs(hooks?.timeoutMs);
|
|
17
|
+
const seatSignal = seatBound?.signal;
|
|
18
|
+
const withIdentity = (ctx) => identity !== undefined ? { ...ctx, identity } : ctx;
|
|
19
|
+
return {
|
|
20
|
+
trigger,
|
|
21
|
+
...(pre
|
|
22
|
+
? {
|
|
23
|
+
preCompact: async (rawCtx) => {
|
|
24
|
+
const ctx = withIdentity(rawCtx);
|
|
25
|
+
const report = (err) => {
|
|
26
|
+
try {
|
|
27
|
+
runner.deps.onError?.(err, { phase: "hook", sessionId });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
const seat = await runHookSeat("preCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => pre.call(hooks, { ...ctx, signal: sig }));
|
|
34
|
+
if (seat.expired) {
|
|
35
|
+
report(hookSeatExpiredError("preCompact", seatMs, seat.cause, `the "${ctx.trigger}" compaction PROCEEDED unblocked and with no additional instructions`));
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const r = seat.value;
|
|
39
|
+
if (r?.block && ctx.trigger === "forced") {
|
|
40
|
+
report(new Error(`a preCompact callback blocked a "forced" compaction — ignored (PTL/trim-pressure compaction is not optional): ${r.block}`));
|
|
41
|
+
}
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
report(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: {}),
|
|
51
|
+
...(post
|
|
52
|
+
? {
|
|
53
|
+
postCompact: async (rawCtx) => {
|
|
54
|
+
try {
|
|
55
|
+
const seat = await runHookSeat("postCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => post.call(hooks, { ...withIdentity(rawCtx), signal: sig }));
|
|
56
|
+
if (seat.expired) {
|
|
57
|
+
try {
|
|
58
|
+
runner.deps.onError?.(hookSeatExpiredError("postCompact", seatMs, seat.cause, "the observation was abandoned; the compaction that already landed is unchanged"), { phase: "hook", sessionId });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
try {
|
|
66
|
+
runner.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function recordCompactionReuse(runner, prepared, comp) {
|
|
77
|
+
if (!runner.deps.summaryProvider || !comp.compacted)
|
|
78
|
+
return;
|
|
79
|
+
prepared.compactionReuseRef.consecutive = comp.reused ? prepared.compactionReuseRef.consecutive + 1 : 0;
|
|
80
|
+
}
|
|
@@ -652,6 +652,24 @@ export interface Prepared {
|
|
|
652
652
|
callIssuedAtRef: {
|
|
653
653
|
current?: number;
|
|
654
654
|
};
|
|
655
|
+
/** #677 — was the run's WORK cut by a stop? Two observation points, one record, read by the answer-settled
|
|
656
|
+
* verdict (`answerSettled`) beside the harness's own undrained-input account:
|
|
657
|
+
* - `continuation` — the loop decided to CONTINUE past its last assistant turn and the abort caught it
|
|
658
|
+
* before that continuation dispatched. Written by the loop-trace sink from the loop's own steps: a
|
|
659
|
+
* `continue` step (a drained steer / follow-up, a truncated-output or thinking-only or degenerate-output
|
|
660
|
+
* nudge, a next tool turn) sets it; the terminal step clears it unless the loop exited at its
|
|
661
|
+
* pre-dispatch abort guard (`aborted_before_stream`) — the one exit where `final` is still the PREVIOUS
|
|
662
|
+
* turn's message. A clean "stop" the loop itself had decided was not the end is not a settled answer.
|
|
663
|
+
* - `abortedBeforeTurnEnd` — the run's abort was already in force when a turn ENDED (written by the
|
|
664
|
+
* `turn_end` handler before the turns cap fires its own abort). The ruling's line is the last
|
|
665
|
+
* `turn_end`: a stop that landed before it landed on work — even when the brain ignored the signal
|
|
666
|
+
* and delivered a full "stop" answer through it (the loop then ends at its own decision, so the
|
|
667
|
+
* continuation bit never sets); a stop that landed after it landed on the tail.
|
|
668
|
+
* Always present; both false on a run nothing stopped. */
|
|
669
|
+
workCutRef: {
|
|
670
|
+
continuation: boolean;
|
|
671
|
+
abortedBeforeTurnEnd: boolean;
|
|
672
|
+
};
|
|
655
673
|
/** RB-458 — records the FIRST brain call this run's outer guardrail gave up on (see
|
|
656
674
|
* {@link import("../../brain/timeout.js").withBrainCallGuardrail}). Always present; `timedOut`
|
|
657
675
|
* stays absent unless the guardrail fired. The run loop reads it AFTER the loop settles and gives
|
|
@@ -2332,13 +2350,11 @@ export interface TurnBoundaryDeps {
|
|
|
2332
2350
|
drainManualCompact: (outcome: CompactOutcome) => void;
|
|
2333
2351
|
runnerHooks: {
|
|
2334
2352
|
onError: RunnerDeps["onError"];
|
|
2335
|
-
seamCCompactionOptions: (prepared: Prepared) => Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
|
|
2336
|
-
compactionHookOptions: (spec: TaskSpec, sessionId: string, trigger: "auto" | "manual" | "forced") => Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
|
|
2337
|
-
recordCompactionReuse: (prepared: Prepared, comp: {
|
|
2338
|
-
compacted: boolean;
|
|
2339
|
-
reused?: boolean;
|
|
2340
|
-
}) => void;
|
|
2341
2353
|
};
|
|
2354
|
+
/** The Runner's deployment deps, read LIVE ({@link RunnerDepsSeat}): the compaction seams (compaction-seams.ts) the
|
|
2355
|
+
* boundary's compaction pass reaches down for read the summary provider, the hooks slot and the error sink through it
|
|
2356
|
+
* on every pass — the three Runner methods that used to sit on `runnerHooks` as delegates (design/393 S7). */
|
|
2357
|
+
runner: RunnerDepsSeat;
|
|
2342
2358
|
}
|
|
2343
2359
|
/** design/157 B15 尾件 — R5(harness 事件处理器族)的依赖包。全部为 runLocked 内声明顺序早于
|
|
2344
2360
|
* 工厂调用点的 const 稳定引用 + 三个 runLocked 形参;this 面(deps.onError)经 runnerHooks 打包
|
|
@@ -2423,6 +2439,28 @@ export interface RunnerSelfSeat {
|
|
|
2423
2439
|
/** Streaming form of {@link RunnerSelfSeat.resume}: the pre-CAS guards and the CAS run first, then the live stream is returned. */
|
|
2424
2440
|
resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskStream>;
|
|
2425
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* design/393 S7 (#675) — the orchestrator's entry (`prepareTask`, prepare-task.ts) as ONE contract: the notification lane's
|
|
2444
|
+
* `prepareTask` seat names it, and the orchestrator pins its own declaration against it (`PrepareTaskIsTheContract`), so a
|
|
2445
|
+
* signature change reds at the declaration — not at the driver's hand-in, and not as a seat spelled a second time. It lives
|
|
2446
|
+
* on the floor because a lane may not name the orchestrator (docs/LAYERING.json: layer 4 sits above the lanes). Positional,
|
|
2447
|
+
* exactly as the entry is declared — the seat carries the orchestrator's function itself, not a lane Input.
|
|
2448
|
+
*/
|
|
2449
|
+
export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
|
|
2450
|
+
runId?: string;
|
|
2451
|
+
}) => Promise<Prepared>;
|
|
2452
|
+
/**
|
|
2453
|
+
* design/393 S7 (#670) — the Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
|
|
2454
|
+
* (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
|
|
2455
|
+
* row's registries) hands nothing back, and says so by extending this marker. The marker is machine-read by gate:phase-api
|
|
2456
|
+
* (design/238 R-1's third clause): a Result with no members MUST extend it, a Result with members MAY NOT, and no Result
|
|
2457
|
+
* inherits anything else — so an empty Result is a stated fact about the phase, never an interface someone forgot to
|
|
2458
|
+
* fill, and a phase that starts handing a product back must drop the declaration in the same edit. Six lanes carry it:
|
|
2459
|
+
* the attachment seats, the stop gate, the recovery lanes, the settle and teardown (run-), the preflight and the claim
|
|
2460
|
+
* (resume-).
|
|
2461
|
+
*/
|
|
2462
|
+
export interface InstallingPhaseResult {
|
|
2463
|
+
}
|
|
2426
2464
|
/** The live-task handle `runLocked` publishes once the harness exists (design/47): the harness + abort
|
|
2427
2465
|
* controller, the loop-liveness latch (`ended` flips when the single `harness.prompt` settles; `userInterrupted`
|
|
2428
2466
|
* / `userHalted` are the interrupt and halt verbs' attribution seats), the run's reminder mark, its session and
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type
|
|
1
|
+
import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type DenialLimitFallback, type DenialLimitFallbackFace, type UnarmedDenialLimitFallback } from "../auto-mode.js";
|
|
2
2
|
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
4
4
|
import { type EngineNotice } from "../types.js";
|
|
5
|
-
import { type AskOrigin } from "../ask-origin.js";
|
|
5
|
+
import { type AskOrigin, type RuleStoreUnreadable } from "../ask-origin.js";
|
|
6
6
|
import type { Prepared } from "./contracts.js";
|
|
7
7
|
/**
|
|
8
8
|
* #548 — the classifier DENIAL-LIMIT arms of the tool gate's inherited (delegation) lane, extracted
|
|
@@ -83,14 +83,12 @@ export type InheritedAsk = Extract<PermissionResult, {
|
|
|
83
83
|
}>;
|
|
84
84
|
/** The members of a surviving ask that ride onto the approval request with a COMPUTED value — what the
|
|
85
85
|
* four request mint stations (the gate's own and the three inherited-lane ones) spread after the seats
|
|
86
|
-
* they spell themselves: the ask's origin word
|
|
87
|
-
* classifier-unavailable fact (#616; the engine's own fact rides the DENY since #661). */
|
|
86
|
+
* they spell themselves: the ask's origin word and its denial-limit fallback. */
|
|
88
87
|
export interface AskRequestCarry {
|
|
89
88
|
origin?: AskOrigin;
|
|
89
|
+
/** #688 C3: rides only from the gate's OWN station (the inherited stations' facts never read a rule store). */
|
|
90
|
+
ruleStoreUnreadable?: RuleStoreUnreadable;
|
|
90
91
|
denialLimitFallback?: DenialLimitFallback;
|
|
91
|
-
classifierUnavailable?: {
|
|
92
|
-
readonly cause: AutoModeUnavailableCause;
|
|
93
|
-
};
|
|
94
92
|
}
|
|
95
93
|
/** The GATE's own mint station: both members are read off the surviving decision, which is the gate's
|
|
96
94
|
* OWN snapshot (its stamp point spreads the policy's object and stamps the word onto the copy), so the
|
|
@@ -124,7 +122,7 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
124
122
|
* stateful accessor cannot answer the exclusion with one word and the card with another); on the
|
|
125
123
|
* bound branch it is derived from the re-spoken ask this station minted (its own object). */
|
|
126
124
|
/** `ask` is the incoming ask unchanged (the caller's own object); an UNAVAILABLE round never reaches this arm —
|
|
127
|
-
* it is a `deny`
|
|
125
|
+
* it is a `deny` whose attested cause reads `unavailable` (#661). */
|
|
128
126
|
| {
|
|
129
127
|
kind: "resolve";
|
|
130
128
|
ask: A;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, classifierUnavailableDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
2
2
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
3
|
+
import { attestClassifierDenyCause } from "../tool-policy.js";
|
|
3
4
|
import { deliverEngineNotice } from "../types.js";
|
|
4
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
|
-
import { askOriginOf, classifierMayAnswer } from "../ask-origin.js";
|
|
6
|
+
import { askOriginOf, classifierMayAnswer, probeMandatedAsk } from "../ask-origin.js";
|
|
6
7
|
export function attachRebuiltDenialTrackers(entries, denialLimit) {
|
|
7
8
|
if (entries === undefined)
|
|
8
9
|
return undefined;
|
|
@@ -62,22 +63,22 @@ function fallbackCarry(fallback, liveApprover, tracker) {
|
|
|
62
63
|
export function gateAskCarry(decision, liveApprover, tracker) {
|
|
63
64
|
return {
|
|
64
65
|
...(decision.origin !== undefined ? { origin: decision.origin } : {}),
|
|
66
|
+
...(decision.ruleStoreUnreadable !== undefined ? { ruleStoreUnreadable: decision.ruleStoreUnreadable } : {}),
|
|
65
67
|
...fallbackCarry(decision.denialLimitFallback, liveApprover, tracker),
|
|
66
|
-
...(decision.classifierUnavailable !== undefined ? { classifierUnavailable: { cause: decision.classifierUnavailable.cause } } : {}),
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
70
71
|
return {
|
|
71
72
|
origin: judged.origin,
|
|
72
73
|
...fallbackCarry(judged.fallback, liveApprover, tracker),
|
|
73
|
-
...(judged.ask.classifierUnavailable !== undefined ? { classifierUnavailable: { cause: judged.ask.classifierUnavailable.cause } } : {}),
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
export async function judgeInheritedClassifier(opts) {
|
|
77
77
|
const { autoMode, ask, req } = opts;
|
|
78
78
|
const origin = askOriginOf(ask, INHERITED_STATION_FACTS);
|
|
79
|
-
if (autoMode === undefined || !classifierMayAnswer(origin))
|
|
79
|
+
if (autoMode === undefined || !classifierMayAnswer(origin) || probeMandatedAsk(ask)) {
|
|
80
80
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
81
|
+
}
|
|
81
82
|
const verdict = await autoMode.decider
|
|
82
83
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
83
84
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
@@ -88,21 +89,19 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
88
89
|
if (verdict.kind === "unavailable") {
|
|
89
90
|
return {
|
|
90
91
|
kind: "deny",
|
|
91
|
-
result: { action: "deny", message: classifierUnavailableDenyMessage(req.toolName, verdict.cause), decisionReason: "classifier"
|
|
92
|
+
result: attestClassifierDenyCause({ action: "deny", message: classifierUnavailableDenyMessage(req.toolName, verdict.cause), decisionReason: "classifier" }, "unavailable", req),
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE;
|
|
95
96
|
const category = verdict.kind === "block" && verdict.category ? inlineUntrusted(verdict.category) : "";
|
|
96
97
|
const tracked = autoMode.denialTracking?.recordBlock();
|
|
97
98
|
if (tracked?.limitReached !== true) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
message: verdict.kind === "block" ? `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : `${reason} (${opts.subject}, at an inherited ancestor layer)`,
|
|
103
|
-
decisionReason: "classifier",
|
|
104
|
-
},
|
|
99
|
+
const denied = {
|
|
100
|
+
action: "deny",
|
|
101
|
+
message: verdict.kind === "block" ? `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : `${reason} (${opts.subject}, at an inherited ancestor layer)`,
|
|
102
|
+
decisionReason: "classifier",
|
|
105
103
|
};
|
|
104
|
+
return { kind: "deny", result: verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied };
|
|
106
105
|
}
|
|
107
106
|
const fallback = tracked.fallback;
|
|
108
107
|
const minted = { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
|
|
22
22
|
* contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
|
|
23
23
|
*/
|
|
24
|
-
import { type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
24
|
+
import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
25
25
|
import type { AskOrigin, AskOriginFacts } from "../ask-origin.js";
|
|
26
26
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
27
27
|
import type { SafeNotifier } from "../safe-notify.js";
|
|
@@ -32,10 +32,13 @@ export interface SettledAsk {
|
|
|
32
32
|
settlement: Settlement;
|
|
33
33
|
origin: AskOrigin;
|
|
34
34
|
}
|
|
35
|
-
/** The facts a gate pass hands the mint. `deniedBy` is present exactly when the pass refused; `
|
|
36
|
-
*
|
|
35
|
+
/** The facts a gate pass hands the mint. `deniedBy` is present exactly when the pass refused; `cause` only
|
|
36
|
+
* beside it, when the refusing round was a classifier round that did not rule (the station's attestation,
|
|
37
|
+
* read once by the fold or the classifier step); `settled` exactly when the pass consumed an ask's
|
|
38
|
+
* settlement (a person's yes that was later vetoed included). */
|
|
37
39
|
export interface GateExitFacts {
|
|
38
40
|
deniedBy?: DeniedBy;
|
|
41
|
+
cause?: ClassifierDenyCause;
|
|
39
42
|
settled?: SettledAsk;
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
@@ -80,6 +83,10 @@ export interface SettlementLedger {
|
|
|
80
83
|
* for an attested refusal, `policy` otherwise — a veto when a yes rides the settlement); undefined
|
|
81
84
|
* for an allow or an ask. */
|
|
82
85
|
fromDecision(decision: PermissionResult): DeniedBy | undefined;
|
|
86
|
+
/** Read a deny's attested classifier CAUSE for this call (an inherited station's `unavailable` /
|
|
87
|
+
* `parse_error` round travelling through an ancestor wrapper into this fold); undefined for a ruling
|
|
88
|
+
* block, a non-classifier deny, an allow or an ask. */
|
|
89
|
+
causeOf(decision: PermissionResult): ClassifierDenyCause | undefined;
|
|
83
90
|
/** A surviving ask supersedes whatever an earlier decision settled. */
|
|
84
91
|
supersede(): void;
|
|
85
92
|
}
|
|
@@ -208,6 +215,11 @@ export interface GatePass {
|
|
|
208
215
|
* that ended as a refusal). The exit mints the outcome from it beside the ledger's `settled`; a refusal
|
|
209
216
|
* that reaches the exit without it is an engine defect and the mint throws. */
|
|
210
217
|
deniedBy: DeniedBy | undefined;
|
|
218
|
+
/** borrowed-mutable — the FORM of the classifier round behind a deny (`unavailable` / `parse_error`), written
|
|
219
|
+
* beside `deniedBy` by the gate's own classifier step (its attestation, read once) or by the fold / the
|
|
220
|
+
* re-check when an inherited station's attested deny arrives through the chain; undefined for a ruling
|
|
221
|
+
* block and every non-classifier deny. The exit mints `GateDisposition.cause` from it. */
|
|
222
|
+
denyCause: ClassifierDenyCause | undefined;
|
|
211
223
|
/** borrowed-mutable — the standing verdict. The no-policy default until the adjudication station writes the
|
|
212
224
|
* policy's answer; then rewritten by every station that changes the verdict (fold, tightens, org, rule
|
|
213
225
|
* lane, stamp, classifier, sandbox, content lane, resolution, edit chain). */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
-
import { decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
2
|
+
import { classifierDenyCauseOf, decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
3
3
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
4
|
import { formatHookFeedback } from "../reminder-mint.js";
|
|
5
5
|
export function mintGateOutcome(facts) {
|
|
6
6
|
const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
|
|
7
7
|
const outcome = Object.freeze({
|
|
8
|
-
disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy } : { kind: "allowed" }),
|
|
8
|
+
disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy, ...(facts.cause !== undefined ? { cause: facts.cause } : {}) } : { kind: "allowed" }),
|
|
9
9
|
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
10
10
|
});
|
|
11
11
|
const defects = screenGateOutcome(outcome);
|
|
@@ -50,6 +50,9 @@ export function createSettlementLedger(call) {
|
|
|
50
50
|
return undefined;
|
|
51
51
|
return attested !== undefined && SETTLEMENT_IS_REFUSAL[attested.settlement.kind] ? "ask_resolution" : "policy";
|
|
52
52
|
},
|
|
53
|
+
causeOf(decision) {
|
|
54
|
+
return decision.action === "deny" ? classifierDenyCauseOf(decision, call) : undefined;
|
|
55
|
+
},
|
|
53
56
|
supersede() {
|
|
54
57
|
settled = undefined;
|
|
55
58
|
},
|
|
@@ -102,7 +105,7 @@ export async function exitGate(pass) {
|
|
|
102
105
|
}
|
|
103
106
|
if (pass.deniedBy === undefined)
|
|
104
107
|
throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
|
|
105
|
-
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
|
|
108
|
+
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
|
|
106
109
|
await notifyPermissionDenied({
|
|
107
110
|
toolName,
|
|
108
111
|
input: cloneObserverInput(pass.currentInput),
|
|
@@ -110,7 +113,6 @@ export async function exitGate(pass) {
|
|
|
110
113
|
reason: denyReason,
|
|
111
114
|
gate,
|
|
112
115
|
...(input.identity !== undefined ? { identity: input.identity } : {}),
|
|
113
|
-
...(pass.decision.action === "deny" && pass.decision.classifierUnavailable !== undefined ? { classifierUnavailable: { cause: pass.decision.classifierUnavailable.cause } } : {}),
|
|
114
116
|
});
|
|
115
117
|
return {
|
|
116
118
|
block: true,
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* silence the ungated-write warning for deployments that wired no policy at all.
|
|
27
27
|
*/
|
|
28
28
|
import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
29
|
-
import { persistedRuleMandateOf
|
|
29
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
30
|
+
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
30
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
31
32
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
32
33
|
import type { PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { persistedRuleMandateOf } from "../
|
|
1
|
+
import { persistedRuleMandateOf } from "../ask-origin.js";
|
|
2
|
+
import {} from "../hooks.js";
|
|
2
3
|
import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
4
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|