@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 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
- /** A recovery gate rejected this input before any native execution. */
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
- if (prompt.recoveryDeferred && prompt.sourceMessageId) {
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
- checkpoints.settle(prompt.sourceMessageId);
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
- // The local queue is going away, but never-started deferred input still
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
- if (prompt.recoveryDeferred && prompt.sourceMessageId)
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
- recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
1945
- await endpoint.setInboundState(`message:${input.conversationId}:${input.message.id}`, 'settled', 'not-dispatched');
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
- recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
1954
- await endpoint.setInboundState(`message:${input.conversationId}:${input.message.id}`, 'settled', 'not-dispatched');
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
- recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
1963
- await endpoint.setInboundState(`message:${input.conversationId}:${input.message.id}`, 'settled', 'not-dispatched');
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
- recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
2038
- await endpoint.setInboundState(`message:${input.conversationId}:${input.message.id}`, 'settled', 'not-dispatched');
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
- recoveryCheckpointsFor(input.conversationId).settle(input.message.id);
2074
- await endpoint.setInboundState(`message:${input.conversationId}:${input.message.id}`, 'settled', 'not-dispatched');
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
- let journaledInput = false;
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
- if (inboundId && !await endpoint.claimInbound(inboundId))
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
- journaledInput = !!inboundId;
2386
- let result = await runTurnOnce();
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: journaledInput ? nextTurn.sourceMessageId : null,
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 (!journaledInput && inboundId && error instanceof Error
2555
- && 'code' in error && error.code === 'SYNC_NOT_READY') {
2556
- // Recovery can be invalidated after the inbox offers an input. No
2557
- // native work has started: keep this same queued turn for the host
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
- if (!claimInboundMessageId(message.id))
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
- recoveryCheckpointsFor(payload.conversationId).settle(message.id);
2954
- settleInboundMessageId(message.id, true);
2955
- await endpoint.setInboundState(`message:${payload.conversationId}:${message.id}`, 'settled', 'observe-only');
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
- }).then(() => settleInboundMessageId(message.id, true), (error) => {
2970
- settleInboundMessageId(message.id, false);
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",
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.1",
35
+ "@canonmsg/agent-sdk": "^11.0.2",
36
36
  "@canonmsg/agent-tools": "^0.11.0",
37
- "@canonmsg/coding-agent-host": "^0.9.0",
38
- "@canonmsg/core": "^13.0.4",
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
  },