@ganglion/xacpx 0.23.0-beta.5 → 0.23.0-beta.6
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/cli.js
CHANGED
|
@@ -28499,7 +28499,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
28499
28499
|
})
|
|
28500
28500
|
}, {
|
|
28501
28501
|
name: "agent_send",
|
|
28502
|
-
description: "Send a high-value asynchronous peer message to another agent. ONLY use this when: (1) you have critical information that directly alters the target's current work (e.g. breaking schema change, interface takeover), or (2) you need an unblocking decision only the target can provide. DO NOT send acknowledgments ('Received', 'OK', 'Working on it'), status polling ('Are you done?'), or conversational chatter. One-way notifications require NO reply. Provide replyTo when answering an earlier message. Completion modes: none (one-way message; no completion signal is returned), notify (after the peer turn triggered by this message reaches terminal state, xacpx notifies the sender that the peer completed/failed/cancelled), result (after the peer turn reaches terminal state, xacpx returns the peer's bounded final assistant result to the sender). Note agent_send still returns delivery admission immediately.",
|
|
28502
|
+
description: "Send a high-value asynchronous peer message to another agent. ONLY use this when: (1) you have critical information that directly alters the target's current work (e.g. breaking schema change, interface takeover), or (2) you need an unblocking decision only the target can provide. DO NOT send acknowledgments ('Received', 'OK', 'Working on it'), status polling ('Are you done?'), or conversational chatter. One-way notifications require NO reply. Provide replyTo when answering an earlier message. Completion modes: none (one-way message; no completion signal is returned), notify (after the peer turn triggered by this message reaches terminal state, xacpx notifies the sender that the peer completed/failed/cancelled), result (after the peer turn reaches terminal state, xacpx returns the peer's bounded final assistant result to the sender). Note agent_send still returns delivery admission immediately. mode='interrupt' requests cancellation of the target Agent's current turn and reserves this message as its highest-priority next turn; xacpx waits for the current turn to fully settle before starting the message, and never runs the two turns in parallel. Some runtimes may finish the current turn instead of stopping immediately. Use interrupt only when waiting behind ordinary queued work would materially harm the task. The default/auto mode NEVER selects interrupt, and xacpx never escalates a message to interrupt based on its wording.",
|
|
28503
28503
|
inputSchema: exports_external.object({
|
|
28504
28504
|
to: exports_external.string().min(1).optional().describe("Target opaque handle from agent_list"),
|
|
28505
28505
|
selector: exports_external.object({
|
|
@@ -28508,7 +28508,7 @@ function buildXacpxMcpToolRegistry(input) {
|
|
|
28508
28508
|
agent: exports_external.string().min(1).optional().describe("Target agent type")
|
|
28509
28509
|
}).strict().refine((s) => Boolean(s.displayName || s.workspace || s.agent), "Target selector must specify at least one criterion (displayName, workspace, or agent)").optional().describe("Target selector criteria when handle is not known"),
|
|
28510
28510
|
message: exports_external.string().min(1).describe("Concise, actionable message content"),
|
|
28511
|
-
mode: exports_external.enum(["auto", "steer", "queue", "interrupt"]).optional().describe("Delivery mode (default: auto -> queue)"),
|
|
28511
|
+
mode: exports_external.enum(["auto", "steer", "queue", "interrupt"]).optional().describe("Delivery mode (default: auto -> queue). 'interrupt' REQUESTS cancellation of the target's current turn and reserves this message as its highest-priority next turn; xacpx waits for the current turn to fully settle first, and some runtimes may finish it instead of stopping immediately. 'auto' and 'queue' never cancel. 'steer' injects into the target's active turn when the runtime supports it."),
|
|
28512
28512
|
replyTo: exports_external.string().min(1).optional().describe("Message ID being replied to, if continuing a thread"),
|
|
28513
28513
|
completion: exports_external.enum(["none", "notify", "result"]).optional().describe("Completion notification mode: 'none' (one-way message; no completion signal is returned), 'notify' (notifies sender when peer turn reaches terminal state), or 'result' (returns peer's bounded final assistant result). Note agent_send still returns delivery admission immediately.")
|
|
28514
28514
|
}).strict().refine((data) => data.to !== undefined && data.selector === undefined || data.to === undefined && data.selector !== undefined, {
|
|
@@ -50667,7 +50667,7 @@ class AgentEndpointRegistry {
|
|
|
50667
50667
|
activity: {
|
|
50668
50668
|
status: isRunning ? "working" : "idle"
|
|
50669
50669
|
},
|
|
50670
|
-
capabilities: queueOnlyCapabilities({ completion: true }),
|
|
50670
|
+
capabilities: queueOnlyCapabilities({ completion: true, interrupt: true }),
|
|
50671
50671
|
endpointKind: "logical",
|
|
50672
50672
|
channelId: getChannelIdFromChatKey(session3.alias)
|
|
50673
50673
|
};
|
|
@@ -50718,7 +50718,7 @@ function queueOnlyCapabilities(options) {
|
|
|
50718
50718
|
receive: true,
|
|
50719
50719
|
steer: false,
|
|
50720
50720
|
queue: true,
|
|
50721
|
-
interrupt: false,
|
|
50721
|
+
interrupt: options?.interrupt ?? false,
|
|
50722
50722
|
conversation: true,
|
|
50723
50723
|
completion: options?.completion ?? false
|
|
50724
50724
|
};
|
|
@@ -50981,7 +50981,8 @@ var init_agent_message_router = __esm(() => {
|
|
|
50981
50981
|
"TARGET_UNAVAILABLE",
|
|
50982
50982
|
"MESSAGE_QUEUE_FULL",
|
|
50983
50983
|
"DELIVERY_DENIED",
|
|
50984
|
-
"COMPLETION_NOT_SUPPORTED"
|
|
50984
|
+
"COMPLETION_NOT_SUPPORTED",
|
|
50985
|
+
"TARGET_NOT_INTERRUPTIBLE"
|
|
50985
50986
|
]);
|
|
50986
50987
|
AgentMessageRouter = class AgentMessageRouter {
|
|
50987
50988
|
deps;
|
|
@@ -51636,6 +51637,9 @@ var init_agent_message_router = __esm(() => {
|
|
|
51636
51637
|
createdAt,
|
|
51637
51638
|
...input.replyTo ? { replyTo: input.replyTo } : {}
|
|
51638
51639
|
};
|
|
51640
|
+
if (message.requestedMode === "interrupt" && target.endpoint.capabilities.interrupt !== true) {
|
|
51641
|
+
throw new AgentMessagingError("TARGET_NOT_INTERRUPTIBLE", "The target does not support explicit interrupt delivery.");
|
|
51642
|
+
}
|
|
51639
51643
|
if (message.completion !== "none") {
|
|
51640
51644
|
if (target.endpoint.capabilities.completion !== true) {
|
|
51641
51645
|
throw new AgentMessagingError("COMPLETION_NOT_SUPPORTED", `Completion mode '${message.completion}' requires a target that advertises completion capability.`);
|
|
@@ -52454,10 +52458,11 @@ class LocalAgentMessageDeliveryAdapter {
|
|
|
52454
52458
|
source: message.from,
|
|
52455
52459
|
target: message.to
|
|
52456
52460
|
};
|
|
52457
|
-
const res = await this.deps.deliverLogicalTurn(target.runtime.alias, renderedText, message.id, peerOrigin);
|
|
52461
|
+
const res = await this.deps.deliverLogicalTurn(target.runtime.alias, renderedText, message.id, peerOrigin, message.requestedMode);
|
|
52458
52462
|
return {
|
|
52459
52463
|
status: res.status,
|
|
52460
|
-
modeUsed: "queue"
|
|
52464
|
+
modeUsed: res.modeUsed ?? "queue",
|
|
52465
|
+
...res.targetState ? { targetState: res.targetState } : {}
|
|
52461
52466
|
};
|
|
52462
52467
|
}
|
|
52463
52468
|
const session3 = target.runtime.kind === "logical" ? await this.deps.resolveLogicalSession(target.runtime.transportSession) : this.deps.resolveWorkerSession(target.runtime);
|
|
@@ -60156,6 +60161,7 @@ class TurnQueue {
|
|
|
60156
60161
|
settledRequestIds = new Map;
|
|
60157
60162
|
static SETTLED_REQUEST_IDS_MAX = 2000;
|
|
60158
60163
|
queues = new Map;
|
|
60164
|
+
pendingInterrupts = new Map;
|
|
60159
60165
|
draining = new Set;
|
|
60160
60166
|
removing = new Set;
|
|
60161
60167
|
resolveKey(chatKey, sessionAlias, concurrencyKey) {
|
|
@@ -60163,7 +60169,7 @@ class TurnQueue {
|
|
|
60163
60169
|
return concurrencyKey;
|
|
60164
60170
|
}
|
|
60165
60171
|
const directKey = turnKey(chatKey, sessionAlias);
|
|
60166
|
-
if (this.inFlight.has(directKey) || this.queues.has(directKey) || this.draining.has(directKey) || this.removing.has(directKey)) {
|
|
60172
|
+
if (this.inFlight.has(directKey) || this.queues.has(directKey) || this.draining.has(directKey) || this.removing.has(directKey) || this.pendingInterrupts.has(directKey)) {
|
|
60167
60173
|
return directKey;
|
|
60168
60174
|
}
|
|
60169
60175
|
const cleanAlias = sessionAlias.includes(":") ? sessionAlias.split(":").pop() : sessionAlias;
|
|
@@ -60183,6 +60189,10 @@ class TurnQueue {
|
|
|
60183
60189
|
if (k === directKey || k.endsWith(":" + cleanAlias) || k.endsWith(" " + cleanAlias) || k === cleanAlias)
|
|
60184
60190
|
return k;
|
|
60185
60191
|
}
|
|
60192
|
+
for (const k of this.pendingInterrupts.keys()) {
|
|
60193
|
+
if (k === directKey || k.endsWith(":" + cleanAlias) || k.endsWith(" " + cleanAlias) || k === cleanAlias)
|
|
60194
|
+
return k;
|
|
60195
|
+
}
|
|
60186
60196
|
return directKey;
|
|
60187
60197
|
}
|
|
60188
60198
|
queueLength(chatKey, sessionAlias, concurrencyKey) {
|
|
@@ -60254,6 +60264,99 @@ class TurnQueue {
|
|
|
60254
60264
|
this.recordSettledRequestId(params.promptRequestId);
|
|
60255
60265
|
return { status: "injected" };
|
|
60256
60266
|
}
|
|
60267
|
+
pendingInterruptCount(chatKey, sessionAlias, concurrencyKey) {
|
|
60268
|
+
return this.pendingInterrupts.has(this.resolveKey(chatKey, sessionAlias, concurrencyKey)) ? 1 : 0;
|
|
60269
|
+
}
|
|
60270
|
+
submitPeerInterrupt(params) {
|
|
60271
|
+
const key = this.resolveKey(params.chatKey, params.sessionAlias, params.concurrencyKey);
|
|
60272
|
+
if (this.removing.has(key)) {
|
|
60273
|
+
return { status: "rejected", reason: "target-unavailable" };
|
|
60274
|
+
}
|
|
60275
|
+
if (params.promptRequestId !== undefined && params.isPeerMessage) {
|
|
60276
|
+
const pendingDup = this.pendingInterrupts.get(key);
|
|
60277
|
+
if (pendingDup?.promptRequestId === params.promptRequestId) {
|
|
60278
|
+
return { status: "queued", modeUsed: "interrupt", queueItemId: pendingDup.id };
|
|
60279
|
+
}
|
|
60280
|
+
if (this.hasSettledRequestId(params.promptRequestId)) {
|
|
60281
|
+
return { status: "injected", modeUsed: "prompt" };
|
|
60282
|
+
}
|
|
60283
|
+
const queuedDup = (this.queues.get(key) ?? []).some((item2) => item2.promptRequestId === params.promptRequestId);
|
|
60284
|
+
const inFlightDup = this.inFlight.get(key)?.promptRequestId === params.promptRequestId;
|
|
60285
|
+
if (queuedDup || inFlightDup) {
|
|
60286
|
+
return { status: "injected", modeUsed: "prompt" };
|
|
60287
|
+
}
|
|
60288
|
+
}
|
|
60289
|
+
const existing = this.inFlight.get(key);
|
|
60290
|
+
const busy = this.draining.has(key) || existing !== undefined;
|
|
60291
|
+
if (!busy) {
|
|
60292
|
+
const idle = this.submitPeerTurn(params);
|
|
60293
|
+
if (idle.status === "rejected")
|
|
60294
|
+
return idle;
|
|
60295
|
+
if (idle.status === "queued") {
|
|
60296
|
+
return { status: "rejected", reason: "queue-full" };
|
|
60297
|
+
}
|
|
60298
|
+
return { status: "injected", modeUsed: "prompt" };
|
|
60299
|
+
}
|
|
60300
|
+
const pending = this.pendingInterrupts.get(key);
|
|
60301
|
+
if (pending) {
|
|
60302
|
+
this.emitPeerInterrupt("rejected_pending", params.chatKey, params.sessionAlias, pending, {
|
|
60303
|
+
requestMessageId: params.peerOrigin?.requestMessageId,
|
|
60304
|
+
promptRequestId: params.promptRequestId,
|
|
60305
|
+
pendingRequestMessageId: pending.peerOrigin?.requestMessageId
|
|
60306
|
+
});
|
|
60307
|
+
return { status: "rejected", reason: "queue-full" };
|
|
60308
|
+
}
|
|
60309
|
+
const id = randomUUID13();
|
|
60310
|
+
const item = {
|
|
60311
|
+
id,
|
|
60312
|
+
text: params.text,
|
|
60313
|
+
enqueuedAt: new Date().toISOString(),
|
|
60314
|
+
senderId: params.senderId,
|
|
60315
|
+
executionContext: {
|
|
60316
|
+
chatKey: params.chatKey,
|
|
60317
|
+
sessionAlias: params.sessionAlias,
|
|
60318
|
+
...params.boundSessionAlias ? { boundSessionAlias: params.boundSessionAlias } : {}
|
|
60319
|
+
},
|
|
60320
|
+
concurrencyKey: params.concurrencyKey,
|
|
60321
|
+
isPeerMessage: true,
|
|
60322
|
+
allowRestoreArchived: false,
|
|
60323
|
+
...params.isOwner !== undefined ? { isOwner: params.isOwner } : {},
|
|
60324
|
+
...params.preserveCoordinatorRoute !== undefined ? { preserveCoordinatorRoute: params.preserveCoordinatorRoute } : {},
|
|
60325
|
+
...params.accountId !== undefined ? { accountId: params.accountId } : {},
|
|
60326
|
+
...params.media !== undefined ? { media: params.media } : {},
|
|
60327
|
+
...params.agentMentions !== undefined ? { agentMentions: params.agentMentions } : {},
|
|
60328
|
+
...params.promptRequestId !== undefined ? { promptRequestId: params.promptRequestId } : {},
|
|
60329
|
+
...params.peerOrigin !== undefined ? { peerOrigin: params.peerOrigin } : {},
|
|
60330
|
+
...params.trustedPeerCompletion !== undefined ? { trustedPeerCompletion: params.trustedPeerCompletion } : {}
|
|
60331
|
+
};
|
|
60332
|
+
this.pendingInterrupts.set(key, item);
|
|
60333
|
+
this.recordSettledRequestId(params.promptRequestId);
|
|
60334
|
+
const predecessorWasAlreadyAborted = existing?.controller.signal.aborted ?? false;
|
|
60335
|
+
this.emitPeerInterrupt("reserved", params.chatKey, params.sessionAlias, item, {
|
|
60336
|
+
predecessorWasAlreadyAborted
|
|
60337
|
+
});
|
|
60338
|
+
if (existing && !predecessorWasAlreadyAborted) {
|
|
60339
|
+
existing.controller.abort();
|
|
60340
|
+
this.emitPeerInterrupt("abort_signalled", params.chatKey, params.sessionAlias, item);
|
|
60341
|
+
}
|
|
60342
|
+
this.emitQueueUpdated(params.chatKey, params.sessionAlias, key);
|
|
60343
|
+
return { status: "queued", modeUsed: "interrupt", queueItemId: id };
|
|
60344
|
+
}
|
|
60345
|
+
emitPeerInterrupt(kind, chatKey, sessionAlias, item, ids) {
|
|
60346
|
+
if (!this.deps.onPeerInterruptEvent)
|
|
60347
|
+
return;
|
|
60348
|
+
try {
|
|
60349
|
+
this.deps.onPeerInterruptEvent({
|
|
60350
|
+
kind,
|
|
60351
|
+
chatKey,
|
|
60352
|
+
sessionAlias,
|
|
60353
|
+
...ids?.requestMessageId !== undefined ? { requestMessageId: ids.requestMessageId } : item?.peerOrigin?.requestMessageId !== undefined ? { requestMessageId: item.peerOrigin.requestMessageId } : {},
|
|
60354
|
+
...ids?.promptRequestId !== undefined ? { promptRequestId: ids.promptRequestId } : item?.promptRequestId !== undefined ? { promptRequestId: item.promptRequestId } : {},
|
|
60355
|
+
...ids?.pendingRequestMessageId !== undefined ? { pendingRequestMessageId: ids.pendingRequestMessageId } : {},
|
|
60356
|
+
...ids?.predecessorWasAlreadyAborted !== undefined ? { predecessorWasAlreadyAborted: ids.predecessorWasAlreadyAborted } : {}
|
|
60357
|
+
});
|
|
60358
|
+
} catch {}
|
|
60359
|
+
}
|
|
60257
60360
|
hasSettledRequestId(id) {
|
|
60258
60361
|
const expiresAt = this.settledRequestIds.get(id);
|
|
60259
60362
|
if (expiresAt === undefined)
|
|
@@ -60293,11 +60396,20 @@ class TurnQueue {
|
|
|
60293
60396
|
}
|
|
60294
60397
|
emitQueueUpdated(chatKey, sessionAlias, queueKey) {
|
|
60295
60398
|
const key = queueKey ?? this.resolveKey(chatKey, sessionAlias);
|
|
60296
|
-
const
|
|
60297
|
-
|
|
60298
|
-
|
|
60299
|
-
|
|
60300
|
-
|
|
60399
|
+
const interrupt = this.pendingInterrupts.get(key);
|
|
60400
|
+
const items = interrupt ? [{
|
|
60401
|
+
id: interrupt.id,
|
|
60402
|
+
textPreview: interrupt.text.length > QUEUE_PREVIEW_MAX ? interrupt.text.slice(0, QUEUE_PREVIEW_MAX) : interrupt.text,
|
|
60403
|
+
enqueuedAt: interrupt.enqueuedAt,
|
|
60404
|
+
kind: "interrupt"
|
|
60405
|
+
}] : [];
|
|
60406
|
+
for (const q of this.queues.get(key) ?? []) {
|
|
60407
|
+
items.push({
|
|
60408
|
+
id: q.id,
|
|
60409
|
+
textPreview: q.text.length > QUEUE_PREVIEW_MAX ? q.text.slice(0, QUEUE_PREVIEW_MAX) : q.text,
|
|
60410
|
+
enqueuedAt: q.enqueuedAt
|
|
60411
|
+
});
|
|
60412
|
+
}
|
|
60301
60413
|
this.deps.emitQueueUpdated(chatKey, sessionAlias, items);
|
|
60302
60414
|
}
|
|
60303
60415
|
async submit(params) {
|
|
@@ -60418,7 +60530,7 @@ class TurnQueue {
|
|
|
60418
60530
|
} finally {
|
|
60419
60531
|
if (idleTimer)
|
|
60420
60532
|
this.clearTimer(idleTimer);
|
|
60421
|
-
if ((this.queues.get(key)?.length ?? 0) > 0) {
|
|
60533
|
+
if ((this.queues.get(key)?.length ?? 0) > 0 || this.pendingInterrupts.has(key)) {
|
|
60422
60534
|
this.draining.add(key);
|
|
60423
60535
|
}
|
|
60424
60536
|
const detection = result?.postTurnDetection;
|
|
@@ -60435,40 +60547,50 @@ class TurnQueue {
|
|
|
60435
60547
|
};
|
|
60436
60548
|
}
|
|
60437
60549
|
advanceQueue(key) {
|
|
60550
|
+
const interrupt = this.pendingInterrupts.get(key);
|
|
60551
|
+
if (interrupt) {
|
|
60552
|
+
this.pendingInterrupts.delete(key);
|
|
60553
|
+
this.emitPeerInterrupt("started", interrupt.executionContext.chatKey, interrupt.executionContext.sessionAlias, interrupt);
|
|
60554
|
+
this.drainQueuedPrompt(key, interrupt);
|
|
60555
|
+
return;
|
|
60556
|
+
}
|
|
60438
60557
|
const q = this.queues.get(key);
|
|
60439
60558
|
const next = q?.shift();
|
|
60440
60559
|
if (q && q.length === 0)
|
|
60441
60560
|
this.queues.delete(key);
|
|
60442
60561
|
if (next) {
|
|
60443
|
-
this.
|
|
60444
|
-
const concurrencyKey = next.concurrencyKey ?? key;
|
|
60445
|
-
const turnStarted = next.isPeerMessage ? { queueItemId: next.id, ...next.promptRequestId !== undefined ? { promptRequestId: next.promptRequestId } : {} } : { prompt: next.text, queueItemId: next.id, ...next.promptRequestId !== undefined ? { promptRequestId: next.promptRequestId } : {} };
|
|
60446
|
-
this.submit({
|
|
60447
|
-
chatKey: next.executionContext.chatKey,
|
|
60448
|
-
sessionAlias: next.executionContext.sessionAlias,
|
|
60449
|
-
boundSessionAlias: next.executionContext.boundSessionAlias,
|
|
60450
|
-
concurrencyKey,
|
|
60451
|
-
text: next.text,
|
|
60452
|
-
senderId: next.senderId,
|
|
60453
|
-
queueable: true,
|
|
60454
|
-
drained: true,
|
|
60455
|
-
isPeerMessage: next.isPeerMessage,
|
|
60456
|
-
allowRestoreArchived: next.allowRestoreArchived,
|
|
60457
|
-
preserveCoordinatorRoute: next.preserveCoordinatorRoute,
|
|
60458
|
-
promptRequestId: next.promptRequestId,
|
|
60459
|
-
peerOrigin: next.peerOrigin,
|
|
60460
|
-
trustedPeerCompletion: next.trustedPeerCompletion,
|
|
60461
|
-
...next.isOwner !== undefined ? { isOwner: next.isOwner } : {},
|
|
60462
|
-
...next.accountId !== undefined ? { accountId: next.accountId } : {},
|
|
60463
|
-
...next.media !== undefined ? { media: next.media } : {},
|
|
60464
|
-
...next.agentMentions !== undefined ? { agentMentions: next.agentMentions } : {},
|
|
60465
|
-
turnStarted
|
|
60466
|
-
});
|
|
60562
|
+
this.drainQueuedPrompt(key, next);
|
|
60467
60563
|
} else {
|
|
60468
60564
|
this.draining.delete(key);
|
|
60469
60565
|
this.inFlight.delete(key);
|
|
60470
60566
|
}
|
|
60471
60567
|
}
|
|
60568
|
+
drainQueuedPrompt(key, next) {
|
|
60569
|
+
this.emitQueueUpdated(next.executionContext.chatKey, next.executionContext.sessionAlias, key);
|
|
60570
|
+
const concurrencyKey = next.concurrencyKey ?? key;
|
|
60571
|
+
const turnStarted = next.isPeerMessage ? { queueItemId: next.id, ...next.promptRequestId !== undefined ? { promptRequestId: next.promptRequestId } : {} } : { prompt: next.text, queueItemId: next.id, ...next.promptRequestId !== undefined ? { promptRequestId: next.promptRequestId } : {} };
|
|
60572
|
+
this.submit({
|
|
60573
|
+
chatKey: next.executionContext.chatKey,
|
|
60574
|
+
sessionAlias: next.executionContext.sessionAlias,
|
|
60575
|
+
boundSessionAlias: next.executionContext.boundSessionAlias,
|
|
60576
|
+
concurrencyKey,
|
|
60577
|
+
text: next.text,
|
|
60578
|
+
senderId: next.senderId,
|
|
60579
|
+
queueable: true,
|
|
60580
|
+
drained: true,
|
|
60581
|
+
isPeerMessage: next.isPeerMessage,
|
|
60582
|
+
allowRestoreArchived: next.allowRestoreArchived,
|
|
60583
|
+
preserveCoordinatorRoute: next.preserveCoordinatorRoute,
|
|
60584
|
+
promptRequestId: next.promptRequestId,
|
|
60585
|
+
peerOrigin: next.peerOrigin,
|
|
60586
|
+
trustedPeerCompletion: next.trustedPeerCompletion,
|
|
60587
|
+
...next.isOwner !== undefined ? { isOwner: next.isOwner } : {},
|
|
60588
|
+
...next.accountId !== undefined ? { accountId: next.accountId } : {},
|
|
60589
|
+
...next.media !== undefined ? { media: next.media } : {},
|
|
60590
|
+
...next.agentMentions !== undefined ? { agentMentions: next.agentMentions } : {},
|
|
60591
|
+
turnStarted
|
|
60592
|
+
});
|
|
60593
|
+
}
|
|
60472
60594
|
cancelTurn(chatKey, sessionAlias, concurrencyKey) {
|
|
60473
60595
|
const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
|
|
60474
60596
|
const entry = this.inFlight.get(key);
|
|
@@ -60480,12 +60602,21 @@ class TurnQueue {
|
|
|
60480
60602
|
}
|
|
60481
60603
|
async clearSession(chatKey, sessionAlias, concurrencyKey) {
|
|
60482
60604
|
const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
|
|
60605
|
+
const guardPreexisting = this.removing.has(key);
|
|
60606
|
+
this.removing.add(key);
|
|
60483
60607
|
const droppedFirst = this.queues.get(key);
|
|
60484
60608
|
if (droppedFirst) {
|
|
60485
60609
|
this.queues.delete(key);
|
|
60486
60610
|
this.emitQueueUpdated(chatKey, sessionAlias, key);
|
|
60487
60611
|
this.notifyQueuedPeerCancelled(droppedFirst);
|
|
60488
60612
|
}
|
|
60613
|
+
const droppedInterruptFirst = this.pendingInterrupts.get(key);
|
|
60614
|
+
if (droppedInterruptFirst) {
|
|
60615
|
+
this.pendingInterrupts.delete(key);
|
|
60616
|
+
this.emitPeerInterrupt("cancelled_before_start", droppedInterruptFirst.executionContext.chatKey, droppedInterruptFirst.executionContext.sessionAlias, droppedInterruptFirst);
|
|
60617
|
+
this.notifyQueuedPeerCancelled([droppedInterruptFirst]);
|
|
60618
|
+
this.emitQueueUpdated(chatKey, sessionAlias, key);
|
|
60619
|
+
}
|
|
60489
60620
|
const entry = this.inFlight.get(key);
|
|
60490
60621
|
if (entry) {
|
|
60491
60622
|
entry.controller.abort();
|
|
@@ -60496,10 +60627,17 @@ class TurnQueue {
|
|
|
60496
60627
|
this.emitQueueUpdated(chatKey, sessionAlias, key);
|
|
60497
60628
|
this.notifyQueuedPeerCancelled(droppedSecond);
|
|
60498
60629
|
}
|
|
60630
|
+
const droppedInterruptSecond = this.pendingInterrupts.get(key);
|
|
60631
|
+
if (droppedInterruptSecond) {
|
|
60632
|
+
this.pendingInterrupts.delete(key);
|
|
60633
|
+
this.emitPeerInterrupt("cancelled_before_start", droppedInterruptSecond.executionContext.chatKey, droppedInterruptSecond.executionContext.sessionAlias, droppedInterruptSecond);
|
|
60634
|
+
this.notifyQueuedPeerCancelled([droppedInterruptSecond]);
|
|
60635
|
+
this.emitQueueUpdated(chatKey, sessionAlias, key);
|
|
60636
|
+
}
|
|
60499
60637
|
}
|
|
60500
|
-
const cleared = !this.inFlight.has(key) && !this.draining.has(key);
|
|
60501
|
-
if (cleared) {
|
|
60502
|
-
this.removing.
|
|
60638
|
+
const cleared = !this.inFlight.has(key) && !this.draining.has(key) && !this.pendingInterrupts.has(key);
|
|
60639
|
+
if (cleared) {} else if (!guardPreexisting) {
|
|
60640
|
+
this.removing.delete(key);
|
|
60503
60641
|
}
|
|
60504
60642
|
return { cleared };
|
|
60505
60643
|
}
|
|
@@ -60508,6 +60646,14 @@ class TurnQueue {
|
|
|
60508
60646
|
}
|
|
60509
60647
|
cancelQueuedItem(chatKey, sessionAlias, itemId, concurrencyKey) {
|
|
60510
60648
|
const key = this.resolveKey(chatKey, sessionAlias, concurrencyKey);
|
|
60649
|
+
const pendingInterrupt = this.pendingInterrupts.get(key);
|
|
60650
|
+
if (pendingInterrupt?.id === itemId) {
|
|
60651
|
+
this.pendingInterrupts.delete(key);
|
|
60652
|
+
this.emitQueueUpdated(chatKey, sessionAlias, key);
|
|
60653
|
+
this.emitPeerInterrupt("cancelled_before_start", pendingInterrupt.executionContext.chatKey, pendingInterrupt.executionContext.sessionAlias, pendingInterrupt);
|
|
60654
|
+
this.notifyQueuedPeerCancelled([pendingInterrupt]);
|
|
60655
|
+
return { cancelled: true };
|
|
60656
|
+
}
|
|
60511
60657
|
const q = this.queues.get(key);
|
|
60512
60658
|
if (!q)
|
|
60513
60659
|
return { cancelled: false };
|
|
@@ -60578,6 +60724,7 @@ class ControlService {
|
|
|
60578
60724
|
...this.deps.onTurnIdleTimeout ? { onIdleTimeout: this.deps.onTurnIdleTimeout } : {},
|
|
60579
60725
|
...this.deps.cancelDrainTimeoutMs !== undefined ? { cancelDrainTimeoutMs: this.deps.cancelDrainTimeoutMs } : {},
|
|
60580
60726
|
...this.deps.onQueuedPeerCancelled ? { onQueuedPeerCancelled: this.deps.onQueuedPeerCancelled } : {},
|
|
60727
|
+
...this.deps.onPeerInterruptEvent ? { onPeerInterruptEvent: this.deps.onPeerInterruptEvent } : {},
|
|
60581
60728
|
emitQueueUpdated: (chatKey, sessionAlias, items) => this.deps.events.emit({
|
|
60582
60729
|
type: "queue-updated",
|
|
60583
60730
|
chatKey,
|
|
@@ -60865,6 +61012,7 @@ class ControlService {
|
|
|
60865
61012
|
return {
|
|
60866
61013
|
alias: toDisplaySessionAlias(session3.alias),
|
|
60867
61014
|
agent: session3.agent,
|
|
61015
|
+
driver: session3.driver,
|
|
60868
61016
|
workspace: session3.workspace,
|
|
60869
61017
|
transportSession: session3.transportSession,
|
|
60870
61018
|
running,
|
|
@@ -61101,7 +61249,7 @@ class ControlService {
|
|
|
61101
61249
|
if (!session3 || session3.archived === true) {
|
|
61102
61250
|
throw new AgentMessagingError("TARGET_UNAVAILABLE", "The target Agent session is not currently available or is archived.");
|
|
61103
61251
|
}
|
|
61104
|
-
const
|
|
61252
|
+
const peerParams = {
|
|
61105
61253
|
chatKey: input.chatKey,
|
|
61106
61254
|
sessionAlias: input.sessionAlias,
|
|
61107
61255
|
boundSessionAlias: internalAlias,
|
|
@@ -61113,17 +61261,28 @@ class ControlService {
|
|
|
61113
61261
|
allowRestoreArchived: false,
|
|
61114
61262
|
preserveCoordinatorRoute: true,
|
|
61115
61263
|
peerOrigin: input.peerOrigin
|
|
61116
|
-
}
|
|
61117
|
-
if (
|
|
61118
|
-
|
|
61119
|
-
|
|
61120
|
-
|
|
61121
|
-
if (admission.reason === "target-unavailable") {
|
|
61122
|
-
throw new AgentMessagingError("TARGET_UNAVAILABLE", "The target Agent session is currently being removed or archived.");
|
|
61264
|
+
};
|
|
61265
|
+
if (input.requestedMode === "interrupt") {
|
|
61266
|
+
const admission2 = this.turnQueue.submitPeerInterrupt(peerParams);
|
|
61267
|
+
if (admission2.status === "rejected") {
|
|
61268
|
+
this.throwPeerAdmissionError(admission2.reason, "interrupt");
|
|
61123
61269
|
}
|
|
61124
|
-
|
|
61270
|
+
return admission2.status === "injected" ? { status: "injected", modeUsed: "prompt", targetState: "idle" } : { status: "queued", modeUsed: "interrupt", targetState: "running" };
|
|
61271
|
+
}
|
|
61272
|
+
const admission = this.turnQueue.submitPeerTurn(peerParams);
|
|
61273
|
+
if (admission.status === "rejected") {
|
|
61274
|
+
this.throwPeerAdmissionError(admission.reason, "peer");
|
|
61125
61275
|
}
|
|
61126
|
-
return { status: admission.status };
|
|
61276
|
+
return { status: admission.status, modeUsed: "queue" };
|
|
61277
|
+
}
|
|
61278
|
+
throwPeerAdmissionError(reason, mode) {
|
|
61279
|
+
if (reason === "queue-full") {
|
|
61280
|
+
throw new AgentMessagingError("MESSAGE_QUEUE_FULL", mode === "interrupt" ? "The target Agent already has a pending peer interrupt; only one interrupt may be reserved at a time." : "The target Agent's message queue is full.");
|
|
61281
|
+
}
|
|
61282
|
+
if (reason === "target-unavailable") {
|
|
61283
|
+
throw new AgentMessagingError("TARGET_UNAVAILABLE", "The target Agent session is currently being removed or archived.");
|
|
61284
|
+
}
|
|
61285
|
+
throw new AgentMessagingError("DELIVERY_FAILED", `Peer turn admission rejected: ${reason}`);
|
|
61127
61286
|
}
|
|
61128
61287
|
async submitCompletionTurn(input) {
|
|
61129
61288
|
const session3 = await this.deps.sessions.getSession(input.sourceAlias);
|
|
@@ -61234,9 +61393,9 @@ var init_control_service = __esm(() => {
|
|
|
61234
61393
|
init_native_session_history();
|
|
61235
61394
|
init_workspace_fs();
|
|
61236
61395
|
init_workspace_git();
|
|
61396
|
+
init_turn_support();
|
|
61237
61397
|
init_session_turn_runner();
|
|
61238
61398
|
init_turn_queue();
|
|
61239
|
-
init_turn_support();
|
|
61240
61399
|
init_command_timeouts();
|
|
61241
61400
|
MODEL_SET_SETTLE_BUDGET_MS = 2 * (DEFAULT_MANAGEMENT_COMMAND_TIMEOUT_MS + BRIDGE_REQUEST_TIMEOUT_GRACE_MS);
|
|
61242
61401
|
});
|
|
@@ -62892,7 +63051,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
62892
63051
|
return null;
|
|
62893
63052
|
}
|
|
62894
63053
|
},
|
|
62895
|
-
deliverLogicalTurn: async (alias, renderedText, messageId, peerOrigin) => {
|
|
63054
|
+
deliverLogicalTurn: async (alias, renderedText, messageId, peerOrigin, requestedMode) => {
|
|
62896
63055
|
if (controlRef) {
|
|
62897
63056
|
const chatKey = `relay:agent-message:${alias}`;
|
|
62898
63057
|
return await controlRef.submitPeerTurn({
|
|
@@ -62902,10 +63061,11 @@ async function buildApp(paths, deps = {}) {
|
|
|
62902
63061
|
text: renderedText,
|
|
62903
63062
|
senderId: "agent-messaging",
|
|
62904
63063
|
messageId,
|
|
63064
|
+
requestedMode,
|
|
62905
63065
|
peerOrigin
|
|
62906
63066
|
});
|
|
62907
63067
|
}
|
|
62908
|
-
return { status: "queued" };
|
|
63068
|
+
return { status: "queued", modeUsed: "queue" };
|
|
62909
63069
|
},
|
|
62910
63070
|
deliverCompletionTurn: async (alias, completion, requestMessageId) => {
|
|
62911
63071
|
if (controlRef) {
|
|
@@ -63090,6 +63250,16 @@ async function buildApp(paths, deps = {}) {
|
|
|
63090
63250
|
idleMs
|
|
63091
63251
|
});
|
|
63092
63252
|
},
|
|
63253
|
+
onPeerInterruptEvent: (event) => {
|
|
63254
|
+
logger.info(`agent_messaging.peer_interrupt_${event.kind}`, "peer interrupt lane event", {
|
|
63255
|
+
chatKey: event.chatKey,
|
|
63256
|
+
sessionAlias: event.sessionAlias,
|
|
63257
|
+
...event.requestMessageId !== undefined ? { requestMessageId: event.requestMessageId } : {},
|
|
63258
|
+
...event.promptRequestId !== undefined ? { promptRequestId: event.promptRequestId } : {},
|
|
63259
|
+
...event.pendingRequestMessageId !== undefined ? { pendingRequestMessageId: event.pendingRequestMessageId } : {},
|
|
63260
|
+
...event.predecessorWasAlreadyAborted !== undefined ? { predecessorWasAlreadyAborted: event.predecessorWasAlreadyAborted } : {}
|
|
63261
|
+
});
|
|
63262
|
+
},
|
|
63093
63263
|
agentMessaging: {
|
|
63094
63264
|
deliverInbound: async (input) => await agentMessaging.deliverInbound(input),
|
|
63095
63265
|
deliverInboundCompletion: async (input) => await agentMessaging.deliverInboundCompletion(input),
|
|
@@ -14,6 +14,9 @@ export interface QueuedItemInfo {
|
|
|
14
14
|
id: string;
|
|
15
15
|
textPreview: string;
|
|
16
16
|
enqueuedAt: string;
|
|
17
|
+
/** v0.4: present ONLY for a reserved-but-not-started peer interrupt
|
|
18
|
+
* (snapshot-first item). Additive and backward compatible. */
|
|
19
|
+
kind?: "interrupt";
|
|
17
20
|
}
|
|
18
21
|
export type ControlEvent = {
|
|
19
22
|
type: "turn-output";
|
|
@@ -6,15 +6,16 @@ import type { CreateScheduledTaskInput, ScheduledTaskService } from "../schedule
|
|
|
6
6
|
import type { ScheduledTaskRecord } from "../scheduled/scheduled-types";
|
|
7
7
|
import type { CancelTaskInput, OrchestrationService, OrchestrationTaskFilter } from "../orchestration/orchestration-service";
|
|
8
8
|
import type { OrchestrationTaskRecord } from "../orchestration/orchestration-types";
|
|
9
|
-
import type { AgentMessageCompletion } from "../orchestration/agent-messaging-types";
|
|
9
|
+
import type { AgentMessageCompletion, AgentMessageMode } from "../orchestration/agent-messaging-types";
|
|
10
10
|
import type { ControlEventBus } from "./control-event-bus";
|
|
11
11
|
import type { AgentCatalogEntry } from "../config/agent-catalog";
|
|
12
12
|
import { type BrowseDirsResult, type DirListing, type FileContent, type SearchOptions, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
|
|
13
13
|
import { type GitCheckoutOptions, type GitCommitResult, type GitPushOptions, type GitStatus, type GitWorktreeCreateOptions, type GitWorktreeCreateResult } from "./workspace-git";
|
|
14
14
|
import type { AgentEndpointView, AgentMessageTraceRecord } from "../orchestration/agent-messaging-types";
|
|
15
15
|
import type { PromptAttachmentRef } from "@ganglion/xacpx-relay-protocol";
|
|
16
|
-
import type { UploadStore } from "./upload-store.js";
|
|
17
16
|
import { type TurnIdleTimeoutDetail, type PeerTurnOrigin } from "./turn-support";
|
|
17
|
+
import type { PeerInterruptEvent } from "./turn-queue";
|
|
18
|
+
import type { UploadStore } from "./upload-store.js";
|
|
18
19
|
import type { AppLogger } from "../logging/app-logger";
|
|
19
20
|
export interface ModelSetRequestOptions {
|
|
20
21
|
/** Connector-side deadline derived from the Hub request lifetime. */
|
|
@@ -23,6 +24,12 @@ export interface ModelSetRequestOptions {
|
|
|
23
24
|
export interface ControlSessionInfo {
|
|
24
25
|
alias: string;
|
|
25
26
|
agent: string;
|
|
27
|
+
/** The acpx driver backing `agent`, resolved from the agent's config at listing
|
|
28
|
+
* time. Lets the web render the brand icon without an agents-map lookup — which
|
|
29
|
+
* fails for sleeping sessions whose rows live outside the active session list.
|
|
30
|
+
* Optional: mirrors ResolvedSession.driver (custom agents always resolve one,
|
|
31
|
+
* but the type stays tolerant). */
|
|
32
|
+
driver?: string;
|
|
26
33
|
workspace: string;
|
|
27
34
|
transportSession: string;
|
|
28
35
|
running: boolean;
|
|
@@ -111,6 +118,7 @@ export interface ControlServiceDeps {
|
|
|
111
118
|
peerOrigin: PeerTurnOrigin;
|
|
112
119
|
promptRequestId?: string;
|
|
113
120
|
}) => void;
|
|
121
|
+
onPeerInterruptEvent?: (event: PeerInterruptEvent) => void;
|
|
114
122
|
agentMessaging?: {
|
|
115
123
|
deliverInbound(input: {
|
|
116
124
|
sourceNodeId: string;
|
|
@@ -394,10 +402,17 @@ export declare class ControlService {
|
|
|
394
402
|
text: string;
|
|
395
403
|
senderId: string;
|
|
396
404
|
messageId: string;
|
|
405
|
+
/** v0.4: "interrupt" routes through TurnQueue.submitPeerInterrupt (reserve →
|
|
406
|
+
* abort → true-settle → priority drain); every other mode keeps the
|
|
407
|
+
* non-cancelling peer path. */
|
|
408
|
+
requestedMode?: AgentMessageMode;
|
|
397
409
|
peerOrigin?: PeerTurnOrigin;
|
|
398
410
|
}): Promise<{
|
|
399
411
|
status: "injected" | "queued";
|
|
412
|
+
modeUsed: "prompt" | "queue" | "interrupt";
|
|
413
|
+
targetState?: "idle" | "running";
|
|
400
414
|
}>;
|
|
415
|
+
private throwPeerAdmissionError;
|
|
401
416
|
submitCompletionTurn(input: {
|
|
402
417
|
sourceAlias: string;
|
|
403
418
|
completion: AgentMessageCompletion;
|
|
@@ -5,6 +5,10 @@ export interface QueuedItemSnapshot {
|
|
|
5
5
|
id: string;
|
|
6
6
|
textPreview: string;
|
|
7
7
|
enqueuedAt: string;
|
|
8
|
+
/** v0.4: present ONLY for a reserved-but-not-started peer interrupt
|
|
9
|
+
* (snapshot-first item). Lets the existing per-item cancel surface reach
|
|
10
|
+
* the interrupt slot by id (spec §11.1) without a new RPC. */
|
|
11
|
+
kind?: "interrupt";
|
|
8
12
|
}
|
|
9
13
|
export interface TurnQueueDeps {
|
|
10
14
|
runTurn(req: TurnRequest, signal: AbortSignal, onActivity: () => void): Promise<TurnResult>;
|
|
@@ -21,6 +25,20 @@ export interface TurnQueueDeps {
|
|
|
21
25
|
peerOrigin: PeerTurnOrigin;
|
|
22
26
|
promptRequestId?: string;
|
|
23
27
|
}) => void;
|
|
28
|
+
onPeerInterruptEvent?: (event: PeerInterruptEvent) => void;
|
|
29
|
+
}
|
|
30
|
+
/** v0.4: structured peer-interrupt lifecycle event (spec §17). Ids and lane
|
|
31
|
+
* keys only — never message content. */
|
|
32
|
+
export interface PeerInterruptEvent {
|
|
33
|
+
kind: "reserved" | "abort_signalled" | "started" | "cancelled_before_start" | "rejected_pending";
|
|
34
|
+
chatKey: string;
|
|
35
|
+
sessionAlias: string;
|
|
36
|
+
requestMessageId?: string;
|
|
37
|
+
promptRequestId?: string;
|
|
38
|
+
/** rejected_pending only: requestMessageId of the reservation that occupied
|
|
39
|
+
* the slot (the event's own ids describe the REJECTED request). */
|
|
40
|
+
pendingRequestMessageId?: string;
|
|
41
|
+
predecessorWasAlreadyAborted?: boolean;
|
|
24
42
|
}
|
|
25
43
|
export interface SubmitParams {
|
|
26
44
|
chatKey: string;
|
|
@@ -42,6 +60,7 @@ export interface SubmitParams {
|
|
|
42
60
|
allowRestoreArchived?: boolean;
|
|
43
61
|
preserveCoordinatorRoute?: boolean;
|
|
44
62
|
/** Hub-issued pre-write correlation; stored on the queue item and carried onto the
|
|
63
|
+
|
|
45
64
|
* drained turn-started so the hub can correlate a queue item back to its
|
|
46
65
|
* pre-written inbound row (see PromptPayload.promptRequestId). */
|
|
47
66
|
promptRequestId?: string;
|
|
@@ -56,6 +75,23 @@ export type SubmitResult = TurnResult | {
|
|
|
56
75
|
queued: true;
|
|
57
76
|
queueItemId: string;
|
|
58
77
|
};
|
|
78
|
+
/** v0.4: result of `submitPeerInterrupt`. `queued`+`interrupt` = busy target:
|
|
79
|
+
* the reservation is held and the predecessor cancel has been signalled.
|
|
80
|
+
* `injected`+`prompt` = idle target: ordinary synchronous peer admission with
|
|
81
|
+
* zero cancellations. Rejections reuse the existing reasons ("target-unavailable",
|
|
82
|
+
* "queue-full" — the latter also enforces the one-pending-interrupt slot rule,
|
|
83
|
+
* spec §8/§16). */
|
|
84
|
+
export type PeerInterruptAdmission = {
|
|
85
|
+
status: "injected";
|
|
86
|
+
modeUsed: "prompt";
|
|
87
|
+
} | {
|
|
88
|
+
status: "queued";
|
|
89
|
+
modeUsed: "interrupt";
|
|
90
|
+
queueItemId: string;
|
|
91
|
+
} | {
|
|
92
|
+
status: "rejected";
|
|
93
|
+
reason: string;
|
|
94
|
+
};
|
|
59
95
|
export declare class TurnQueue {
|
|
60
96
|
private readonly deps;
|
|
61
97
|
private readonly setTimer;
|
|
@@ -72,6 +108,7 @@ export declare class TurnQueue {
|
|
|
72
108
|
private readonly settledRequestIds;
|
|
73
109
|
private static readonly SETTLED_REQUEST_IDS_MAX;
|
|
74
110
|
private readonly queues;
|
|
111
|
+
private readonly pendingInterrupts;
|
|
75
112
|
private readonly draining;
|
|
76
113
|
private readonly removing;
|
|
77
114
|
private resolveKey;
|
|
@@ -83,6 +120,20 @@ export declare class TurnQueue {
|
|
|
83
120
|
status: "rejected";
|
|
84
121
|
reason: string;
|
|
85
122
|
};
|
|
123
|
+
/** v0.4 test/observability seam: 1 when a reserved-but-not-started peer
|
|
124
|
+
* interrupt occupies the lane's slot, else 0 (spec §7: at most one). */
|
|
125
|
+
pendingInterruptCount(chatKey: string, sessionAlias: string, concurrencyKey?: string): number;
|
|
126
|
+
/**
|
|
127
|
+
* v0.4 Peer Interrupt Delivery (spec §6.4/§9): atomic reserve → abort →
|
|
128
|
+
* true-settle → priority-drain, owned entirely by this lane. Synchronous:
|
|
129
|
+
* by the time the accepted result returns, the reservation exists in the
|
|
130
|
+
* lane and a live predecessor's AbortController has been signalled. The
|
|
131
|
+
* interrupting turn itself starts only after the predecessor's `settled`
|
|
132
|
+
* resolves and its inFlight entry is gone (drain hand-off in advanceQueue).
|
|
133
|
+
* Router-level cancel();submit() races are impossible by construction here.
|
|
134
|
+
*/
|
|
135
|
+
submitPeerInterrupt(params: SubmitParams): PeerInterruptAdmission;
|
|
136
|
+
private emitPeerInterrupt;
|
|
86
137
|
private hasSettledRequestId;
|
|
87
138
|
private recordSettledRequestId;
|
|
88
139
|
/** Terminal cancellation for queued peer items dropped before execution. */
|
|
@@ -90,6 +141,7 @@ export declare class TurnQueue {
|
|
|
90
141
|
private emitQueueUpdated;
|
|
91
142
|
submit(params: SubmitParams): Promise<SubmitResult>;
|
|
92
143
|
private advanceQueue;
|
|
144
|
+
private drainQueuedPrompt;
|
|
93
145
|
cancelTurn(chatKey: string, sessionAlias: string, concurrencyKey?: string): boolean;
|
|
94
146
|
/** Tear down all turn state for a session that is being removed or archived: drop every
|
|
95
147
|
* queued prompt, abort a running turn, and wait (bounded) for it to unwind. The queue is
|
|
@@ -104,9 +156,14 @@ export declare class TurnQueue {
|
|
|
104
156
|
* in-flight turn and dropped every queued prompt (emitting `queue-updated([])`). The caller
|
|
105
157
|
* should surface a retry, not present the failure as a no-op.
|
|
106
158
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
159
|
+
* The teardown guard (`removing`) is armed SYNCHRONOUSLY on ENTRY — before
|
|
160
|
+
* any await — so peer admissions (`submitPeerTurn` / `submitPeerInterrupt`)
|
|
161
|
+
* fail closed `target-unavailable` for the ENTIRE clear lifecycle; a peer
|
|
162
|
+
* interrupt arriving mid-unwind can never be reserved and drained onto the
|
|
163
|
+
* dying session. On `cleared: true` the guard stays armed across the
|
|
164
|
+
* caller's transport teardown; on `cleared: false` it is released so a
|
|
165
|
+
* retry sees a usable lane. The caller MUST call `finishClear` once
|
|
166
|
+
* teardown settles (success or failure) to release the guard. */
|
|
110
167
|
clearSession(chatKey: string, sessionAlias: string, concurrencyKey?: string): Promise<{
|
|
111
168
|
cleared: boolean;
|
|
112
169
|
}>;
|