@canonmsg/codex-plugin 0.32.3 → 0.32.5
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/host.d.ts +3 -2
- package/dist/host.js +93 -81
- package/package.json +4 -4
package/dist/host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type TurnArtifactRoutingDecision, type TurnArtifactRoutingMode, type RecoveryCheckpointTracker } from '@canonmsg/coding-agent-host';
|
|
3
3
|
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeFact, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type PreparedExecutionEnvironment, type WorkspaceOption, type CanonWorkspaceRootMetadata, type DeliveryIntent, type RuntimeStreamingPayload, type TurnLifecycleState, type TurnOutputBlock, type TurnVerbosity, type TurnVerbosityConfig } from '@canonmsg/core';
|
|
4
|
+
import type { EndpointInboundHandoff } from '@canonmsg/core';
|
|
4
5
|
import type { AgentReplyAuthorityV1 } from '@canonmsg/backend-contracts';
|
|
5
6
|
import { CodexConversationAdapter, type CodexSandboxMode } from './adapter.js';
|
|
6
7
|
import { CodexAppServerAdapter, type CodexSkillMetadata } from './app-server-adapter.js';
|
|
@@ -36,8 +37,7 @@ interface Session {
|
|
|
36
37
|
planMode?: boolean;
|
|
37
38
|
intent: DeliveryIntent;
|
|
38
39
|
sourceMessageId?: string | null;
|
|
39
|
-
|
|
40
|
-
recoveryDeferred?: boolean;
|
|
40
|
+
inboundHandoff?: EndpointInboundHandoff;
|
|
41
41
|
markAccepted?: boolean;
|
|
42
42
|
imagePaths?: string[];
|
|
43
43
|
mediaAddDirs?: string[];
|
|
@@ -64,6 +64,7 @@ interface Session {
|
|
|
64
64
|
currentReplyAuthority: AgentReplyAuthorityV1 | null;
|
|
65
65
|
/** Cancels Canon interactions created by the currently running Codex turn. */
|
|
66
66
|
currentTurnAbortController: AbortController | null;
|
|
67
|
+
currentInboundHandoff?: EndpointInboundHandoff;
|
|
67
68
|
/**
|
|
68
69
|
* The verbosity the RUNNING turn was opened with. Promoted off the queue
|
|
69
70
|
* entry at `runNextTurn` and never re-read mid-turn, so the live writer, the
|
package/dist/host.js
CHANGED
|
@@ -1072,9 +1072,7 @@ export async function main() {
|
|
|
1072
1072
|
}
|
|
1073
1073
|
async function markQueuedPromptsRejected(conversationId, prompts) {
|
|
1074
1074
|
await Promise.all(prompts.map(async (prompt) => {
|
|
1075
|
-
|
|
1076
|
-
await endpoint.setInboundState(`message:${conversationId}:${prompt.sourceMessageId}`, 'settled', 'canceled-before-execution');
|
|
1077
|
-
}
|
|
1075
|
+
await prompt.inboundHandoff?.cancel('canceled-before-execution');
|
|
1078
1076
|
if (!prompt.markAccepted || !prompt.sourceMessageId)
|
|
1079
1077
|
return Promise.resolve();
|
|
1080
1078
|
return client.updateMessageDisposition(conversationId, prompt.sourceMessageId, 'rejected').catch(() => { });
|
|
@@ -1082,10 +1080,12 @@ export async function main() {
|
|
|
1082
1080
|
}
|
|
1083
1081
|
function settleRejectedPromptCheckpoints(conversationId, prompts) {
|
|
1084
1082
|
const checkpoints = recoveryCheckpointsFor(conversationId);
|
|
1085
|
-
for (const prompt of prompts)
|
|
1086
|
-
|
|
1083
|
+
for (const prompt of prompts) {
|
|
1084
|
+
if (!prompt.inboundHandoff || prompt.inboundHandoff.phase === 'settled')
|
|
1085
|
+
checkpoints.settle(prompt.sourceMessageId);
|
|
1086
|
+
}
|
|
1087
1087
|
}
|
|
1088
|
-
function removeQueuedPrompt(conversationId, sourceMessageId) {
|
|
1088
|
+
async function removeQueuedPrompt(conversationId, sourceMessageId) {
|
|
1089
1089
|
const session = sessions.get(conversationId);
|
|
1090
1090
|
if (!session || session.queue.length === 0)
|
|
1091
1091
|
return;
|
|
@@ -1093,6 +1093,7 @@ export async function main() {
|
|
|
1093
1093
|
if (removed.length === 0)
|
|
1094
1094
|
return;
|
|
1095
1095
|
session.queue = session.queue.filter((prompt) => prompt.sourceMessageId !== sourceMessageId);
|
|
1096
|
+
await Promise.all(removed.map((prompt) => prompt.inboundHandoff?.cancel('message-deleted')));
|
|
1096
1097
|
settleRejectedPromptCheckpoints(conversationId, removed);
|
|
1097
1098
|
writeTurn(session);
|
|
1098
1099
|
}
|
|
@@ -1267,13 +1268,11 @@ export async function main() {
|
|
|
1267
1268
|
if (!session)
|
|
1268
1269
|
return;
|
|
1269
1270
|
session.closed = true;
|
|
1270
|
-
//
|
|
1271
|
-
// belongs to the durable inbox. Release only that queue's dedupe ownership
|
|
1272
|
-
// so an eligible reoffer can reconstruct it after eviction or removal.
|
|
1271
|
+
// Closing a native session relinquishes only unsubmitted Canon work.
|
|
1273
1272
|
for (const prompt of session.queue) {
|
|
1274
|
-
|
|
1275
|
-
acceptedInboundMessageIds.delete(prompt.sourceMessageId);
|
|
1273
|
+
void prompt.inboundHandoff?.release('session-closed').catch(console.error);
|
|
1276
1274
|
}
|
|
1275
|
+
void session.currentInboundHandoff?.release('session-closed').catch(console.error);
|
|
1277
1276
|
session.currentTurnAbortController?.abort(new Error('Codex session closed'));
|
|
1278
1277
|
session.currentTurnAbortController = null;
|
|
1279
1278
|
stopVisibleWorkSignal(session);
|
|
@@ -1291,6 +1290,7 @@ export async function main() {
|
|
|
1291
1290
|
async function resetRuntimeSession(session) {
|
|
1292
1291
|
const conversationId = session.conversationId;
|
|
1293
1292
|
session.resetRequested = true;
|
|
1293
|
+
void session.currentInboundHandoff?.cancel('canceled-before-execution').catch(console.error);
|
|
1294
1294
|
const droppedPrompts = session.queue.splice(0);
|
|
1295
1295
|
await markQueuedPromptsRejected(conversationId, droppedPrompts);
|
|
1296
1296
|
settleRejectedPromptCheckpoints(conversationId, droppedPrompts);
|
|
@@ -1516,6 +1516,7 @@ export async function main() {
|
|
|
1516
1516
|
const nextPrompt = {
|
|
1517
1517
|
prompt,
|
|
1518
1518
|
skillInvocationText: turn.skillInvocationText,
|
|
1519
|
+
inboundHandoff: turn.inboundHandoff,
|
|
1519
1520
|
intent,
|
|
1520
1521
|
sourceMessageId,
|
|
1521
1522
|
markAccepted,
|
|
@@ -1941,8 +1942,9 @@ export async function main() {
|
|
|
1941
1942
|
knownConversationIds.add(input.conversationId);
|
|
1942
1943
|
if (input.turnDispatch && input.turnDispatch.kind !== 'run_turn') {
|
|
1943
1944
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed server-dispatched turn: ${input.turnDispatch.reason}`);
|
|
1944
|
-
|
|
1945
|
-
|
|
1945
|
+
if (await input.inboundHandoff.settle('not-dispatched')) {
|
|
1946
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1947
|
+
}
|
|
1946
1948
|
return;
|
|
1947
1949
|
}
|
|
1948
1950
|
if (input.message.metadata?.type === 'plan_approval_reply') {
|
|
@@ -1950,8 +1952,9 @@ export async function main() {
|
|
|
1950
1952
|
// A service agent never advertises or enters plan mode. Consume stale
|
|
1951
1953
|
// replies left by an older coding descriptor instead of turning them
|
|
1952
1954
|
// into hidden plan/implementation prompts after an upgrade or restart.
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
+
if (await input.inboundHandoff.settle('not-dispatched')) {
|
|
1956
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1957
|
+
}
|
|
1955
1958
|
return;
|
|
1956
1959
|
}
|
|
1957
1960
|
const planId = readString(input.message.metadata, 'planId');
|
|
@@ -1959,8 +1962,9 @@ export async function main() {
|
|
|
1959
1962
|
senderId: input.message.senderId,
|
|
1960
1963
|
metadata: input.message.metadata,
|
|
1961
1964
|
})) {
|
|
1962
|
-
|
|
1963
|
-
|
|
1965
|
+
if (await input.inboundHandoff.settle('not-dispatched')) {
|
|
1966
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
1967
|
+
}
|
|
1964
1968
|
return;
|
|
1965
1969
|
}
|
|
1966
1970
|
const session = await getOrCreateSession(input.conversationId);
|
|
@@ -1974,6 +1978,7 @@ export async function main() {
|
|
|
1974
1978
|
enqueuePrompt(session, prompt, 'queue', false, input.message.id, false, [], [], decision !== 'approve', {
|
|
1975
1979
|
canUseCodexAppTools: input.isOwner || serviceAgentMode,
|
|
1976
1980
|
requestingUserId: getCodexRequestingUserId(input.message),
|
|
1981
|
+
inboundHandoff: input.inboundHandoff,
|
|
1977
1982
|
});
|
|
1978
1983
|
return;
|
|
1979
1984
|
}
|
|
@@ -2034,8 +2039,9 @@ export async function main() {
|
|
|
2034
2039
|
: decideAutoReply(participantContext, behavior);
|
|
2035
2040
|
if (!autoReply.allow) {
|
|
2036
2041
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Suppressed auto-reply: ${autoReply.reason}`);
|
|
2037
|
-
|
|
2038
|
-
|
|
2042
|
+
if (await input.inboundHandoff.settle('not-dispatched')) {
|
|
2043
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
2044
|
+
}
|
|
2039
2045
|
return;
|
|
2040
2046
|
}
|
|
2041
2047
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Message from ${input.senderName}: "${content.slice(0, 80)}" (${autoReply.reason})`);
|
|
@@ -2070,8 +2076,9 @@ export async function main() {
|
|
|
2070
2076
|
},
|
|
2071
2077
|
...(input.replyAuthority ? { replyAuthority: input.replyAuthority } : {}),
|
|
2072
2078
|
}).catch(() => { });
|
|
2073
|
-
|
|
2074
|
-
|
|
2079
|
+
if (await input.inboundHandoff.settle('not-dispatched')) {
|
|
2080
|
+
recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
|
|
2081
|
+
}
|
|
2075
2082
|
return;
|
|
2076
2083
|
}
|
|
2077
2084
|
session.activeSelfContextId = activeSelfContextId;
|
|
@@ -2090,10 +2097,12 @@ export async function main() {
|
|
|
2090
2097
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
2091
2098
|
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
|
|
2092
2099
|
...resolveCodexTurnModes(participantContext, input.message),
|
|
2100
|
+
inboundHandoff: input.inboundHandoff,
|
|
2093
2101
|
skillInvocationText: input.message.senderType === 'human' ? input.message.text ?? undefined : undefined,
|
|
2094
2102
|
replyAuthority: input.replyAuthority ?? null,
|
|
2095
2103
|
});
|
|
2096
2104
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
2105
|
+
void session.currentInboundHandoff?.cancel('canceled-before-execution').catch(console.error);
|
|
2097
2106
|
session.currentTurnAbortController?.abort(new CodexTurnCanceledError('Codex turn interrupted by a newer message'));
|
|
2098
2107
|
await session.adapter.interrupt().catch(() => { });
|
|
2099
2108
|
clearStreaming(input.conversationId);
|
|
@@ -2102,6 +2111,7 @@ export async function main() {
|
|
|
2102
2111
|
}
|
|
2103
2112
|
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, {
|
|
2104
2113
|
...resolveCodexTurnModes(participantContext, input.message),
|
|
2114
|
+
inboundHandoff: input.inboundHandoff,
|
|
2105
2115
|
skillInvocationText: input.message.senderType === 'human' ? input.message.text ?? undefined : undefined,
|
|
2106
2116
|
replyAuthority: input.replyAuthority ?? null,
|
|
2107
2117
|
});
|
|
@@ -2151,7 +2161,7 @@ export async function main() {
|
|
|
2151
2161
|
return;
|
|
2152
2162
|
}
|
|
2153
2163
|
const inboundId = nextTurn.sourceMessageId ? `message:${session.conversationId}:${nextTurn.sourceMessageId}` : null;
|
|
2154
|
-
|
|
2164
|
+
session.currentInboundHandoff = nextTurn.inboundHandoff;
|
|
2155
2165
|
let inputDeferred = false;
|
|
2156
2166
|
let preserveUnclaimedInput = false;
|
|
2157
2167
|
let completedOutput;
|
|
@@ -2380,10 +2390,33 @@ export async function main() {
|
|
|
2380
2390
|
// Canon-origin queues must retain a durable execution owner, including
|
|
2381
2391
|
// legacy queues restored before endpoint migration. Native continuations
|
|
2382
2392
|
// without a Canon source message remain a separate operator path.
|
|
2383
|
-
|
|
2393
|
+
const handoff = nextTurn.inboundHandoff ?? (inboundId ? await endpoint.acquireInbound(inboundId) : undefined);
|
|
2394
|
+
session.currentInboundHandoff = handoff ?? undefined;
|
|
2395
|
+
if (inboundId && !handoff) {
|
|
2396
|
+
preserveUnclaimedInput = true;
|
|
2384
2397
|
return;
|
|
2385
|
-
|
|
2386
|
-
|
|
2398
|
+
}
|
|
2399
|
+
if (handoff && (session.resetRequested || session.currentTurnAbortController?.signal.reason instanceof CodexTurnCanceledError)) {
|
|
2400
|
+
await handoff.cancel('canceled-before-execution');
|
|
2401
|
+
}
|
|
2402
|
+
else if (handoff && (session.closed || session.currentTurnAbortController?.signal.aborted)) {
|
|
2403
|
+
await handoff.release('session-closed');
|
|
2404
|
+
}
|
|
2405
|
+
const started = handoff
|
|
2406
|
+
? await endpoint.startInbound(handoff, runTurnOnce)
|
|
2407
|
+
: { status: 'submitted', value: await runTurnOnce() };
|
|
2408
|
+
if (started.status !== 'submitted') {
|
|
2409
|
+
if (started.status === 'deferred' && !session.closed && !session.resetRequested
|
|
2410
|
+
&& !session.currentTurnAbortController?.signal.aborted) {
|
|
2411
|
+
nextTurn.inboundHandoff = handoff ?? undefined;
|
|
2412
|
+
session.queue.unshift(nextTurn);
|
|
2413
|
+
inputDeferred = true;
|
|
2414
|
+
}
|
|
2415
|
+
preserveUnclaimedInput = inputDeferred || handoff?.phase === 'released';
|
|
2416
|
+
clearStreaming(session.conversationId);
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
let result = started.value;
|
|
2387
2420
|
if (!result.interrupted
|
|
2388
2421
|
&& !result.finalMessage
|
|
2389
2422
|
&& result.exitCode
|
|
@@ -2427,7 +2460,7 @@ export async function main() {
|
|
|
2427
2460
|
: { kind: 'none', reason: result.interrupted ? 'interrupted' : session.currentTurnSilenced ? 'silent' : 'empty' };
|
|
2428
2461
|
const record = {
|
|
2429
2462
|
version: 1, turnId: session.currentTurnId, conversationId: session.conversationId,
|
|
2430
|
-
sourceMessageId:
|
|
2463
|
+
sourceMessageId: inboundId ? nextTurn.sourceMessageId : null,
|
|
2431
2464
|
nativeThreadId: result.threadId, createdAt: new Date().toISOString(), output,
|
|
2432
2465
|
audience: conversationCache.has(session.conversationId) ? {
|
|
2433
2466
|
memberIds: [...conversationCache.get(session.conversationId).memberIds].sort(),
|
|
@@ -2551,31 +2584,12 @@ export async function main() {
|
|
|
2551
2584
|
await deliverCompletedOutput(completedOutput);
|
|
2552
2585
|
}
|
|
2553
2586
|
catch (error) {
|
|
2554
|
-
if (!
|
|
2555
|
-
|
|
2556
|
-
//
|
|
2557
|
-
|
|
2558
|
-
// heartbeat instead of publishing a failure or suppressing its retry
|
|
2559
|
-
// through the accepted-message dedupe cache.
|
|
2560
|
-
if (session.resetRequested || session.currentTurnAbortController?.signal.reason instanceof CodexTurnCanceledError) {
|
|
2561
|
-
await endpoint.setInboundState(inboundId, 'settled', 'canceled-before-execution');
|
|
2562
|
-
}
|
|
2563
|
-
else {
|
|
2564
|
-
preserveUnclaimedInput = true;
|
|
2565
|
-
if (!session.closed && !session.currentTurnAbortController?.signal.aborted) {
|
|
2566
|
-
nextTurn.recoveryDeferred = true;
|
|
2567
|
-
session.queue.unshift(nextTurn);
|
|
2568
|
-
inputDeferred = true;
|
|
2569
|
-
}
|
|
2570
|
-
else {
|
|
2571
|
-
// A closed/shutting-down session cannot retain the local queue.
|
|
2572
|
-
// Leave its durable offer available to the next live session.
|
|
2573
|
-
acceptedInboundMessageIds.delete(nextTurn.sourceMessageId);
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
// The attempt may already have published its empty thinking row.
|
|
2577
|
-
// With no native output there is nothing to hand off or salvage.
|
|
2587
|
+
if (!nativeCompleted && inboundId && session.currentInboundHandoff?.phase !== 'submitted') {
|
|
2588
|
+
// A store/preflight failure proves no native callback ran. The shared
|
|
2589
|
+
// inbox can reoffer it; a generic failure reply is not completion proof.
|
|
2590
|
+
preserveUnclaimedInput = session.currentInboundHandoff?.phase !== 'settled';
|
|
2578
2591
|
clearStreaming(session.conversationId);
|
|
2592
|
+
console.error('[canon-codex] Native input awaits handoff recovery:', error);
|
|
2579
2593
|
return;
|
|
2580
2594
|
}
|
|
2581
2595
|
if (nativeCompleted) {
|
|
@@ -2626,8 +2640,26 @@ export async function main() {
|
|
|
2626
2640
|
finally {
|
|
2627
2641
|
if (completedOutput)
|
|
2628
2642
|
activeCompletedOutputs.delete(completedOutput.turnId);
|
|
2643
|
+
const handoff = session.currentInboundHandoff;
|
|
2644
|
+
if (!inputDeferred && handoff && handoff.phase !== 'submitted' && handoff.phase !== 'settled') {
|
|
2645
|
+
preserveUnclaimedInput = true;
|
|
2646
|
+
try {
|
|
2647
|
+
await handoff.release('native-preflight');
|
|
2648
|
+
}
|
|
2649
|
+
catch (error) {
|
|
2650
|
+
// Keep the existing queue/heartbeat as the local retry owner if the
|
|
2651
|
+
// durable release itself fails. Never silently discard the handle.
|
|
2652
|
+
if (!session.closed && !session.currentTurnAbortController?.signal.aborted) {
|
|
2653
|
+
nextTurn.inboundHandoff = handoff;
|
|
2654
|
+
session.queue.unshift(nextTurn);
|
|
2655
|
+
inputDeferred = true;
|
|
2656
|
+
}
|
|
2657
|
+
console.error('[canon-codex] Failed to release unsubmitted input:', error);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2629
2660
|
session.currentTurnAbortController?.abort(new Error('Codex turn ended'));
|
|
2630
2661
|
session.currentTurnAbortController = null;
|
|
2662
|
+
session.currentInboundHandoff = undefined;
|
|
2631
2663
|
if (!preserveUnclaimedInput)
|
|
2632
2664
|
recoveryCheckpointsFor(session.conversationId).settle(nextTurn.sourceMessageId);
|
|
2633
2665
|
stopVisibleWorkSignal(session);
|
|
@@ -2650,30 +2682,6 @@ export async function main() {
|
|
|
2650
2682
|
}
|
|
2651
2683
|
}
|
|
2652
2684
|
}
|
|
2653
|
-
const acceptedInboundMessageIds = new Set();
|
|
2654
|
-
const inFlightInboundMessageIds = new Set();
|
|
2655
|
-
function claimInboundMessageId(messageId) {
|
|
2656
|
-
if (!messageId)
|
|
2657
|
-
return true;
|
|
2658
|
-
if (acceptedInboundMessageIds.has(messageId) || inFlightInboundMessageIds.has(messageId))
|
|
2659
|
-
return false;
|
|
2660
|
-
inFlightInboundMessageIds.add(messageId);
|
|
2661
|
-
return true;
|
|
2662
|
-
}
|
|
2663
|
-
function settleInboundMessageId(messageId, accepted) {
|
|
2664
|
-
if (!messageId)
|
|
2665
|
-
return;
|
|
2666
|
-
inFlightInboundMessageIds.delete(messageId);
|
|
2667
|
-
if (!accepted)
|
|
2668
|
-
return;
|
|
2669
|
-
acceptedInboundMessageIds.add(messageId);
|
|
2670
|
-
while (acceptedInboundMessageIds.size > 2_048) {
|
|
2671
|
-
const oldest = acceptedInboundMessageIds.values().next().value;
|
|
2672
|
-
if (!oldest)
|
|
2673
|
-
break;
|
|
2674
|
-
acceptedInboundMessageIds.delete(oldest);
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
2685
|
const hostAvailableExecutionModes = serviceAgentMode
|
|
2678
2686
|
? ['locked']
|
|
2679
2687
|
: [...EXECUTION_ENVIRONMENT_MODES];
|
|
@@ -2804,6 +2812,7 @@ export async function main() {
|
|
|
2804
2812
|
}
|
|
2805
2813
|
console.error(`[canon-codex] [${conversationId.slice(0, 8)}] ${type} signal`);
|
|
2806
2814
|
if (session.running) {
|
|
2815
|
+
void session.currentInboundHandoff?.cancel('canceled-before-execution').catch(console.error);
|
|
2807
2816
|
session.currentTurnAbortController?.abort(new CodexTurnCanceledError(`Codex turn interrupted by ${type}`));
|
|
2808
2817
|
}
|
|
2809
2818
|
if (type === 'stop_and_drop') {
|
|
@@ -2945,17 +2954,19 @@ export async function main() {
|
|
|
2945
2954
|
await endpoint.setInboundState(event.id, 'settled', 'own-message');
|
|
2946
2955
|
return;
|
|
2947
2956
|
}
|
|
2948
|
-
|
|
2957
|
+
const handoff = await endpoint.acquireInbound(event.id);
|
|
2958
|
+
if (!handoff)
|
|
2949
2959
|
return;
|
|
2950
2960
|
recoveryCheckpointsFor(payload.conversationId).track(message.id);
|
|
2951
2961
|
if (payload.turnDispatch && payload.turnDispatch.kind !== 'run_turn') {
|
|
2952
2962
|
console.error(`[canon-codex] [${payload.conversationId.slice(0, 8)}] Ignoring server-dispatched observe-only message: ${payload.turnDispatch.reason}`);
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2963
|
+
if (await handoff.settle('observe-only')) {
|
|
2964
|
+
recoveryCheckpointsFor(payload.conversationId).settle(message.id);
|
|
2965
|
+
}
|
|
2956
2966
|
return;
|
|
2957
2967
|
}
|
|
2958
2968
|
await enqueueInboundMessage({
|
|
2969
|
+
inboundHandoff: handoff,
|
|
2959
2970
|
conversationId: payload.conversationId,
|
|
2960
2971
|
message,
|
|
2961
2972
|
senderName: message.senderName || message.senderId,
|
|
@@ -2966,8 +2977,8 @@ export async function main() {
|
|
|
2966
2977
|
provenance: payload.provenance,
|
|
2967
2978
|
turnDispatch: payload.turnDispatch,
|
|
2968
2979
|
replyAuthority: payload.replyAuthority,
|
|
2969
|
-
}).
|
|
2970
|
-
|
|
2980
|
+
}).catch(async (error) => {
|
|
2981
|
+
await handoff.release('queue-failed');
|
|
2971
2982
|
console.error(`[canon-codex] [${payload.conversationId.slice(0, 8)}] Failed to queue inbound message:`, error instanceof Error ? error.message : error);
|
|
2972
2983
|
throw error;
|
|
2973
2984
|
});
|
|
@@ -2983,7 +2994,7 @@ export async function main() {
|
|
|
2983
2994
|
data: payload });
|
|
2984
2995
|
},
|
|
2985
2996
|
onMessageDeleted: (payload) => {
|
|
2986
|
-
removeQueuedPrompt(payload.conversationId, payload.messageId);
|
|
2997
|
+
void removeQueuedPrompt(payload.conversationId, payload.messageId).catch(console.error);
|
|
2987
2998
|
},
|
|
2988
2999
|
onConversationUpdated: (payload) => {
|
|
2989
3000
|
handleConversationUpdated(payload);
|
|
@@ -3063,6 +3074,7 @@ export async function main() {
|
|
|
3063
3074
|
clearInterval(heartbeat);
|
|
3064
3075
|
clearInterval(idleCheck);
|
|
3065
3076
|
for (const session of sessions.values()) {
|
|
3077
|
+
void session.currentInboundHandoff?.release('host-shutdown').catch(console.error);
|
|
3066
3078
|
session.currentTurnAbortController?.abort(new Error('Codex host shutting down'));
|
|
3067
3079
|
}
|
|
3068
3080
|
runtimeRequests.dispose();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.5",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"smoke:protocol": "node scripts/smoke-protocol.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@canonmsg/agent-sdk": "^11.0.
|
|
35
|
+
"@canonmsg/agent-sdk": "^11.0.2",
|
|
36
36
|
"@canonmsg/agent-tools": "^0.11.0",
|
|
37
|
-
"@canonmsg/coding-agent-host": "^0.
|
|
38
|
-
"@canonmsg/core": "^13.0.
|
|
37
|
+
"@canonmsg/coding-agent-host": "^0.10.0",
|
|
38
|
+
"@canonmsg/core": "^13.0.5",
|
|
39
39
|
"@canonmsg/rich-cards": "^0.10.6",
|
|
40
40
|
"ws": "^8.21.3"
|
|
41
41
|
},
|