@parall/agent-core 1.44.0 → 1.46.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/dist/channel-capability.d.ts +2 -0
- package/dist/channel-capability.d.ts.map +1 -1
- package/dist/channel-capability.js +15 -0
- package/dist/dispatch-adapter.d.ts +9 -0
- package/dist/dispatch-adapter.d.ts.map +1 -1
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +27 -7
- package/dist/fork-session-finalizer.d.ts +65 -0
- package/dist/fork-session-finalizer.d.ts.map +1 -0
- package/dist/fork-session-finalizer.js +70 -0
- package/dist/gateway-base.d.ts +55 -0
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +640 -263
- package/dist/gateway-lane-flow.d.ts +75 -5
- package/dist/gateway-lane-flow.d.ts.map +1 -1
- package/dist/gateway-lane-flow.js +240 -18
- package/dist/http-keepalive.d.ts +4 -0
- package/dist/http-keepalive.d.ts.map +1 -0
- package/dist/http-keepalive.js +33 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/lane-ledger.d.ts +8 -0
- package/dist/lane-ledger.d.ts.map +1 -1
- package/dist/lane-ledger.js +14 -0
- package/dist/session-lifecycle.d.ts +198 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +446 -0
- package/dist/skills/parall-clips.d.ts +1 -1
- package/dist/skills/parall-clips.d.ts.map +1 -1
- package/dist/skills/parall-clips.js +3 -0
- package/dist/skills/parall-schedules.d.ts +1 -1
- package/dist/skills/parall-schedules.d.ts.map +1 -1
- package/dist/skills/parall-schedules.js +1 -1
- package/dist/skills/parall-tasks.d.ts +1 -1
- package/dist/skills/parall-tasks.d.ts.map +1 -1
- package/dist/skills/parall-tasks.js +21 -5
- package/dist/step-persister.d.ts +66 -0
- package/dist/step-persister.d.ts.map +1 -0
- package/dist/step-persister.js +116 -0
- package/dist/step-retry-queue.d.ts +91 -0
- package/dist/step-retry-queue.d.ts.map +1 -0
- package/dist/step-retry-queue.js +259 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/channel-capability.ts +16 -0
- package/src/dispatch-adapter.ts +10 -0
- package/src/event-format.ts +27 -7
- package/src/fork-session-finalizer.ts +122 -0
- package/src/gateway-base.ts +747 -331
- package/src/gateway-lane-flow.ts +275 -18
- package/src/http-keepalive.ts +36 -0
- package/src/index.ts +7 -1
- package/src/lane-ledger.ts +14 -0
- package/src/session-lifecycle.ts +552 -0
- package/src/skills/parall-clips.ts +3 -0
- package/src/skills/parall-schedules.ts +1 -1
- package/src/skills/parall-tasks.ts +21 -5
- package/src/step-persister.ts +161 -0
- package/src/step-retry-queue.ts +296 -0
- package/src/types.ts +2 -1
|
@@ -13,8 +13,15 @@ import type { ParallEvent } from './types.js';
|
|
|
13
13
|
export interface LaneFlowHost {
|
|
14
14
|
laneLedger?: LaneLedger;
|
|
15
15
|
ledgerDisabled: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Sticky: the server answered a by-id complete with 400 (predates the
|
|
18
|
+
* form, v1.44). Typed resolution falls back to the legacy ack for the
|
|
19
|
+
* rest of the process; claims stay on the ledger (that face is older).
|
|
20
|
+
*/
|
|
21
|
+
typedByIdCompleteUnsupported: boolean;
|
|
16
22
|
shuttingDown: boolean;
|
|
17
23
|
dispatchedMessages: Set<string>;
|
|
24
|
+
dispatchedTasks: Set<string>;
|
|
18
25
|
/**
|
|
19
26
|
* Per-WorkItem failure backoff for typed dispatch consumption. A consume
|
|
20
27
|
* that ends without an ack re-arms the entry; the next attempt for the
|
|
@@ -60,14 +67,77 @@ export declare function dispatchLaneGroup(host: LaneFlowHost, opts: {
|
|
|
60
67
|
captureText?: string[];
|
|
61
68
|
hasMoreLocal: () => boolean;
|
|
62
69
|
}): Promise<'dispatched' | 'foreign' | 'shutdown' | 'failed'>;
|
|
70
|
+
/**
|
|
71
|
+
* The WorkItem ids of a typed event group whose lifecycle the ledger owns
|
|
72
|
+
* (claimed into dsp lanes by consumeTypedDispatch), or null when the group
|
|
73
|
+
* must stay on the legacy received/ack surface — ledger unavailable, an id
|
|
74
|
+
* missing, or a member that rides a message lane. Non-null means: skip
|
|
75
|
+
* legacy received+ack, resolve via the by-id complete, and fold the
|
|
76
|
+
* turn-error signal into the outcome.
|
|
77
|
+
*/
|
|
78
|
+
export declare function typedLedgerEventIds(host: LaneFlowHost, events: ParallEvent[]): string[] | null;
|
|
79
|
+
/** Outcome of a by-id complete attempt. */
|
|
80
|
+
export type TypedResolveOutcome = 'ok' | 'stale' | 'unsupported' | 'failed';
|
|
81
|
+
/**
|
|
82
|
+
* By-id complete (turn_outcome ok): terminal resolution of ONE WorkItem —
|
|
83
|
+
* exact where the source pair is ambiguous across task siblings. `lane`
|
|
84
|
+
* fences the call while the row is claim-owned (a dethroned caller gets
|
|
85
|
+
* 'stale' and must leave the row to its successor); omit it for the
|
|
86
|
+
* wrapper-less pending close (buffered drain, administrative drops).
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveDispatchByID(host: LaneFlowHost, dispatchEventId: string, lane?: string): Promise<TypedResolveOutcome>;
|
|
89
|
+
/**
|
|
90
|
+
* Free a typed event's in-memory hot-path dedupe claim by its source pair —
|
|
91
|
+
* the ParallEvent-keyed mirror of the gateway's clearTypedDispatchDedupe
|
|
92
|
+
* (which keys on the wire DispatchNewData). Used when a buffered typed
|
|
93
|
+
* dispatch ran but its resolution failed: the member re-drives, and a stale
|
|
94
|
+
* local claim would make this pod reject the retry forever.
|
|
95
|
+
*
|
|
96
|
+
* PARITY: the switch below and clearTypedDispatchDedupe's must handle the
|
|
97
|
+
* same typed source families — when adding a new typed event type, extend
|
|
98
|
+
* BOTH (they key the same dedupe entries from different event shapes).
|
|
99
|
+
*/
|
|
100
|
+
export declare function clearTypedDedupeForEvent(host: LaneFlowHost, event: ParallEvent): void;
|
|
101
|
+
/**
|
|
102
|
+
* Settle a drained typed group — the one wrapper-less dispatch path: these
|
|
103
|
+
* events buffered behind a busy main (fork-less adapter), their consume
|
|
104
|
+
* guards returned false and released the claims, so the drain site owns the
|
|
105
|
+
* terminal resolution. Rows are pending — the by-id close resolves them
|
|
106
|
+
* without a fence; a row a successor lane meanwhile claimed answers stale and
|
|
107
|
+
* is that owner's to finish. `turnErrored` (consumed at the call site before
|
|
108
|
+
* the drain can start another turn) leaves the rows for the renotify pacing
|
|
109
|
+
* instead. Either way a row left live gets its local dedupe claim freed, or
|
|
110
|
+
* this pod would reject the retry forever.
|
|
111
|
+
*/
|
|
112
|
+
export declare function settleDrainedTypedGroup(host: LaneFlowHost, events: ParallEvent[], ids: string[], turnErrored: boolean): Promise<void>;
|
|
113
|
+
/** Consumption hooks for one typed dispatch. */
|
|
114
|
+
export interface TypedConsumeHooks {
|
|
115
|
+
/**
|
|
116
|
+
* Legacy administrative ack — invoked ONLY on the ledger-disabled fallback
|
|
117
|
+
* path (old server). On the ledger path the WorkItem resolves through the
|
|
118
|
+
* sources-form complete instead; this callback never fires there.
|
|
119
|
+
*/
|
|
120
|
+
legacyAck: (dispatchEventId?: string) => boolean | void | Promise<boolean | void>;
|
|
121
|
+
/**
|
|
122
|
+
* Free the item's in-memory hot-path dedupe claim. Invoked when the handler
|
|
123
|
+
* ran but the resolution could not commit (sources-form complete failed) —
|
|
124
|
+
* the member is released for re-drive, and a stale local claim would make
|
|
125
|
+
* this pod reject its own retry forever.
|
|
126
|
+
*/
|
|
127
|
+
clearDedupe?: () => void;
|
|
128
|
+
}
|
|
63
129
|
/**
|
|
64
130
|
* Consume one typed dispatch (task/comment/schedule/trigger/approval) under
|
|
65
131
|
* its typed-lane occupancy guard: claim the dsp:<id> lane (skip when another
|
|
66
|
-
* pod holds it or the WorkItem is already resolved), run the handler,
|
|
67
|
-
*
|
|
68
|
-
*
|
|
132
|
+
* pod holds it or the WorkItem is already resolved), run the handler, then
|
|
133
|
+
* settle. A successful run resolves the WorkItem terminally via the
|
|
134
|
+
* sources-form complete (turn_outcome ok — the server sweeps it, or lets a
|
|
135
|
+
* typed Effect committed during the turn stand, and drops the vacated lane);
|
|
136
|
+
* a failed / errored / unresolved run releases the member back to pending on
|
|
137
|
+
* the redrive budget via the lane complete. Legacy run+ack flow when the
|
|
138
|
+
* ledger is unavailable.
|
|
69
139
|
*
|
|
70
|
-
* Repeated failures back off: a consume that ends
|
|
140
|
+
* Repeated failures back off: a consume that ends unresolved re-arms the
|
|
71
141
|
* WorkItem's backoff entry, and the next attempt sleeps out the remaining
|
|
72
142
|
* window before claiming. Without this, complete's release re-drives the
|
|
73
143
|
* item instantly and a persistently-failing consume (e.g. buffered behind a
|
|
@@ -78,7 +148,7 @@ export declare function consumeTypedDispatch(host: LaneFlowHost, ref: {
|
|
|
78
148
|
dispatchEventId?: string;
|
|
79
149
|
sourceType?: string;
|
|
80
150
|
sourceId?: string;
|
|
81
|
-
}, run: (dispatchEventId?: string) => Promise<boolean>,
|
|
151
|
+
}, run: (dispatchEventId?: string) => Promise<boolean>, hooks: TypedConsumeHooks): Promise<void>;
|
|
82
152
|
/**
|
|
83
153
|
* Shared consumption of a message WorkItem referenced by id — the ONE
|
|
84
154
|
* protocol for dispatch catch-up pages and post-complete re-drive hints
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-lane-flow.d.ts","sourceRoot":"","sources":["../src/gateway-lane-flow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gateway-lane-flow.d.ts","sourceRoot":"","sources":["../src/gateway-lane-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiError } from '@parall/sdk';
|
|
1
2
|
import { LedgerUnsupportedError } from './lane-ledger.js';
|
|
2
3
|
/**
|
|
3
4
|
* Dispatch one group of same-lane message events under the ledger contract:
|
|
@@ -101,6 +102,161 @@ export async function dispatchLaneGroup(host, opts) {
|
|
|
101
102
|
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
102
103
|
return 'dispatched';
|
|
103
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The WorkItem ids of a typed event group whose lifecycle the ledger owns
|
|
107
|
+
* (claimed into dsp lanes by consumeTypedDispatch), or null when the group
|
|
108
|
+
* must stay on the legacy received/ack surface — ledger unavailable, an id
|
|
109
|
+
* missing, or a member that rides a message lane. Non-null means: skip
|
|
110
|
+
* legacy received+ack, resolve via the by-id complete, and fold the
|
|
111
|
+
* turn-error signal into the outcome.
|
|
112
|
+
*/
|
|
113
|
+
export function typedLedgerEventIds(host, events) {
|
|
114
|
+
if (!host.laneLedger || host.ledgerDisabled)
|
|
115
|
+
return null;
|
|
116
|
+
const ids = [];
|
|
117
|
+
for (const ev of events) {
|
|
118
|
+
if (!ev.dispatchEventId || host.usesLaneLedger(ev))
|
|
119
|
+
return null;
|
|
120
|
+
ids.push(ev.dispatchEventId);
|
|
121
|
+
}
|
|
122
|
+
return ids.length > 0 ? ids : null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A pre-by-id server (v1.44) routes a by-id body into its lane-form
|
|
126
|
+
* validation and answers 400 INVALID_REQUEST. Any 400 here means the server
|
|
127
|
+
* did not understand the form — a well-formed by-id call never 400s on a
|
|
128
|
+
* current server — so the caller falls back to the legacy typed ack for the
|
|
129
|
+
* rest of the process (sticky; the bridge restarts on the next update).
|
|
130
|
+
*/
|
|
131
|
+
function isByIDCompleteUnsupported(err) {
|
|
132
|
+
return err instanceof ApiError && err.status === 400;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* By-id complete (turn_outcome ok): terminal resolution of ONE WorkItem —
|
|
136
|
+
* exact where the source pair is ambiguous across task siblings. `lane`
|
|
137
|
+
* fences the call while the row is claim-owned (a dethroned caller gets
|
|
138
|
+
* 'stale' and must leave the row to its successor); omit it for the
|
|
139
|
+
* wrapper-less pending close (buffered drain, administrative drops).
|
|
140
|
+
*/
|
|
141
|
+
export async function resolveDispatchByID(host, dispatchEventId, lane) {
|
|
142
|
+
try {
|
|
143
|
+
await host.opts.client.completeDispatch(host.opts.config.org_id, {
|
|
144
|
+
dispatch_event_id: dispatchEventId,
|
|
145
|
+
...(lane ? { lane } : {}),
|
|
146
|
+
turn_outcome: 'ok',
|
|
147
|
+
});
|
|
148
|
+
return 'ok';
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err instanceof ApiError && err.status === 409)
|
|
152
|
+
return 'stale';
|
|
153
|
+
if (isByIDCompleteUnsupported(err))
|
|
154
|
+
return 'unsupported';
|
|
155
|
+
host.opts.log?.warn(`by-id complete failed for ${dispatchEventId} — leaving for re-drive: ${String(err)}`);
|
|
156
|
+
return 'failed';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Free a typed event's in-memory hot-path dedupe claim by its source pair —
|
|
161
|
+
* the ParallEvent-keyed mirror of the gateway's clearTypedDispatchDedupe
|
|
162
|
+
* (which keys on the wire DispatchNewData). Used when a buffered typed
|
|
163
|
+
* dispatch ran but its resolution failed: the member re-drives, and a stale
|
|
164
|
+
* local claim would make this pod reject the retry forever.
|
|
165
|
+
*
|
|
166
|
+
* PARITY: the switch below and clearTypedDispatchDedupe's must handle the
|
|
167
|
+
* same typed source families — when adding a new typed event type, extend
|
|
168
|
+
* BOTH (they key the same dedupe entries from different event shapes).
|
|
169
|
+
*/
|
|
170
|
+
export function clearTypedDedupeForEvent(host, event) {
|
|
171
|
+
const sourceId = event.ackSourceId;
|
|
172
|
+
if (!sourceId)
|
|
173
|
+
return;
|
|
174
|
+
switch (event.ackSourceType) {
|
|
175
|
+
case 'task_activity': {
|
|
176
|
+
// Task dedupe keys are `${task_id}:${updated_at}`; the task id is the
|
|
177
|
+
// event target. Prefix-clear mirrors clearTypedDispatchDedupe.
|
|
178
|
+
const prefix = `${event.targetId}:`;
|
|
179
|
+
for (const key of host.dispatchedTasks) {
|
|
180
|
+
if (key.startsWith(prefix))
|
|
181
|
+
host.dispatchedTasks.delete(key);
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'comment':
|
|
186
|
+
host.dispatchedTasks.delete(`comment:${sourceId}`);
|
|
187
|
+
break;
|
|
188
|
+
case 'schedule_run':
|
|
189
|
+
host.dispatchedTasks.delete(`schedule_run:${sourceId}`);
|
|
190
|
+
break;
|
|
191
|
+
case 'external_trigger_run':
|
|
192
|
+
host.dispatchedTasks.delete(`external_trigger_run:${sourceId}`);
|
|
193
|
+
break;
|
|
194
|
+
case 'channel_message':
|
|
195
|
+
host.dispatchedMessages.delete(`channel_message:${sourceId}`);
|
|
196
|
+
break;
|
|
197
|
+
case 'approval':
|
|
198
|
+
host.dispatchedTasks.delete(`approval:${sourceId}`);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Settle a drained typed group — the one wrapper-less dispatch path: these
|
|
204
|
+
* events buffered behind a busy main (fork-less adapter), their consume
|
|
205
|
+
* guards returned false and released the claims, so the drain site owns the
|
|
206
|
+
* terminal resolution. Rows are pending — the by-id close resolves them
|
|
207
|
+
* without a fence; a row a successor lane meanwhile claimed answers stale and
|
|
208
|
+
* is that owner's to finish. `turnErrored` (consumed at the call site before
|
|
209
|
+
* the drain can start another turn) leaves the rows for the renotify pacing
|
|
210
|
+
* instead. Either way a row left live gets its local dedupe claim freed, or
|
|
211
|
+
* this pod would reject the retry forever.
|
|
212
|
+
*/
|
|
213
|
+
export async function settleDrainedTypedGroup(host, events, ids, turnErrored) {
|
|
214
|
+
if (turnErrored) {
|
|
215
|
+
host.opts.log?.info(`buffered typed turn for ${events[events.length - 1]?.messageId} surfaced a runtime error — leaving for re-drive`);
|
|
216
|
+
for (const event of events)
|
|
217
|
+
clearTypedDedupeForEvent(host, event);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// The legacy ack BY ID, never by source — task siblings share a source
|
|
221
|
+
// pair, and a by-source ack would sweep the undispatched one. Awaited, and
|
|
222
|
+
// the dedupe claim is freed on EVERY outcome: a claim's lifetime is the
|
|
223
|
+
// buffer stay (#1149). On failure the row is still pending and its retry
|
|
224
|
+
// must not be self-rejected; on success the row is terminal — but a
|
|
225
|
+
// shared-key sibling WorkItem (task assign+update fetch the same
|
|
226
|
+
// `task:updated_at` pair) must not be rejected by a leftover claim either.
|
|
227
|
+
const legacyAckFrom = async (start) => {
|
|
228
|
+
for (const [j, id] of ids.slice(start).entries()) {
|
|
229
|
+
try {
|
|
230
|
+
await host.opts.client.ackDispatchByID(host.opts.config.org_id, id);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// Transient ack failure: the row stays live server-side and its
|
|
234
|
+
// re-drive re-converges — keep settling the REST of the group (a
|
|
235
|
+
// propagated error would strand every later id's retained claim).
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
clearTypedDedupeForEvent(host, events[start + j]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
if (host.typedByIdCompleteUnsupported) {
|
|
243
|
+
// Sticky: the server already answered one by-id probe with 400 — go
|
|
244
|
+
// straight to the legacy acks instead of re-probing per group.
|
|
245
|
+
await legacyAckFrom(0);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const [i, id] of ids.entries()) {
|
|
249
|
+
const outcome = await resolveDispatchByID(host, id);
|
|
250
|
+
if (outcome === 'unsupported') {
|
|
251
|
+
host.typedByIdCompleteUnsupported = true;
|
|
252
|
+
host.opts.log?.warn('server predates the by-id dispatch complete — falling back to legacy typed acks');
|
|
253
|
+
await legacyAckFrom(i);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Freed on every outcome — see legacyAckFrom's contract note.
|
|
257
|
+
clearTypedDedupeForEvent(host, events[i]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
104
260
|
/** Failure backoff pacing for typed dispatch retries (base 2s, cap 5min). */
|
|
105
261
|
const TYPED_BACKOFF_BASE_MS = 2_000;
|
|
106
262
|
const TYPED_BACKOFF_CAP_MS = 5 * 60_000;
|
|
@@ -108,18 +264,22 @@ const TYPED_BACKOFF_MAP_CAP = 512;
|
|
|
108
264
|
/**
|
|
109
265
|
* Consume one typed dispatch (task/comment/schedule/trigger/approval) under
|
|
110
266
|
* its typed-lane occupancy guard: claim the dsp:<id> lane (skip when another
|
|
111
|
-
* pod holds it or the WorkItem is already resolved), run the handler,
|
|
112
|
-
*
|
|
113
|
-
*
|
|
267
|
+
* pod holds it or the WorkItem is already resolved), run the handler, then
|
|
268
|
+
* settle. A successful run resolves the WorkItem terminally via the
|
|
269
|
+
* sources-form complete (turn_outcome ok — the server sweeps it, or lets a
|
|
270
|
+
* typed Effect committed during the turn stand, and drops the vacated lane);
|
|
271
|
+
* a failed / errored / unresolved run releases the member back to pending on
|
|
272
|
+
* the redrive budget via the lane complete. Legacy run+ack flow when the
|
|
273
|
+
* ledger is unavailable.
|
|
114
274
|
*
|
|
115
|
-
* Repeated failures back off: a consume that ends
|
|
275
|
+
* Repeated failures back off: a consume that ends unresolved re-arms the
|
|
116
276
|
* WorkItem's backoff entry, and the next attempt sleeps out the remaining
|
|
117
277
|
* window before claiming. Without this, complete's release re-drives the
|
|
118
278
|
* item instantly and a persistently-failing consume (e.g. buffered behind a
|
|
119
279
|
* saturated fork pool) spins at wire speed — the client half of the
|
|
120
280
|
* 2026-07-11 poison loop (the server half is the redrive budget).
|
|
121
281
|
*/
|
|
122
|
-
export async function consumeTypedDispatch(host, ref, run,
|
|
282
|
+
export async function consumeTypedDispatch(host, ref, run, hooks) {
|
|
123
283
|
const backoffKey = ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`;
|
|
124
284
|
const armed = host.typedRedriveBackoff.get(backoffKey);
|
|
125
285
|
if (armed) {
|
|
@@ -142,8 +302,8 @@ export async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
142
302
|
// consume — clearing backoff there would let an ack outage re-create the
|
|
143
303
|
// wire-speed release/re-drive loop against a pre-budget server.
|
|
144
304
|
const settleAck = (ackResult) => ackResult !== false;
|
|
145
|
-
const settle = (
|
|
146
|
-
if (
|
|
305
|
+
const settle = (resolved) => {
|
|
306
|
+
if (resolved) {
|
|
147
307
|
host.typedRedriveBackoff.delete(backoffKey);
|
|
148
308
|
return;
|
|
149
309
|
}
|
|
@@ -168,7 +328,7 @@ export async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
168
328
|
let acked = false;
|
|
169
329
|
try {
|
|
170
330
|
if (await run(ref.dispatchEventId)) {
|
|
171
|
-
acked = settleAck(await
|
|
331
|
+
acked = settleAck(await hooks.legacyAck(ref.dispatchEventId));
|
|
172
332
|
}
|
|
173
333
|
}
|
|
174
334
|
finally {
|
|
@@ -197,21 +357,68 @@ export async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
197
357
|
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) — skipping`);
|
|
198
358
|
return;
|
|
199
359
|
}
|
|
200
|
-
let
|
|
360
|
+
let resolved = false;
|
|
361
|
+
let viaLegacyAck = false;
|
|
201
362
|
try {
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
363
|
+
// A successful run resolves the member terminally through the by-id
|
|
364
|
+
// complete — one fenced call that sweeps exactly this WorkItem (a typed
|
|
365
|
+
// Effect committed during the turn already made it terminal — idempotent
|
|
366
|
+
// no-op) AND drops the vacated lane. Resolution must settle inside this
|
|
367
|
+
// guard: a fire-and-forget resolve would race the finally's release and
|
|
368
|
+
// spuriously re-drive handled work.
|
|
205
369
|
if (await run(lane.typedDispatchEventId)) {
|
|
206
|
-
|
|
370
|
+
if (host.typedByIdCompleteUnsupported || !lane.typedDispatchEventId) {
|
|
371
|
+
// Pre-by-id server (or a defensive claim shape without the id):
|
|
372
|
+
// the legacy administrative ack is still live there.
|
|
373
|
+
viaLegacyAck = true;
|
|
374
|
+
resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const outcome = await resolveDispatchByID(host, lane.typedDispatchEventId, lane.lane);
|
|
378
|
+
if (outcome === 'unsupported') {
|
|
379
|
+
host.typedByIdCompleteUnsupported = true;
|
|
380
|
+
host.opts.log?.warn('server predates the by-id dispatch complete — falling back to the legacy typed ack');
|
|
381
|
+
viaLegacyAck = true;
|
|
382
|
+
resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
|
|
383
|
+
}
|
|
384
|
+
else if (outcome === 'stale') {
|
|
385
|
+
// A successor lane owns the row — its turn resolves it. Nothing to
|
|
386
|
+
// spin on locally; our claim record is already dethroned. Free the
|
|
387
|
+
// local dedupe claim though: if the successor later FAILS and
|
|
388
|
+
// releases the row, the re-drive may land back on this pod, and a
|
|
389
|
+
// stale local claim would self-reject the retry into the budget.
|
|
390
|
+
hooks.clearDedupe?.();
|
|
391
|
+
resolved = true;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
resolved = outcome === 'ok';
|
|
395
|
+
if (!resolved) {
|
|
396
|
+
// The member is about to be released for re-drive — free its
|
|
397
|
+
// hot-path dedupe claim first, or this pod rejects its own retry
|
|
398
|
+
// forever (same contract as the legacy ack-failure path).
|
|
399
|
+
hooks.clearDedupe?.();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
207
403
|
}
|
|
208
404
|
}
|
|
209
405
|
finally {
|
|
210
|
-
settle(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
406
|
+
settle(resolved);
|
|
407
|
+
if (resolved && !viaLegacyAck) {
|
|
408
|
+
// The by-id complete already dropped (or a takeover already owns) the
|
|
409
|
+
// server-side lane row; only the local record and context file remain.
|
|
410
|
+
host.laneLedger.dropLocal(lane.laneKey);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Two jobs, one call: an unresolved turn (unconsumed / failed /
|
|
414
|
+
// errored / resolution-failed) releases the member back to pending on
|
|
415
|
+
// the redrive budget; a legacy-acked turn still needs its server-side
|
|
416
|
+
// lane row dropped (the ack resolves the row but not the occupancy).
|
|
417
|
+
// A buffered dispatch may outlive this guard (lane TTL) — acceptable
|
|
418
|
+
// at-least-once; the ledger's resolution paths still dedupe the
|
|
419
|
+
// persistent side effects.
|
|
420
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => { });
|
|
421
|
+
}
|
|
215
422
|
}
|
|
216
423
|
}
|
|
217
424
|
/**
|
|
@@ -227,7 +434,22 @@ export async function consumeMessageWorkItem(host, item) {
|
|
|
227
434
|
return;
|
|
228
435
|
if (!host.tryClaimMessage(item.source_id))
|
|
229
436
|
return;
|
|
437
|
+
// Administrative drop (deleted source / self-sender / skip decision): on
|
|
438
|
+
// the ledger path the by-id complete closes the pending row terminally and
|
|
439
|
+
// refuses (409) a row a live lane owns — the owner's turn resolves it. The
|
|
440
|
+
// legacy ack remains the ledger-disabled / pre-by-id fallback.
|
|
441
|
+
// Fire-and-forget either way: a failed drop leaves the row live and the
|
|
442
|
+
// next re-drive converges on the same drop.
|
|
230
443
|
const ackItem = () => {
|
|
444
|
+
if (host.laneLedger && !host.ledgerDisabled && !host.typedByIdCompleteUnsupported) {
|
|
445
|
+
void resolveDispatchByID(host, item.id).then((outcome) => {
|
|
446
|
+
if (outcome === 'unsupported') {
|
|
447
|
+
host.typedByIdCompleteUnsupported = true;
|
|
448
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => { });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
231
453
|
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => { });
|
|
232
454
|
};
|
|
233
455
|
let msg = null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-keepalive.d.ts","sourceRoot":"","sources":["../src/http-keepalive.ts"],"names":[],"mappings":"AAsBA,wBAAgB,sBAAsB,CAAC,IAAI,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAanF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
2
|
+
/**
|
|
3
|
+
* Keep bridge→api HTTP connections long-lived.
|
|
4
|
+
*
|
|
5
|
+
* Node's built-in fetch closes idle pooled connections after ~4s when the
|
|
6
|
+
* server sends no Keep-Alive hint (our NLB+Envoy edge sends none), so any two
|
|
7
|
+
* bridge API calls spaced more than a few seconds apart pay a fresh TCP+TLS
|
|
8
|
+
* handshake. On a degraded long-haul link that handshake is exactly what
|
|
9
|
+
* fails — established connections (the WS, the runtime's own model stream)
|
|
10
|
+
* ride through such windows while every bridge call times out (2026-07-10:
|
|
11
|
+
* step creation losses). A 2-minute idle timeout keeps one warm connection
|
|
12
|
+
* across an active turn's step cadence, comfortably below the NLB's 350s
|
|
13
|
+
* idle cutoff.
|
|
14
|
+
*
|
|
15
|
+
* Uses undici's global-dispatcher registry, which Node's built-in fetch
|
|
16
|
+
* shares (verified on Node 22 / undici 7) — so this covers every fetch in
|
|
17
|
+
* the process: ParallClient, lane-ledger heartbeats, OTLP export.
|
|
18
|
+
* Node-only; call once from a bridge/daemon entrypoint before any fetch.
|
|
19
|
+
*/
|
|
20
|
+
let installed = false;
|
|
21
|
+
export function configureHttpKeepAlive(opts) {
|
|
22
|
+
// Idempotent: a second call (e.g. a process hosting several bridge
|
|
23
|
+
// entrypoints) must not replace — and leak — the already-installed Agent.
|
|
24
|
+
if (installed)
|
|
25
|
+
return;
|
|
26
|
+
installed = true;
|
|
27
|
+
const keepAliveTimeout = opts?.keepAliveTimeoutMs ?? 120_000;
|
|
28
|
+
setGlobalDispatcher(new Agent({
|
|
29
|
+
keepAliveTimeout,
|
|
30
|
+
// Also cap the server-hinted value: never idle past the edge's cutoff.
|
|
31
|
+
keepAliveMaxTimeout: keepAliveTimeout,
|
|
32
|
+
}));
|
|
33
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './provider-config.js';
|
|
|
2
2
|
export * from './types.js';
|
|
3
3
|
export * from './lane-key.js';
|
|
4
4
|
export type { LaneFlowHost } from './gateway-lane-flow.js';
|
|
5
|
-
export { consumeTypedDispatch } from './gateway-lane-flow.js';
|
|
5
|
+
export { consumeTypedDispatch, settleDrainedTypedGroup } from './gateway-lane-flow.js';
|
|
6
6
|
export * from './session-state.js';
|
|
7
7
|
export * from './routing.js';
|
|
8
8
|
export * from './event-format.js';
|
|
@@ -11,6 +11,7 @@ export * from './bridge-workspace.js';
|
|
|
11
11
|
export * from './dispatch-adapter.js';
|
|
12
12
|
export { createLogger, childLogger } from './logger.js';
|
|
13
13
|
export * from './gateway-base.js';
|
|
14
|
+
export { configureHttpKeepAlive } from './http-keepalive.js';
|
|
14
15
|
export * from './platform-config.js';
|
|
15
16
|
export * from './channel-capability.js';
|
|
16
17
|
export * from './channel-token.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACvF,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAM7D,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from './provider-config.js';
|
|
2
2
|
export * from './types.js';
|
|
3
3
|
export * from './lane-key.js';
|
|
4
|
-
export { consumeTypedDispatch } from './gateway-lane-flow.js';
|
|
4
|
+
export { consumeTypedDispatch, settleDrainedTypedGroup } from './gateway-lane-flow.js';
|
|
5
5
|
export * from './session-state.js';
|
|
6
6
|
export * from './routing.js';
|
|
7
7
|
export * from './event-format.js';
|
|
@@ -10,6 +10,12 @@ export * from './bridge-workspace.js';
|
|
|
10
10
|
export * from './dispatch-adapter.js';
|
|
11
11
|
export { createLogger, childLogger } from './logger.js';
|
|
12
12
|
export * from './gateway-base.js';
|
|
13
|
+
export { configureHttpKeepAlive } from './http-keepalive.js';
|
|
14
|
+
// StepPersister / StepRetryQueue / SessionLifecycleCoordinator /
|
|
15
|
+
// ForkSessionFinalizer are internal gateway collaborators — deliberately NOT
|
|
16
|
+
// re-exported from the package root (no external consumer; a root export
|
|
17
|
+
// would become an accidental long-term compat promise). Tests import their
|
|
18
|
+
// built modules directly (e.g. `../dist/step-retry-queue.js`).
|
|
13
19
|
export * from './platform-config.js';
|
|
14
20
|
export * from './channel-capability.js';
|
|
15
21
|
export * from './channel-token.js';
|
package/dist/lane-ledger.d.ts
CHANGED
|
@@ -123,6 +123,14 @@ export declare class LaneLedger {
|
|
|
123
123
|
sourceType?: string;
|
|
124
124
|
sourceId?: string;
|
|
125
125
|
}): Promise<ActiveLane | null>;
|
|
126
|
+
/**
|
|
127
|
+
* Drop a lane's local record without any server call — for a typed lane
|
|
128
|
+
* whose member the by-id complete just resolved (the server dropped the
|
|
129
|
+
* vacated lane row in the same transaction). Calling completeIfIdle
|
|
130
|
+
* instead would fire a lane-form Complete at a lane that no longer exists
|
|
131
|
+
* and burn an RPC on the guaranteed STALE_LANE answer.
|
|
132
|
+
*/
|
|
133
|
+
dropLocal(laneKey: string): void;
|
|
126
134
|
/**
|
|
127
135
|
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
128
136
|
* ends. A leftover file would make a later cross-context send to the same
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lane-ledger.d.ts","sourceRoot":"","sources":["../src/lane-ledger.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,6EAA6E;IAC7E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAEpD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAC;AAqBrD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAInB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,IAAI,EAAE;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,aAAa,CAAC;KACrB;IAGH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+FAA+F;IAC/F,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAIpC,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IAOtC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS;IAIvD,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;IAIzC;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAsFnE;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BrD;;;;;OAKG;IACH;;;;;;OAMG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B3E;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;;;OAOG;IACH,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IA6BlC;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,yEAAyE;IACzE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA8B9B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;CAW1B"}
|
|
1
|
+
{"version":3,"file":"lane-ledger.d.ts","sourceRoot":"","sources":["../src/lane-ledger.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,6EAA6E;IAC7E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAEpD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAC;AAqBrD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAInB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,IAAI,EAAE;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,aAAa,CAAC;KACrB;IAGH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+FAA+F;IAC/F,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAIpC,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IAOtC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS;IAIvD,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;IAIzC;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAsFnE;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BrD;;;;;OAKG;IACH;;;;;;OAMG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B3E;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;;;OAOG;IACH,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IA6BlC;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,yEAAyE;IACzE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA8B9B;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOhC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;CAW1B"}
|
package/dist/lane-ledger.js
CHANGED
|
@@ -347,6 +347,20 @@ export class LaneLedger {
|
|
|
347
347
|
this.lanes.set(lane.laneKey, lane);
|
|
348
348
|
return lane;
|
|
349
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Drop a lane's local record without any server call — for a typed lane
|
|
352
|
+
* whose member the by-id complete just resolved (the server dropped the
|
|
353
|
+
* vacated lane row in the same transaction). Calling completeIfIdle
|
|
354
|
+
* instead would fire a lane-form Complete at a lane that no longer exists
|
|
355
|
+
* and burn an RPC on the guaranteed STALE_LANE answer.
|
|
356
|
+
*/
|
|
357
|
+
dropLocal(laneKey) {
|
|
358
|
+
const lane = this.lanes.get(laneKey);
|
|
359
|
+
if (!lane)
|
|
360
|
+
return;
|
|
361
|
+
this.lanes.delete(laneKey);
|
|
362
|
+
this.removeLaneContext(lane);
|
|
363
|
+
}
|
|
350
364
|
/**
|
|
351
365
|
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
352
366
|
* ends. A leftover file would make a later cross-context send to the same
|