@sema-agent/core 7.17.7 → 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.
Files changed (163) hide show
  1. package/CHANGELOG.md +140 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/route-adjudicator.d.ts +18 -0
  8. package/dist/brain/route-adjudicator.js +16 -0
  9. package/dist/core/agent-definition.d.ts +10 -0
  10. package/dist/core/artifact-host.d.ts +41 -4
  11. package/dist/core/artifact-host.js +18 -2
  12. package/dist/core/auto-mode.d.ts +141 -45
  13. package/dist/core/auto-mode.js +109 -3
  14. package/dist/core/checkpoint-store.d.ts +16 -2
  15. package/dist/core/engine-notice.d.ts +47 -1
  16. package/dist/core/engine-notice.js +7 -0
  17. package/dist/core/gate-lanes.js +2 -1
  18. package/dist/core/gate-outcome.d.ts +17 -2
  19. package/dist/core/gate-outcome.js +16 -0
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +8 -0
  22. package/dist/core/hook-seats.d.ts +66 -0
  23. package/dist/core/hook-seats.js +37 -0
  24. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  25. package/dist/core/runner/assemble-result.d.ts +2 -1
  26. package/dist/core/runner/assemble-result.js +1 -1
  27. package/dist/core/runner/classifier-ladder.d.ts +89 -0
  28. package/dist/core/runner/classifier-ladder.js +65 -0
  29. package/dist/core/runner/contracts.d.ts +69 -13
  30. package/dist/core/runner/decide-continuation.d.ts +1 -3
  31. package/dist/core/runner/decide-continuation.js +1 -7
  32. package/dist/core/runner/deferral-census.d.ts +52 -0
  33. package/dist/core/runner/deferral-census.js +44 -0
  34. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  35. package/dist/core/runner/derived-route-fallback.js +8 -3
  36. package/dist/core/runner/gate-exit.d.ts +12 -0
  37. package/dist/core/runner/gate-exit.js +6 -3
  38. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  39. package/dist/core/runner/prepare-artifact.js +5 -4
  40. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  41. package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
  42. package/dist/core/runner/prepare-config-doors.d.ts +1 -1
  43. package/dist/core/runner/prepare-config-doors.js +5 -3
  44. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  45. package/dist/core/runner/prepare-defer-classify.js +8 -3
  46. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  47. package/dist/core/runner/prepare-gate-stations.js +1 -1
  48. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  49. package/dist/core/runner/prepare-hands-readface.js +60 -24
  50. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  51. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  52. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  53. package/dist/core/runner/prepare-lsp.js +1 -1
  54. package/dist/core/runner/prepare-policy-chain.js +2 -0
  55. package/dist/core/runner/prepare-project-context.js +4 -2
  56. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  57. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  58. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  59. package/dist/core/runner/prepare-run-refs.js +3 -2
  60. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  61. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  62. package/dist/core/runner/prepare-task.js +18 -13
  63. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  64. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  65. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  66. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  67. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  68. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  69. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  70. package/dist/core/runner/run-harness-handlers.js +4 -1
  71. package/dist/core/runner/run-identity-wiring.js +6 -2
  72. package/dist/core/runner/run-leg.d.ts +2 -0
  73. package/dist/core/runner/run-leg.js +35 -41
  74. package/dist/core/runner/run-notification-lane.js +55 -27
  75. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  76. package/dist/core/runner/run-terminal-adoption.js +16 -13
  77. package/dist/core/runner/run-turn-boundary.js +3 -1
  78. package/dist/core/runner/runtask.d.ts +6 -5
  79. package/dist/core/runner/runtask.js +26 -14
  80. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  81. package/dist/core/runner/skill-base-dir.js +42 -0
  82. package/dist/core/runner/synthetic-tools.d.ts +16 -19
  83. package/dist/core/runner/synthetic-tools.js +18 -10
  84. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  85. package/dist/core/runner/tool-defer-gate.js +15 -5
  86. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  87. package/dist/core/runner/tool-disclosure.js +2 -0
  88. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  89. package/dist/core/runner/tool-progress-lane.js +61 -0
  90. package/dist/core/runner-deps.d.ts +48 -7
  91. package/dist/core/swappable-deps.d.ts +9 -7
  92. package/dist/core/swappable-deps.js +7 -3
  93. package/dist/core/task-event.d.ts +96 -29
  94. package/dist/core/task-notification.d.ts +142 -16
  95. package/dist/core/task-notification.js +106 -0
  96. package/dist/core/task-result.d.ts +9 -1
  97. package/dist/core/task-spec.d.ts +20 -3
  98. package/dist/core/tighten-task-spec.js +7 -5
  99. package/dist/core/tool-model-gate.d.ts +1 -1
  100. package/dist/core/tool-search-setting.d.ts +78 -0
  101. package/dist/core/tool-search-setting.js +90 -0
  102. package/dist/core/tool-spec.d.ts +8 -0
  103. package/dist/core/types.d.ts +1 -1
  104. package/dist/core/types.js +1 -1
  105. package/dist/core/wiring-manifest.d.ts +46 -0
  106. package/dist/core/wiring-manifest.js +9 -1
  107. package/dist/engine/execution-env/node-execution-env.js +10 -4
  108. package/dist/engine/harness/agent-harness.d.ts +42 -0
  109. package/dist/engine/harness/agent-harness.js +61 -29
  110. package/dist/engine/loop/agent-loop.d.ts +68 -3
  111. package/dist/engine/loop/agent-loop.js +109 -16
  112. package/dist/index.d.ts +10 -5
  113. package/dist/index.js +9 -5
  114. package/dist/internal/harness.d.ts +1 -1
  115. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  116. package/dist/orchestration/run-workflow-tool.js +6 -1
  117. package/dist/orchestration/workflow-governance.d.ts +7 -1
  118. package/dist/orchestration/workflow-governance.js +9 -5
  119. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  120. package/dist/orchestration/workflow-primitives.js +6 -1
  121. package/dist/orchestration/workflow-types.d.ts +48 -13
  122. package/dist/orchestration/workflow-types.js +1 -1
  123. package/dist/orchestration/workflow.js +45 -17
  124. package/dist/prompt-assembly/epoch.js +2 -0
  125. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  126. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  127. package/dist/prompt-assembly/section-usage.js +10 -0
  128. package/dist/prompt-assembly/types.d.ts +8 -6
  129. package/dist/prompts/default.d.ts +13 -0
  130. package/dist/prompts/default.js +16 -8
  131. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  132. package/dist/tools/artifact/artifact-text.js +6 -3
  133. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  134. package/dist/tools/artifact/artifact-tool.js +203 -70
  135. package/dist/tools/artifact/conformance.d.ts +56 -0
  136. package/dist/tools/artifact/conformance.js +349 -0
  137. package/dist/tools/artifact/local-stub.js +1 -1
  138. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  139. package/dist/tools/artifact/reject-breaker.js +42 -0
  140. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  141. package/dist/tools/fs/bash-lexer.js +563 -0
  142. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  143. package/dist/tools/fs/bash-program-position.js +125 -0
  144. package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
  145. package/dist/tools/fs/bash-readonly-classifier.js +121 -552
  146. package/dist/tools/fs/containment-face.d.ts +89 -0
  147. package/dist/tools/fs/containment-face.js +81 -0
  148. package/dist/tools/fs/fs-bash.d.ts +46 -1
  149. package/dist/tools/fs/fs-bash.js +158 -22
  150. package/dist/tools/fs/fs-read.d.ts +1 -1
  151. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  152. package/dist/tools/fs/fs-write.d.ts +4 -3
  153. package/dist/tools/fs/fs-write.js +9 -9
  154. package/dist/tools/fs/index.d.ts +24 -5
  155. package/dist/tools/fs/index.js +18 -6
  156. package/dist/tools/fs/repo-map.d.ts +2 -2
  157. package/dist/tools/fs/safety.d.ts +3 -3
  158. package/dist/tools/fs/safety.js +4 -4
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +85 -9
  161. package/wire-changes/core@7.17.7.json +49 -0
  162. package/dist/tools/fs/read-face.d.ts +0 -68
  163. 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/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
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/read-face.js").ReadFace;
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). A round past the cap is `unavailable(timeout)`: the call is
667
- * DENIED with a sentence that says so (CC 2.1.250 form), and the next call is classified afresh — there is no
668
- * failure count and no session latch. */
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 10% of the
1361
- * model's context window; below that they ship inline in full and no ToolSearch is mounted.
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 (`assertReadFaceValue`: `"open"` | `"roots"`
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); the read face has its own.
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 (`readFace` ⇒
87
- * `assertReadFaceValue` with the deployment-seat name a prepare door uses — one value, one loudness, one
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 { assertReadFaceValue } from "../tools/fs/read-face.js";
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 = assertReadFaceValue(v, "readFace (deployment seat)");
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, and until this existed the
93
- * only carriers of it were the HTTP status and the syscall code, neither of which crossed this channel
94
- * at the time. (The status has since gained its own seat, {@link BrainStatus.errorStatus}; this bucket
95
- * remains the reason a consumer RENDERS, and it is the only carrier for the transport classes, which
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, and nothing more: a `transport` frame
104
- * reports something that happened to the call, a `stall` frame reports a local deadline. It is not
105
- * a verdict on the remedy — a blackholed connection (no reset, no close) produces silence, and
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. A consumer several process hops away
142
- * (serving layer → client) recomputes a deadline already shifted by the transit, so a countdown
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
- * monotonic reading. A consumer must not compare it against its own monotonic timers, and must
148
- * treat it as approximate across a clock adjustment or a machine boundary — {@link retryInMs} stays
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 (the idle-flush
265
- * heuristic this retires cut one slow-model reply into N fragments). CC-aligned: CC's agent
266
- * stream yields each finished content block as its own assistant-message unit at the provider's
267
- * `content_block_stop` — block completion IS the segmentation signal there; a single-process
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. Core does not derive it
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
- * ONE completion event. The same task's completion fans out across several independent read faces
123
- * today (this live notification frame, a durable inbox entry, and the `TaskOutput` poll projection)
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()`) — that lane has its own caller-supplied `seq`
130
- * correlation key (confirmed posture) and no registry-side settle point to
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 turn is
335
- * live (the run's notification lane is torn down, or the harness rejected the injection because the agent
336
- * was already idle) used to be silently unreachable: the Monitor main use-case (long-watch events between
337
- * turns) and the stoppedBy notifications (a killed-evicted child whose registry row is already gone)
338
- * both land exactly in that window. This store parks them PER SESSION; the session's next run drains it at
339
- * start and rides the payloads down the SAME notification lane (steer at the next turn boundary), so
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: