@canonmsg/claude-code-plugin 0.12.3 → 0.12.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.
@@ -1,2 +1,2 @@
1
- export declare function isDirectExecution(moduleUrl: string): boolean;
2
- export declare function runCli(moduleUrl: string, main: () => void | Promise<void>, onError: (error: unknown) => void): void;
1
+ export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
2
+ export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core';
package/dist/cli-entry.js CHANGED
@@ -1,26 +1 @@
1
- import { realpathSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
- import { pathToFileURL } from 'node:url';
4
- export function isDirectExecution(moduleUrl) {
5
- const entry = process.argv[1];
6
- if (!entry)
7
- return false;
8
- const abs = resolve(entry);
9
- let real = abs;
10
- try {
11
- real = realpathSync(abs);
12
- }
13
- catch {
14
- // argv[1] may not exist on disk in rare harnesses; fall back to abs
15
- }
16
- return (pathToFileURL(real).href === moduleUrl ||
17
- pathToFileURL(abs).href === moduleUrl);
18
- }
19
- export function runCli(moduleUrl, main, onError) {
20
- if (!isDirectExecution(moduleUrl)) {
21
- return;
22
- }
23
- Promise.resolve()
24
- .then(() => main())
25
- .catch(onError);
26
- }
1
+ export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
package/dist/host.js CHANGED
@@ -26,10 +26,32 @@ import { execFileSync } from 'node:child_process';
26
26
  import { existsSync } from 'node:fs';
27
27
  import { query, } from '@anthropic-ai/claude-agent-sdk';
28
28
  import { isAnthropicImageAttachment, materializeMessageMedia, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
29
- import { CLAUDE_PERMISSION_MODE_OPTIONS, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, rtdbRead, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
29
+ import { CLAUDE_PERMISSION_MODE_OPTIONS, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, rtdbRead, rtdbWrite, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
30
30
  import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
31
31
  import { createStreamingBuffer } from './streaming-buffer.js';
32
32
  import { runCli } from './cli-entry.js';
33
+ import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError, mergeClaudeModelOptions, shouldDeliverClaudeFinal, } from './session-state.js';
34
+ const HELP = `canon-claude — run a local Claude Code agent host for Canon
35
+
36
+ USAGE
37
+ canon-claude [flags]
38
+
39
+ COMMON FLAGS
40
+ --cwd <path> Project directory to run Claude Code from
41
+ --workspace <path> Additional project to expose in Canon
42
+ --workspace-root <path> Discover projects under an approved root
43
+ --help, -h Show this help
44
+ --version, -V Show package version
45
+
46
+ AUTH
47
+ CANON_AGENT=<profile> canon-claude --cwd /path/to/project
48
+ CANON_API_KEY=agk_live_... canon-claude --cwd /path/to/project
49
+
50
+ EXAMPLES
51
+ canon-claude --cwd ~/dev/canon
52
+ canon-claude --cwd ~/dev/canon --workspace-root ~/dev
53
+
54
+ Keep this terminal open while you want Canon to reach the agent.`;
33
55
  let workingDir = process.cwd();
34
56
  let workspaceOptions = [];
35
57
  let workspaceRoots = [];
@@ -142,6 +164,16 @@ function buildClaudeRuntimeDescriptor(input) {
142
164
  availability: ['busy'],
143
165
  dispatch: { kind: 'signal', signal: 'interrupt' },
144
166
  },
167
+ {
168
+ id: 'stop-and-clear-queue',
169
+ label: 'Stop & clear queue',
170
+ description: 'Interrupt the current Claude turn and drop queued Canon messages.',
171
+ aliases: ['stop-clear', 'clear-queue'],
172
+ category: 'turn',
173
+ placements: ['composer_slash', 'command_palette', 'session_strip'],
174
+ availability: ['busy_with_queue'],
175
+ dispatch: { kind: 'signal', signal: 'stop_and_drop' },
176
+ },
145
177
  ],
146
178
  });
147
179
  }
@@ -450,17 +482,45 @@ const CLAUDE_RUNTIME_CAPABILITIES = {
450
482
  ...DEFAULT_RUNTIME_CAPABILITIES,
451
483
  supportsInterrupt: true,
452
484
  supportsQueue: true,
453
- supportsInterleave: true,
485
+ supportsInterleave: false,
454
486
  supportsRequiresAction: true,
455
487
  supportsNonFinalPermanentMessages: false,
456
488
  };
457
489
  function buildCanonPrompt(input) {
490
+ const { session, ...promptInput } = input;
458
491
  return buildCanonHostPrompt({
459
492
  hostLabel: 'Claude Code',
460
493
  buildInboundContextLines,
461
- ...input,
494
+ sessionContextLines: session ? buildClaudeSessionContextLines(session) : undefined,
495
+ ...promptInput,
462
496
  });
463
497
  }
498
+ function optionLabel(value, options) {
499
+ if (!value)
500
+ return null;
501
+ const label = options.find((option) => option.value === value)?.label ?? value;
502
+ return label === value ? value : `${label} (${value})`;
503
+ }
504
+ function buildClaudeSessionContextLines(session) {
505
+ const workspace = workspaceOptions.find((option) => option.cwd === session.environment.baseCwd);
506
+ const permissionMode = optionLabel(session.state.permissionMode, CLAUDE_PERMISSION_MODE_OPTIONS);
507
+ const effort = optionLabel(session.state.effort, [
508
+ { value: 'low', label: 'Low' },
509
+ { value: 'medium', label: 'Medium' },
510
+ { value: 'high', label: 'High' },
511
+ ]);
512
+ const model = optionLabel(session.state.model, session.availableModels);
513
+ return [
514
+ model ? `Selected model: ${model}` : null,
515
+ permissionMode ? `Permission mode: ${permissionMode}` : null,
516
+ effort ? `Thinking level: ${effort}` : null,
517
+ workspace ? `Project: ${workspace.label} (${workspace.id})` : null,
518
+ `Execution mode: ${session.environment.mode}`,
519
+ `Working directory: ${session.cwd}`,
520
+ session.environment.branch ? `Git branch: ${session.environment.branch}` : null,
521
+ session.environment.worktreePath ? `Worktree: ${session.environment.worktreePath}` : null,
522
+ ].filter((line) => Boolean(line));
523
+ }
464
524
  function renderInboundContent(message, materialized) {
465
525
  return renderCanonHostInboundContent(message, materialized);
466
526
  }
@@ -491,19 +551,25 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
491
551
  let resolveInput = null;
492
552
  let inputDone = false;
493
553
  const messageQueue = [];
494
- const sendInput = (msg) => {
554
+ const sendInput = (input) => {
495
555
  if (resolveInput) {
496
556
  const r = resolveInput;
497
557
  resolveInput = null;
498
- r(msg);
558
+ r(input);
499
559
  }
500
560
  else {
501
561
  // Queue if iterator isn't waiting yet (e.g., during init)
502
- messageQueue.push(msg);
562
+ messageQueue.push(input);
503
563
  }
504
564
  };
505
- let enqueueInboundMessage = (msg, _intent = 'queue', _sourceMessageId, _markAccepted = false) => {
506
- sendInput(msg);
565
+ let enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId, markAccepted = false) => {
566
+ sendInput(createClaudeInputEnvelope({
567
+ kind: 'canon',
568
+ msg,
569
+ intent,
570
+ sourceMessageId,
571
+ markAccepted,
572
+ }));
507
573
  };
508
574
  const inputStream = {
509
575
  [Symbol.asyncIterator]() {
@@ -513,10 +579,15 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
513
579
  return Promise.resolve({ done: true, value: undefined });
514
580
  // Drain queued messages first (sent before iterator was ready)
515
581
  if (messageQueue.length > 0) {
516
- return Promise.resolve({ done: false, value: messageQueue.shift() });
582
+ const next = messageQueue.shift();
583
+ session.activeInput = next;
584
+ return Promise.resolve({ done: false, value: next.msg });
517
585
  }
518
586
  return new Promise((resolve) => {
519
- resolveInput = (msg) => resolve({ done: false, value: msg });
587
+ resolveInput = (input) => {
588
+ session.activeInput = input;
589
+ resolve({ done: false, value: input.msg });
590
+ };
520
591
  });
521
592
  },
522
593
  return() {
@@ -659,6 +730,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
659
730
  state: {
660
731
  model: config.model,
661
732
  permissionMode: config.permissionMode,
733
+ effort: config.effort,
662
734
  state: 'idle',
663
735
  },
664
736
  availableModels: config.availableModels,
@@ -668,8 +740,12 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
668
740
  finalDeliveryTimer: null,
669
741
  seedResponseHandled: !!resumeSessionId, // Resumed sessions don't need seed handling
670
742
  pendingInputs: [],
743
+ activeInput: null,
744
+ finalizedTurnKeys: new Set(),
745
+ interruptedTurnKeys: new Set(),
671
746
  pendingFinalText: null,
672
- pendingFinalDeliveryText: null,
747
+ pendingFinalDelivery: null,
748
+ runtimeControlErrors: {},
673
749
  toolInProgress: false,
674
750
  turnState: 'idle',
675
751
  currentTurnId: null,
@@ -680,6 +756,10 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
680
756
  closed: false,
681
757
  sdkSessionId: resumeSessionId || null,
682
758
  pendingReply: null,
759
+ writeState: () => writeState(),
760
+ writeTurn: () => writeTurn(),
761
+ setRuntimeControlError: (controlId, value, error) => setRuntimeControlError(controlId, value, error),
762
+ clearRuntimeControlError: (controlId) => clearRuntimeControlError(controlId),
683
763
  };
684
764
  const streamingBuffer = createStreamingBuffer({
685
765
  throttleMs: STREAMING_THROTTLE_MS,
@@ -729,7 +809,8 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
729
809
  session.currentTurnOpenedAt = null;
730
810
  session.lastAcceptedIntent = null;
731
811
  session.pendingFinalText = null;
732
- session.pendingFinalDeliveryText = null;
812
+ session.pendingFinalDelivery = null;
813
+ session.activeInput = null;
733
814
  session.toolInProgress = false;
734
815
  session.turnState = 'idle';
735
816
  stopVisibleWorkSignal();
@@ -745,10 +826,18 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
745
826
  resetTurnToIdle();
746
827
  }, FINAL_MESSAGE_HANDOFF_MS);
747
828
  }
748
- function markPendingFinalDelivery(finalText) {
829
+ function markPendingFinalDelivery(finalText, turn) {
749
830
  clearIdleResetTimer();
750
831
  session.pendingFinalText = finalText;
751
- session.pendingFinalDeliveryText = finalText;
832
+ session.pendingFinalDelivery = {
833
+ turnKey: turn.turnKey,
834
+ text: finalText,
835
+ messageId: buildClaudeFinalMessageId({
836
+ agentId,
837
+ conversationId,
838
+ turnKey: turn.turnKey,
839
+ }),
840
+ };
752
841
  session.state.state = 'running';
753
842
  session.turnState = 'streaming';
754
843
  stopVisibleWorkSignal();
@@ -757,49 +846,75 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
757
846
  writeTurn();
758
847
  client.setTyping(conversationId, true, 'typing').catch(() => { });
759
848
  }
760
- async function deliverFinalReply(finalText) {
849
+ async function deliverFinalReply(finalText, turn = session.activeInput) {
850
+ if (!shouldDeliverClaudeFinal({
851
+ turn,
852
+ finalizedTurnKeys: session.finalizedTurnKeys,
853
+ interruptedTurnKeys: session.interruptedTurnKeys,
854
+ })) {
855
+ return true;
856
+ }
857
+ const deliverTurn = turn;
858
+ if (!deliverTurn)
859
+ return true;
860
+ const finalMessageId = buildClaudeFinalMessageId({
861
+ agentId,
862
+ conversationId,
863
+ turnKey: deliverTurn.turnKey,
864
+ });
761
865
  try {
762
866
  await client.sendMessage(conversationId, finalText, {
867
+ messageId: finalMessageId,
763
868
  metadata: {
764
869
  turnId: session.currentTurnId,
870
+ turnKey: deliverTurn.turnKey,
871
+ sourceMessageId: deliverTurn.sourceMessageId ?? undefined,
765
872
  turnSemantics: 'turn_complete',
766
873
  turnComplete: true,
767
874
  deliveryIntent: session.lastAcceptedIntent ?? undefined,
768
875
  },
769
876
  });
877
+ session.finalizedTurnKeys.add(deliverTurn.turnKey);
770
878
  session.state.state = 'idle';
771
- session.pendingFinalDeliveryText = null;
879
+ session.pendingFinalDelivery = null;
772
880
  clearFinalDeliveryTimer();
773
881
  writeState();
774
882
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Sent final reply (${finalText.length} chars)`);
775
883
  return true;
776
884
  }
777
885
  catch (err) {
778
- markPendingFinalDelivery(finalText);
886
+ markPendingFinalDelivery(finalText, deliverTurn);
779
887
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to send final reply:`, err);
780
888
  return false;
781
889
  }
782
890
  }
783
- function scheduleFinalDeliveryRetry(finalText) {
784
- if (session.closed || session.pendingFinalDeliveryText !== finalText)
891
+ function scheduleFinalDeliveryRetry(pending = session.pendingFinalDelivery) {
892
+ if (!pending || session.closed || session.pendingFinalDelivery?.turnKey !== pending.turnKey)
785
893
  return;
786
894
  clearFinalDeliveryTimer();
787
895
  session.finalDeliveryTimer = setTimeout(() => {
788
896
  session.finalDeliveryTimer = null;
789
- void deliverFinalReply(finalText).then((sent) => {
897
+ const retryTurn = session.activeInput?.turnKey === pending.turnKey
898
+ ? session.activeInput
899
+ : null;
900
+ void deliverFinalReply(pending.text, retryTurn).then((sent) => {
790
901
  if (sent) {
791
902
  scheduleFinalHandoffReset();
792
903
  return;
793
904
  }
794
- scheduleFinalDeliveryRetry(finalText);
905
+ scheduleFinalDeliveryRetry();
795
906
  });
796
907
  }, FINAL_DELIVERY_RETRY_MS);
797
908
  }
798
909
  function writeState() {
910
+ const runtimeControlErrors = Object.keys(session.runtimeControlErrors).length > 0
911
+ ? session.runtimeControlErrors
912
+ : null;
799
913
  runtimeState.writeSessionState(conversationId, {
800
914
  model: session.state.model,
801
915
  permissionMode: session.state.permissionMode,
802
916
  effort: session.state.effort,
917
+ runtimeControlErrors,
803
918
  contextUsage: session.state.contextUsage,
804
919
  ...(session.availableModels.length ? { availableModels: session.availableModels } : {}),
805
920
  cwd,
@@ -813,6 +928,25 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
813
928
  isActive: true,
814
929
  }).catch(() => { });
815
930
  }
931
+ function setRuntimeControlError(controlId, value, error) {
932
+ session.runtimeControlErrors = {
933
+ ...session.runtimeControlErrors,
934
+ [controlId]: {
935
+ value,
936
+ message: formatClaudeControlError(error),
937
+ updatedAt: Date.now(),
938
+ },
939
+ };
940
+ writeState();
941
+ }
942
+ function clearRuntimeControlError(controlId) {
943
+ if (!(controlId in session.runtimeControlErrors))
944
+ return;
945
+ const next = { ...session.runtimeControlErrors };
946
+ delete next[controlId];
947
+ session.runtimeControlErrors = next;
948
+ writeState();
949
+ }
816
950
  function writeTurn() {
817
951
  runtimeState.writeTurnState(conversationId, {
818
952
  turnId: session.currentTurnId,
@@ -860,22 +994,32 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
860
994
  session.lastAcceptedIntent = next.intent;
861
995
  writeTurn();
862
996
  void markQueuedMessageAccepted(next.sourceMessageId, next.markAccepted)
863
- .finally(() => sendInput(next.msg));
997
+ .finally(() => sendInput(next));
864
998
  }
865
999
  enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId = null, markAccepted = false) => {
866
1000
  session.lastActivity = Date.now();
867
- if (session.pendingFinalDeliveryText) {
868
- session.pendingInputs.push({ msg, intent, sourceMessageId, markAccepted });
1001
+ const input = createClaudeInputEnvelope({
1002
+ kind: 'canon',
1003
+ msg,
1004
+ intent,
1005
+ sourceMessageId,
1006
+ markAccepted,
1007
+ });
1008
+ if (session.pendingFinalDelivery) {
1009
+ session.pendingInputs.push(input);
869
1010
  writeTurn();
870
1011
  return;
871
1012
  }
872
1013
  if (session.state.state === 'running' && intent !== 'interrupt') {
873
- session.pendingInputs.push({ msg, intent, sourceMessageId, markAccepted });
1014
+ session.pendingInputs.push(input);
874
1015
  writeTurn();
875
1016
  return;
876
1017
  }
877
1018
  if (session.state.state === 'running' && intent === 'interrupt') {
878
- session.pendingInputs.unshift({ msg, intent, sourceMessageId, markAccepted });
1019
+ if (session.activeInput) {
1020
+ session.interruptedTurnKeys.add(session.activeInput.turnKey);
1021
+ }
1022
+ session.pendingInputs.unshift(input);
879
1023
  session.turnState = 'interrupted';
880
1024
  stopVisibleWorkSignal();
881
1025
  writeTurn();
@@ -885,7 +1029,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
885
1029
  session.lastAcceptedIntent = intent;
886
1030
  writeTurn();
887
1031
  void markQueuedMessageAccepted(sourceMessageId, markAccepted)
888
- .finally(() => sendInput(msg));
1032
+ .finally(() => sendInput(input));
889
1033
  };
890
1034
  writeTurn();
891
1035
  // ── Process SDK output messages ──
@@ -981,12 +1125,22 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
981
1125
  const resultSessionId = msg.session_id;
982
1126
  if (resultSessionId)
983
1127
  session.sdkSessionId = resultSessionId;
1128
+ const completedInput = session.activeInput;
984
1129
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Turn complete (${msg.subtype})`);
985
1130
  const resultText = typeof msg.result === 'string'
986
1131
  ? msg.result.trim()
987
1132
  : '';
988
1133
  const finalText = resultText || session.pendingFinalText?.trim() || null;
989
- const finalDelivered = finalText ? await deliverFinalReply(finalText) : true;
1134
+ const shouldDeliverFinal = finalText
1135
+ ? shouldDeliverClaudeFinal({
1136
+ turn: completedInput,
1137
+ finalizedTurnKeys: session.finalizedTurnKeys,
1138
+ interruptedTurnKeys: session.interruptedTurnKeys,
1139
+ })
1140
+ : false;
1141
+ const finalDelivered = finalText && shouldDeliverFinal
1142
+ ? await deliverFinalReply(finalText, completedInput)
1143
+ : true;
990
1144
  try {
991
1145
  const usage = await q.getContextUsage();
992
1146
  session.state.contextUsage = {
@@ -1001,10 +1155,19 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
1001
1155
  // Non-fatal
1002
1156
  }
1003
1157
  if (finalDelivered) {
1004
- scheduleFinalHandoffReset();
1158
+ if (completedInput?.turnKey === session.activeInput?.turnKey) {
1159
+ session.activeInput = null;
1160
+ }
1161
+ session.pendingFinalText = null;
1162
+ if (shouldDeliverFinal && finalText) {
1163
+ scheduleFinalHandoffReset();
1164
+ }
1165
+ else {
1166
+ resetTurnToIdle();
1167
+ }
1005
1168
  }
1006
1169
  else if (finalText) {
1007
- scheduleFinalDeliveryRetry(finalText);
1170
+ scheduleFinalDeliveryRetry();
1008
1171
  }
1009
1172
  break;
1010
1173
  }
@@ -1046,19 +1209,26 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
1046
1209
  }
1047
1210
  if (!resumeSessionId) {
1048
1211
  // New session — send seed message to activate streaming input mode
1049
- sendInput({
1050
- type: 'user',
1051
- message: {
1052
- role: 'user',
1053
- content: 'You are connected to Canon messaging. Follow-up messages in this session come from Canon participants, and your text responses will be sent back to the conversation. You have full Claude Code capabilities (file read/write, bash, etc).',
1212
+ sendInput(createClaudeInputEnvelope({
1213
+ kind: 'seed',
1214
+ msg: {
1215
+ type: 'user',
1216
+ message: {
1217
+ role: 'user',
1218
+ content: 'You are connected to Canon messaging. Follow-up messages in this session come from Canon participants, and your text responses will be sent back to the conversation. You have full Claude Code capabilities (file read/write, bash, etc).',
1219
+ },
1220
+ parent_tool_use_id: null,
1054
1221
  },
1055
- parent_tool_use_id: null,
1056
- });
1222
+ }));
1057
1223
  }
1058
1224
  // Refresh the canonical runtime descriptor after Claude reports supported models.
1059
1225
  try {
1060
1226
  const models = await q.supportedModels();
1061
- const modelList = toModelOptions(models);
1227
+ const modelList = mergeClaudeModelOptions({
1228
+ previous: runtimeModels,
1229
+ next: toModelOptions(models),
1230
+ selectedModel: session.state.model,
1231
+ });
1062
1232
  session.availableModels = modelList;
1063
1233
  runtimeModels = modelList;
1064
1234
  onRuntimeDescriptorUpdate({
@@ -1075,21 +1245,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
1075
1245
  executionModes: config.availableExecutionModes,
1076
1246
  }),
1077
1247
  });
1078
- runtimeState.writeSessionState(conversationId, {
1079
- model: session.state.model,
1080
- permissionMode: session.state.permissionMode,
1081
- effort: session.state.effort,
1082
- contextUsage: session.state.contextUsage,
1083
- cwd,
1084
- executionMode: session.environment.mode,
1085
- ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1086
- ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1087
- ...(resolveExecutionFallbackReason(session.environment)
1088
- ? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
1089
- : {}),
1090
- hostMode: true,
1091
- isActive: true,
1092
- }).catch(() => { });
1248
+ writeState();
1093
1249
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Models: ${models.map((m) => m.displayName).join(', ')}`);
1094
1250
  }
1095
1251
  catch {
@@ -1322,6 +1478,10 @@ export async function main() {
1322
1478
  for (const conversation of convos) {
1323
1479
  knownConversationIds.add(conversation.id);
1324
1480
  conversationCache.set(conversation.id, conversation);
1481
+ runtimeState.clearStreaming(conversation.id).catch(() => { });
1482
+ runtimeState.clearSessionState(conversation.id).catch(() => { });
1483
+ runtimeState.clearTurnState(conversation.id).catch(() => { });
1484
+ client.setTyping(conversation.id, false).catch(() => { });
1325
1485
  }
1326
1486
  console.error(`[canon-host] ${convos.length} conversations`);
1327
1487
  }
@@ -1331,6 +1491,9 @@ export async function main() {
1331
1491
  // ── Session manager ──
1332
1492
  const sessions = new Map();
1333
1493
  const pendingSessionCreations = new Map();
1494
+ let controlStopped = false;
1495
+ const lastSeenControl = new Map();
1496
+ const lastSeenSignal = new Map();
1334
1497
  /** Saved SDK session IDs for resume after idle eviction, keyed by conversation + workspace */
1335
1498
  const savedSessionIds = new Map();
1336
1499
  const { getConversationMeta } = createConversationMetadataLoader({
@@ -1408,6 +1571,13 @@ export async function main() {
1408
1571
  sessions.delete(conversationId);
1409
1572
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Session suspended (sessionId saved for resume)`);
1410
1573
  }
1574
+ async function markSessionPendingInputsRejected(conversationId, inputs) {
1575
+ await Promise.all(inputs.map((input) => {
1576
+ if (!input.markAccepted || !input.sourceMessageId)
1577
+ return Promise.resolve();
1578
+ return client.updateMessageDisposition(conversationId, input.sourceMessageId, 'rejected').catch(() => { });
1579
+ }));
1580
+ }
1411
1581
  function evictOldestIdle() {
1412
1582
  let oldest = null;
1413
1583
  for (const s of sessions.values()) {
@@ -1489,6 +1659,10 @@ export async function main() {
1489
1659
  availableExecutionModes: hostAvailableExecutionModes,
1490
1660
  }, resumeId);
1491
1661
  sessions.set(conversationId, session);
1662
+ await Promise.all([
1663
+ baselineControlSignal(conversationId),
1664
+ baselineSessionControl(conversationId),
1665
+ ]);
1492
1666
  return session;
1493
1667
  }
1494
1668
  catch (error) {
@@ -1504,6 +1678,28 @@ export async function main() {
1504
1678
  pendingSessionCreations.delete(conversationId);
1505
1679
  }
1506
1680
  }
1681
+ async function baselineControlSignal(conversationId) {
1682
+ if (lastSeenSignal.has(conversationId))
1683
+ return;
1684
+ const raw = await rtdbRead(`/control/${conversationId}/${agentId}/signal`).catch(() => null);
1685
+ if (!raw || typeof raw !== 'object')
1686
+ return;
1687
+ const timestamp = Number(raw.updatedAt ?? 0);
1688
+ if (timestamp > 0) {
1689
+ lastSeenSignal.set(conversationId, timestamp);
1690
+ }
1691
+ }
1692
+ async function baselineSessionControl(conversationId) {
1693
+ if (lastSeenControl.has(conversationId))
1694
+ return;
1695
+ const raw = await rtdbRead(`/control/${conversationId}/${agentId}/session`).catch(() => null);
1696
+ if (!raw || typeof raw !== 'object')
1697
+ return;
1698
+ const timestamp = Number(raw.updatedAt ?? 0);
1699
+ if (timestamp > 0) {
1700
+ lastSeenControl.set(conversationId, timestamp);
1701
+ }
1702
+ }
1507
1703
  async function enqueueRecoveredInboundMessage(input) {
1508
1704
  const m = input.message;
1509
1705
  if (m.senderId === agentId)
@@ -1563,6 +1759,7 @@ export async function main() {
1563
1759
  behavior,
1564
1760
  workSession: m.workSession,
1565
1761
  workSessions,
1762
+ session,
1566
1763
  });
1567
1764
  const messageContent = await buildCanonUserContent({ promptText, materialized });
1568
1765
  session.enqueueInbound({
@@ -1711,6 +1908,7 @@ export async function main() {
1711
1908
  behavior,
1712
1909
  workSession: m.workSession,
1713
1910
  workSessions,
1911
+ session,
1714
1912
  });
1715
1913
  const messageContent = await buildCanonUserContent({ promptText, materialized });
1716
1914
  session.enqueueInbound({
@@ -1747,9 +1945,6 @@ export async function main() {
1747
1945
  console.error('[canon-host] SSE start error:', err instanceof Error ? err.message : err);
1748
1946
  });
1749
1947
  // ── Control signal watcher ──
1750
- let controlStopped = false;
1751
- const lastSeenControl = new Map();
1752
- const lastSeenSignal = new Map();
1753
1948
  const pollControl = async () => {
1754
1949
  while (!controlStopped) {
1755
1950
  for (const convoId of [...sessions.keys()]) {
@@ -1768,9 +1963,11 @@ export async function main() {
1768
1963
  try {
1769
1964
  await session.query.setModel(ctrl.model);
1770
1965
  session.state.model = ctrl.model;
1966
+ session.clearRuntimeControlError('model');
1771
1967
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Model → ${ctrl.model}`);
1772
1968
  }
1773
1969
  catch (err) {
1970
+ session.setRuntimeControlError('model', ctrl.model, err);
1774
1971
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set model:`, err);
1775
1972
  }
1776
1973
  }
@@ -1778,9 +1975,11 @@ export async function main() {
1778
1975
  try {
1779
1976
  await session.query.setPermissionMode(ctrl.permissionMode);
1780
1977
  session.state.permissionMode = ctrl.permissionMode;
1978
+ session.clearRuntimeControlError('permissionMode');
1781
1979
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Mode → ${ctrl.permissionMode}`);
1782
1980
  }
1783
1981
  catch (err) {
1982
+ session.setRuntimeControlError('permissionMode', ctrl.permissionMode, err);
1784
1983
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
1785
1984
  }
1786
1985
  }
@@ -1789,41 +1988,46 @@ export async function main() {
1789
1988
  try {
1790
1989
  await session.query.applyFlagSettings({ effortLevel: nextEffort });
1791
1990
  session.state.effort = nextEffort;
1991
+ session.clearRuntimeControlError('effort');
1792
1992
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
1793
1993
  }
1794
1994
  catch (err) {
1995
+ session.setRuntimeControlError('effort', nextEffort, err);
1795
1996
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
1796
1997
  }
1797
1998
  }
1798
- runtimeState.writeSessionState(convoId, {
1799
- model: session.state.model,
1800
- permissionMode: session.state.permissionMode,
1801
- effort: session.state.effort,
1802
- contextUsage: session.state.contextUsage,
1803
- cwd: session.cwd,
1804
- executionMode: session.environment.mode,
1805
- ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1806
- ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1807
- ...(resolveExecutionFallbackReason(session.environment)
1808
- ? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
1809
- : {}),
1810
- hostMode: true,
1811
- isActive: true,
1812
- }).catch(() => { });
1999
+ else if (ctrl.effort && !nextEffort) {
2000
+ session.setRuntimeControlError('effort', String(ctrl.effort), new Error('Unsupported Claude thinking level.'));
2001
+ }
2002
+ session.writeState();
2003
+ await rtdbWrite(`/control/${convoId}/${agentId}/session`, null).catch(() => { });
1813
2004
  }
1814
2005
  }
1815
2006
  }
1816
- // Interrupt signal
2007
+ // Interrupt/stop-clear signal
1817
2008
  const sig = await rtdbRead(`/control/${convoId}/${agentId}/signal`);
1818
2009
  if (sig && typeof sig === 'object') {
1819
2010
  const s = sig;
1820
2011
  const ts = s.updatedAt ?? 0;
1821
- if (s.type === 'interrupt' && ts > (lastSeenSignal.get(convoId) ?? 0)) {
2012
+ if ((s.type === 'interrupt' || s.type === 'stop_and_drop') && ts > (lastSeenSignal.get(convoId) ?? 0)) {
1822
2013
  lastSeenSignal.set(convoId, ts);
1823
2014
  const session = sessions.get(convoId);
1824
2015
  if (session && !session.closed) {
1825
- console.error(`[canon-host] [${convoId.slice(0, 8)}] Interrupt signal`);
2016
+ const isActive = session.state.state === 'running';
2017
+ if (!isActive && (s.type !== 'stop_and_drop' || session.pendingInputs.length === 0)) {
2018
+ await rtdbWrite(`/control/${convoId}/${agentId}/signal`, null).catch(() => { });
2019
+ continue;
2020
+ }
2021
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] ${s.type} signal`);
1826
2022
  try {
2023
+ if (s.type === 'stop_and_drop') {
2024
+ const droppedInputs = session.pendingInputs.splice(0);
2025
+ await markSessionPendingInputsRejected(convoId, droppedInputs);
2026
+ }
2027
+ if (session.activeInput) {
2028
+ session.interruptedTurnKeys.add(session.activeInput.turnKey);
2029
+ }
2030
+ session.pendingFinalText = null;
1827
2031
  session.turnState = 'interrupted';
1828
2032
  runtimeState.writeTurnState(convoId, {
1829
2033
  turnId: session.currentTurnId,
@@ -1835,7 +2039,9 @@ export async function main() {
1835
2039
  ...(session.currentTurnOpenedAt ? { openedAt: session.currentTurnOpenedAt } : {}),
1836
2040
  completedAt: { '.sv': 'timestamp' },
1837
2041
  }).catch(() => { });
1838
- await session.query.interrupt();
2042
+ if (isActive) {
2043
+ await session.query.interrupt();
2044
+ }
1839
2045
  if (session.streamingTimer) {
1840
2046
  clearTimeout(session.streamingTimer);
1841
2047
  session.streamingTimer = null;
@@ -1843,9 +2049,10 @@ export async function main() {
1843
2049
  session.streamingText = '';
1844
2050
  runtimeState.clearStreaming(convoId).catch(() => { });
1845
2051
  client.setTyping(convoId, false).catch(() => { });
2052
+ await rtdbWrite(`/control/${convoId}/${agentId}/signal`, null).catch(() => { });
1846
2053
  }
1847
2054
  catch (err) {
1848
- console.error(`[canon-host] [${convoId.slice(0, 8)}] Interrupt failed:`, err);
2055
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] ${s.type} failed:`, err);
1849
2056
  }
1850
2057
  }
1851
2058
  }
@@ -1862,30 +2069,8 @@ export async function main() {
1862
2069
  // ── Heartbeat (per-session) ──
1863
2070
  const heartbeat = setInterval(() => {
1864
2071
  for (const session of sessions.values()) {
1865
- runtimeState.writeSessionState(session.conversationId, {
1866
- model: session.state.model,
1867
- permissionMode: session.state.permissionMode,
1868
- effort: session.state.effort,
1869
- contextUsage: session.state.contextUsage,
1870
- cwd: session.cwd,
1871
- executionMode: session.environment.mode,
1872
- ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1873
- ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1874
- hostMode: true,
1875
- isActive: true,
1876
- }).catch(() => { });
1877
- runtimeState.writeTurnState(session.conversationId, {
1878
- turnId: session.currentTurnId,
1879
- state: session.turnState,
1880
- queueDepth: session.pendingInputs.length,
1881
- currentSpeakerId: agentId,
1882
- lastAcceptedIntent: session.lastAcceptedIntent,
1883
- capabilities: CLAUDE_RUNTIME_CAPABILITIES,
1884
- ...(session.currentTurnOpenedAt ? { openedAt: session.currentTurnOpenedAt } : {}),
1885
- ...(session.turnState === 'idle' || session.turnState === 'completed' || session.turnState === 'interrupted'
1886
- ? { completedAt: { '.sv': 'timestamp' } }
1887
- : {}),
1888
- }).catch(() => { });
2072
+ session.writeState();
2073
+ session.writeTurn();
1889
2074
  }
1890
2075
  void publishRuntimeHeartbeat();
1891
2076
  }, HEARTBEAT_MS);
@@ -1926,7 +2111,11 @@ export async function main() {
1926
2111
  }
1927
2112
  // ── Entry ───────────────────────────────────────────────────────────
1928
2113
  runCli(import.meta.url, main, (err) => {
1929
- console.error('[canon-host] Fatal error:', err);
2114
+ const message = err instanceof Error ? err.message : String(err);
2115
+ console.error(`[canon-host] ${message}`);
1930
2116
  getActiveProfileLock()?.release();
1931
2117
  process.exit(1);
2118
+ }, {
2119
+ name: 'canon-claude',
2120
+ help: HELP,
1932
2121
  });
package/dist/register.js CHANGED
@@ -13,6 +13,27 @@ import { ackRegistrationApproval, clearPendingRegistration, getOrCreatePendingRe
13
13
  import { readFileSync } from 'node:fs';
14
14
  import { parseArgs } from 'node:util';
15
15
  import { runCli } from './cli-entry.js';
16
+ const HELP = `canon-register — register or reconnect a Claude Code agent in Canon
17
+
18
+ USAGE
19
+ canon-register --name <name> --description <text> --phone <e164> [flags]
20
+
21
+ REQUIRED
22
+ --name <name> Agent display name shown in Canon
23
+ --description <text> Short profile description
24
+ --phone <e164> Owner phone number, for example +15551234567
25
+
26
+ FLAGS
27
+ --profile <name> Local profile name in ~/.canon/agents.json
28
+ --base-url <url> Canon API base URL override
29
+ --help, -h Show this help
30
+ --version, -V Show package version
31
+
32
+ EXAMPLES
33
+ canon-register --name "My Claude" --description "Claude Code agent" --phone "+15551234567"
34
+ canon-register --name "Frontend" --description "React work" --phone "+15551234567" --profile frontend
35
+
36
+ After approval, start it with CANON_AGENT=<profile> canon-claude --cwd /path/to/project.`;
16
37
  export async function main() {
17
38
  setDefaultResultOrder('ipv4first');
18
39
  const { values } = parseArgs({
@@ -102,4 +123,7 @@ export async function main() {
102
123
  runCli(import.meta.url, main, (error) => {
103
124
  console.error('[canon-register] Fatal error:', error);
104
125
  process.exit(1);
126
+ }, {
127
+ name: 'canon-register',
128
+ help: HELP,
105
129
  });
package/dist/server.js CHANGED
@@ -13,6 +13,17 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
13
13
  import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, getActiveProfileLock, markLocalRuntimeStopped, loadProfiles, isProfileLocked, resolveCanonAgent, getActiveProfile, releaseLock, upsertLocalRuntimeEntry, initRTDBAuth, writeSessionState, clearSessionState, } from '@canonmsg/core';
14
14
  import { ApprovalHttpServer } from './approval-server.js';
15
15
  import { runCli } from './cli-entry.js';
16
+ const HELP = `canon-channel-server — Claude Code MCP channel server for Canon
17
+
18
+ USAGE
19
+ canon-channel-server
20
+
21
+ FLAGS
22
+ --help, -h Show this help
23
+ --version, -V Show package version
24
+
25
+ This command is normally launched by Claude Code as an MCP stdio server. For
26
+ phone-controlled local sessions, use canon-claude instead.`;
16
27
  // ── MCP server ─────────────────────────────────────────────────────────
17
28
  const server = new Server({ name: 'canon-channel', version: '0.1.0' }, {
18
29
  capabilities: {
@@ -672,4 +683,7 @@ export async function main() {
672
683
  runCli(import.meta.url, main, (err) => {
673
684
  console.error('[canon] Fatal error:', err);
674
685
  process.exit(1);
686
+ }, {
687
+ name: 'canon-channel-server',
688
+ help: HELP,
675
689
  });
@@ -0,0 +1,44 @@
1
+ import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
2
+ import type { DeliveryIntent, ModelOption } from '@canonmsg/core';
3
+ export type ClaudeInputKind = 'seed' | 'canon';
4
+ export interface ClaudeInputEnvelope {
5
+ kind: ClaudeInputKind;
6
+ msg: SDKUserMessage;
7
+ intent: DeliveryIntent;
8
+ sourceMessageId: string | null;
9
+ markAccepted: boolean;
10
+ turnKey: string;
11
+ }
12
+ export interface ClaudeRuntimeControlError {
13
+ value: string;
14
+ message: string;
15
+ updatedAt: number;
16
+ }
17
+ export interface ClaudePendingFinalDelivery {
18
+ turnKey: string;
19
+ text: string;
20
+ messageId: string;
21
+ }
22
+ export declare function createClaudeInputEnvelope(input: {
23
+ kind: ClaudeInputKind;
24
+ msg: SDKUserMessage;
25
+ intent?: DeliveryIntent;
26
+ sourceMessageId?: string | null;
27
+ markAccepted?: boolean;
28
+ }): ClaudeInputEnvelope;
29
+ export declare function buildClaudeFinalMessageId(input: {
30
+ agentId: string;
31
+ conversationId: string;
32
+ turnKey: string;
33
+ }): string;
34
+ export declare function shouldDeliverClaudeFinal(input: {
35
+ turn: ClaudeInputEnvelope | null;
36
+ finalizedTurnKeys: ReadonlySet<string>;
37
+ interruptedTurnKeys: ReadonlySet<string>;
38
+ }): boolean;
39
+ export declare function mergeClaudeModelOptions(input: {
40
+ previous: ReadonlyArray<ModelOption>;
41
+ next: ReadonlyArray<ModelOption>;
42
+ selectedModel?: string | null;
43
+ }): ModelOption[];
44
+ export declare function formatClaudeControlError(error: unknown): string;
@@ -0,0 +1,67 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ export function createClaudeInputEnvelope(input) {
3
+ const sourceMessageId = input.sourceMessageId ?? null;
4
+ const fallbackId = randomUUID();
5
+ return {
6
+ kind: input.kind,
7
+ msg: input.msg,
8
+ intent: input.intent ?? 'queue',
9
+ sourceMessageId,
10
+ markAccepted: Boolean(input.markAccepted),
11
+ turnKey: input.kind === 'canon' && sourceMessageId
12
+ ? `canon:${sourceMessageId}`
13
+ : `${input.kind}:${fallbackId}`,
14
+ };
15
+ }
16
+ export function buildClaudeFinalMessageId(input) {
17
+ const digest = createHash('sha256')
18
+ .update(`${input.agentId}\n${input.conversationId}\n${input.turnKey}`)
19
+ .digest('hex')
20
+ .slice(0, 32);
21
+ return `claude-final-${digest}`;
22
+ }
23
+ export function shouldDeliverClaudeFinal(input) {
24
+ if (!input.turn || input.turn.kind !== 'canon')
25
+ return false;
26
+ if (input.finalizedTurnKeys.has(input.turn.turnKey))
27
+ return false;
28
+ if (input.interruptedTurnKeys.has(input.turn.turnKey))
29
+ return false;
30
+ return true;
31
+ }
32
+ export function mergeClaudeModelOptions(input) {
33
+ const byValue = new Map();
34
+ for (const option of input.previous) {
35
+ byValue.set(option.value, { ...option });
36
+ }
37
+ for (const option of input.next) {
38
+ byValue.set(option.value, { ...option });
39
+ }
40
+ if (input.selectedModel && !byValue.has(input.selectedModel)) {
41
+ byValue.set(input.selectedModel, {
42
+ value: input.selectedModel,
43
+ label: input.selectedModel,
44
+ description: 'Selected for this Canon session but not returned by the latest Claude model probe.',
45
+ });
46
+ }
47
+ const nextValues = new Set(input.next.map((option) => option.value));
48
+ return [...byValue.values()].map((option) => {
49
+ if (nextValues.has(option.value))
50
+ return option;
51
+ return {
52
+ ...option,
53
+ description: option.description
54
+ ? `${option.description} Previously advertised; not returned by the latest Claude model probe.`
55
+ : 'Previously advertised; not returned by the latest Claude model probe.',
56
+ };
57
+ });
58
+ }
59
+ export function formatClaudeControlError(error) {
60
+ if (error instanceof Error && error.message.trim()) {
61
+ return error.message.trim();
62
+ }
63
+ if (typeof error === 'string' && error.trim()) {
64
+ return error.trim();
65
+ }
66
+ return 'Claude Code did not apply that control.';
67
+ }
package/dist/setup.js CHANGED
@@ -10,6 +10,17 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
10
10
  import { join, dirname } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { runCli } from './cli-entry.js';
13
+ const HELP = `canon-setup — install Claude Code Canon skills and print MCP setup
14
+
15
+ USAGE
16
+ canon-setup
17
+
18
+ FLAGS
19
+ --help, -h Show this help
20
+ --version, -V Show package version
21
+
22
+ This command writes Canon Claude Code skill files under ~/.claude/skills and
23
+ prints the MCP server configuration for Claude Code channel mode.`;
13
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
25
  const home = process.env.HOME || process.env.USERPROFILE || '';
15
26
  const skillsDir = join(home, '.claude', 'skills');
@@ -70,4 +81,7 @@ export function main() {
70
81
  runCli(import.meta.url, main, (error) => {
71
82
  console.error('[canon-setup] Fatal error:', error);
72
83
  process.exit(1);
84
+ }, {
85
+ name: 'canon-setup',
86
+ help: HELP,
73
87
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "^0.2.101",
34
- "@canonmsg/agent-sdk": "^1.0.0",
35
- "@canonmsg/core": "^0.15.0",
34
+ "@canonmsg/agent-sdk": "^1.1.0",
35
+ "@canonmsg/core": "^0.15.1",
36
36
  "@modelcontextprotocol/sdk": "^1.12.0"
37
37
  },
38
38
  "engines": {