@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.
- package/CHANGELOG.md +473 -1
- package/dist/agents/child-model-seat.d.ts +59 -7
- package/dist/agents/child-model-seat.js +33 -7
- package/dist/agents/cross-session-judge.d.ts +27 -5
- package/dist/agents/cross-session-judge.js +23 -12
- package/dist/agents/list-agents-tool.d.ts +31 -6
- package/dist/agents/list-agents-tool.js +66 -14
- package/dist/agents/peer-admission.d.ts +20 -8
- package/dist/agents/peer-admission.js +21 -12
- package/dist/agents/peer-directory.d.ts +29 -0
- package/dist/agents/peer-directory.js +20 -0
- package/dist/agents/peer-held-queue.d.ts +31 -4
- package/dist/agents/peer-held-queue.js +32 -1
- package/dist/agents/peer-session-drain.d.ts +91 -13
- package/dist/agents/peer-session-drain.js +409 -102
- package/dist/agents/read-inbox-tool.d.ts +22 -0
- package/dist/agents/read-inbox-tool.js +39 -0
- package/dist/agents/roster-store.d.ts +2 -4
- package/dist/agents/roster-store.js +1 -1
- package/dist/agents/send-message-tool.d.ts +4 -4
- package/dist/agents/send-message-tool.js +22 -2
- package/dist/agents/subagent.js +62 -44
- package/dist/agents/team-role.d.ts +34 -0
- package/dist/agents/team-role.js +17 -0
- package/dist/agents/verify.js +2 -2
- package/dist/brain/anthropic.js +4 -2
- package/dist/brain/open-responses.js +4 -3
- package/dist/brain/openai.js +2 -1
- package/dist/brain/stream-engine.js +6 -0
- package/dist/brain/stream-shared.d.ts +8 -0
- package/dist/brain/stream-shared.js +8 -0
- package/dist/core/auto-compaction.d.ts +42 -0
- package/dist/core/auto-compaction.js +35 -2
- package/dist/core/child-clamp-seats.d.ts +11 -0
- package/dist/core/child-clamp-seats.js +3 -0
- package/dist/core/context-edit.d.ts +3 -2
- package/dist/core/context-edit.js +6 -5
- package/dist/core/context-guard.d.ts +2 -1
- package/dist/core/context-guard.js +3 -2
- package/dist/core/context-settings.d.ts +103 -0
- package/dist/core/context-settings.js +60 -0
- package/dist/core/engine-notice.d.ts +18 -1
- package/dist/core/frame-settlement.d.ts +97 -0
- package/dist/core/frame-settlement.js +36 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +10 -10
- package/dist/core/mailbox-store.d.ts +41 -7
- package/dist/core/mailbox-store.js +9 -3
- package/dist/core/message-utils.d.ts +13 -0
- package/dist/core/message-utils.js +6 -0
- package/dist/core/parent-spec-seat.d.ts +14 -1
- package/dist/core/parent-spec-seat.js +6 -0
- package/dist/core/permission-rule-model.js +1 -1
- package/dist/core/roles.d.ts +20 -0
- package/dist/core/roles.js +35 -16
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +3 -1
- package/dist/core/runner/compaction-call-options.d.ts +26 -2
- package/dist/core/runner/compaction-call-options.js +9 -0
- package/dist/core/runner/compaction-seams.d.ts +4 -3
- package/dist/core/runner/compaction-seams.js +13 -6
- package/dist/core/runner/contracts.d.ts +61 -7
- package/dist/core/runner/decide-continuation.d.ts +2 -1
- package/dist/core/runner/decide-continuation.js +11 -10
- package/dist/core/runner/initial-run-state.js +2 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
- package/dist/core/runner/prepare-config-doors.js +4 -1
- package/dist/core/runner/prepare-context-lane.d.ts +9 -2
- package/dist/core/runner/prepare-context-lane.js +15 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +9 -3
- package/dist/core/runner/prepare-file-history.js +10 -8
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -1
- package/dist/core/runner/prepare-task.js +22 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
- package/dist/core/runner/prepare-wiring-manifest.js +6 -1
- package/dist/core/runner/response-model-seat.d.ts +16 -0
- package/dist/core/runner/response-model-seat.js +27 -0
- package/dist/core/runner/retired-runner-deps.d.ts +3 -1
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
- package/dist/core/runner/run-compaction-machinery.js +5 -3
- package/dist/core/runner/run-harness-handlers.js +9 -2
- package/dist/core/runner/run-identity-wiring.js +19 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +8 -2
- package/dist/core/runner/run-recovery-lanes.js +9 -5
- package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
- package/dist/core/runner/run-settle-and-teardown.js +6 -10
- package/dist/core/runner/run-stop-and-final-verify.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +32 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +33 -2
- package/dist/core/runner-deps.d.ts +44 -13
- package/dist/core/side-query.d.ts +6 -5
- package/dist/core/side-query.js +3 -1
- package/dist/core/swappable-deps.d.ts +20 -4
- package/dist/core/swappable-deps.js +6 -1
- package/dist/core/task-event.d.ts +13 -0
- package/dist/core/task-notification.d.ts +144 -2
- package/dist/core/task-notification.js +154 -5
- package/dist/core/task-result.d.ts +27 -3
- package/dist/core/task-spec.d.ts +7 -0
- package/dist/core/tool-catalog-entries.js +2 -1
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-policy.d.ts +9 -4
- package/dist/core/tool-spec.d.ts +14 -0
- package/dist/core/wiring-manifest.d.ts +12 -0
- package/dist/core/wiring-manifest.js +5 -1
- package/dist/engine/harness/agent-harness.d.ts +14 -1
- package/dist/engine/harness/agent-harness.js +28 -3
- package/dist/engine/harness/types.d.ts +3 -1
- package/dist/engine/llm/types.d.ts +8 -7
- package/dist/engine/llm/types.js +1 -1
- package/dist/engine/session/import-validate.js +4 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +7 -5
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/orchestration/workflow.js +8 -5
- package/dist/shell/lex.d.ts +30 -51
- package/dist/shell/lex.js +237 -284
- package/dist/stores/cc/mailbox-store.js +3 -2
- package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
- package/dist/stores/file/file-history-store.d.ts +2 -4
- package/dist/stores/file/file-history-store.js +1 -1
- package/dist/stores/file/fs-atomic.d.ts +16 -11
- package/dist/stores/file/fs-atomic.js +17 -6
- package/dist/stores/file/index.d.ts +5 -6
- package/dist/stores/file/index.js +1 -1
- package/dist/stores/file/mailbox-store.d.ts +4 -9
- package/dist/stores/file/mailbox-store.js +29 -30
- package/dist/stores/file/session-policy-store.d.ts +2 -0
- package/dist/stores/file/session-policy-store.js +1 -1
- package/dist/stores/file/session-store.d.ts +11 -7
- package/dist/stores/file/session-store.js +23 -16
- package/dist/stores/file/shared-ledger.js +3 -14
- package/dist/stores/file/tool-result-store.d.ts +2 -0
- package/dist/stores/file/tool-result-store.js +5 -2
- package/dist/stores/file/workflow-journal-store.js +14 -9
- package/dist/tools/fs/bash-program-position.d.ts +15 -24
- package/dist/tools/fs/bash-program-position.js +11 -29
- package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
- package/dist/tools/fs/bash-readonly-classifier.js +400 -272
- package/dist/tools/fs/shell-read-check.js +3 -3
- package/dist/tools/fs/shell-read-establish.js +1 -1
- package/dist/tools/fs/shell-read-plan.d.ts +11 -6
- package/dist/tools/fs/shell-read-plan.js +167 -183
- package/dist/tools/fs/shell-read-word.d.ts +50 -0
- package/dist/tools/fs/shell-read-word.js +59 -0
- package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
- package/dist/tools/fs/shell-reentry-text.js +73 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +58 -8
- package/wire-changes/core@7.29.0.json +185 -0
- 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):
|
|
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
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
|
|
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 (
|
|
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(["
|
|
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
|
|
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
|
-
|
|
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 ??
|
|
48
|
+
if (RANK[v] > RANK[value ?? UNSET_BASELINE]) {
|
|
39
49
|
value = v;
|
|
40
50
|
decidedBy = "repo";
|
|
41
51
|
}
|
|
42
|
-
else if (v !==
|
|
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 ??
|
|
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 "
|
|
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 "
|
|
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
|
|
11
|
-
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
|
-
/**
|
|
44
|
-
|
|
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<{
|
|
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.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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;
|