@sema-agent/core 7.17.6 → 7.18.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 +165 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/auto-mode.d.ts +141 -45
- package/dist/core/auto-mode.js +109 -3
- package/dist/core/checkpoint-store.d.ts +16 -2
- package/dist/core/engine-notice.d.ts +47 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +17 -2
- package/dist/core/gate-outcome.js +16 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +89 -0
- package/dist/core/runner/classifier-ladder.js +65 -0
- package/dist/core/runner/contracts.d.ts +69 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
- package/dist/core/runner/prepare-config-doors.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.js +5 -3
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.js +4 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +18 -13
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +35 -41
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +3 -1
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +16 -19
- package/dist/core/runner/synthetic-tools.js +18 -10
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +20 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +8 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +10 -5
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
- package/dist/tools/fs/bash-readonly-classifier.js +375 -530
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +158 -22
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +3 -3
- package/dist/tools/fs/safety.js +4 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -9
- package/wire-changes/core@7.17.6.json +81 -0
- package/wire-changes/core@7.17.7.json +49 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -393,7 +393,7 @@ export interface RunnerDeps {
|
|
|
393
393
|
};
|
|
394
394
|
/**
|
|
395
395
|
* design/199 件A — the DEPLOYMENT's read-face declaration
|
|
396
|
-
* ({@link import("../tools/fs/
|
|
396
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.readFace} for the
|
|
397
397
|
* task layer and the resolution order). An interactive single-user product declares
|
|
398
398
|
* `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
|
|
399
399
|
* governance this seat may still open (it is the deployment's own declaration); the task layer
|
|
@@ -406,7 +406,19 @@ export interface RunnerDeps {
|
|
|
406
406
|
* later prepare reads the new declaration; announced as `config.read_face_swapped`). A host whose
|
|
407
407
|
* declaration arrives late (a governance source read after boot) swaps it in rather than restarting.
|
|
408
408
|
*/
|
|
409
|
-
readFace?: import("../tools/fs/
|
|
409
|
+
readFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
410
|
+
/**
|
|
411
|
+
* The DEPLOYMENT's WRITE-face declaration
|
|
412
|
+
* ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.writeFace}
|
|
413
|
+
* for the task layer and the resolution order — the SAME order the read seat runs, one body). The
|
|
414
|
+
* engine default stays "roots". Under org governance this seat may still open (it is the deployment's
|
|
415
|
+
* own declaration); the task layer may not. Beside a read-only (verifier) mount this seat's "open"
|
|
416
|
+
* silently CLAMPS to roots and is ANNOUNCED (`config.write_face_deployment_clamped`); TaskSpec.writeFace's
|
|
417
|
+
* own "open" there refuses loudly. INDEPENDENT of {@link readFace}: neither seat reads the other. A LIVE
|
|
418
|
+
* seat — `Runner.swapDeps({ writeFace })` replaces it without a restart (announced as
|
|
419
|
+
* `config.write_face_swapped`).
|
|
420
|
+
*/
|
|
421
|
+
writeFace?: import("../tools/fs/containment-face.js").ContainmentFace;
|
|
410
422
|
/**
|
|
411
423
|
* design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
|
|
412
424
|
* `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
|
|
@@ -663,10 +675,28 @@ export interface RunnerDeps {
|
|
|
663
675
|
sessionContext?: readonly string[];
|
|
664
676
|
/** Transcript-window bounds for the classify prompt (defaults: 40 entries / 2000 chars each). */
|
|
665
677
|
window?: import("./auto-mode-prompt.js").AutoModeWindowOptions;
|
|
666
|
-
/** Classify round-trip cap, ms (default 15_000)
|
|
667
|
-
*
|
|
668
|
-
*
|
|
678
|
+
/** Classify round-trip cap, ms (default 15_000) — ONE ladder rung (see `classifierFallback`), not the
|
|
679
|
+
* whole classification. A rung past the cap did not rule: the walk moves to the next candidate, and with
|
|
680
|
+
* none left the call is DENIED with a sentence that says so. The next call is classified afresh. */
|
|
669
681
|
timeoutMs?: number;
|
|
682
|
+
/**
|
|
683
|
+
* What a classification does when the classifier SEAT does not rule (the leg threw / rejected, the cap
|
|
684
|
+
* elapsed, or the reply carried no verdict):
|
|
685
|
+
* - `"main"` (DEFAULT) — re-ask the SAME prompt on this run's own main model, once, and act on what it
|
|
686
|
+
* says; only if that rung does not rule either does the call reach the fail-closed deny. The record
|
|
687
|
+
* says which model answered (`GateDisposition.classifier`) and the first move per (rung, cause)
|
|
688
|
+
* announces `classifier.fallback` to the operator.
|
|
689
|
+
* - `"none"` — one rung: a classifier that does not rule denies the call exactly as it did before this
|
|
690
|
+
* knob existed, at exactly one model call.
|
|
691
|
+
*
|
|
692
|
+
* COST of the default, stated: while the seat is broken every gated call pays a second round-trip
|
|
693
|
+
* (worst case two `timeoutMs` caps); `"none"` buys that back for the deny.
|
|
694
|
+
*
|
|
695
|
+
* A value outside the closed set ({@link import("./auto-mode.js").AUTO_MODE_CLASSIFIER_FALLBACKS}) is
|
|
696
|
+
* REFUSED loudly at prepare (`config.auto_mode_classifier_fallback_invalid`) — never clamped, never read
|
|
697
|
+
* as the default.
|
|
698
|
+
*/
|
|
699
|
+
classifierFallback?: import("./auto-mode.js").AutoModeClassifierFallback;
|
|
670
700
|
/**
|
|
671
701
|
* The classifier DENIAL LIMIT (CC 2.1.250 `FO`/`AKe`): a run whose classifier keeps blocking falls
|
|
672
702
|
* back to a PERSON instead of being denied without end. Per run: a `block` first increments the
|
|
@@ -1357,8 +1387,9 @@ export interface RunnerDeps {
|
|
|
1357
1387
|
* knob. ② The engine's own candidates — `ToolSpec.defer === true`, every REMOTE-PROTOCOL tool name
|
|
1358
1388
|
* (MCP **and** A2A peer tools), and built-in tools that declare their own `defer` — sit behind ONE
|
|
1359
1389
|
* threshold gate, all or nothing: deferred iff their inlined size (name + description + schema; the
|
|
1360
|
-
* candidate set only — the caller's list, pins and non-candidates are not measured) reaches
|
|
1361
|
-
* model's context window
|
|
1390
|
+
* candidate set only — the caller's list, pins and non-candidates are not measured) reaches the
|
|
1391
|
+
* {@link toolSearch} threshold (10% of the model's context window when that seat is absent); below that
|
|
1392
|
+
* they ship inline in full and no ToolSearch is mounted.
|
|
1362
1393
|
*
|
|
1363
1394
|
* `"auto"` is a CANDIDATE-WIDENING knob, not a second gate: it adds every caller spec — undeclared
|
|
1364
1395
|
* caller tools included — to the candidate set, measured and decided by the same threshold. Engine
|
|
@@ -1369,6 +1400,16 @@ export interface RunnerDeps {
|
|
|
1369
1400
|
* if this text drifts.
|
|
1370
1401
|
*/
|
|
1371
1402
|
deferMode?: "auto";
|
|
1403
|
+
/**
|
|
1404
|
+
* Rule ②'s GATE POLICY — when the engine's candidate set ships as placeholders — in three states:
|
|
1405
|
+
* `true` (always), `false` (never; with no caller list no ToolSearch mounts at all), `{mode:"auto",
|
|
1406
|
+
* thresholdPercent}` (the threshold gate at that percent of the model's context window). Absent ⇒
|
|
1407
|
+
* `{mode:"auto", thresholdPercent:10}`. ORTHOGONAL to {@link deferMode}, which decides WHICH tools are
|
|
1408
|
+
* candidates; neither touches rule ① ({@link TaskSpec.deferTools} still defers,
|
|
1409
|
+
* {@link TaskSpec.alwaysLoadTools} still pins first). A value outside the domain refuses at prepare
|
|
1410
|
+
* (`config.tool_search_invalid`), a `thresholdPercent` outside a finite `[0, 100]` included.
|
|
1411
|
+
*/
|
|
1412
|
+
toolSearch?: import("./tool-search-setting.js").ToolSearchSetting;
|
|
1372
1413
|
/**
|
|
1373
1414
|
* design/84 Seam C — compact-boundary COST optimization (data-gated, default OFF). A FUNCTION seam (a
|
|
1374
1415
|
* trusted control-plane injection), so it lives on `RunnerDeps` — NOT `TaskSpec`: `TaskSpec` is the
|
|
@@ -38,12 +38,14 @@ import type { RunnerDeps } from "./runner-deps.js";
|
|
|
38
38
|
* current bindings are re-applied over the new catalog; present (including `undefined`)
|
|
39
39
|
* ⇒ replaced.
|
|
40
40
|
* - `readFace` — the deployment's read-face declaration ({@link RunnerDeps.readFace}); screened by the
|
|
41
|
-
* same value gate every prepare applies (`
|
|
41
|
+
* same value gate every prepare applies (`assertContainmentFaceValue`: `"open"` | `"roots"`
|
|
42
42
|
* | absent, anything else refused loudly). The RESOLVED face of a leg is still the
|
|
43
43
|
* resolver's business (task seat, governance, the read-only mount); the swap changes the
|
|
44
44
|
* deployment seat that resolution reads.
|
|
45
|
+
* - `writeFace` — the deployment's write-face declaration ({@link RunnerDeps.writeFace}); the read seat's
|
|
46
|
+
* twin (same value gate, same in-flight snapshot rule, its own notice) and independent of it.
|
|
45
47
|
*/
|
|
46
|
-
export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace"];
|
|
48
|
+
export declare const SWAPPABLE_DEP_SEATS: readonly ["models", "tiers", "readFace", "writeFace"];
|
|
47
49
|
export type SwappableDepSeat = (typeof SWAPPABLE_DEP_SEATS)[number];
|
|
48
50
|
/** The candidate a swap takes: the swappable seats of {@link RunnerDeps}, each optional (presence = instruction). */
|
|
49
51
|
export type SwappableDeps = Pick<RunnerDeps, SwappableDepSeat>;
|
|
@@ -52,12 +54,13 @@ export declare function isSwappableDepSeat(k: unknown): k is SwappableDepSeat;
|
|
|
52
54
|
/**
|
|
53
55
|
* Which notice a seat's successful swap is announced under — the disposition table over the set (a seat
|
|
54
56
|
* with no row does not compile: {@link SwapNoticeTableCoversEverySeat}). `models` and `tiers` share the
|
|
55
|
-
* catalog-generation line (one swap, one line, both counts);
|
|
57
|
+
* catalog-generation line (one swap, one line, both counts); each containment face has its own.
|
|
56
58
|
*/
|
|
57
59
|
export declare const SWAP_SEAT_NOTICE: {
|
|
58
60
|
readonly models: "config.models_swapped";
|
|
59
61
|
readonly tiers: "config.models_swapped";
|
|
60
62
|
readonly readFace: "config.read_face_swapped";
|
|
63
|
+
readonly writeFace: "config.write_face_swapped";
|
|
61
64
|
};
|
|
62
65
|
/** The fence over the notice table: `never` while every seat has a row. */
|
|
63
66
|
export type SwapNoticeTableCoversEverySeat = AssertAllKeysHandled<Exclude<SwappableDepSeat, keyof typeof SWAP_SEAT_NOTICE>>;
|
|
@@ -66,7 +69,7 @@ export type SwapNoticeTableCoversEverySeat = AssertAllKeysHandled<Exclude<Swappa
|
|
|
66
69
|
export interface ScreenedSwappableDeps {
|
|
67
70
|
readonly seats: readonly SwappableDepSeat[];
|
|
68
71
|
/** A fresh plain object: the screened value of every present seat (`models`/`tiers` shallow-copied,
|
|
69
|
-
* `readFace` the screened word; an explicit `undefined` is kept as a PRESENT own key so the door can
|
|
72
|
+
* `readFace`/`writeFace` the screened word; an explicit `undefined` is kept as a PRESENT own key so the door can
|
|
70
73
|
* tell "clear this seat" from "not named"). */
|
|
71
74
|
readonly candidate: SwappableDeps;
|
|
72
75
|
}
|
|
@@ -83,8 +86,7 @@ export interface ScreenedSwappableDeps {
|
|
|
83
86
|
* announcement, which runs AFTER the write — a throw there would have left a half-announced swap in
|
|
84
87
|
* force). The prepare doors read their deps seats the same way.
|
|
85
88
|
*
|
|
86
|
-
* The per-seat gate is the SAME one the seat's prepare-time reader applies (
|
|
87
|
-
* `
|
|
88
|
-
* seat name on every leg), so a value the door admits is a value every later prepare admits.
|
|
89
|
+
* The per-seat gate is the SAME one the seat's prepare-time reader applies (both face seats ⇒
|
|
90
|
+
* `assertContainmentFaceValue`), so a value the door admits is a value every later prepare admits.
|
|
89
91
|
*/
|
|
90
92
|
export declare function screenSwappableDeps(next: unknown): ScreenedSwappableDeps;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export const SWAPPABLE_DEP_SEATS = ["models", "tiers", "readFace"];
|
|
1
|
+
import { assertContainmentFaceValue } from "../tools/fs/containment-face.js";
|
|
2
|
+
export const SWAPPABLE_DEP_SEATS = ["models", "tiers", "readFace", "writeFace"];
|
|
3
3
|
const SWAPPABLE_DEP_SEAT_SET = new Set(SWAPPABLE_DEP_SEATS);
|
|
4
4
|
export function isSwappableDepSeat(k) {
|
|
5
5
|
return SWAPPABLE_DEP_SEAT_SET.has(k);
|
|
@@ -8,6 +8,7 @@ export const SWAP_SEAT_NOTICE = {
|
|
|
8
8
|
models: "config.models_swapped",
|
|
9
9
|
tiers: "config.models_swapped",
|
|
10
10
|
readFace: "config.read_face_swapped",
|
|
11
|
+
writeFace: "config.write_face_swapped",
|
|
11
12
|
};
|
|
12
13
|
export function screenSwappableDeps(next) {
|
|
13
14
|
if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
@@ -43,7 +44,10 @@ export function screenSwappableDeps(next) {
|
|
|
43
44
|
candidate.tiers = v === undefined ? undefined : { ...v };
|
|
44
45
|
break;
|
|
45
46
|
case "readFace":
|
|
46
|
-
candidate.readFace =
|
|
47
|
+
candidate.readFace = assertContainmentFaceValue(v, "read", "deps");
|
|
48
|
+
break;
|
|
49
|
+
case "writeFace":
|
|
50
|
+
candidate.writeFace = assertContainmentFaceValue(v, "write", "deps");
|
|
47
51
|
break;
|
|
48
52
|
default: {
|
|
49
53
|
const _exhaustive = seat;
|
|
@@ -89,24 +89,19 @@ export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "c
|
|
|
89
89
|
/**
|
|
90
90
|
* WHY a retry wait is happening, as a closed, provider-NEUTRAL bucket — the companion to
|
|
91
91
|
* {@link BrainStatusPhase}, which says what the brain is doing about it. A consumer rendering an
|
|
92
|
-
* unattended progress line ("no answer for three minutes") needs the reason
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* have no status at all.) Values are about the SHAPE of the failure, never its provider taxonomy:
|
|
92
|
+
* unattended progress line ("no answer for three minutes") needs the reason. (The status has its own seat,
|
|
93
|
+
* {@link BrainStatus.errorStatus}; this bucket is the reason a consumer RENDERS and the only carrier for the
|
|
94
|
+
* transport classes, which have no status at all.) Values are about the SHAPE of the failure, never its
|
|
95
|
+
* provider taxonomy:
|
|
97
96
|
* - `connect_refused` — the attempt got a definite negative about the target itself (nothing accepts
|
|
98
97
|
* at that address, or the name has no address). This is the class the SHORT retry lane serves.
|
|
99
98
|
* - `transport` — any other transport-level failure: a connect timeout, a reset, a mid-stream
|
|
100
99
|
* tear. No verdict about the target; the full ladder applies.
|
|
101
100
|
* - `stall` — no failure was observed on the link at all: THIS process cancelled its own
|
|
102
101
|
* request because one of its stall watchdogs reached its deadline while the stream produced
|
|
103
|
-
* nothing. The distinction from `transport` is WHO GAVE UP
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* silence is what a watchdog measures, so a `stall` can still turn out to be a network fault; what
|
|
107
|
-
* it always tells you truthfully is that the request was cancelled here, at a configured deadline,
|
|
108
|
-
* rather than lost out there. Reporting these as `transport` asserted the opposite — that the link
|
|
109
|
-
* had dropped — when nothing about the link was known at all.
|
|
102
|
+
* nothing. The distinction from `transport` is WHO GAVE UP: a `transport` frame reports something
|
|
103
|
+
* that happened to the call, a `stall` frame reports a local deadline. It is not a verdict on the
|
|
104
|
+
* remedy — a blackholed connection produces silence too, and silence is what a watchdog measures.
|
|
110
105
|
* - `rate_limit` — the provider asked the caller to slow down.
|
|
111
106
|
* - `server` — the provider reported a failure on its own side.
|
|
112
107
|
* - `http` — a response the status predicate calls terminal, retried anyway because the
|
|
@@ -138,15 +133,12 @@ export interface BrainStatus {
|
|
|
138
133
|
* output-cap re-send, which takes no backoff).
|
|
139
134
|
*
|
|
140
135
|
* WHY IT IS PRODUCED HERE rather than derived by the consumer: `Date.now() + retryInMs` is only
|
|
141
|
-
* correct for a reader that received the frame instantly
|
|
142
|
-
*
|
|
143
|
-
* built that way restarts on every re-announced slice instead of converging. The producer is the
|
|
144
|
-
* only party that can state the instant, so it does.
|
|
136
|
+
* correct for a reader that received the frame instantly, so a countdown built several process hops
|
|
137
|
+
* away restarts on every re-announced slice instead of converging.
|
|
145
138
|
*
|
|
146
|
-
* CLOCK DOMAIN, stated to remove the ambiguity: WALL CLOCK (`Date.now()`, epoch ms), NOT a
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* the authoritative RELATIVE quantity, and this seat is the absolute convenience derived from it.
|
|
139
|
+
* CLOCK DOMAIN, stated to remove the ambiguity: WALL CLOCK (`Date.now()`, epoch ms), NOT a monotonic
|
|
140
|
+
* reading. A consumer must not compare it against its own monotonic timers, and must treat it as
|
|
141
|
+
* approximate across a clock adjustment — {@link retryInMs} stays the authoritative RELATIVE quantity.
|
|
150
142
|
*/
|
|
151
143
|
retryAtMs?: number;
|
|
152
144
|
/** On a `waiting_first_token` frame: how long the first token has been awaited, ms, at the
|
|
@@ -261,13 +253,10 @@ export type TaskEvent = ({
|
|
|
261
253
|
/**
|
|
262
254
|
* The assistant's streaming PROSE SEGMENT is COMPLETE: the model closed the text content
|
|
263
255
|
* block whose bytes just streamed as `text_delta`s. This is the explicit segment boundary a
|
|
264
|
-
* REMOTE consumer needs so it never has to guess segment ends from wire silence
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* consumer reads it off the provider stream natively, and this event is that same boundary
|
|
269
|
-
* surfaced on the TaskEvent wire (CC pays the byte cost of re-carrying the block at the stop;
|
|
270
|
-
* so does this event's `content`).
|
|
256
|
+
* REMOTE consumer needs so it never has to guess segment ends from wire silence. CC-aligned: block
|
|
257
|
+
* completion IS the segmentation signal there (CC's agent stream yields each finished content block at
|
|
258
|
+
* the provider's `content_block_stop`), and this event is that same boundary surfaced on the TaskEvent
|
|
259
|
+
* wire.
|
|
271
260
|
*
|
|
272
261
|
* `content` = the authoritative FULL text of the completed segment (byte-equal to that
|
|
273
262
|
* segment's accumulated deltas, from the brain's own accumulation) — a consumer commits the
|
|
@@ -292,6 +281,61 @@ export type TaskEvent = ({
|
|
|
292
281
|
toolName: string; /** {@link ToolSpec.label} when distinct from `toolName` (design/71 liveness); omitted otherwise. */
|
|
293
282
|
label?: string;
|
|
294
283
|
args: unknown;
|
|
284
|
+
} & TaskEventIdentity) | ({
|
|
285
|
+
/**
|
|
286
|
+
* A LIVENESS TICK for a tool call that is still running. Emitted on the run's own queue between this
|
|
287
|
+
* call's `tool_start` and `tool_end`, under the same ordering law as every other content frame, and
|
|
288
|
+
* forwarded to a parent's display sink under the SAME opt-in as those two
|
|
289
|
+
* (`TaskSpec.forwardSubagentEvents`).
|
|
290
|
+
*
|
|
291
|
+
* ONE FRAME, not two: CC 2.1.259 splits this into a per-tool `tool_heartbeat` plus Bash's own
|
|
292
|
+
* `bash_progress`; this engine mints the single shape and lets the OUTPUT KEYS be what is conditional.
|
|
293
|
+
* Registered divergence.
|
|
294
|
+
*
|
|
295
|
+
* @contract tool_progress.cadence — AT MOST one frame per second per tool call, enforced at the mint over
|
|
296
|
+
* ALL producers rather than inside each one (a producer's own timer is a sampling clock, not the contract),
|
|
297
|
+
* and a call's first frame is never earlier than one interval after it entered execution. A call that
|
|
298
|
+
* finishes inside one interval emits NO frame at all — this frame's absence is never evidence that a call
|
|
299
|
+
* did not run, and a consumer must not expect one per `tool_start`.
|
|
300
|
+
*
|
|
301
|
+
* @contract tool_progress.ephemeral — LIVE ONLY, like `task_progress`/`status`: core never persists this
|
|
302
|
+
* frame (no session-tree entry, no checkpoint state, no resume replay), and a resumed leg re-ticks from
|
|
303
|
+
* its own clock. A consumer wanting a DURABLE record of a long call keeps {@link elapsedTimeSeconds} —
|
|
304
|
+
* never {@link output}, untrusted process bytes bounded for a pane (the tool's result is the kept face).
|
|
305
|
+
*
|
|
306
|
+
* Additive and ignorable: a consumer switching on event type and dropping unknown members is unchanged,
|
|
307
|
+
* and no gate, policy, memory or model-context path reads this frame.
|
|
308
|
+
*/
|
|
309
|
+
type: "tool_progress";
|
|
310
|
+
/** The still-running call's id — the join key to its own `tool_start`/`tool_end`. */
|
|
311
|
+
toolCallId: string;
|
|
312
|
+
/** The running tool's name (the `tool_start` value verbatim). */
|
|
313
|
+
toolName: string;
|
|
314
|
+
/** Whole seconds this tool call has been running, floored and MONOTONIC across its frames. ONE ORIGIN per
|
|
315
|
+
* call, stamped by the mint rather than by whichever producer emitted — the call's entry into execution,
|
|
316
|
+
* earlier than a shell command's spawn. It does NOT equal the `tool_end` duration. */
|
|
317
|
+
elapsedTimeSeconds: number;
|
|
318
|
+
/**
|
|
319
|
+
* The TAIL of what the command has written so far (stdout and stderr interleaved in arrival order),
|
|
320
|
+
* bounded by the mint to a small number of trailing lines — a live pane, never a log.
|
|
321
|
+
*
|
|
322
|
+
* UNTRUSTED RAW, exactly as `tool_end.output`: process output that may carry secrets, paths or PII;
|
|
323
|
+
* core bounds only its SIZE, and a consumer MUST redact before persisting or displaying it. Never
|
|
324
|
+
* re-feed it to a model — it is a moving, truncated window.
|
|
325
|
+
*
|
|
326
|
+
* ABSENT, together with {@link totalLines} and {@link totalBytes}, exactly when the call has produced
|
|
327
|
+
* NO output through this environment — it printed nothing (`sleep 3`), or the environment does not
|
|
328
|
+
* stream (a buffered remote `exec` never invokes the output callbacks). The three ride or stay away
|
|
329
|
+
* together: present = "output has been observed", absent = "none has been observed", never
|
|
330
|
+
* "unchanged since the last frame".
|
|
331
|
+
*/
|
|
332
|
+
output?: string;
|
|
333
|
+
/** Newlines observed in the command's output so far (≥ the line count of {@link output}, which is
|
|
334
|
+
* only its tail). Rides iff {@link output} does — see its absence law. */
|
|
335
|
+
totalLines?: number;
|
|
336
|
+
/** Bytes observed in the command's output so far, counted BEFORE the tail bound, so it keeps
|
|
337
|
+
* growing after {@link output} stops being able to. Rides iff {@link output} does. */
|
|
338
|
+
totalBytes?: number;
|
|
295
339
|
} & TaskEventIdentity) | ({
|
|
296
340
|
type: "tool_end";
|
|
297
341
|
toolCallId: string;
|
|
@@ -433,8 +477,7 @@ export type TaskEvent = ({
|
|
|
433
477
|
* faces — so a renderer never branches on prose to tell "a person refused" from "the window closed".
|
|
434
478
|
*
|
|
435
479
|
* WHERE IT COMES FROM — never inferred here: the gate mints it at its exit, the decide lane mints it
|
|
436
|
-
* from the host's decision facts joined with the row's persisted origin.
|
|
437
|
-
* from `isError`, from the result, or from anything a tool or a post-tool hook can write.
|
|
480
|
+
* from the host's decision facts joined with the row's persisted origin.
|
|
438
481
|
*
|
|
439
482
|
* ABSENT on every frame of a call the gate never saw (a tool call on an unarmed gate, a deferred
|
|
440
483
|
* re-issue, a reconcile-recovered orphan). A gated call's frame ALWAYS carries it — an absence is
|
|
@@ -485,6 +528,14 @@ export type TaskEvent = ({
|
|
|
485
528
|
usedTokens: number;
|
|
486
529
|
windowTokens: number;
|
|
487
530
|
compactAtTokens: number;
|
|
531
|
+
/**
|
|
532
|
+
* Additive — the leg's system prompt broken down by pack SECTION. Meaning, and the readings it
|
|
533
|
+
* forbids, are owned by `contextUsageSections` (`prompt-assembly/section-usage.ts`, `@contract
|
|
534
|
+
* context_usage.sections`): PREPARE-TIME estimates in a different coordinate from {@link usedTokens},
|
|
535
|
+
* never compared to {@link compactAtTokens}. Absent when the leg assembled no section IR — "no section
|
|
536
|
+
* view", never "the sections weigh nothing".
|
|
537
|
+
*/
|
|
538
|
+
sections?: readonly import("../prompt-assembly/section-usage.js").ContextUsageSection[];
|
|
488
539
|
} & TaskEventIdentity) | ({
|
|
489
540
|
type: "turn_end";
|
|
490
541
|
/**
|
|
@@ -907,6 +958,22 @@ export type TaskEvent = ({
|
|
|
907
958
|
*/
|
|
908
959
|
type: "tool_roster_delta";
|
|
909
960
|
delta: import("./tool-roster.js").ToolRosterDelta;
|
|
961
|
+
} & TaskEventIdentity) | ({
|
|
962
|
+
/**
|
|
963
|
+
* This leg's DEFERRAL CENSUS: which mounted tools ship as name-only placeholders, which the model has activated, and the policy
|
|
964
|
+
* (`RunnerDeps.toolSearch`) that decided it — meaning owned by `ToolDisclosureSnapshot` (`runner/tool-disclosure.ts`). EXISTS ONLY ON A LEG THAT
|
|
965
|
+
* DEFERS SOMETHING: an empty deferred set emits no frame, and that absence reads as "nothing was deferred here", never "no disclosure in this build"
|
|
966
|
+
* and never "unknown". First frame = the leg-start census, right after `wiring_manifest` — `activated` is EMPTY on a fresh leg and, on a RESUMED one,
|
|
967
|
+
* whatever the engine reseeded (never read a non-empty first census as "this leg activated them"); every later frame = a census that CHANGED, i.e. an
|
|
968
|
+
* activation landed (either lane — one section, one census). WHOLE SNAPSHOT: replace your copy. `deferred` is frozen at prepare; lists sorted.
|
|
969
|
+
*/
|
|
970
|
+
type: "tool_disclosure";
|
|
971
|
+
policy: import("./tool-search-setting.js").ToolSearchPolicy;
|
|
972
|
+
/** Meaning owned by `ToolDisclosureSnapshot.thresholdPercent`: absent under `always` / `never`, where
|
|
973
|
+
* no threshold was consulted, and an absence must not be read as "the default percent". */
|
|
974
|
+
thresholdPercent?: number;
|
|
975
|
+
deferred: readonly string[];
|
|
976
|
+
activated: readonly string[];
|
|
910
977
|
} & TaskEventIdentity) | {
|
|
911
978
|
type: "done";
|
|
912
979
|
result: TaskResult;
|
|
@@ -118,18 +118,14 @@ export interface TaskNotificationPayload {
|
|
|
118
118
|
* preconditions, so the claim and the next SendMessage agree. Lets the parent decide
|
|
119
119
|
* continue-vs-restart without trial-and-error. Background-agent lane only. */
|
|
120
120
|
resumable?: boolean;
|
|
121
|
-
/** A cross-channel correlation key for
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* — a consumer previously had to reconstruct "are these the same completion?" from weaker signals
|
|
125
|
-
* (task_id + status + timing). Minted ONCE per completion, fill-once (first writer wins), on the
|
|
121
|
+
/** A cross-channel correlation key for ONE completion event — the same task's completion fans out
|
|
122
|
+
* across several independent read faces (this live notification frame, a durable inbox entry, and the
|
|
123
|
+
* `TaskOutput` poll projection). Minted ONCE per completion, fill-once (first writer wins), on the
|
|
126
124
|
* same task-registry handle/record every read face serves from — see
|
|
127
125
|
* {@link "./task-registry.js".UnifiedTaskOutput.completionId} for the shared contract (open-shape
|
|
128
126
|
* `string`, absent until terminal settle, absent on a pre-P1-3 core build). Deliberately ABSENT on
|
|
129
|
-
* the `"external"` lane (`TaskStream.notify()`)
|
|
130
|
-
* correlation key
|
|
131
|
-
* mint this from; core-minting one there would create two competing correlation keys on the same
|
|
132
|
-
* frame. */
|
|
127
|
+
* the `"external"` lane (`TaskStream.notify()`): that lane has its own caller-supplied `seq`
|
|
128
|
+
* correlation key and no registry-side settle point to mint this from. */
|
|
133
129
|
completionId?: string;
|
|
134
130
|
/** design/176 — the peer-message marker + hop-chain carrier, present ONLY on payloads minted by
|
|
135
131
|
* the SendMessage delivery legs (the engine's single peer-carrier producer). Three uses: it
|
|
@@ -331,13 +327,12 @@ export declare function renderCrossSessionMessageFrame(frame: NonNullable<TaskNo
|
|
|
331
327
|
export declare function renderCrossSessionNoticeLine(notice: NonNullable<TaskNotificationPayload["crossSessionNotice"]>): string;
|
|
332
328
|
export declare function renderTaskNotificationXml(n: TaskNotificationPayload): string;
|
|
333
329
|
/**
|
|
334
|
-
* The BETWEEN-TURNS pending lane. A task notification born while NO
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
* in-turn and between-turn events keep one shape.
|
|
330
|
+
* The BETWEEN-TURNS pending lane. A task notification born while the session has NO live lane at all
|
|
331
|
+
* (every run of it is over, or the one that is live rejected the injection because its agent was
|
|
332
|
+
* already idle) is parked PER SESSION; the session's next run drains it at start and rides the payloads
|
|
333
|
+
* down the SAME notification lane (steer at the next turn boundary), so in-turn and between-turn events
|
|
334
|
+
* keep one shape. A frame whose session DOES have a live lane never reaches this store —
|
|
335
|
+
* {@link SessionLiveNotificationLanes} takes it to that run's next turn boundary.
|
|
341
336
|
*
|
|
342
337
|
* Bounds (1.254 F4 家族纪律): per-task cap + per-session cap + session-count cap, oldest dropped first —
|
|
343
338
|
* preferring NON-terminal victims (an exit/killed terminal is the single most load-bearing notification of
|
|
@@ -416,8 +411,108 @@ export interface DrainedPendingNotifications {
|
|
|
416
411
|
*/
|
|
417
412
|
droppedWholeSession?: number;
|
|
418
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* One run's seat in {@link SessionLiveNotificationLanes}: hand this payload to that run's live
|
|
416
|
+
* injection queue. Four answers:
|
|
417
|
+
* · `"taken"` — newly enqueued on that run's lane; `onDisposition` (when supplied) is settled by the
|
|
418
|
+
* queue with the real outcome. Nothing is DELIVERED yet: the boundary can still refuse it;
|
|
419
|
+
* · `"pending"` — that lane already holds this exact key IN FLIGHT. `onDisposition` is NOT settled
|
|
420
|
+
* here: the in-flight entry owns the OUTCOME and this copy is no delivery;
|
|
421
|
+
* · `"duplicate"` — that stream has already DELIVERED this exact frame: nothing to enqueue, nothing
|
|
422
|
+
* to park; `onDisposition` is NOT settled, the word belongs to the caller;
|
|
423
|
+
* · `"unavailable"` — the lane can no longer take anything; the caller tries the next lane, and
|
|
424
|
+
* finally the parked store.
|
|
425
|
+
*/
|
|
426
|
+
export type LiveNotificationLane = (payload: TaskNotificationPayload, priority: SystemInjectionPriority, onDisposition?: (d: "queued" | "parked") => void) => "taken" | "pending" | "duplicate" | "unavailable";
|
|
427
|
+
/** One run's seat: its two entry points, plus the two bookkeeping verbs its owner drives. */
|
|
428
|
+
export interface LiveNotificationLaneSeat {
|
|
429
|
+
/**
|
|
430
|
+
* The entry for THIS run's OWN producers. It accepts whenever the lane is live: the frame is
|
|
431
|
+
* enqueued (so the queue's own dedup folds a retry), the wire event goes out, and if the agent turns
|
|
432
|
+
* out to be idle the steer's rejection parks it. No route-specific condition is allowed onto it.
|
|
433
|
+
*/
|
|
434
|
+
readonly take: LiveNotificationLane;
|
|
435
|
+
/**
|
|
436
|
+
* The entry {@link SessionLiveNotificationLanes} holds (stable identity for register/withdraw) — a
|
|
437
|
+
* frame ANOTHER run of the session is offering. It additionally requires that this lane can deliver
|
|
438
|
+
* at this instant: accepting a foreign frame and then refusing it asynchronously strands it behind
|
|
439
|
+
* this run's own parked-store intake, whereas declining hands it straight back to the router.
|
|
440
|
+
*/
|
|
441
|
+
readonly takeAsDestination: LiveNotificationLane;
|
|
442
|
+
/**
|
|
443
|
+
* Record that this lane HANDED this frame to the session route: it accepted the frame and then
|
|
444
|
+
* could not deliver it (its agent was already idle). The key must never come back — the queue's
|
|
445
|
+
* dedup set keeps it, so a second enqueue would be reported as ACCEPTED while nothing delivers or
|
|
446
|
+
* parks it. Remembered per LANE rather than per delivery, so a route of any length terminates on the
|
|
447
|
+
* parked store. A lane that is no longer live records NOTHING (it already answers "unavailable" by
|
|
448
|
+
* liveness). What IS remembered is bounded exactly like the queue's own dedup set — same cap, same
|
|
449
|
+
* insertion-order eviction, and an evicted key is as re-deliverable here as it is there.
|
|
450
|
+
*/
|
|
451
|
+
handOff(payload: TaskNotificationPayload): void;
|
|
452
|
+
/** Drop that memory when the lane stops; every later answer is "unavailable" by liveness anyway. */
|
|
453
|
+
release(): void;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Build one run's lane seat over its injection queue: the SINGLE enqueue site a frame reaches,
|
|
457
|
+
* whether the run produced it or another run of the same session routed it here — so the tier
|
|
458
|
+
* routing, the dedup key and the already-delivered fold cannot fork between the two arrivals.
|
|
459
|
+
*/
|
|
460
|
+
export declare function createLiveNotificationLane(seat: {
|
|
461
|
+
/** The run's injection queue; its subscriber is what routes an accepted frame by tier. */
|
|
462
|
+
queue: SystemInjectionQueue;
|
|
463
|
+
/**
|
|
464
|
+
* Is the agent in a turn, so the boundary a frame would ride actually exists? Read LIVE, and read
|
|
465
|
+
* ONLY by {@link LiveNotificationLaneSeat.takeAsDestination}.
|
|
466
|
+
*/
|
|
467
|
+
canTakeNow: () => boolean;
|
|
468
|
+
/**
|
|
469
|
+
* Has this lane NOT been torn down? The gate for this run's own producers, and the gate on
|
|
470
|
+
* remembering a handoff: a lane merely BETWEEN turns may take again in a moment, so what it handed
|
|
471
|
+
* away must still be remembered; a lane that is over will never take anything.
|
|
472
|
+
*/
|
|
473
|
+
isLive: () => boolean;
|
|
474
|
+
/** Keys this run's stream already delivered at turn-open (the cross-layer double-park fold). */
|
|
475
|
+
alreadyDelivered: (dedupKey: string) => boolean;
|
|
476
|
+
}): LiveNotificationLaneSeat;
|
|
477
|
+
/**
|
|
478
|
+
* The per-session LIVE LANE registry — the destination half of {@link PendingSessionNotifications}.
|
|
479
|
+
* A run registers its injection lane once its session anchor is bound and withdraws it the moment the
|
|
480
|
+
* lane stops accepting (a clean end, a throw, an abort — the run's own teardown), so an entry's
|
|
481
|
+
* lifetime is exactly the lane's liveness.
|
|
482
|
+
*
|
|
483
|
+
* The key is the session the producing lane is ANCHORED to, so this is never a cross-session route: a
|
|
484
|
+
* peer session's live run is not a destination, and a delegated child's own session is not its
|
|
485
|
+
* parent's. A session with no live lane keeps the parked form unchanged.
|
|
486
|
+
*
|
|
487
|
+
* Several lanes may sit under one key: the NEWEST registration is tried first — "the session's current
|
|
488
|
+
* run" — and every lane is tried once, so a lane that has stopped, or that has already handed this very
|
|
489
|
+
* frame away, is passed over rather than swallowing it. A lane that refused a frame therefore cannot be
|
|
490
|
+
* reached by it again, which is what makes a route of any length terminate on the parked store.
|
|
491
|
+
*/
|
|
492
|
+
export declare class SessionLiveNotificationLanes {
|
|
493
|
+
private readonly lanes;
|
|
494
|
+
register(sessionId: string, lane: LiveNotificationLane): void;
|
|
495
|
+
/** Idempotent (a lane withdraws at its stop AND again at its outermost teardown). The key is dropped
|
|
496
|
+
* with its last lane so an idle process holds no per-session entry. */
|
|
497
|
+
unregister(sessionId: string, lane: LiveNotificationLane): void;
|
|
498
|
+
/**
|
|
499
|
+
* Offer the payload to the session's live lanes, newest first. The caller's OWN lane is not
|
|
500
|
+
* special-cased: a lane that just refused this frame reports `"unavailable"` for it from then on.
|
|
501
|
+
*/
|
|
502
|
+
deliver(sessionId: string, payload: TaskNotificationPayload, priority: SystemInjectionPriority, opts?: {
|
|
503
|
+
onDisposition?: (d: "queued" | "parked") => void;
|
|
504
|
+
}): "taken" | "pending" | "duplicate" | "unavailable";
|
|
505
|
+
/** Sessions holding at least one live lane (observability + the tests' rollback detector). */
|
|
506
|
+
get size(): number;
|
|
507
|
+
}
|
|
419
508
|
export declare class PendingSessionNotifications {
|
|
420
509
|
private readonly sessions;
|
|
510
|
+
/**
|
|
511
|
+
* The LIVE half of the same question this store answers. Together they are one seat — "where does a
|
|
512
|
+
* notification addressed to session S go?" — with two answers: the session's live lane when it has
|
|
513
|
+
* one, this parked queue when it does not. One object, one lifetime, one key (the session id).
|
|
514
|
+
*/
|
|
515
|
+
readonly liveLanes: SessionLiveNotificationLanes;
|
|
421
516
|
/** Whole sessions evicted by {@link MAX_PENDING_SESSIONS} (their pendings were never delivered). */
|
|
422
517
|
droppedSessions: number;
|
|
423
518
|
/**
|
|
@@ -438,8 +533,39 @@ export declare class PendingSessionNotifications {
|
|
|
438
533
|
* design/373 §3.5: the batch comes out PRIORITY-MAJOR (stable sort — pend order within a class;
|
|
439
534
|
* a no-priority record ranks as later), the idle-dequeue min-value-first form. */
|
|
440
535
|
drain(sessionId: string): DrainedPendingNotifications | undefined;
|
|
536
|
+
/**
|
|
537
|
+
* Keys a live lane of this session has already put in front of a model (the delivered half of the
|
|
538
|
+
* ledger below). Kept beside the parked keys because the WINDOW is the session's, not any one run's:
|
|
539
|
+
* the destination run's own queue dedup dies with it. Released at {@link drain}, exactly like the
|
|
540
|
+
* parked keys. Bounded like everything else here; an evicted key only ever costs a duplicate in the
|
|
541
|
+
* extreme, never a loss.
|
|
542
|
+
*/
|
|
543
|
+
private readonly routed;
|
|
544
|
+
/**
|
|
545
|
+
* Does this session ALREADY own this exact frame — parked, or routed onto a live lane? Only one of
|
|
546
|
+
* the two may hold a key: a producer retrying a frame the session has already taken must FOLD rather
|
|
547
|
+
* than be handed to whichever run has since gone live, which would deliver it there and again at the
|
|
548
|
+
* next drain. Callers ask BEFORE routing and BEFORE parking.
|
|
549
|
+
*/
|
|
550
|
+
holdsKey(sessionId: string, dedupKey: string): boolean;
|
|
551
|
+
/** Record that this session has DELIVERED this frame — see {@link routed}. Written at ONE place, the
|
|
552
|
+
* harness's consumption sink (the step where a queued frame enters the model's input), so the record
|
|
553
|
+
* can never run ahead of the delivery it claims. */
|
|
554
|
+
markDelivered(sessionId: string, dedupKey: string): void;
|
|
441
555
|
get size(): number;
|
|
442
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* WHERE A FRAME ADDRESSED TO ONE SESSION GOES — the single decision, so the two addresses that ask it
|
|
559
|
+
* (a run's own session, and the delegation tree's root anchor) cannot drift apart. Three outcomes:
|
|
560
|
+
* · the session ALREADY OWNS the key (parked, or routed onto a live lane) ⇒ fold — neither route it
|
|
561
|
+
* nor park a second copy; `"parked"` is the word a folded repeat has always been given;
|
|
562
|
+
* · a live lane takes it ⇒ it rides that run's next turn boundary. This decision records NOTHING in
|
|
563
|
+
* the ledger: a destination that ACCEPTED a frame can still refuse it at its boundary and park it
|
|
564
|
+
* then, so only the lane that actually delivers writes ownership
|
|
565
|
+
* ({@link LiveNotificationLaneSeat.markDelivered});
|
|
566
|
+
* · nothing can take it ⇒ park, carrying the tier, for the session's next run.
|
|
567
|
+
*/
|
|
568
|
+
export declare function routeOrParkNotification(store: PendingSessionNotifications, sessionId: string, payload: TaskNotificationPayload, priority: SystemInjectionPriority, onDisposition?: (d: "queued" | "parked") => void): void;
|
|
443
569
|
/**
|
|
444
570
|
* Delivery-side overflow disclosure: fold the per-task drop counts into the drained payloads. Two
|
|
445
571
|
* carrier shapes, chosen per survivor:
|