@sema-agent/core 7.28.1 → 7.30.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 (162) hide show
  1. package/CHANGELOG.md +473 -1
  2. package/dist/agents/child-model-seat.d.ts +59 -7
  3. package/dist/agents/child-model-seat.js +33 -7
  4. package/dist/agents/cross-session-judge.d.ts +27 -5
  5. package/dist/agents/cross-session-judge.js +23 -12
  6. package/dist/agents/list-agents-tool.d.ts +31 -6
  7. package/dist/agents/list-agents-tool.js +66 -14
  8. package/dist/agents/peer-admission.d.ts +20 -8
  9. package/dist/agents/peer-admission.js +21 -12
  10. package/dist/agents/peer-directory.d.ts +29 -0
  11. package/dist/agents/peer-directory.js +20 -0
  12. package/dist/agents/peer-held-queue.d.ts +31 -4
  13. package/dist/agents/peer-held-queue.js +32 -1
  14. package/dist/agents/peer-session-drain.d.ts +91 -13
  15. package/dist/agents/peer-session-drain.js +409 -102
  16. package/dist/agents/read-inbox-tool.d.ts +22 -0
  17. package/dist/agents/read-inbox-tool.js +39 -0
  18. package/dist/agents/roster-store.d.ts +2 -4
  19. package/dist/agents/roster-store.js +1 -1
  20. package/dist/agents/send-message-tool.d.ts +4 -4
  21. package/dist/agents/send-message-tool.js +22 -2
  22. package/dist/agents/subagent.js +62 -44
  23. package/dist/agents/team-role.d.ts +34 -0
  24. package/dist/agents/team-role.js +17 -0
  25. package/dist/agents/verify.js +2 -2
  26. package/dist/brain/anthropic.js +4 -2
  27. package/dist/brain/open-responses.js +4 -3
  28. package/dist/brain/openai.js +2 -1
  29. package/dist/brain/stream-engine.js +6 -0
  30. package/dist/brain/stream-shared.d.ts +8 -0
  31. package/dist/brain/stream-shared.js +8 -0
  32. package/dist/core/auto-compaction.d.ts +42 -0
  33. package/dist/core/auto-compaction.js +35 -2
  34. package/dist/core/child-clamp-seats.d.ts +11 -0
  35. package/dist/core/child-clamp-seats.js +3 -0
  36. package/dist/core/context-edit.d.ts +3 -2
  37. package/dist/core/context-edit.js +6 -5
  38. package/dist/core/context-guard.d.ts +2 -1
  39. package/dist/core/context-guard.js +3 -2
  40. package/dist/core/context-settings.d.ts +103 -0
  41. package/dist/core/context-settings.js +60 -0
  42. package/dist/core/engine-notice.d.ts +18 -1
  43. package/dist/core/frame-settlement.d.ts +97 -0
  44. package/dist/core/frame-settlement.js +36 -0
  45. package/dist/core/governance-codes.d.ts +1 -1
  46. package/dist/core/governance-codes.js +4 -0
  47. package/dist/core/hooks.d.ts +10 -10
  48. package/dist/core/mailbox-store.d.ts +41 -7
  49. package/dist/core/mailbox-store.js +9 -3
  50. package/dist/core/message-utils.d.ts +13 -0
  51. package/dist/core/message-utils.js +6 -0
  52. package/dist/core/parent-spec-seat.d.ts +14 -1
  53. package/dist/core/parent-spec-seat.js +6 -0
  54. package/dist/core/permission-rule-model.js +1 -1
  55. package/dist/core/roles.d.ts +20 -0
  56. package/dist/core/roles.js +35 -16
  57. package/dist/core/runner/assemble-result.d.ts +2 -0
  58. package/dist/core/runner/assemble-result.js +3 -1
  59. package/dist/core/runner/compaction-call-options.d.ts +26 -2
  60. package/dist/core/runner/compaction-call-options.js +9 -0
  61. package/dist/core/runner/compaction-seams.d.ts +4 -3
  62. package/dist/core/runner/compaction-seams.js +13 -6
  63. package/dist/core/runner/contracts.d.ts +61 -7
  64. package/dist/core/runner/decide-continuation.d.ts +2 -1
  65. package/dist/core/runner/decide-continuation.js +11 -10
  66. package/dist/core/runner/initial-run-state.js +2 -2
  67. package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
  68. package/dist/core/runner/prepare-config-doors.js +4 -1
  69. package/dist/core/runner/prepare-context-lane.d.ts +9 -2
  70. package/dist/core/runner/prepare-context-lane.js +15 -10
  71. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  72. package/dist/core/runner/prepare-delegation-surface.js +9 -3
  73. package/dist/core/runner/prepare-file-history.js +10 -8
  74. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  75. package/dist/core/runner/prepare-gate-stations.js +3 -1
  76. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  77. package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
  78. package/dist/core/runner/prepare-prompt-inputs.js +2 -1
  79. package/dist/core/runner/prepare-task.js +22 -9
  80. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
  81. package/dist/core/runner/prepare-wiring-manifest.js +6 -1
  82. package/dist/core/runner/response-model-seat.d.ts +16 -0
  83. package/dist/core/runner/response-model-seat.js +27 -0
  84. package/dist/core/runner/retired-runner-deps.d.ts +3 -1
  85. package/dist/core/runner/retired-runner-deps.js +1 -0
  86. package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
  87. package/dist/core/runner/run-compaction-machinery.js +5 -3
  88. package/dist/core/runner/run-harness-handlers.js +9 -2
  89. package/dist/core/runner/run-identity-wiring.js +19 -1
  90. package/dist/core/runner/run-leg.js +1 -1
  91. package/dist/core/runner/run-notification-lane.js +8 -2
  92. package/dist/core/runner/run-recovery-lanes.js +9 -5
  93. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
  94. package/dist/core/runner/run-settle-and-teardown.js +6 -10
  95. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  96. package/dist/core/runner/run-terminal-adoption.js +5 -5
  97. package/dist/core/runner/run-turn-boundary.js +32 -5
  98. package/dist/core/runner/runtask.d.ts +8 -1
  99. package/dist/core/runner/runtask.js +33 -2
  100. package/dist/core/runner-deps.d.ts +44 -13
  101. package/dist/core/side-query.d.ts +6 -5
  102. package/dist/core/side-query.js +3 -1
  103. package/dist/core/swappable-deps.d.ts +20 -4
  104. package/dist/core/swappable-deps.js +6 -1
  105. package/dist/core/task-event.d.ts +13 -0
  106. package/dist/core/task-notification.d.ts +144 -2
  107. package/dist/core/task-notification.js +154 -5
  108. package/dist/core/task-result.d.ts +27 -3
  109. package/dist/core/task-spec.d.ts +7 -0
  110. package/dist/core/tool-catalog-entries.js +2 -1
  111. package/dist/core/tool-conformance.js +1 -0
  112. package/dist/core/tool-policy.d.ts +9 -4
  113. package/dist/core/tool-spec.d.ts +14 -0
  114. package/dist/core/wiring-manifest.d.ts +12 -0
  115. package/dist/core/wiring-manifest.js +5 -1
  116. package/dist/engine/harness/agent-harness.d.ts +14 -1
  117. package/dist/engine/harness/agent-harness.js +28 -3
  118. package/dist/engine/harness/types.d.ts +3 -1
  119. package/dist/engine/llm/types.d.ts +8 -7
  120. package/dist/engine/llm/types.js +1 -1
  121. package/dist/engine/session/import-validate.js +4 -0
  122. package/dist/index.d.ts +10 -7
  123. package/dist/index.js +7 -5
  124. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  125. package/dist/orchestration/workflow.d.ts +1 -1
  126. package/dist/orchestration/workflow.js +8 -5
  127. package/dist/shell/lex.d.ts +30 -51
  128. package/dist/shell/lex.js +237 -284
  129. package/dist/stores/cc/mailbox-store.js +3 -2
  130. package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
  131. package/dist/stores/file/file-history-store.d.ts +2 -4
  132. package/dist/stores/file/file-history-store.js +1 -1
  133. package/dist/stores/file/fs-atomic.d.ts +16 -11
  134. package/dist/stores/file/fs-atomic.js +17 -6
  135. package/dist/stores/file/index.d.ts +5 -6
  136. package/dist/stores/file/index.js +1 -1
  137. package/dist/stores/file/mailbox-store.d.ts +4 -9
  138. package/dist/stores/file/mailbox-store.js +29 -30
  139. package/dist/stores/file/session-policy-store.d.ts +2 -0
  140. package/dist/stores/file/session-policy-store.js +1 -1
  141. package/dist/stores/file/session-store.d.ts +11 -7
  142. package/dist/stores/file/session-store.js +23 -16
  143. package/dist/stores/file/shared-ledger.js +3 -14
  144. package/dist/stores/file/tool-result-store.d.ts +2 -0
  145. package/dist/stores/file/tool-result-store.js +5 -2
  146. package/dist/stores/file/workflow-journal-store.js +14 -9
  147. package/dist/tools/fs/bash-program-position.d.ts +15 -24
  148. package/dist/tools/fs/bash-program-position.js +11 -29
  149. package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
  150. package/dist/tools/fs/bash-readonly-classifier.js +400 -272
  151. package/dist/tools/fs/shell-read-check.js +3 -3
  152. package/dist/tools/fs/shell-read-establish.js +1 -1
  153. package/dist/tools/fs/shell-read-plan.d.ts +11 -6
  154. package/dist/tools/fs/shell-read-plan.js +167 -183
  155. package/dist/tools/fs/shell-read-word.d.ts +50 -0
  156. package/dist/tools/fs/shell-read-word.js +59 -0
  157. package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
  158. package/dist/tools/fs/shell-reentry-text.js +73 -0
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +58 -8
  161. package/wire-changes/core@7.29.0.json +185 -0
  162. package/wire-changes/core@7.30.0.json +1821 -0
@@ -62,15 +62,17 @@ export declare function childThinkingSeat(explicit: ThinkingLevel | undefined, d
62
62
  export declare function deploymentSubagentTierModel(deps: {
63
63
  models?: Record<string, Model>;
64
64
  roles?: ModelRoles;
65
- } | undefined): Model | undefined;
65
+ } | undefined): InheritedModelPick | undefined;
66
66
  /**
67
67
  * The child run's model seat — ONE rule for every engine-spawned run (the delegation child at
68
68
  * `buildChildSpec`, the observer sidecar, the workflow launch site): an EXPLICIT model for this spawn wins
69
69
  * (per-call `model`, the agent definition's `model`, the tool-level `model`); else a deployment that STATED
70
70
  * a subagent tier gets it (no model on the seat — the role chain resolves the stated entry, `select` forms
71
- * included); else the child runs on the PARENT run's effective model (the caller's current `Model` object,
72
- * so per-model routing/credentials ride along); else — no parent model readable, which only happens outside
73
- * a Runner-enriched ctx — the bare role, whose chain ends at the deployment default.
71
+ * included); else the child runs on the PARENT run's effective model — taken as the REF that chose it and
72
+ * resolved against the catalog generation in force at THIS spawn ({@link inheritedParentModel}), so
73
+ * per-model routing/credentials ride along and a hot-swapped catalog reaches the child; else — no parent
74
+ * model readable, which only happens outside a Runner-enriched ctx — the bare role, whose chain ends at the
75
+ * deployment default.
74
76
  *
75
77
  * THE ROLE IDENTITY IS CONSTANT: every seat carries `modelRole: "subagent"`, the explicit-model seat too.
76
78
  * `resolveTaskModel` keys the role's `systemPrompt` / `thinking` presets on `modelRole` in BOTH of its
@@ -93,7 +95,57 @@ export type ChildModelSeat = {
93
95
  modelRole: "subagent";
94
96
  model?: ModelRef;
95
97
  };
96
- export declare function childModelSeat(explicit: ModelRef | undefined, deploymentStated: boolean, parent: Model | undefined): ChildModelSeat;
98
+ /**
99
+ * The PARENT rung's value, resolved at THIS spawn tick. The parent leg travels as the REF that chose its
100
+ * model (`ToolExecuteContext.modelRef`), not as the object it has been holding since its own prepare, so:
101
+ * · a catalog word resolves against the catalog generation in force NOW — a child spawned after a hot
102
+ * swap runs on the generation its own prepare would have seen, which is what the swap law already
103
+ * promises every string ref ("resolves against the new catalog at the next prepare");
104
+ * · a word the swap RETIRED throws here, loudly, under the same unknown-ref policy a caller-written
105
+ * retired word meets — never a silent fall back to the retired object, which is the shape that let a
106
+ * child run for its whole life on a generation the deployment had withdrawn;
107
+ * · a `Model` OBJECT is its own ref and passes through pinned, the swap law's other existing arm.
108
+ * Resolved lazily by {@link childModelSeat}, on the arm actually taken: a spawn that names its own model,
109
+ * or a deployment that stated a subagent tier, never consults the parent rung and so never inherits its
110
+ * refusal. A word with NO catalog to ask refuses under that same policy rather than travelling on as a
111
+ * word: a seat that cannot name the model it seats is not a seat, and the caller learns it here.
112
+ *
113
+ * @contract child_seat.parent_model_travels_as_its_catalog_word — an engine-spawned run that inherits its
114
+ * parent's model inherits the parent's CURRENT model BY THE REF THAT CHOSE IT, resolved once against the
115
+ * catalog generation in force at the child's own spawn: a catalog word therefore lands the child on the
116
+ * generation its own prepare would have seen (and refuses loudly, under the shared unknown-ref policy, when
117
+ * the word has been retired), while a parent whose model is a `Model` object travels as that object and is
118
+ * never re-resolved. The resolution happens at the spawn, not in the child's prepare, so the model the spawn
119
+ * frames name and the model the child runs on stay one value.
120
+ */
121
+ export declare function inheritedParentModel(parent: ModelRef | undefined, catalog: Record<string, Model> | undefined): Model | undefined;
122
+ /**
123
+ * What a spawn seats, and WHICH catalog word chose it.
124
+ *
125
+ * The two travel on different channels ON PURPOSE. `seat` is spread onto the child SPEC and carries the
126
+ * RESOLVED object, so the model the spawn frames name is the model the child runs — nothing re-resolves
127
+ * between the judgement and the child's prepare. `modelRef` is the word that object came from, and it rides
128
+ * the TRUSTED internals channel (`RunInternals.inheritedModelRef`) so the child is not silently PINNED by
129
+ * having been handed an object: its own children inherit the word in turn, and the deployment's per-model
130
+ * window overlay — which is keyed by word — still reaches it. Absent `modelRef` = there is no word (an
131
+ * object the caller pinned, a `select` criteria), and a leg with no word is never re-resolved and never
132
+ * overlaid, which is exactly what pinning means.
133
+ */
134
+ /** A resolved model together with the catalog WORD that chose it — the shape a lane hands its launch site
135
+ * when the two travel separately (the object onto the child spec, the word onto its trusted channel). */
136
+ export interface InheritedModelPick {
137
+ readonly model: Model;
138
+ readonly modelRef?: ModelRef;
139
+ }
140
+ export interface ChildModelSeatPick<S> {
141
+ readonly seat: S;
142
+ readonly modelRef?: ModelRef;
143
+ }
144
+ export declare function childModelSeat(explicit: ModelRef | undefined, deploymentStated: boolean, parent: ModelRef | undefined, catalog?: Record<string, Model>,
145
+ /** The word the CALLER resolved `explicit` from, when the caller resolved one (a per-call model word a
146
+ * delegation gate judged into an object before the seat ever saw it). An explicit STRING needs none — it
147
+ * travels as itself and the child's own prepare resolves it, which already leaves the child its word. */
148
+ explicitRef?: ModelRef): ChildModelSeatPick<ChildModelSeat>;
97
149
  /**
98
150
  * The FORK's model seat — the caller CONTINUED, not a subagent to the role chain. A fork always runs on the
99
151
  * caller's model (the caller's current `Model`, else the delegation tool's own `model`), and it carries NO
@@ -103,6 +155,6 @@ export declare function childModelSeat(explicit: ModelRef | undefined, deploymen
103
155
  * stated for its workers). No caller model readable (only outside a Runner-enriched ctx) ⇒ an empty seat: the
104
156
  * role chain's `default` start, exactly what a caller run with no `modelRole` of its own resolves.
105
157
  */
106
- export declare function forkModelSeat(callerModel: ModelRef | undefined): {
158
+ export declare function forkModelSeat(callerModel: ModelRef | undefined, inheritedRef?: ModelRef, catalog?: Record<string, Model>): ChildModelSeatPick<{
107
159
  model?: ModelRef;
108
- };
160
+ }>;
@@ -1,4 +1,4 @@
1
- import { resolveTaskModel } from "../core/roles.js";
1
+ import { resolveModel, resolveTaskModel } from "../core/roles.js";
2
2
  export function runnerRoleMap(runner) {
3
3
  try {
4
4
  return runner?.agentCatalog?.roles;
@@ -39,12 +39,38 @@ export function childThinkingSeat(explicit, deploymentStated, parent) {
39
39
  return level !== undefined ? { thinking: level } : {};
40
40
  }
41
41
  export function deploymentSubagentTierModel(deps) {
42
- return deps !== undefined && deploymentStatesSubagentModel(deps.roles) ? resolveTaskModel({ modelRole: "subagent" }, deps).model : undefined;
42
+ if (deps === undefined || !deploymentStatesSubagentModel(deps.roles))
43
+ return undefined;
44
+ const resolved = resolveTaskModel({ modelRole: "subagent" }, deps);
45
+ return { model: resolved.model, ...(inheritableRef(resolved.modelRef, resolved.model, deps.models) !== undefined ? { modelRef: resolved.modelRef } : {}) };
46
+ }
47
+ export function inheritedParentModel(parent, catalog) {
48
+ return typeof parent === "string" ? resolveModel(parent, catalog) : parent;
49
+ }
50
+ function inheritableRef(word, seated, catalog) {
51
+ if (typeof word !== "string")
52
+ return undefined;
53
+ try {
54
+ return resolveModel(word, catalog) === seated ? word : undefined;
55
+ }
56
+ catch {
57
+ return undefined;
58
+ }
43
59
  }
44
- export function childModelSeat(explicit, deploymentStated, parent) {
45
- const model = explicit ?? (deploymentStated ? undefined : parent);
46
- return { modelRole: "subagent", ...(model !== undefined ? { model } : {}) };
60
+ export function childModelSeat(explicit, deploymentStated, parent, catalog, explicitRef) {
61
+ const inheritArm = explicit === undefined && !deploymentStated;
62
+ const model = explicit ?? (inheritArm ? inheritedParentModel(parent, catalog) : undefined);
63
+ const word = inheritArm ? parent : typeof explicit === "string" ? undefined : explicitRef;
64
+ const modelRef = model !== undefined && typeof model !== "string" ? inheritableRef(word, model, catalog) : undefined;
65
+ return {
66
+ seat: { modelRole: "subagent", ...(model !== undefined ? { model } : {}) },
67
+ ...(modelRef !== undefined ? { modelRef } : {}),
68
+ };
47
69
  }
48
- export function forkModelSeat(callerModel) {
49
- return callerModel !== undefined ? { model: callerModel } : {};
70
+ export function forkModelSeat(callerModel, inheritedRef, catalog) {
71
+ const modelRef = callerModel !== undefined && typeof callerModel !== "string" ? inheritableRef(inheritedRef, callerModel, catalog) : undefined;
72
+ return {
73
+ seat: callerModel !== undefined ? { model: callerModel } : {},
74
+ ...(modelRef !== undefined ? { modelRef } : {}),
75
+ };
50
76
  }
@@ -1,10 +1,27 @@
1
1
  import type { AskEffective } from "../core/wiring-manifest.js";
2
2
  import { type PermissionModeClass } from "./cross-session-envelope.js";
3
- /** The explicit values of `crossSessionInbound`. The UI's fourth spelling `"default"` means UNSET
4
- * (mode parity) and is folded to `undefined` by the resolver — it is not a distinct policy. */
5
- export type CrossSessionInboundSetting = "accept" | "hold" | "refuse";
3
+ /**
4
+ * The explicit values of `crossSessionInbound`, loosest first: `wake` (deliver, and the HOST may start
5
+ * a turn on this session when its box grows), `next-turn` (deliver at the session's next turn
6
+ * boundary — the value the pre-7.30 spelling `accept` named), `hold` (park for a human), `refuse`.
7
+ * The UI's spelling `"default"` means UNSET (mode parity) and is folded to `undefined` by the resolver
8
+ * — it is not a distinct policy.
9
+ *
10
+ * @contract cross_session.wake_is_the_hosts_leg — `wake` is judged exactly as `next-turn` at the drain
11
+ * (deliver); what it adds is the HOST's leg: a host that observes a `peer_message` or `engine_frame`
12
+ * record appended to a session whose setting resolves to `wake` may start ONE turn on that session,
13
+ * bounded by `maxWakesPerHour`, and the engine promises that the turn's first drain pass ATTEMPTS the
14
+ * box (a claim that loses, a setting a host cannot read, an admission stop or the pass budget can
15
+ * still leave the record for a later boundary — every such outcome is observable, none is silent).
16
+ * With the budget spent, or on a host that starts no turns, `wake` degrades to `next-turn`: the record
17
+ * stays in the box and the next user turn reads it. The engine never starts a turn itself.
18
+ */
19
+ export type CrossSessionInboundSetting = "wake" | "next-turn" | "hold" | "refuse";
6
20
  export declare const CROSS_SESSION_INBOUND_SETTINGS: readonly CrossSessionInboundSetting[];
7
21
  export declare function isCrossSessionInboundSetting(value: unknown): value is CrossSessionInboundSetting;
22
+ /** The pre-7.30 spelling and the word it now means. A layer carrying the old spelling is READ as the
23
+ * new word (never held, never invalid) and reported in `deprecatedLayers` so a host can say so once. */
24
+ export declare const CROSS_SESSION_INBOUND_DEPRECATED_SPELLINGS: Readonly<Record<string, CrossSessionInboundSetting>>;
8
25
  /** Which layer decided the effective value. `"invalid"` = an unrecognized value was found in some
9
26
  * layer and the fail-closed rule forced `hold` (the loud arm). */
10
27
  export type CrossSessionSettingSource = "managed" | "user" | "repo" | "invalid";
@@ -16,8 +33,9 @@ export interface CrossSessionInboundSettingLayers {
16
33
  /** The user's own layer (CC `userSettings`/`flagSettings`): consulted after managed. */
17
34
  user?: unknown;
18
35
  /** Repository layers (CC `localSettings`/`projectSettings`), in precedence order. A repo layer can
19
- * only TIGHTEN the standing value (accept < hold < refuse), whichever layer set it; it can never
20
- * loosen it. */
36
+ * only TIGHTEN the standing value (wake < next-turn < hold < refuse), whichever layer set it; it can
37
+ * never loosen it — so `wake` written in a repo layer never takes effect (the unset baseline is
38
+ * `next-turn`, and a repo layer cannot loosen past it). */
21
39
  repo?: readonly unknown[];
22
40
  }
23
41
  export interface ResolvedCrossSessionInboundSetting {
@@ -28,6 +46,10 @@ export interface ResolvedCrossSessionInboundSetting {
28
46
  * ⇒ the resolver applied the fail-closed rule (see `decidedBy: "invalid"`) unless the effective
29
47
  * value was already at least as tight as `hold`. Reported so a host can warn — never swallowed. */
30
48
  invalidLayers: Array<"managed" | "user" | "repo">;
49
+ /** Layers that spelled a value the pre-7.30 way (`accept`): read as its current word, and reported
50
+ * here so the drain announces the spelling ONCE per leg — the value in force is never silently
51
+ * different from the value written. Absent from the object when empty. */
52
+ deprecatedLayers?: Array<"managed" | "user" | "repo">;
31
53
  }
32
54
  /**
33
55
  * CC `I` transcribed: managed beats user; repo layers may only tighten the standing value — including a
@@ -1,53 +1,63 @@
1
1
  import { isPermissionModeClass } from "./cross-session-envelope.js";
2
- export const CROSS_SESSION_INBOUND_SETTINGS = Object.freeze(["accept", "hold", "refuse"]);
2
+ export const CROSS_SESSION_INBOUND_SETTINGS = Object.freeze(["wake", "next-turn", "hold", "refuse"]);
3
3
  export function isCrossSessionInboundSetting(value) {
4
4
  return typeof value === "string" && CROSS_SESSION_INBOUND_SETTINGS.includes(value);
5
5
  }
6
- const RANK = { accept: 0, hold: 1, refuse: 2 };
6
+ export const CROSS_SESSION_INBOUND_DEPRECATED_SPELLINGS = Object.freeze({ accept: "next-turn" });
7
+ const RANK = { wake: 0, "next-turn": 1, hold: 2, refuse: 3 };
8
+ const UNSET_BASELINE = "next-turn";
7
9
  function readLayer(v) {
8
10
  if (v === undefined || v === null || v === "default")
9
- return undefined;
10
- return isCrossSessionInboundSetting(v) ? v : "invalid";
11
+ return { value: undefined };
12
+ if (isCrossSessionInboundSetting(v))
13
+ return { value: v };
14
+ const migrated = typeof v === "string" && Object.hasOwn(CROSS_SESSION_INBOUND_DEPRECATED_SPELLINGS, v) ? CROSS_SESSION_INBOUND_DEPRECATED_SPELLINGS[v] : undefined;
15
+ return migrated !== undefined ? { value: migrated, deprecated: true } : { value: "invalid" };
11
16
  }
12
17
  export function resolveCrossSessionInboundSetting(layers) {
13
18
  const invalidLayers = [];
19
+ const deprecatedLayers = [];
14
20
  let value;
15
21
  let decidedBy;
16
22
  for (const [name, raw] of [
17
23
  ["managed", layers.managed],
18
24
  ["user", layers.user],
19
25
  ]) {
20
- const v = readLayer(raw);
26
+ const { value: v, deprecated } = readLayer(raw);
21
27
  if (v === "invalid") {
22
28
  invalidLayers.push(name);
23
29
  continue;
24
30
  }
31
+ if (deprecated === true)
32
+ deprecatedLayers.push(name);
25
33
  if (v !== undefined && value === undefined) {
26
34
  value = v;
27
35
  decidedBy = name;
28
36
  }
29
37
  }
30
38
  for (const raw of layers.repo ?? []) {
31
- const v = readLayer(raw);
39
+ const { value: v, deprecated } = readLayer(raw);
32
40
  if (v === "invalid") {
33
41
  invalidLayers.push("repo");
34
42
  continue;
35
43
  }
44
+ if (deprecated === true)
45
+ deprecatedLayers.push("repo");
36
46
  if (v === undefined)
37
47
  continue;
38
- if (RANK[v] > RANK[value ?? "accept"]) {
48
+ if (RANK[v] > RANK[value ?? UNSET_BASELINE]) {
39
49
  value = v;
40
50
  decidedBy = "repo";
41
51
  }
42
- else if (v !== "accept" && value !== undefined && RANK[v] === RANK[value] && decidedBy !== "managed") {
52
+ else if (v !== UNSET_BASELINE && value !== undefined && RANK[v] === RANK[value] && decidedBy !== "managed") {
43
53
  decidedBy = "repo";
44
54
  }
45
55
  }
46
- if (invalidLayers.length > 0 && RANK[value ?? "accept"] < RANK.hold) {
56
+ if (invalidLayers.length > 0 && RANK[value ?? UNSET_BASELINE] < RANK.hold) {
47
57
  value = "hold";
48
58
  decidedBy = "invalid";
49
59
  }
50
- return { ...(value !== undefined ? { value } : {}), ...(decidedBy !== undefined ? { decidedBy } : {}), invalidLayers };
60
+ return { ...(value !== undefined ? { value } : {}), ...(decidedBy !== undefined ? { decidedBy } : {}), invalidLayers, ...(deprecatedLayers.length > 0 ? { deprecatedLayers } : {}) };
51
61
  }
52
62
  export const CROSS_SESSION_HOLD_CAUSES = Object.freeze([
53
63
  "mode-mismatch",
@@ -71,7 +81,7 @@ export function describeCrossSessionHoldCause(cause) {
71
81
  case "managed-setting":
72
82
  return `Your organization's managed settings set "crossSessionInbound" to "hold".`;
73
83
  case "repo-setting":
74
- return `This repository's settings set "crossSessionInbound" to "hold" (your own "accept" cannot override a repo tightening).`;
84
+ return `This repository's settings set "crossSessionInbound" to "hold" (your own "next-turn" cannot override a repo tightening).`;
75
85
  case "invalid-setting":
76
86
  return 'A settings file has an unrecognized "crossSessionInbound" value (see the settings warning), so messages are held while it is present.';
77
87
  case "bypass-default":
@@ -121,7 +131,8 @@ export function judgeCrossSessionInbound(input) {
121
131
  const { setting, selfModeClass, sender } = input;
122
132
  if (setting.value !== undefined) {
123
133
  switch (setting.value) {
124
- case "accept":
134
+ case "wake":
135
+ case "next-turn":
125
136
  return { verdict: "deliver" };
126
137
  case "refuse":
127
138
  return { verdict: "refuse", cause: "opt-out" };
@@ -1,14 +1,27 @@
1
1
  import { Type } from "typebox";
2
2
  import type { TaskAccess } from "../core/task-registry.js";
3
3
  import { type RosterEntry } from "./roster-store.js";
4
- import { type PeerDirectory } from "./peer-directory.js";
4
+ import { type PeerDirectory, type PeerPresence, type PeerSessionRecord } from "./peer-directory.js";
5
5
  export declare const LIST_AGENTS_TOOL_NAME = "ListAgents";
6
6
  export declare const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
7
7
  /** CC `maxResultSizeChars` for this tool. */
8
8
  export declare const LIST_AGENTS_MAX_RESULT_CHARS = 10000;
9
+ /** The default freshness window (24 h) — one source, the peer lane's knob table. */
10
+ export declare const PEER_LISTING_FRESH_WINDOW_MS: number;
11
+ /**
12
+ * THE freshness predicate: a peer row is listed by default iff it is live AND (its host says it is
13
+ * working — `tempo` active/blocked — OR it changed within the window). One rule over rendering
14
+ * visibility only: refs are minted and names resolve over the FULL set, so a row hidden here is still
15
+ * addressable by name. Dead and stale rows are what `includeAll` adds.
16
+ */
17
+ export declare function isFreshPeerRow(r: Pick<PeerSessionRecord, "liveness" | "tempo" | "updatedAt">, now: number, windowMs?: number): boolean;
18
+ /** `Ns` / `Nm` / `Nh` / `Nd` — a bounded age word for a listing row; a future stamp reads as `0s`. */
19
+ export declare function formatAgo(ms: number): string;
20
+ /** The last two path segments of a working directory (what a person recognizes a project by). */
21
+ export declare function cwdTail(cwd: string): string;
9
22
  export interface ListAgentsToolOptions {
10
- /** The peer-session directory (the mount reason; without it the tool is not mounted). */
11
- peerDirectory: PeerDirectory;
23
+ /** The peer-session directory. Absent ⇒ no peer rows (the two agent lanes still list). */
24
+ peerDirectory?: PeerDirectory;
12
25
  /** This session's own identity — excluded from the peer listing, included in ref disambiguation. */
13
26
  self: {
14
27
  sessionId: string;
@@ -40,8 +53,10 @@ export interface ListAgentsToolOptions {
40
53
  roster?: {
41
54
  list(): RosterEntry[] | Promise<RosterEntry[]>;
42
55
  };
43
- /** Include dead (offline) peer sessions in the listing. Default false (CC observable form). */
44
- includeOffline?: boolean;
56
+ /** The resolved freshness window ({@link isFreshPeerRow}); absent ⇒ {@link PEER_LISTING_FRESH_WINDOW_MS}. */
57
+ freshWindowMs?: number;
58
+ /** The clock (tests). */
59
+ now?: () => number;
45
60
  }
46
61
  /** One rendered row. `kind` names the lane; `address` is what SendMessage accepts. */
47
62
  export interface ListAgentsRow {
@@ -49,6 +64,14 @@ export interface ListAgentsRow {
49
64
  name: string;
50
65
  /** Peer rows only: the listing-minted ref. */
51
66
  ref?: string;
67
+ /** Peer rows, live: when the row last changed (rendered `active <N> ago`). */
68
+ lastActiveAt?: number;
69
+ /** Peer rows, dead: when the host marked it (rendered `offline <N> ago`); a dead row with no stamp renders `offline`. */
70
+ offlineAt?: number;
71
+ /** Peer rows: the last two segments of the session's working directory, when the row carries one. */
72
+ cwd?: string;
73
+ /** Peer rows: the session's self-reported presence, when it reported any. */
74
+ presence?: PeerPresence;
52
75
  /** What SendMessage accepts: on both AGENT lanes the registry `task_id` VERBATIM (no prefixing or
53
76
  * re-minting), never the child's session id — a different domain, tabled in INTEGRATION-CORE §1.6a.
54
77
  * Peer rows carry the `session.<id>` box handle instead. */
@@ -59,4 +82,6 @@ export interface ListAgentsRow {
59
82
  * COMPLETED and nothing else: a model applies the continuable rule to that word. */
60
83
  state: "busy" | "idle" | "offline" | "running" | "parked" | "finished" | "failed" | "killed" | "cancelled" | "unknown";
61
84
  }
62
- export declare function createListAgentsTool(opts: ListAgentsToolOptions): import("../internal/harness-types.js").AgentTool<Type.TObject<{}>, unknown>;
85
+ export declare function createListAgentsTool(opts: ListAgentsToolOptions): import("../internal/harness-types.js").AgentTool<Type.TObject<{
86
+ includeAll: Type.TOptional<Type.TBoolean>;
87
+ }>, unknown>;
@@ -3,12 +3,38 @@ import { defineTool } from "../core/tools.js";
3
3
  import { inlineUntrusted } from "../core/untrusted-text.js";
4
4
  import { formatPeerNameRef } from "./cross-session-ref.js";
5
5
  import { entryAccessible } from "./roster-store.js";
6
- import { mintPeerSessionCandidates, peerSessionBoxHandle } from "./peer-directory.js";
6
+ import { mintPeerSessionCandidates, PEER_PRESENCE_MAX_CHARS, peerSessionBoxHandle } from "./peer-directory.js";
7
+ import { PEER_LANE_DEFAULTS } from "./peer-admission.js";
7
8
  import { toolFace } from "../core/tool-catalog-entries.js";
8
9
  import { thrownText } from "../core/thrown-value.js";
10
+ import { assertNoRetiredKeys } from "../core/retired-keys.js";
9
11
  export const LIST_AGENTS_TOOL_NAME = "ListAgents";
10
12
  export const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
11
13
  export const LIST_AGENTS_MAX_RESULT_CHARS = 10_000;
14
+ export const PEER_LISTING_FRESH_WINDOW_MS = PEER_LANE_DEFAULTS.listingFreshWindowMs;
15
+ export function isFreshPeerRow(r, now, windowMs = PEER_LISTING_FRESH_WINDOW_MS) {
16
+ if (r.liveness !== "live")
17
+ return false;
18
+ if (r.tempo === "active" || r.tempo === "blocked")
19
+ return true;
20
+ return now - r.updatedAt <= windowMs;
21
+ }
22
+ export function formatAgo(ms) {
23
+ const sec = Math.max(0, Math.floor(ms / 1000));
24
+ if (sec < 60)
25
+ return `${sec}s`;
26
+ const min = Math.floor(sec / 60);
27
+ if (min < 60)
28
+ return `${min}m`;
29
+ const h = Math.floor(min / 60);
30
+ if (h < 48)
31
+ return `${h}h`;
32
+ return `${Math.floor(h / 24)}d`;
33
+ }
34
+ export function cwdTail(cwd) {
35
+ const parts = cwd.split(/[\\/]+/).filter((p) => p !== "");
36
+ return parts.slice(-2).join("/");
37
+ }
12
38
  const AGENT_ROW_STATE = {
13
39
  pending: "running",
14
40
  running: "running",
@@ -32,26 +58,46 @@ function peerRowState(r) {
32
58
  const tempo = r.tempo;
33
59
  return tempo !== undefined && Object.hasOwn(PEER_ROW_STATE, tempo) ? PEER_ROW_STATE[tempo] : "unknown";
34
60
  }
61
+ function renderPresence(p) {
62
+ const parts = [...(p.agent !== undefined ? [`agent=${inlineUntrusted(p.agent, PEER_PRESENCE_MAX_CHARS)}`] : []), ...(p.intent !== undefined ? [`intent=${inlineUntrusted(p.intent, PEER_PRESENCE_MAX_CHARS)}`] : [])];
63
+ return `self: ${parts.join(", ")}`;
64
+ }
65
+ const RETIRED_LIST_AGENTS_OPTION_KEYS = {
66
+ includeOffline: "the model asks for stale and offline rows per call with the `includeAll` tool parameter (a superset: stale + offline)",
67
+ };
35
68
  export function createListAgentsTool(opts) {
69
+ assertNoRetiredKeys(opts, RETIRED_LIST_AGENTS_OPTION_KEYS, "ListAgentsToolOptions");
70
+ const peers = opts.peerDirectory !== undefined;
71
+ const windowMs = opts.freshWindowMs ?? PEER_LISTING_FRESH_WINDOW_MS;
36
72
  return defineTool({
37
73
  name: LIST_AGENTS_TOOL_NAME,
38
74
  ...toolFace("list-agents"),
39
75
  isConcurrencySafe: () => true,
40
- description: `List the agents and peer sessions you can message with SendMessage. Peer sessions are other sessions of this ` +
41
- `engine running for the same user on this machine, shown as \`name [ref]\`; send to one with its name (or \`name [ref]\` ` +
42
- `when a name is ambiguous, or its \`session.<id>\` address). A peer that is offline still accepts messages — they are parked ` +
43
- `durably in its session box and delivered when it next runs. Background agents you spawned are listed with their task_id. ` +
76
+ description: `List the agents${peers ? " and peer sessions" : ""} you can message with SendMessage. ` +
77
+ (peers
78
+ ? `Peer sessions are other sessions of this engine running for the same user, shown as \`name [ref] · state · active <N> ago · <directory> · self: agent=…, intent=…\`; send to one with its name (or \`name [ref]\` ` +
79
+ `when a name is ambiguous, or its \`session.<id>\` address). By default only FRESH peers are listed (working now, or active within the last ${formatAgo(windowMs)}); ` +
80
+ `pass includeAll: true to also list stale and offline ones. A peer that is offline or unlisted still accepts messages — they are parked ` +
81
+ `durably in its session box and delivered when it next runs. `
82
+ : `Peer sessions of this engine are not addressable on this mount (no session directory). `) +
83
+ `Background agents you spawned are listed with their task_id. ` +
44
84
  `A ref is only valid against the listing (or disambiguation error) you read it from — do not invent one.`,
45
- parameters: Type.Object({}),
46
- execute: async () => {
85
+ parameters: Type.Object({
86
+ includeAll: Type.Optional(Type.Boolean({ description: "Also list stale (no activity within the freshness window) and offline peer sessions. Default false." })),
87
+ }),
88
+ execute: async (args) => {
89
+ const includeAll = args.includeAll === true;
90
+ const now = opts.now?.() ?? Date.now();
47
91
  const rows = [];
48
92
  let peerRows = [];
49
93
  let directoryFault;
50
- try {
51
- peerRows = await opts.peerDirectory.listPeerSessions({ scope: opts.self.scope });
52
- }
53
- catch (e) {
54
- directoryFault = thrownText(e);
94
+ if (opts.peerDirectory !== undefined) {
95
+ try {
96
+ peerRows = await opts.peerDirectory.listPeerSessions({ scope: opts.self.scope });
97
+ }
98
+ catch (e) {
99
+ directoryFault = thrownText(e);
100
+ }
55
101
  }
56
102
  for (const c of mintPeerSessionCandidates(peerRows, { sessionId: opts.self.sessionId })) {
57
103
  const r = c.record;
@@ -59,14 +105,18 @@ export function createListAgentsTool(opts) {
59
105
  continue;
60
106
  if (r.liveness === "deleted")
61
107
  continue;
62
- if (r.liveness === "dead" && opts.includeOffline !== true)
108
+ if (!includeAll && !isFreshPeerRow(r, now, windowMs))
63
109
  continue;
110
+ const presence = { ...(r.agent !== undefined ? { agent: r.agent } : {}), ...(r.intent !== undefined ? { intent: r.intent } : {}) };
64
111
  rows.push({
65
112
  kind: "peer_session",
66
113
  name: r.name,
67
114
  ref: c.ref,
68
115
  address: peerSessionBoxHandle(r.sessionId),
69
116
  state: peerRowState(r),
117
+ ...(r.liveness === "live" ? { lastActiveAt: r.updatedAt } : r.diedAt !== undefined ? { offlineAt: r.diedAt } : {}),
118
+ ...(r.cwd !== undefined ? { cwd: cwdTail(r.cwd) } : {}),
119
+ ...(presence.agent !== undefined || presence.intent !== undefined ? { presence } : {}),
70
120
  });
71
121
  }
72
122
  let registryCapped = false;
@@ -101,7 +151,9 @@ export function createListAgentsTool(opts) {
101
151
  }
102
152
  const lines = rows.map((r) => {
103
153
  const label = r.kind === "peer_session" ? formatPeerNameRef(inlineUntrusted(r.name, 64), r.ref) : inlineUntrusted(r.name, 64);
104
- return `${label} · ${r.state} · ${r.kind === "peer_session" ? "peer session" : r.kind === "roster" ? "agent (durable record)" : "agent"} · ${r.address}`;
154
+ const age = r.kind !== "peer_session" ? undefined : r.lastActiveAt !== undefined ? `active ${formatAgo(now - r.lastActiveAt)} ago` : r.offlineAt !== undefined ? `offline ${formatAgo(now - r.offlineAt)} ago` : "offline";
155
+ const cols = [label, r.state, age, r.cwd !== undefined ? inlineUntrusted(r.cwd, 64) : undefined, r.presence !== undefined ? renderPresence(r.presence) : undefined, r.kind === "peer_session" ? "peer session" : r.kind === "roster" ? "agent (durable record)" : "agent", r.address];
156
+ return cols.filter((c) => c !== undefined).join(" · ");
105
157
  });
106
158
  const finishedCount = rows.filter((r) => r.state === "finished").length;
107
159
  const header = rows.length === 0 ? "No agents or peer sessions are listed right now." : `${rows.length} listed:`;
@@ -7,7 +7,7 @@ export type PeerAxisTag = "h" | "s" | "t";
7
7
  export type PeerRefusalCode = "rate_limited" | "duplicate" | "hop_loop" | "hop_runaway" | "queue_full";
8
8
  /** Gate verdict subset of {@link PeerRefusalCode} (what `admit` itself can refuse with). */
9
9
  export type PeerAdmissionRefusal = Exclude<PeerRefusalCode, "queue_full">;
10
- export interface PeerAdmissionConfig {
10
+ export interface PeerLaneConfig {
11
11
  /** Per-sender burst budget (bucket size). */
12
12
  bucketCapacity: number;
13
13
  /** Per-sender steady-state refill rate (tokens per second). */
@@ -27,12 +27,24 @@ export interface PeerAdmissionConfig {
27
27
  * senders restart with full buckets — no correctness debt (the gate holds no state that must
28
28
  * survive across messages). */
29
29
  maxTrackedRecipients: number;
30
+ /** Peer WAKE budget, HOST-enforced: how many times per hour a host may start a turn on a session
31
+ * whose `crossSessionInbound` resolves to `wake` because its box grew. The engine spells the
32
+ * number and the contract; the host that starts turns is the executor. Integer. */
33
+ maxWakesPerHour: number;
34
+ /** Peer delivery budget, ENGINE-enforced: how many box RECORDS one drain pass injects (held
35
+ * releases, notice lines, peer messages and engine frames count alike — one counter over every
36
+ * record injection); the rest stay leased for the next boundary, and the pass's own `inbox_notice`
37
+ * line — which is not a record — says how many. Integer. */
38
+ maxMessagesPerRound: number;
39
+ /** The `ListAgents` freshness window: a live row with no activity word is listed by default only
40
+ * while `now − updatedAt` is within it (`includeAll` lists the rest). Milliseconds. */
41
+ listingFreshWindowMs: number;
30
42
  }
31
43
  /** Upstream defaults, adopted verbatim (anchored; deviation would need evidence sema does not have). */
32
- export declare const PEER_ADMISSION_DEFAULTS: Readonly<PeerAdmissionConfig>;
44
+ export declare const PEER_LANE_DEFAULTS: Readonly<PeerLaneConfig>;
33
45
  /** Test seam (mirrors `__resetMaterializeEnvAnnouncements`); never called by production code.
34
46
  * Re-arms BOTH arms (a WeakMap has no clear — it is re-minted). */
35
- export declare function __resetPeerAdmissionRangeAnnouncements(): void;
47
+ export declare function __resetPeerLaneRangeAnnouncements(): void;
36
48
  /**
37
49
  * Resolve a caller's partial config against the defaults, per-field range-clamped-to-default.
38
50
  * There is no off switch: the guard is always on; the ranges ARE the host's tuning space.
@@ -46,7 +58,7 @@ export declare function __resetPeerAdmissionRangeAnnouncements(): void;
46
58
  * the deployment's sink; absent, the announcement still reaches `console.warn` through the shared
47
59
  * delivery throat (an unwired host is not a silent one).
48
60
  */
49
- export declare function resolvePeerAdmissionConfig(overrides?: Partial<PeerAdmissionConfig>, onNotice?: (notice: EngineNotice) => void): PeerAdmissionConfig;
61
+ export declare function resolvePeerLaneConfig(overrides?: Partial<PeerLaneConfig>, onNotice?: (notice: EngineNotice) => void): PeerLaneConfig;
50
62
  /** Hop-chain carrier sliding window: a chain keeps its most recent entries (upstream `Xbr`=32).
51
63
  * The chain grows ONLY at send time (the sender appends its own token); delivery/replay never
52
64
  * appends — a message-driven revival is not a hop. */
@@ -135,10 +147,10 @@ export interface PeerAdmission {
135
147
  * records the body IN THIS CALL (no rollback protocol — a downstream delivery failure does not
136
148
  * refund; the bucket refills on its own). Refusal has zero side effects. Config is read
137
149
  * per call (state is long-lived, values are current — upstream form). */
138
- admit(req: PeerAdmissionRequest, config: PeerAdmissionConfig): PeerAdmissionVerdict;
150
+ admit(req: PeerAdmissionRequest, config: PeerLaneConfig): PeerAdmissionVerdict;
139
151
  /** Hop-only check (the identity-less-sender arm, and any caller that needs the chain verdict
140
152
  * without touching rate/dedup state). */
141
- checkHopChain(prospectiveChain: readonly string[], ownTokens: readonly string[], config: PeerAdmissionConfig): PeerAdmissionVerdict;
153
+ checkHopChain(prospectiveChain: readonly string[], ownTokens: readonly string[], config: PeerLaneConfig): PeerAdmissionVerdict;
142
154
  /** Observation face: senders currently tracked by this recipient instance. */
143
155
  trackedSenderCount(): number;
144
156
  /** Observation face: refusals by reason since this instance was created. */
@@ -149,7 +161,7 @@ export interface PeerAdmissionOptions {
149
161
  now?: () => number;
150
162
  }
151
163
  export declare function createPeerAdmission(options?: PeerAdmissionOptions): PeerAdmission;
152
- export declare function peerAdmissionFor(scope: string | undefined, recipientKey: string, config: PeerAdmissionConfig, options?: PeerAdmissionOptions): PeerAdmission;
164
+ export declare function peerAdmissionFor(scope: string | undefined, recipientKey: string, config: PeerLaneConfig, options?: PeerAdmissionOptions): PeerAdmission;
153
165
  /**
154
166
  * The delivery legs' ONE admission entry: registry seat-commit follows the SAME
155
167
  * refusal-is-side-effect-free rule as the sender table inside `admit` — the instance is looked up
@@ -166,7 +178,7 @@ export declare function peerAdmissionFor(scope: string | undefined, recipientKey
166
178
  * sent would read the send-point body record and refuse its own message as `duplicate`. Two gate
167
179
  * instances, one per stage; the stage-less key is the pre-385 spelling and stays byte-identical. */
168
180
  export type PeerAdmissionStage = "send" | "drain";
169
- export declare function judgePeerAdmission(scope: string | undefined, recipientKey: string, req: PeerAdmissionRequest, config: PeerAdmissionConfig, options?: PeerAdmissionOptions, stage?: PeerAdmissionStage): PeerAdmissionVerdict;
181
+ export declare function judgePeerAdmission(scope: string | undefined, recipientKey: string, req: PeerAdmissionRequest, config: PeerLaneConfig, options?: PeerAdmissionOptions, stage?: PeerAdmissionStage): PeerAdmissionVerdict;
170
182
  /** Test seam ONLY (module-level registry hygiene between test files — same posture as the lane map,
171
183
  * which drains itself; this one is long-lived by design so tests reset it explicitly). */
172
184
  export declare function resetPeerAdmissionRegistryForTests(): void;