@sema-agent/core 7.29.0 → 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 +361 -0
- 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/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 +11 -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 +2 -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/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 +1 -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 +56 -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 +1 -1
- 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/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 +1 -1
- 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 +32 -1
- package/dist/core/runner-deps.d.ts +44 -13
- package/dist/core/swappable-deps.d.ts +20 -4
- package/dist/core/swappable-deps.js +6 -1
- package/dist/core/task-event.d.ts +5 -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 +14 -0
- 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.30.0.json +1821 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { mailboxCrossProcessMountVerdict } from "../core/mailbox-store.js";
|
|
2
|
+
import { decodeEngineFrame, ENGINE_FRAME_CODEC_VERSION, renderTaskNotificationXml, taskNotificationDedupKey } from "../core/task-notification.js";
|
|
3
|
+
import { createFrameSettlement, toolResultReceiptKey } from "../core/frame-settlement.js";
|
|
2
4
|
import { deliverEngineNotice } from "../core/types.js";
|
|
3
5
|
import { buildCrossSessionEnvelope, CrossSessionCodecError, encodePeerAddress } from "./cross-session-envelope.js";
|
|
4
6
|
import { judgeCrossSessionInbound, resolveCrossSessionInboundSetting } from "./cross-session-judge.js";
|
|
5
|
-
import { judgePeerAdmission, peerAxisToken,
|
|
7
|
+
import { judgePeerAdmission, peerAxisToken, resolvePeerLaneConfig } from "./peer-admission.js";
|
|
6
8
|
import { isPeerSessionId, PEER_SESSION_ID_GRAMMAR, peerSessionBoxHandle } from "./peer-directory.js";
|
|
7
9
|
import { inlineUntrusted } from "../core/untrusted-text.js";
|
|
8
10
|
import { createListAgentsTool, LIST_AGENTS_TOOL_ALIAS, LIST_AGENTS_TOOL_NAME } from "./list-agents-tool.js";
|
|
@@ -43,9 +45,11 @@ export function judgeSessionBoxDrain(deps, leg) {
|
|
|
43
45
|
scope: leg.scope,
|
|
44
46
|
handle: peerSessionBoxHandle(leg.sessionId),
|
|
45
47
|
sessionId: leg.sessionId,
|
|
48
|
+
...(deps.sessionBoxEngineFrames === true ? { engineFrames: true } : {}),
|
|
46
49
|
onError: (e) => deps.onError?.(e, { phase: "degraded", sessionId: leg.sessionId, classification: "session-box-park" }),
|
|
47
50
|
};
|
|
48
51
|
}
|
|
52
|
+
export const PEER_RESUPPLY_CAP = 1;
|
|
49
53
|
export function createPeerSessionDrain(opts) {
|
|
50
54
|
const { mailbox, sessionId, runId, scope } = opts;
|
|
51
55
|
const boxHandle = peerSessionBoxHandle(sessionId);
|
|
@@ -53,28 +57,41 @@ export function createPeerSessionDrain(opts) {
|
|
|
53
57
|
const drainOwner = `drain:${runId}`;
|
|
54
58
|
const recipientKey = peerAxisToken(scope, "s", sessionId);
|
|
55
59
|
const ttl = opts.leaseTtlMs ?? PEER_DRAIN_LEASE_TTL_MS;
|
|
56
|
-
|
|
60
|
+
const TERMINAL_DISPOSITIONS = new Set(["committed", "resupply_exhausted", "terminal"]);
|
|
61
|
+
const isTerminalDisposition = (d) => d !== undefined && TERMINAL_DISPOSITIONS.has(d.kind);
|
|
62
|
+
const ledger = new Map();
|
|
63
|
+
let ackedUpTo;
|
|
64
|
+
const seam = opts.settlement ?? createFrameSettlement();
|
|
57
65
|
const disclosedHeld = new Set();
|
|
58
66
|
let inFlight = Promise.resolve();
|
|
67
|
+
let noticeRound = 0;
|
|
68
|
+
let deprecatedSpellingAnnounced = false;
|
|
69
|
+
let currentRound;
|
|
59
70
|
const held = peerHeldQueueFor(scope, sessionId, opts.clock !== undefined ? { clock: opts.clock } : {});
|
|
60
71
|
const idleMachine = peerIdleMachineFor(scope, sessionId, opts.clock !== undefined ? { clock: opts.clock } : {});
|
|
61
72
|
const disclose = (disposition, cause, m, heldNote) => deliverEngineNotice(opts.onNotice, {
|
|
62
73
|
code: "peer.inbound_disposition",
|
|
63
74
|
message: `cross-session message seq ${m.seq} in this session's box was ${disposition === "held"
|
|
64
|
-
? `HELD${heldNote !== undefined ? ` (${heldNote})` : " (it entered this session's held queue, acked
|
|
75
|
+
? `HELD${heldNote !== undefined ? ` (${heldNote})` : " (it entered this session's held queue and STAYS IN THE BOX \u2014 the box is that queue's durable seat, so it is not acked until it settles; it is re-judged at every boundary, released or refused with a receipt to the sender, and expires with a receipt under dialogExpiry)"}`
|
|
65
76
|
: disposition === "refused"
|
|
66
77
|
? "refused by this session's crossSessionInbound setting"
|
|
67
|
-
: disposition === "
|
|
68
|
-
? "settled WITHOUT
|
|
69
|
-
: disposition === "
|
|
70
|
-
? "
|
|
71
|
-
:
|
|
78
|
+
: disposition === "resupply_exhausted"
|
|
79
|
+
? "settled WITHOUT being read: its frame was summarized away by a compaction, re-supplied once, and summarized away again \u2014 the engine has stopped handing it over. The sender was not told; reply-worthy content is GONE from this session's context"
|
|
80
|
+
: disposition === "notice_unrouted"
|
|
81
|
+
? "settled WITHOUT delivery (a notice-kind record the notice face cannot render)"
|
|
82
|
+
: disposition === "notice_user_only"
|
|
83
|
+
? "surfaced to the user only (a notice whose sender this session's parity would hold; the model did not read it)"
|
|
84
|
+
: "refused by the inbound admission gate"}: ${cause}`,
|
|
72
85
|
detail: { disposition, cause, seq: m.seq, box: boxHandle, ...(m.peerMeta?.fromSession !== undefined ? { fromSession: m.peerMeta.fromSession } : {}), sessionId, runId },
|
|
73
86
|
});
|
|
74
|
-
const readFacts = () =>
|
|
75
|
-
setting
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
const readFacts = () => {
|
|
88
|
+
const setting = resolveCrossSessionInboundSetting(opts.settingLayers() ?? {});
|
|
89
|
+
if (setting.deprecatedLayers !== undefined && !deprecatedSpellingAnnounced) {
|
|
90
|
+
deprecatedSpellingAnnounced = true;
|
|
91
|
+
opts.onError?.(new Error(`crossSessionInbound is spelled "accept" in the ${setting.deprecatedLayers.join(", ")} layer(s); that spelling is read as "next-turn" (deliver at the next turn boundary) — update the settings file`), { phase: "degraded", sessionId, classification: "peer-inbound-setting-spelling" });
|
|
92
|
+
}
|
|
93
|
+
return { setting, selfModeClass: opts.selfModeClass() };
|
|
94
|
+
};
|
|
78
95
|
const judgeMeta = (facts, meta) => judgeCrossSessionInbound({ setting: facts.setting, selfModeClass: facts.selfModeClass, sender: { ...(meta?.fromMode !== undefined ? { fromMode: meta.fromMode } : {}), selfSent: meta?.fromSession !== undefined && meta.fromSession.toLowerCase() === sessionId.toLowerCase() } });
|
|
79
96
|
const route = () => ({
|
|
80
97
|
mailbox,
|
|
@@ -125,7 +142,19 @@ export function createPeerSessionDrain(opts) {
|
|
|
125
142
|
};
|
|
126
143
|
const heldSink = (live) => ({
|
|
127
144
|
judge: (entry) => judgeMeta(readFacts(), entry.peerMeta),
|
|
128
|
-
...(live
|
|
145
|
+
...(live
|
|
146
|
+
? {
|
|
147
|
+
inject: (entry) => {
|
|
148
|
+
if (currentRound === undefined)
|
|
149
|
+
return "deferred";
|
|
150
|
+
const payload = buildPeerMessagePayload(entry);
|
|
151
|
+
if (currentRound.deliver(payload, "next") === "deferred")
|
|
152
|
+
return "deferred";
|
|
153
|
+
noteDelivered(entry.seq, 1, payload, currentRound.carrier);
|
|
154
|
+
return undefined;
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
129
158
|
receipt,
|
|
130
159
|
onHeld: (entry, count, cause, reannounce) => {
|
|
131
160
|
if (!reannounce && disclosedHeld.has(entry.seq))
|
|
@@ -133,11 +162,15 @@ export function createPeerSessionDrain(opts) {
|
|
|
133
162
|
disclosedHeld.add(entry.seq);
|
|
134
163
|
disclose("held", cause, entry, reannounce ? `still held; its cause moved to ${cause} (${count} held)` : undefined);
|
|
135
164
|
},
|
|
136
|
-
onSettled: (entry, settlement, reason, count) =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
onSettled: (entry, settlement, reason, count) => {
|
|
166
|
+
if (entry.box === boxHandle && settlement !== "delivered")
|
|
167
|
+
ledger.set(entry.seq, { kind: "terminal", why: `held_${settlement}` });
|
|
168
|
+
deliverEngineNotice(opts.onNotice, {
|
|
169
|
+
code: "peer.held_settled",
|
|
170
|
+
message: `held cross-session message seq ${entry.seq} left this session's held queue: ${settlement} (${reason}); ${count} still held${settlement === "expired" && reason === "evicted" ? " — the held queue was full, so the oldest entry was expired to make room" : ""}. The sender ${entry.peerMeta?.fromSession !== undefined ? "was sent a receipt" : "has no reply address and was not told"}.`,
|
|
171
|
+
detail: { settlement, reason, seq: entry.seq, box: boxHandle, ...(entry.peerMeta?.fromSession !== undefined ? { fromSession: entry.peerMeta.fromSession } : {}), heldCount: count, sessionId, runId },
|
|
172
|
+
});
|
|
173
|
+
},
|
|
141
174
|
});
|
|
142
175
|
let dialogExpiryMs = resolveCrossSessionDialogExpiry(undefined).ms;
|
|
143
176
|
let dialogExpiryAnnounced = false;
|
|
@@ -165,18 +198,127 @@ export function createPeerSessionDrain(opts) {
|
|
|
165
198
|
idleMachine.requester.configure({
|
|
166
199
|
expired: (o) => void routePeerIdleNotice(route(), sessionId, { kind: "expired", label: o.label }),
|
|
167
200
|
});
|
|
168
|
-
const
|
|
169
|
-
|
|
201
|
+
const adoptHeldDecisions = () => {
|
|
202
|
+
const decided = held.decisions();
|
|
203
|
+
if (decided.size === 0)
|
|
170
204
|
return;
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
205
|
+
for (const [seq, settlement] of decided) {
|
|
206
|
+
if (ackedUpTo !== undefined && seq <= ackedUpTo)
|
|
207
|
+
continue;
|
|
208
|
+
const mine = ledger.get(seq);
|
|
209
|
+
if (mine !== undefined && mine.kind !== "held")
|
|
210
|
+
continue;
|
|
211
|
+
ledger.set(seq, { kind: "terminal", why: `held_${settlement}` });
|
|
175
212
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
213
|
+
};
|
|
214
|
+
const reconcileHeld = (lease) => {
|
|
215
|
+
held.reconcile(new Set(lease.messages.map((m) => m.seq)));
|
|
216
|
+
let queued;
|
|
217
|
+
for (const [seq, d] of ledger) {
|
|
218
|
+
if (d.kind !== "held")
|
|
219
|
+
continue;
|
|
220
|
+
queued ??= new Set(held.list().filter((e) => e.box === boxHandle).map((e) => e.seq));
|
|
221
|
+
if (!queued.has(seq))
|
|
222
|
+
ledger.delete(seq);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const wantsCompactionEffects = () => {
|
|
226
|
+
for (const d of ledger.values())
|
|
227
|
+
if (d.kind === "delivered" || d.kind === "committed")
|
|
228
|
+
return true;
|
|
229
|
+
return false;
|
|
230
|
+
};
|
|
231
|
+
const noteDelivered = (seq, attempt, payload, carrier) => {
|
|
232
|
+
const own = taskNotificationDedupKey(payload);
|
|
233
|
+
const key = carrier === undefined ? own : `${carrier}|${own}`;
|
|
234
|
+
ledger.set(seq, { kind: "delivered", attempt, key, payload });
|
|
235
|
+
seam.awaiting.set(key, carrier === undefined ? { carrier: own } : { carrier, frameText: renderTaskNotificationXml(payload) });
|
|
236
|
+
};
|
|
237
|
+
const deriveAckPrefix = (messages) => {
|
|
238
|
+
let prefix;
|
|
239
|
+
for (const m of messages) {
|
|
240
|
+
if (ackedUpTo !== undefined && m.seq <= ackedUpTo)
|
|
241
|
+
continue;
|
|
242
|
+
if (!isTerminalDisposition(ledger.get(m.seq)))
|
|
243
|
+
break;
|
|
244
|
+
prefix = m.seq;
|
|
245
|
+
}
|
|
246
|
+
return prefix;
|
|
247
|
+
};
|
|
248
|
+
const settleLocally = () => {
|
|
249
|
+
adoptHeldDecisions();
|
|
250
|
+
const hostWithheldSeqs = [];
|
|
251
|
+
if (seam.receipts.size > 0 || seam.withheld.size > 0 || seam.hostWithheld.size > 0) {
|
|
252
|
+
for (const [seq, d] of ledger) {
|
|
253
|
+
if (d.kind !== "delivered")
|
|
254
|
+
continue;
|
|
255
|
+
if (seam.hostWithheld.delete(d.key)) {
|
|
256
|
+
ledger.set(seq, { kind: "terminal", why: "host_withheld" });
|
|
257
|
+
hostWithheldSeqs.push(seq);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (seam.withheld.delete(d.key)) {
|
|
261
|
+
if (d.attempt > 1)
|
|
262
|
+
ledger.set(seq, { kind: "invalidated", attempt: d.attempt - 1, payload: d.payload });
|
|
263
|
+
else
|
|
264
|
+
ledger.delete(seq);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const entryId = seam.receipts.get(d.key);
|
|
268
|
+
if (entryId === undefined)
|
|
269
|
+
continue;
|
|
270
|
+
seam.receipts.delete(d.key);
|
|
271
|
+
ledger.set(seq, { kind: "committed", attempt: d.attempt, key: d.key, entryId, payload: d.payload });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (hostWithheldSeqs.length > 0) {
|
|
275
|
+
opts.onError?.(new Error(`${hostWithheldSeqs.length} cross-session message frame(s) (seq ${hostWithheldSeqs.join(", ")}) could not be found in this session's ReadInbox result as a postToolUse hook rewrote it (removed, truncated or reshaped beyond whitespace) — settled as the host's decision: they leave ${boxHandle} and are not handed over again`), { phase: "degraded", sessionId, classification: "peer-inbox-host-withheld" });
|
|
276
|
+
}
|
|
277
|
+
for (const effect of seam.compactions.splice(0)) {
|
|
278
|
+
for (const [seq, d] of ledger) {
|
|
279
|
+
if (d.kind !== "committed")
|
|
280
|
+
continue;
|
|
281
|
+
if (effect.summarizedEntryIds === "unknown" || effect.summarizedEntryIds.has(d.entryId)) {
|
|
282
|
+
ledger.set(seq, { kind: "invalidated", attempt: d.attempt, payload: d.payload });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const [seq, d] of ledger) {
|
|
287
|
+
if (d.kind === "invalidated" && d.attempt > PEER_RESUPPLY_CAP) {
|
|
288
|
+
const meta = d.payload._sema_provenance?.peerMeta;
|
|
289
|
+
disclose("resupply_exhausted", `attempt ${d.attempt} of ${PEER_RESUPPLY_CAP + 1} was summarized away too`, { seq, ...(meta !== undefined ? { peerMeta: meta } : {}) });
|
|
290
|
+
ledger.set(seq, { kind: "resupply_exhausted" });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const ackSettledPrefix = async (lease) => {
|
|
295
|
+
const decided = held.decisions();
|
|
296
|
+
if (decided.size > 0) {
|
|
297
|
+
const live = new Set(lease.messages.map((m) => m.seq));
|
|
298
|
+
held.forgetDecisions([...decided.keys()].filter((seq) => !live.has(seq)));
|
|
299
|
+
}
|
|
300
|
+
const prefix = deriveAckPrefix(lease.messages);
|
|
301
|
+
if (prefix !== undefined && (ackedUpTo === undefined || prefix > ackedUpTo)) {
|
|
302
|
+
try {
|
|
303
|
+
const result = await mailbox.ack(scope, boxHandle, drainOwner, prefix);
|
|
304
|
+
if (result.acked) {
|
|
305
|
+
ackedUpTo = prefix;
|
|
306
|
+
const gone = [];
|
|
307
|
+
for (const seq of [...ledger.keys()]) {
|
|
308
|
+
if (seq > prefix)
|
|
309
|
+
continue;
|
|
310
|
+
gone.push(seq);
|
|
311
|
+
ledger.delete(seq);
|
|
312
|
+
}
|
|
313
|
+
held.forgetDecisions(gone);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
opts.onError?.(new Error(`the session box ${boxHandle} refused this leg's ack up to seq ${prefix}: the drain lease is no longer held by ${drainOwner} (it expired and another cycle claimed it). The records stay parked and the lease holder re-serves them.`), { phase: "degraded", sessionId, classification: "peer-box-ack" });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-ack" });
|
|
321
|
+
}
|
|
180
322
|
}
|
|
181
323
|
};
|
|
182
324
|
const settleNoticeRecord = (facts, m, meta) => {
|
|
@@ -185,7 +327,7 @@ export function createPeerSessionDrain(opts) {
|
|
|
185
327
|
const stateFits = notice !== undefined && (kind === "idle_notice" ? isPeerIdleNoticeKind(notice.state) : isPeerDeliveryReceiptState(notice.state));
|
|
186
328
|
if (!stateFits) {
|
|
187
329
|
disclose("notice_unrouted", `kind=${kind}${notice === undefined ? " without a typed notice" : ` with state=${inlineUntrusted(notice.state, 32)}`}`, m);
|
|
188
|
-
|
|
330
|
+
currentRound.dispose(m.seq, `notice_unrouted:${kind}`);
|
|
189
331
|
return;
|
|
190
332
|
}
|
|
191
333
|
let text;
|
|
@@ -210,15 +352,16 @@ export function createPeerSessionDrain(opts) {
|
|
|
210
352
|
disclose("notice_user_only", `${judged.cause}: ${text}`, m);
|
|
211
353
|
}
|
|
212
354
|
else {
|
|
213
|
-
|
|
355
|
+
currentRound.batch.push({ payload: buildNoticePayload(m, kind, text), priority: "next", urgent: false, seq: m.seq, attempt: 1 });
|
|
356
|
+
return;
|
|
214
357
|
}
|
|
215
|
-
|
|
358
|
+
currentRound.dispose(m.seq, judged.verdict === "refuse" ? "refused" : "notice_user_only");
|
|
216
359
|
};
|
|
217
360
|
const settleSubscription = (facts, m, meta) => {
|
|
218
361
|
const requester = meta.fromSession;
|
|
219
362
|
if (requester === undefined) {
|
|
220
363
|
disclose("notice_unrouted", "kind=idle_subscription without a reply address (fromSession)", m);
|
|
221
|
-
|
|
364
|
+
currentRound.dispose(m.seq, "subscription_unaddressed");
|
|
222
365
|
return;
|
|
223
366
|
}
|
|
224
367
|
const sub = { requester, ...(m.from !== undefined ? { requesterName: m.from } : {}), ...(meta.fromMode !== undefined ? { fromMode: meta.fromMode } : {}), selfSent: requester.toLowerCase() === sessionId.toLowerCase(), requestedAt: m.sentAt };
|
|
@@ -230,17 +373,67 @@ export function createPeerSessionDrain(opts) {
|
|
|
230
373
|
if (facts.setting.value === "refuse") {
|
|
231
374
|
void routePeerIdleNotice(route(), requester, { kind: "unavailable" });
|
|
232
375
|
announce("refused", idleMachine.target.size());
|
|
233
|
-
|
|
376
|
+
currentRound.dispose(m.seq, "subscription_refused");
|
|
234
377
|
return;
|
|
235
378
|
}
|
|
236
379
|
const outcome = idleMachine.target.subscribe(sub);
|
|
237
380
|
if (outcome === "full")
|
|
238
381
|
void routePeerIdleNotice(route(), requester, { kind: "unavailable" });
|
|
239
382
|
announce(outcome, idleMachine.target.size());
|
|
240
|
-
|
|
383
|
+
currentRound.dispose(m.seq, `subscription_${outcome}`);
|
|
384
|
+
};
|
|
385
|
+
const countWaiting = (lease) => {
|
|
386
|
+
if (lease === null)
|
|
387
|
+
return { pending: 0, urgent: 0 };
|
|
388
|
+
const waiting = lease.messages.filter((m) => {
|
|
389
|
+
if (ackedUpTo !== undefined && m.seq <= ackedUpTo)
|
|
390
|
+
return false;
|
|
391
|
+
const d = ledger.get(m.seq);
|
|
392
|
+
return d === undefined || d.kind === "invalidated";
|
|
393
|
+
});
|
|
394
|
+
return { pending: waiting.length, urgent: waiting.filter((m) => m.peerMeta?.urgent === true).length };
|
|
241
395
|
};
|
|
242
396
|
let warmedUp = opts.warmup === undefined;
|
|
243
|
-
|
|
397
|
+
class DrainRound {
|
|
398
|
+
limit;
|
|
399
|
+
out;
|
|
400
|
+
carrier;
|
|
401
|
+
charBudget;
|
|
402
|
+
injected = 0;
|
|
403
|
+
resupplied = 0;
|
|
404
|
+
dispose(seq, why) {
|
|
405
|
+
ledger.set(seq, { kind: "terminal", why });
|
|
406
|
+
}
|
|
407
|
+
batch = [];
|
|
408
|
+
chars = 0;
|
|
409
|
+
constructor(limit, out, carrier, charBudget) {
|
|
410
|
+
this.limit = limit;
|
|
411
|
+
this.out = out;
|
|
412
|
+
this.carrier = carrier;
|
|
413
|
+
this.charBudget = charBudget;
|
|
414
|
+
}
|
|
415
|
+
get spent() {
|
|
416
|
+
return this.injected + this.batch.length >= this.limit;
|
|
417
|
+
}
|
|
418
|
+
deliver(payload, priority) {
|
|
419
|
+
if (this.spent)
|
|
420
|
+
return "deferred";
|
|
421
|
+
if (this.out !== undefined) {
|
|
422
|
+
if (this.charBudget !== undefined) {
|
|
423
|
+
const size = renderTaskNotificationXml(payload).length;
|
|
424
|
+
if (this.out.length > 0 && this.chars + size > this.charBudget)
|
|
425
|
+
return "deferred";
|
|
426
|
+
this.chars += size;
|
|
427
|
+
}
|
|
428
|
+
this.out.push(payload);
|
|
429
|
+
}
|
|
430
|
+
else
|
|
431
|
+
opts.inject(payload, { priority });
|
|
432
|
+
this.injected += 1;
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const drainRound = async (round, pass) => {
|
|
244
437
|
if (!warmedUp) {
|
|
245
438
|
warmedUp = true;
|
|
246
439
|
try {
|
|
@@ -255,91 +448,185 @@ export function createPeerSessionDrain(opts) {
|
|
|
255
448
|
}
|
|
256
449
|
catch (e) {
|
|
257
450
|
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-claim" });
|
|
258
|
-
return;
|
|
451
|
+
return { pending: 0, urgent: 0 };
|
|
259
452
|
}
|
|
260
|
-
|
|
261
|
-
|
|
453
|
+
if (pass.settle)
|
|
454
|
+
settleLocally();
|
|
455
|
+
if (pass.settle && lease !== null)
|
|
456
|
+
await ackSettledPrefix(lease);
|
|
457
|
+
if (!pass.deliver)
|
|
458
|
+
return countWaiting(lease);
|
|
262
459
|
let facts;
|
|
263
460
|
let admissionConfig;
|
|
264
461
|
try {
|
|
265
462
|
facts = readFacts();
|
|
266
|
-
admissionConfig =
|
|
463
|
+
admissionConfig = resolvePeerLaneConfig(opts.lane, opts.onNotice);
|
|
267
464
|
}
|
|
268
465
|
catch (e) {
|
|
269
466
|
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-settings" });
|
|
270
|
-
return;
|
|
467
|
+
return { pending: 0, urgent: 0 };
|
|
271
468
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
469
|
+
currentRound = round;
|
|
470
|
+
try {
|
|
471
|
+
idleMachine.target.sweep();
|
|
472
|
+
if (lease === null)
|
|
473
|
+
return { pending: 0, urgent: 0 };
|
|
474
|
+
bindHeld(true);
|
|
475
|
+
reconcileHeld(lease);
|
|
476
|
+
held.rejudge();
|
|
477
|
+
try {
|
|
478
|
+
for (const m of lease.messages) {
|
|
479
|
+
const prior = ledger.get(m.seq);
|
|
480
|
+
if (prior === undefined || prior.kind !== "invalidated")
|
|
481
|
+
continue;
|
|
482
|
+
if (round.spent)
|
|
483
|
+
break;
|
|
484
|
+
const attempt = prior.attempt + 1;
|
|
485
|
+
round.batch.push({ payload: { ...prior.payload, attempt, resupplied: true }, priority: "next", urgent: false, seq: m.seq, attempt });
|
|
486
|
+
}
|
|
487
|
+
adoptHeldDecisions();
|
|
488
|
+
for (const m of lease.messages) {
|
|
489
|
+
if (ackedUpTo !== undefined && m.seq <= ackedUpTo)
|
|
490
|
+
continue;
|
|
491
|
+
if (ledger.has(m.seq))
|
|
492
|
+
continue;
|
|
493
|
+
if (round.spent)
|
|
289
494
|
break;
|
|
495
|
+
const meta = m.peerMeta;
|
|
496
|
+
if (m.hopChain !== undefined) {
|
|
497
|
+
const verdict = judgePeerAdmission(scope, recipientKey, { senderKey: meta?.senderKey, body: m.content, prospectiveChain: m.hopChain, ownTokens: opts.ownTokens(), seq: m.seq }, admissionConfig, undefined, "drain");
|
|
498
|
+
if (!verdict.ok) {
|
|
499
|
+
if (verdict.reason === "rate_limited") {
|
|
500
|
+
if (!disclosedHeld.has(m.seq)) {
|
|
501
|
+
disclosedHeld.add(m.seq);
|
|
502
|
+
disclose("held", verdict.reason, m, "it stays in the box; the inbound admission bucket is empty and it is re-judged at a later boundary once the bucket refills");
|
|
503
|
+
}
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
disclose("admission_refused", verdict.reason, m);
|
|
507
|
+
round.dispose(m.seq, `admission_refused:${verdict.reason}`);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
290
510
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
511
|
+
if (meta?.kind === "idle_subscription") {
|
|
512
|
+
settleSubscription(facts, m, meta);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (meta?.kind === "idle_notice" || meta?.kind === "delivery_notice") {
|
|
516
|
+
settleNoticeRecord(facts, m, meta);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (meta?.kind === "engine_frame") {
|
|
520
|
+
const decoded = decodeEngineFrame(m.content);
|
|
521
|
+
if (!decoded.ok && decoded.reason === "newer") {
|
|
522
|
+
opts.onError?.(new Error(`engine_frame record seq ${m.seq} in ${boxHandle} carries codec version ${decoded.codecVersion}; this engine reads up to ${ENGINE_FRAME_CODEC_VERSION} — the box stops at that record until an engine that reads it drains the session`), { phase: "degraded", sessionId, classification: "peer-box-engine-frame" });
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
if (!decoded.ok) {
|
|
526
|
+
disclose("notice_unrouted", `kind=engine_frame (${decoded.detail})`, m);
|
|
527
|
+
round.dispose(m.seq, `engine_frame_malformed:${decoded.detail}`);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
round.batch.push({ payload: decoded.payload, priority: decoded.priority, urgent: false, seq: m.seq, attempt: 1 });
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const sameTree = meta?.fromRoot !== undefined && meta.fromRoot.toLowerCase() === sessionId.toLowerCase();
|
|
534
|
+
if (!sameTree) {
|
|
535
|
+
const judged = judgeMeta(facts, meta);
|
|
536
|
+
if (judged.verdict === "refuse") {
|
|
537
|
+
disclose("refused", judged.cause, m);
|
|
538
|
+
round.dispose(m.seq, `refused:${judged.cause}`);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (judged.verdict === "hold") {
|
|
542
|
+
held.hold({ seq: m.seq, box: boxHandle, content: m.content, ...(m.from !== undefined ? { from: m.from } : {}), sentAt: m.sentAt, ...(m.hopChain !== undefined ? { hopChain: [...m.hopChain] } : {}), ...(meta !== undefined ? { peerMeta: { ...meta } } : {}), cause: judged.cause });
|
|
543
|
+
if (!ledger.has(m.seq))
|
|
544
|
+
ledger.set(m.seq, { kind: "held" });
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
let payload;
|
|
549
|
+
try {
|
|
550
|
+
payload = buildPeerMessagePayload(m);
|
|
551
|
+
}
|
|
552
|
+
catch (e) {
|
|
553
|
+
disclose("refused", `record fields cannot be rendered canonically (${e instanceof CrossSessionCodecError ? e.code : thrownText(e)})`, m);
|
|
554
|
+
round.dispose(m.seq, "uncanonical_fields");
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
round.batch.push({ payload, priority: "next", urgent: meta?.urgent === true, seq: m.seq, attempt: 1 });
|
|
294
558
|
}
|
|
295
559
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
560
|
+
finally {
|
|
561
|
+
const ordered = round.batch.splice(0).sort((a, b) => Number(b.urgent) - Number(a.urgent));
|
|
562
|
+
for (const item of ordered) {
|
|
563
|
+
try {
|
|
564
|
+
if (round.deliver(item.payload, item.priority) === "deferred")
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-inject" });
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
noteDelivered(item.seq, item.attempt, item.payload, round.carrier);
|
|
572
|
+
if (item.attempt > 1)
|
|
573
|
+
round.resupplied += 1;
|
|
574
|
+
}
|
|
303
575
|
}
|
|
304
|
-
const
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
576
|
+
const counts = countWaiting(lease);
|
|
577
|
+
if (counts.pending > 0 && round.out === undefined) {
|
|
578
|
+
noticeRound += 1;
|
|
579
|
+
try {
|
|
580
|
+
opts.inject({
|
|
581
|
+
task_id: `inbox:${boxHandle}`,
|
|
582
|
+
task_type: "background_agent",
|
|
583
|
+
status: "event",
|
|
584
|
+
seq: noticeRound,
|
|
585
|
+
summary: `inbox notice: ${counts.pending} cross-session message(s) waiting`,
|
|
586
|
+
inboxNotice: { pending: counts.pending, delivered: round.injected - round.resupplied, ...(round.resupplied > 0 ? { resupplied: round.resupplied } : {}), ...(counts.urgent > 0 ? { urgent: counts.urgent } : {}), box: boxHandle },
|
|
587
|
+
_sema_provenance: { kind: "inbox_notice", from: boxHandle, taskId: `inbox:${boxHandle}`, seq: noticeRound },
|
|
588
|
+
}, { priority: "next" });
|
|
311
589
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
settledUpTo = m.seq;
|
|
315
|
-
continue;
|
|
590
|
+
catch (e) {
|
|
591
|
+
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-inject" });
|
|
316
592
|
}
|
|
317
593
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
catch (e) {
|
|
323
|
-
disclose("refused", `record fields cannot be rendered canonically (${e instanceof CrossSessionCodecError ? e.code : thrownText(e)})`, m);
|
|
324
|
-
settledUpTo = m.seq;
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
opts.inject(payload, { priority: "next" });
|
|
329
|
-
}
|
|
330
|
-
catch (e) {
|
|
331
|
-
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-inject" });
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
settledUpTo = m.seq;
|
|
594
|
+
return counts;
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
currentRound = undefined;
|
|
335
598
|
}
|
|
336
599
|
};
|
|
337
|
-
const
|
|
338
|
-
|
|
600
|
+
const boundPass = (pass) => async () => {
|
|
601
|
+
await drainRound(new DrainRound(resolvePeerLaneConfig(opts.lane, opts.onNotice).maxMessagesPerRound, undefined), pass);
|
|
602
|
+
};
|
|
603
|
+
const serialize = (once) => {
|
|
604
|
+
inFlight = inFlight.then(once, once);
|
|
339
605
|
return inFlight;
|
|
340
606
|
};
|
|
607
|
+
const drain = () => serialize(boundPass({ settle: true, deliver: true }));
|
|
608
|
+
const boundaryRound = (o) => serialize(boundPass({ settle: true, deliver: o.continues }));
|
|
609
|
+
const pull = (limit, toolCallId, charBudget) => {
|
|
610
|
+
const out = [];
|
|
611
|
+
const bound = limit !== undefined && Number.isInteger(limit) && limit > 0 ? limit : resolvePeerLaneConfig(opts.lane, opts.onNotice).maxMessagesPerRound;
|
|
612
|
+
let counts = { pending: 0, urgent: 0 };
|
|
613
|
+
const once = async () => {
|
|
614
|
+
counts = await drainRound(new DrainRound(bound, out, toolCallId !== undefined ? toolResultReceiptKey(toolCallId) : undefined, charBudget), { settle: false, deliver: true });
|
|
615
|
+
};
|
|
616
|
+
const settled = inFlight.then(once, once);
|
|
617
|
+
inFlight = settled;
|
|
618
|
+
return settled.then(() => ({ delivered: out, pending: counts.pending, urgent: counts.urgent }));
|
|
619
|
+
};
|
|
341
620
|
const finishOnce = async () => {
|
|
342
|
-
|
|
621
|
+
try {
|
|
622
|
+
const lease = await mailbox.claimLease(scope, boxHandle, drainOwner, ttl);
|
|
623
|
+
settleLocally();
|
|
624
|
+
if (lease !== null)
|
|
625
|
+
await ackSettledPrefix(lease);
|
|
626
|
+
}
|
|
627
|
+
catch (e) {
|
|
628
|
+
opts.onError?.(e, { phase: "degraded", sessionId, classification: "peer-box-claim" });
|
|
629
|
+
}
|
|
343
630
|
held.detachLeg();
|
|
344
631
|
try {
|
|
345
632
|
await mailbox.releaseLease(scope, boxHandle, drainOwner);
|
|
@@ -353,7 +640,20 @@ export function createPeerSessionDrain(opts) {
|
|
|
353
640
|
};
|
|
354
641
|
return {
|
|
355
642
|
drain,
|
|
643
|
+
boundaryRound,
|
|
644
|
+
pull,
|
|
645
|
+
isBoxBacked: (payload) => {
|
|
646
|
+
const p = payload;
|
|
647
|
+
if (p === null || typeof p !== "object" || typeof p.task_id !== "string" || typeof p.status !== "string")
|
|
648
|
+
return false;
|
|
649
|
+
const key = taskNotificationDedupKey(p);
|
|
650
|
+
for (const d of ledger.values())
|
|
651
|
+
if (d.kind === "delivered" && d.key === key)
|
|
652
|
+
return true;
|
|
653
|
+
return false;
|
|
654
|
+
},
|
|
356
655
|
finish,
|
|
656
|
+
wantsCompactionEffects,
|
|
357
657
|
busy: () => idleMachine.target.busy(),
|
|
358
658
|
idle: (finishedAt) => idleMachine.target.idle(finishedAt),
|
|
359
659
|
held,
|
|
@@ -396,7 +696,8 @@ export function mountListAgents(args) {
|
|
|
396
696
|
args.toolEffects.set(LIST_AGENTS_TOOL_NAME, "read");
|
|
397
697
|
args.toolEffects.set(LIST_AGENTS_TOOL_ALIAS, "read");
|
|
398
698
|
return createListAgentsTool({
|
|
399
|
-
peerDirectory: args.peerDirectory,
|
|
699
|
+
...(args.peerDirectory !== undefined ? { peerDirectory: args.peerDirectory } : {}),
|
|
700
|
+
...(args.freshWindowMs !== undefined ? { freshWindowMs: args.freshWindowMs } : {}),
|
|
400
701
|
self: { sessionId: args.sessionId, scope: args.scope },
|
|
401
702
|
registry: args.registry,
|
|
402
703
|
access: { owner: args.hostTaskId, scope: args.scope, sessionId: args.sessionId },
|
|
@@ -414,7 +715,6 @@ export function bindPeerSessionDrain(harness, opts) {
|
|
|
414
715
|
drain.busy();
|
|
415
716
|
return round();
|
|
416
717
|
});
|
|
417
|
-
harness.on("turn_boundary", round);
|
|
418
718
|
harness.subscribe((event) => {
|
|
419
719
|
if (event.type === "agent_end")
|
|
420
720
|
return drain.finish().catch(() => undefined);
|
|
@@ -448,22 +748,29 @@ export function bindPeerLaneDrain(harness, args) {
|
|
|
448
748
|
rowName = undefined;
|
|
449
749
|
}
|
|
450
750
|
};
|
|
451
|
-
|
|
751
|
+
const drain = bindPeerSessionDrain(harness, {
|
|
452
752
|
mailbox: deps.mailboxStore,
|
|
453
753
|
sessionId: args.sessionId,
|
|
454
754
|
runId: args.runId,
|
|
455
755
|
scope: args.scope,
|
|
456
756
|
inject: args.inject,
|
|
457
|
-
...(deps.
|
|
757
|
+
...(deps.peerLane !== undefined ? { lane: deps.peerLane } : {}),
|
|
458
758
|
ownTokens: args.ownTokens,
|
|
459
759
|
settingLayers: () => (typeof deps.crossSessionInbound === "function" ? deps.crossSessionInbound() : deps.crossSessionInbound),
|
|
460
760
|
selfModeClass: () => (args.refs.askEffective !== undefined ? foldPermissionModeClass(args.refs.askEffective) : "unknown"),
|
|
461
761
|
dialogExpiry: () => (typeof deps.crossSessionDialogExpiry === "function" ? deps.crossSessionDialogExpiry() : deps.crossSessionDialogExpiry),
|
|
462
762
|
selfName: () => rowName ?? args.selfName?.(),
|
|
463
763
|
warmup: lookupRowName,
|
|
764
|
+
...(args.settlement !== undefined ? { settlement: args.settlement } : {}),
|
|
464
765
|
...(args.parked !== undefined ? { parked: args.parked } : {}),
|
|
465
766
|
...(args.lastTurnText !== undefined ? { lastTurnText: args.lastTurnText } : {}),
|
|
466
767
|
...(deps.onNotice !== undefined ? { onNotice: deps.onNotice } : {}),
|
|
467
768
|
...(deps.onError !== undefined ? { onError: deps.onError } : {}),
|
|
468
769
|
});
|
|
770
|
+
args.refs.pull = (limit, toolCallId, charBudget) => drain.pull(limit, toolCallId, charBudget);
|
|
771
|
+
args.refs.boundaryRound = (o) => drain.boundaryRound(o);
|
|
772
|
+
args.refs.isBoxBacked = (payload) => drain.isBoxBacked(payload);
|
|
773
|
+
if (args.settlement !== undefined)
|
|
774
|
+
args.settlement.wantsEffects = () => drain.wantsCompactionEffects();
|
|
775
|
+
return drain;
|
|
469
776
|
}
|