@staix/agent-hub 0.30.0 → 0.32.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 +52 -0
- package/README.md +13 -7
- package/docs/agent-notes/README.md +1 -0
- package/docs/agent-notes/adapters.md +5 -2
- package/docs/agent-notes/budget.md +1 -0
- package/docs/agent-notes/bus.md +11 -4
- package/docs/agent-notes/daemon.md +16 -8
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +7 -3
- package/docs/agent-notes/tests.md +6 -4
- package/docs/design/adapters.md +52 -0
- package/docs/design/agent-hub-architecture.md +46 -0
- package/docs/design/bus.md +178 -1
- package/docs/design/task-board.md +82 -0
- package/docs/dogfood.md +23 -4
- package/docs/events.md +3 -0
- package/docs/memory.md +17 -1
- package/docs/operations.md +355 -78
- package/docs/profiles.md +35 -0
- package/docs/quickstart.md +24 -5
- package/docs/security.md +178 -60
- package/docs/specs/2026-09-19-agent-hub-design.md +127 -14
- package/docs/specs/2026-09-19-multi-project-design.md +7 -2
- package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
- package/docs/verification/2026-11-05-0.31.0.md +182 -0
- package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
- package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +311 -0
- package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
- package/docs/verification/2026-11-06-0.32.0.md +149 -0
- package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
- package/docs/verification/claude-shell-wrapper-native.py +1 -1
- package/docs/verified.json +14 -14
- package/package.json +3 -4
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +4393 -3609
- package/src/adapters/acp.ts +5 -4
- package/src/adapters/channel-peer.ts +72 -3
- package/src/adapters/claude-channel-evidence.ts +52 -0
- package/src/adapters/claude-channel.ts +61 -18
- package/src/adapters/claude-implementer.ts +29 -7
- package/src/adapters/claude-print-child.ts +12 -2
- package/src/adapters/codex-appserver.ts +13 -12
- package/src/adapters/compat.ts +3 -3
- package/src/adapters/definitions/builtin-metadata.ts +13 -13
- package/src/adapters/definitions/qwen.ts +1 -0
- package/src/adapters/local-worker.ts +61 -18
- package/src/adapters/pi.ts +48 -6
- package/src/adapters/registry-types.ts +12 -0
- package/src/adapters/registry.ts +2 -0
- package/src/adapters/reviewer-print.ts +50 -11
- package/src/broker/server.ts +44 -13
- package/src/broker/store.ts +38 -8
- package/src/cli/answer-args.ts +50 -0
- package/src/cli/attention.ts +17 -2
- package/src/cli/backlog-return-args.ts +37 -0
- package/src/cli/console-question.ts +110 -0
- package/src/cli/console-state.ts +78 -17
- package/src/cli/console.ts +54 -1
- package/src/cli/handoff-args.ts +15 -5
- package/src/cli/handoff-output.ts +15 -2
- package/src/cli/help.ts +8 -5
- package/src/cli/identity.ts +13 -3
- package/src/cli/main.ts +128 -16
- package/src/cli/memory.ts +107 -3
- package/src/cli/output.ts +29 -6
- package/src/cli/pet.ts +4 -4
- package/src/cli/results-args.ts +30 -0
- package/src/cli/room.ts +20 -10
- package/src/cli/runtime-version-warning.ts +50 -0
- package/src/cli/status-lines.ts +41 -1
- package/src/cli/tail-render.ts +1 -0
- package/src/cli/upgrade-interactive.ts +14 -2
- package/src/hub/action-class.ts +9 -1
- package/src/hub/board.ts +369 -18
- package/src/hub/bus.ts +511 -53
- package/src/hub/completion-detail.ts +50 -0
- package/src/hub/completions-store.ts +168 -0
- package/src/hub/completions.ts +120 -0
- package/src/hub/config-apply.ts +7 -5
- package/src/hub/config-reload.ts +10 -1
- package/src/hub/control-client.ts +40 -4
- package/src/hub/daemon.ts +1576 -139
- package/src/hub/delivery-failure.ts +14 -0
- package/src/hub/delivery-hints.ts +14 -4
- package/src/hub/delivery-journal.ts +751 -42
- package/src/hub/delivery-superseded.ts +4 -1
- package/src/hub/envelope.ts +3 -3
- package/src/hub/events.ts +6 -0
- package/src/hub/handoff.ts +145 -6
- package/src/hub/hub-tools.ts +56 -7
- package/src/hub/manager.ts +59 -3
- package/src/hub/memory-policy.ts +44 -1
- package/src/hub/memory-sync-policy.ts +109 -0
- package/src/hub/memory-sync-state.ts +145 -0
- package/src/hub/no-writes.ts +1 -1
- package/src/hub/operational-settings.ts +11 -2
- package/src/hub/operational-view.ts +12 -1
- package/src/hub/peers.ts +25 -2
- package/src/hub/planning.ts +6 -1
- package/src/hub/question-desk.ts +201 -0
- package/src/hub/question-detach.ts +6 -0
- package/src/hub/questions.ts +260 -0
- package/src/hub/research-evidence.ts +106 -0
- package/src/hub/restart.ts +14 -0
- package/src/hub/review-anchor.ts +181 -0
- package/src/hub/review-brief.ts +13 -0
- package/src/hub/review-comments.ts +115 -0
- package/src/hub/review-findings.ts +7 -2
- package/src/hub/review-material.ts +76 -0
- package/src/hub/routing.ts +3 -1
- package/src/hub/task-sweep.ts +2 -0
- package/src/hub/tasks.ts +577 -63
- package/src/memory/capture.ts +5 -2
- package/src/pet/config.ts +25 -13
- package/src/rooms/commands.ts +61 -16
- package/src/rooms/link.ts +338 -36
- package/src/rooms/liveness.ts +11 -0
- package/src/rooms/remote-peer.ts +186 -19
- package/src/rooms/service.ts +475 -35
- package/src/rooms/status.ts +8 -1
- package/src/rooms/store.ts +447 -65
- package/src/rooms/wire.ts +24 -6
- package/src/ui/index.html +142 -9
- package/src/ui/needs.ts +10 -4
- package/src/ui/plugin-questions.ts +56 -0
- package/src/ui/plugin-results.ts +81 -0
- package/src/ui/plugin.ts +207 -27
- package/src/pet/window.html +0 -114
- package/src/pet/window.jxa.js +0 -122
- package/src/pet/window.ts +0 -154
package/src/rooms/remote-peer.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/** A host-owned room transport, never a task owner, reviewer, tool caller or native runtime. */
|
|
2
2
|
import { BasePeer, type DeliveryReceipt } from "../hub/peers.ts";
|
|
3
3
|
import type { Envelope, PeerState } from "../hub/envelope.ts";
|
|
4
|
-
import { parseRemotePeerId, type RoomChatEnvelope } from "./application.ts";
|
|
4
|
+
import { encodeRoomMessage, parseRemotePeerId, type RoomChatEnvelope } from "./application.ts";
|
|
5
5
|
import { readRoomOutbound, roomBoundary, type RoomBoundaryContext } from "./boundary.ts";
|
|
6
|
+
import { RoomNotSentError } from "./link.ts";
|
|
6
7
|
|
|
7
8
|
export interface RemotePeerPorts {
|
|
8
9
|
member: string;
|
|
9
10
|
context: (env: Envelope) => RoomBoundaryContext;
|
|
10
11
|
send: (env: RoomChatEnvelope, deliveryId: string, expiresAt: number, current: () => boolean, withheld: boolean) => Promise<void>;
|
|
11
12
|
now?: () => number;
|
|
13
|
+
/** Current Bus admission, separate from immutable room trust/exposure proof. */
|
|
14
|
+
ready?: () => boolean;
|
|
12
15
|
/** Record post-publication trust loss locally before any late signed outcome can settle it. */
|
|
13
|
-
quarantine?: (deliveryId: string) => void;
|
|
16
|
+
quarantine?: (deliveryId: string, reason?: "not_exposed" | "boundary_refused" | "invalid_message") => void;
|
|
17
|
+
/** Requeue a send that the room link proves never reached its durable outbox. */
|
|
18
|
+
notSent?: (deliveryId: string) => boolean;
|
|
19
|
+
/** True for any durable RoomStore row with this Bus delivery ID; false only proves absence. */
|
|
20
|
+
queued?: (deliveryId: string) => boolean | undefined;
|
|
21
|
+
/** Persist a conservative Bus hold when neither requeue nor durable Store retry is available. */
|
|
22
|
+
holdMissing?: (deliveryId: string) => boolean;
|
|
23
|
+
/** Wake host retry scheduling after an active intent meets an external Bus fence. */
|
|
24
|
+
waiting?: () => void;
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
export class RemotePeer extends BasePeer {
|
|
@@ -20,8 +31,11 @@ export class RemotePeer extends BasePeer {
|
|
|
20
31
|
private generation = 0;
|
|
21
32
|
private running = false;
|
|
22
33
|
private online = false;
|
|
34
|
+
private blockedNotSent = false;
|
|
23
35
|
private observed: PeerState = "offline";
|
|
24
36
|
private readonly pending = new Set<string>();
|
|
37
|
+
private readonly waitingForReady = new Set<string>();
|
|
38
|
+
private readonly sending = new Set<string>();
|
|
25
39
|
private readonly deadlines = new Map<string, number>();
|
|
26
40
|
constructor(id: string, readonly remoteRoom: string, readonly remoteQueueTtlMs: number, private readonly ports: RemotePeerPorts) {
|
|
27
41
|
super(id);
|
|
@@ -31,50 +45,203 @@ export class RemotePeer extends BasePeer {
|
|
|
31
45
|
}
|
|
32
46
|
// Room ack/TTL deadlines, not the native inactivity watchdog, own transport settlement.
|
|
33
47
|
protected override touch(): void { this.clearWatchdog(); }
|
|
34
|
-
private update(): void { this.setState(this.pending.size ? "busy" : this.running && this.online ? this.observed : "offline"); }
|
|
48
|
+
private update(): void { this.setState(this.pending.size ? "busy" : this.running && this.online && !this.blockedNotSent ? this.observed : "offline"); }
|
|
49
|
+
private head(): string | undefined { return this.pending.values().next().value as string | undefined; }
|
|
50
|
+
/** Reserve recovered Store intents synchronously before any proof decrypt/screen await. */
|
|
51
|
+
reservePending(deliveryId: string): void {
|
|
52
|
+
this.pending.add(deliveryId);
|
|
53
|
+
this.waitingForReady.add(deliveryId);
|
|
54
|
+
this.blockedNotSent = false;
|
|
55
|
+
this.update();
|
|
56
|
+
}
|
|
35
57
|
async start(): Promise<void> { this.running = true; this.generation++; this.update(); }
|
|
36
58
|
async stop(): Promise<void> {
|
|
37
59
|
this.running = false; this.generation++;
|
|
38
|
-
|
|
60
|
+
// Active journal rows and their sealed room frames survive a sender restart.
|
|
61
|
+
// Stop only fences in-flight callbacks; it is not evidence of an uncertain send.
|
|
62
|
+
this.pending.clear();
|
|
63
|
+
this.waitingForReady.clear();
|
|
39
64
|
this.deadlines.clear(); this.update();
|
|
40
65
|
}
|
|
41
|
-
private quarantine(id: string): void { try { this.ports.quarantine?.(id); } catch { /*
|
|
42
|
-
|
|
66
|
+
private quarantine(id: string, reason?: "not_exposed" | "boundary_refused" | "invalid_message"): void { try { this.ports.quarantine?.(id, reason); } catch { /* The durable application remains for TTL review if local storage is unavailable. */ } }
|
|
67
|
+
private ready(): boolean { try { return this.ports.ready?.() !== false; } catch { return false; } }
|
|
68
|
+
waitForReady(deliveryId: string): void {
|
|
69
|
+
this.waitingForReady.add(deliveryId);
|
|
70
|
+
this.pending.add(deliveryId);
|
|
71
|
+
this.blockedNotSent = false;
|
|
72
|
+
this.update();
|
|
73
|
+
try { this.ports.waiting?.(); } catch { /* A future real Bus callback will retry. */ }
|
|
74
|
+
}
|
|
75
|
+
/** Consume an immediate resume token while retaining the active FIFO head. */
|
|
76
|
+
deferResume(deliveryId: string): void { this.waitingForReady.delete(deliveryId); }
|
|
77
|
+
readyToResume(deliveryId: string): boolean {
|
|
78
|
+
return this.waitingForReady.has(deliveryId) && this.head() === deliveryId && this.running && this.online && !this.blockedNotSent && this.ready();
|
|
79
|
+
}
|
|
80
|
+
isPendingHead(deliveryId: string): boolean { return this.head() === deliveryId; }
|
|
81
|
+
private holdMissing(id: string): boolean { try { return this.ports.holdMissing?.(id) === true; } catch { return false; } }
|
|
82
|
+
private notSent(id: string, blockUntilWake = true): void {
|
|
83
|
+
let retained: boolean | undefined;
|
|
84
|
+
try { retained = this.ports.queued?.(id); } catch { retained = undefined; }
|
|
85
|
+
if (retained === true) {
|
|
86
|
+
// A prior attempt is already durable. Never create a second Bus delivery ID.
|
|
87
|
+
if (!blockUntilWake || !this.ready()) this.waitForReady(id);
|
|
88
|
+
else { this.blockedNotSent = true; this.update(); }
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (retained === undefined) {
|
|
92
|
+
// Store read failure cannot prove not-sent, so preserve the active RAM fence.
|
|
93
|
+
this.blockedNotSent = true; this.update(); return;
|
|
94
|
+
}
|
|
95
|
+
let requeued = false;
|
|
96
|
+
try { requeued = this.ports.notSent?.(id) === true; } catch { /* Keep uncertainty handling on the state transition below. */ }
|
|
97
|
+
// An expired requeue may have completed the Bus row synchronously. In that
|
|
98
|
+
// case its receipt already removed this ID and drained the following FIFO item.
|
|
99
|
+
if (!this.pending.has(id)) { this.update(); return; }
|
|
100
|
+
if (requeued) {
|
|
101
|
+
this.waitingForReady.delete(id);
|
|
102
|
+
this.blockedNotSent = blockUntilWake;
|
|
103
|
+
this.pending.delete(id);
|
|
104
|
+
this.update();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const held = retained === false && this.holdMissing(id);
|
|
108
|
+
if (held) {
|
|
109
|
+
this.waitingForReady.delete(id);
|
|
110
|
+
this.blockedNotSent = false;
|
|
111
|
+
this.pending.delete(id);
|
|
112
|
+
} else this.blockedNotSent = true; // Keep the ID busy if the journal write failed or the actor went stale.
|
|
113
|
+
this.update();
|
|
114
|
+
}
|
|
115
|
+
presence(state: PeerState, online: boolean): void {
|
|
116
|
+
if (online && (!this.online || state !== this.observed)) this.blockedNotSent = false;
|
|
117
|
+
this.observed = state; this.online = online; this.update();
|
|
118
|
+
}
|
|
119
|
+
/** Rehydrate a journal-owned delivery without creating a new bus delivery or application id. */
|
|
120
|
+
async resume(env: Envelope, deliveryId: string, expiresAt: number, immutable: RoomChatEnvelope): Promise<void> {
|
|
121
|
+
if (!this.running || !this.online || !this.ready() || this.blockedNotSent || !deliveryId || this.head() !== deliveryId || !Number.isSafeInteger(expiresAt) || expiresAt <= (this.ports.now?.() ?? Date.now())) return;
|
|
122
|
+
this.waitingForReady.delete(deliveryId);
|
|
123
|
+
this.pending.add(deliveryId); this.update();
|
|
124
|
+
await this.publish(env, deliveryId, expiresAt, this.generation, immutable);
|
|
125
|
+
}
|
|
126
|
+
/** A link becoming ready is an actual wake-up after a typed not-sent refusal. */
|
|
127
|
+
wake(): void { this.blockedNotSent = false; this.update(); }
|
|
43
128
|
setRemoteDeadline(envelopeId: string, expiresAt: number): void {
|
|
44
129
|
if (Number.isSafeInteger(expiresAt) && expiresAt > 0 && this.deadlines.size < 200) this.deadlines.set(envelopeId, expiresAt);
|
|
45
130
|
}
|
|
46
131
|
receipt(receipt: DeliveryReceipt): boolean {
|
|
47
132
|
if (!this.pending.has(receipt.id)) return false;
|
|
48
|
-
if (receipt.state !== "accepted") this.pending.delete(receipt.id);
|
|
133
|
+
if (receipt.state !== "accepted") { this.pending.delete(receipt.id); this.waitingForReady.delete(receipt.id); }
|
|
49
134
|
this.delivery(receipt); // Journal settlement precedes a state transition that may drain the next item.
|
|
50
135
|
this.update();
|
|
51
136
|
return true;
|
|
52
137
|
}
|
|
138
|
+
/** Clear transport wait after the Bus records a signed outcome or durable hold. */
|
|
139
|
+
settled(deliveryId: string): void { this.pending.delete(deliveryId); this.waitingForReady.delete(deliveryId); this.update(); }
|
|
140
|
+
/** Reserve a restart-era active Store delivery before asynchronous proof validation. */
|
|
141
|
+
retainPending(deliveryId: string): void {
|
|
142
|
+
if (this.pending.has(deliveryId)) return;
|
|
143
|
+
this.pending.add(deliveryId);
|
|
144
|
+
this.waitingForReady.add(deliveryId);
|
|
145
|
+
this.update();
|
|
146
|
+
}
|
|
53
147
|
async deliver(envs: Envelope[], deliveryId?: string): Promise<void> {
|
|
54
|
-
if (!
|
|
148
|
+
if (!deliveryId || envs.length !== 1) throw new Error("room: unavailable");
|
|
149
|
+
if (!this.running || !this.online) { this.notSent(deliveryId); return; }
|
|
150
|
+
if (!this.ready()) { this.notSent(deliveryId, false); return; }
|
|
55
151
|
const env = envs[0]!, generation = this.generation;
|
|
56
152
|
const expiresAt = this.deadlines.get(env.id);
|
|
57
153
|
this.deadlines.delete(env.id);
|
|
58
|
-
|
|
59
|
-
|
|
154
|
+
if (expiresAt === undefined) throw new Error("room: queued deadline unavailable");
|
|
155
|
+
this.pending.add(deliveryId); this.update();
|
|
156
|
+
await this.publish(env, deliveryId, expiresAt, generation);
|
|
157
|
+
}
|
|
158
|
+
private async publish(env: Envelope, deliveryId: string, expiresAt: number, generation: number, immutable?: RoomChatEnvelope): Promise<void> {
|
|
159
|
+
if (this.sending.has(deliveryId)) return;
|
|
160
|
+
this.sending.add(deliveryId);
|
|
161
|
+
try { await this.publishOnce(env, deliveryId, expiresAt, generation, immutable); }
|
|
162
|
+
finally { this.sending.delete(deliveryId); }
|
|
163
|
+
}
|
|
164
|
+
private async publishOnce(env: Envelope, deliveryId: string, expiresAt: number, generation: number, immutable?: RoomChatEnvelope): Promise<void> {
|
|
165
|
+
let context: RoomBoundaryContext;
|
|
166
|
+
try { context = this.ports.context(env); }
|
|
167
|
+
catch { this.notSent(deliveryId); return; }
|
|
168
|
+
const trusted = () => { try { return context.current() === true; } catch { return false; } };
|
|
169
|
+
let readinessLost = false;
|
|
170
|
+
const current = () => {
|
|
171
|
+
const ready = this.ready();
|
|
172
|
+
if (!ready) readinessLost = true;
|
|
173
|
+
return this.pending.has(deliveryId) && this.running && this.online && this.generation === generation && ready && trusted();
|
|
174
|
+
};
|
|
60
175
|
const result = await roomBoundary(env, { ...context, current });
|
|
61
|
-
if (!
|
|
176
|
+
if (!this.pending.has(deliveryId)) return;
|
|
177
|
+
if (!current()) {
|
|
178
|
+
if (!trusted()) { this.quarantine(deliveryId, "boundary_refused"); this.receipt({ id: deliveryId, state: "needs_review", reason: "room trust changed before publication" }); }
|
|
179
|
+
else if (!this.running || !this.online || generation !== this.generation) this.notSent(deliveryId);
|
|
180
|
+
else if (!this.ready()) this.notSent(deliveryId, false);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (readinessLost && result.status === "refused" && trusted()) { this.notSent(deliveryId, false); return; }
|
|
62
184
|
if (result.status === "refused") {
|
|
63
|
-
|
|
185
|
+
const reason = result.code === "not_exposed" || result.code === "invalid_message" ? result.code : "boundary_refused";
|
|
186
|
+
if (immutable) {
|
|
187
|
+
this.quarantine(deliveryId, reason);
|
|
188
|
+
this.receipt({ id: deliveryId, state: "needs_review", reason: `room retry refused: ${result.code}` });
|
|
189
|
+
} else this.receipt({ id: deliveryId, state: "failed_safe", reason: `room: ${result.code}` });
|
|
64
190
|
return;
|
|
65
191
|
}
|
|
66
192
|
const view = readRoomOutbound(result.proof);
|
|
67
|
-
if (!view || expiresAt
|
|
68
|
-
this.
|
|
193
|
+
if (!view || expiresAt <= (this.ports.now?.() ?? Date.now())) {
|
|
194
|
+
if (immutable) this.quarantine(deliveryId, "boundary_refused");
|
|
195
|
+
this.receipt({ id: deliveryId, state: "needs_review", reason: "room queued deadline unavailable or expired" });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (immutable && Buffer.compare(Buffer.from(encodeRoomMessage({ v: 1, type: "chat", envelope: view })), Buffer.from(encodeRoomMessage({ v: 1, type: "chat", envelope: immutable }))) !== 0) {
|
|
199
|
+
this.quarantine(deliveryId, "boundary_refused");
|
|
200
|
+
this.receipt({ id: deliveryId, state: "needs_review", reason: "room retry privacy proof changed from immutable application" });
|
|
69
201
|
return;
|
|
70
202
|
}
|
|
71
|
-
this.pending.add(deliveryId); this.update();
|
|
72
203
|
try {
|
|
73
204
|
await this.ports.send(view, deliveryId, expiresAt, () => current() && readRoomOutbound(result.proof) !== undefined, result.withheld);
|
|
74
|
-
if (!
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
205
|
+
if (!this.pending.has(deliveryId)) return;
|
|
206
|
+
if (!trusted() && this.running && this.online && generation === this.generation) {
|
|
207
|
+
this.quarantine(deliveryId, "boundary_refused"); this.receipt({ id: deliveryId, state: "needs_review", reason: "room trust changed during publication" });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!this.ready()) {
|
|
211
|
+
let retained: boolean | undefined;
|
|
212
|
+
try { retained = this.ports.queued?.(deliveryId); } catch { retained = undefined; }
|
|
213
|
+
if (immutable || retained === true) this.waitForReady(deliveryId);
|
|
214
|
+
else this.notSent(deliveryId, false);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (!this.pending.has(deliveryId)) return;
|
|
219
|
+
if (error instanceof RoomNotSentError) {
|
|
220
|
+
let retained: boolean | undefined;
|
|
221
|
+
try { retained = this.ports.queued?.(deliveryId); } catch { retained = undefined; }
|
|
222
|
+
if (immutable || retained === true) {
|
|
223
|
+
if (!this.ready()) this.waitForReady(deliveryId);
|
|
224
|
+
} else this.notSent(deliveryId);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!trusted()) {
|
|
228
|
+
this.quarantine(deliveryId, "boundary_refused");
|
|
229
|
+
this.receipt({ id: deliveryId, state: "needs_review", reason: "room trust changed during publication" });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!this.running || !this.online || generation !== this.generation) return;
|
|
233
|
+
let retained: boolean | undefined;
|
|
234
|
+
try { retained = this.ports.queued?.(deliveryId); } catch { retained = undefined; }
|
|
235
|
+
if (!this.ready()) {
|
|
236
|
+
if (immutable || retained === true) this.waitForReady(deliveryId);
|
|
237
|
+
else this.notSent(deliveryId, false);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (retained !== true) {
|
|
241
|
+
if (retained === false && this.holdMissing(deliveryId)) {
|
|
242
|
+
this.pending.delete(deliveryId); this.blockedNotSent = false; this.update();
|
|
243
|
+
} else { this.blockedNotSent = true; this.update(); }
|
|
244
|
+
} // A durable intent stays active and retries on its next ACK deadline.
|
|
78
245
|
}
|
|
79
246
|
}
|
|
80
247
|
}
|