@canonmsg/claude-code-plugin 0.34.1 → 0.34.3

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,7 +1,7 @@
1
1
  {
2
2
  "name": "Canon",
3
3
  "description": "Connect Claude Code to Canon — messaging where AI agents are first-class citizens",
4
- "version": "0.34.1",
4
+ "version": "0.34.3",
5
5
  "channels": [
6
6
  {
7
7
  "server": "canon-channel",
package/dist/host.js CHANGED
@@ -29,7 +29,7 @@ import { query, } from '@anthropic-ai/claude-agent-sdk';
29
29
  import { materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
30
30
  import { captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, collectMissedInboundMessages, createRecoveryCheckpointTracker, createReconnectRecoveryCoordinator, STARTUP_RECOVERY_PAGE_SIZE, } from '@canonmsg/coding-agent-host';
31
31
  import { buildCanonUserContent, renderClaudeInboundContent, withClaudeReplyContextMediaReferences, } from './canon-user-content.js';
32
- import { EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonInboundFrameV1, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, normalizeRuntimeCommandDescriptors, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, parseTurnVerbosityConfig, prepareConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, decideAutoReply, initRTDBAuth, isChunkedSendMessageError, sendMessageWithRetry, sendMessageWithRetryChunked, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, resolveLocalRuntimeSessionState, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
32
+ import { EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonInboundFrameV1, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, normalizeRuntimeCommandDescriptors, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createRuntimeHeartbeat, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, parseTurnVerbosityConfig, prepareConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, decideAutoReply, initRTDBAuth, isChunkedSendMessageError, sendMessageWithRetry, sendMessageWithRetryChunked, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, resolveLocalRuntimeSessionState, saveRuntimeSessionState, clearRuntimeSessionState, publishHostSessionSnapshots, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
33
33
  import { runCli } from '@canonmsg/core';
34
34
  import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
35
35
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
@@ -414,9 +414,6 @@ function acceptClaudeCli(source, cliPath) {
414
414
  function toModelOptions(models) {
415
415
  return models.map(claudeModelInfoToOption);
416
416
  }
417
- async function publishAgentRuntime(agentId, runtime, rtdb) {
418
- await publishHostAgentRuntime(agentId, 'claude-code', runtime, rtdb);
419
- }
420
417
  export function resolveSessionExecutionMode(defaultExecutionMode = 'worktree') {
421
418
  return defaultExecutionMode;
422
419
  }
@@ -434,14 +431,6 @@ function resolveWorkspaceCwd() {
434
431
  defaultCwd: workingDir,
435
432
  });
436
433
  }
437
- function resolveExecutionFallbackReason(environment) {
438
- if (!environment?.reason || environment.mode !== 'locked') {
439
- return null;
440
- }
441
- return environment.reason === 'Sharing the base workspace (locked mode)'
442
- ? null
443
- : environment.reason;
444
- }
445
434
  async function detectRuntimeModels(cwd) {
446
435
  const discovered = await detectRuntimeModelsForSelection(cwd, 'default');
447
436
  const supplemental = await detectSupplementalRuntimeModels(cwd, deriveClaudeSupplementalModelProbes(discovered));
@@ -562,147 +551,6 @@ async function readApprovalDiffPreImage(filePath) {
562
551
  throw error;
563
552
  }
564
553
  }
565
- function mapClaudeMcpStatus(status) {
566
- switch (status) {
567
- case 'connected':
568
- return 'ready';
569
- case 'needs-auth':
570
- return 'auth_needed';
571
- case 'disabled':
572
- return 'configured';
573
- case 'failed':
574
- return 'error';
575
- default:
576
- return 'unknown';
577
- }
578
- }
579
- function buildClaudeRuntimeInfo(input) {
580
- const { descriptor, metadata } = input;
581
- const statusItems = [
582
- metadata.account?.email
583
- ? {
584
- id: 'account',
585
- label: 'Account',
586
- value: 'Connected',
587
- tone: 'success',
588
- tier: 'diagnostic',
589
- }
590
- : null,
591
- metadata.account?.subscriptionType
592
- ? {
593
- id: 'subscription',
594
- label: 'Plan',
595
- value: metadata.account.subscriptionType,
596
- }
597
- : null,
598
- metadata.account?.apiProvider
599
- ? {
600
- id: 'provider',
601
- label: 'Provider',
602
- value: metadata.account.apiProvider,
603
- }
604
- : null,
605
- metadata.account?.tokenSource || metadata.account?.apiKeySource
606
- ? {
607
- id: 'auth',
608
- label: 'Auth',
609
- value: metadata.account.tokenSource ?? metadata.account?.apiKeySource ?? 'connected',
610
- tier: 'diagnostic',
611
- }
612
- : null,
613
- runtimeModels[0]
614
- ? {
615
- id: 'fallbackModel',
616
- label: 'Fallback model',
617
- value: runtimeModels[0].label,
618
- tier: 'diagnostic',
619
- source: 'host-default',
620
- }
621
- : null,
622
- {
623
- id: 'toolPolicy',
624
- label: 'Tool policy',
625
- value: 'Claude Code preset',
626
- tier: 'detail',
627
- },
628
- {
629
- id: 'browser',
630
- label: 'Browser tools',
631
- value: 'Runtime policy',
632
- tier: 'detail',
633
- },
634
- {
635
- id: 'mediaOut',
636
- label: 'Media out',
637
- value: 'Generated media artifacts',
638
- tier: 'detail',
639
- },
640
- ].filter((item) => Boolean(item));
641
- return {
642
- descriptor,
643
- surfaceMode: 'host',
644
- statusItems,
645
- inventories: [
646
- {
647
- id: 'mcp',
648
- label: 'MCP',
649
- entries: metadata.mcpServers.map((server) => ({
650
- id: server.name,
651
- label: server.name,
652
- status: mapClaudeMcpStatus(server.status),
653
- description: server.error
654
- ?? server.serverInfo?.version
655
- ?? server.scope
656
- ?? undefined,
657
- })),
658
- tier: 'diagnostic',
659
- },
660
- {
661
- id: 'plugins',
662
- label: 'Plugins',
663
- entries: metadata.plugins.map((plugin) => ({
664
- id: plugin.name,
665
- label: plugin.name,
666
- status: 'configured',
667
- description: plugin.source ?? plugin.path,
668
- })),
669
- tier: 'diagnostic',
670
- },
671
- {
672
- id: 'commands',
673
- label: 'Commands',
674
- entries: metadata.commands.map((command) => ({
675
- id: command.name,
676
- label: command.name.startsWith('/') ? command.name : `/${command.name}`,
677
- status: 'configured',
678
- description: command.description ?? command.argumentHint ?? undefined,
679
- })),
680
- tier: 'diagnostic',
681
- },
682
- {
683
- id: 'agents',
684
- label: 'Agents',
685
- entries: metadata.agents.map((agent) => ({
686
- id: agent.name,
687
- label: agent.name,
688
- status: 'configured',
689
- description: agent.description ?? agent.model ?? undefined,
690
- })),
691
- tier: 'diagnostic',
692
- },
693
- ],
694
- execution: {
695
- resolvedWorkspaceLabel: input.workspaceLabel,
696
- resolvedCwd: input.resolvedCwd,
697
- workspaceRootId: input.workspaceRootId ?? null,
698
- workspaceRelativePath: input.workspaceRelativePath ?? null,
699
- executionMode: input.executionMode,
700
- executionBranch: input.executionBranch,
701
- worktreePath: input.worktreePath,
702
- fallbackReason: input.fallbackReason,
703
- },
704
- };
705
- }
706
554
  // ── Constants ───────────────────────────────────────────────────────
707
555
  const MAX_SESSIONS = 5;
708
556
  // IDLE_TIMEOUT_MS (30 minutes) is shared with the other coding-agent hosts.
@@ -2529,29 +2377,29 @@ export async function main() {
2529
2377
  console.error('[canon-host] Failed to publish session snapshots:', error);
2530
2378
  });
2531
2379
  }
2532
- const publishRuntimeHeartbeat = async () => {
2380
+ const publishLocalHeartbeat = () => {
2533
2381
  heartbeatLocalRuntimeEntry(runtimeId, {
2534
2382
  agentId,
2535
2383
  agentName: profileAgentName,
2536
2384
  cwd: workingDir,
2537
2385
  baseCwd: workingDir,
2538
2386
  });
2539
- if (!streamConnected)
2540
- return;
2387
+ };
2388
+ const refreshRuntimeDetails = async (signal) => {
2541
2389
  await refreshKnownConversationIds().catch((error) => {
2542
2390
  console.error('[canon-host] Failed to refresh known conversations:', error);
2543
2391
  });
2392
+ if (signal.aborted)
2393
+ return;
2544
2394
  const activeSessionQuery = sessions.values().next().value?.query ?? null;
2545
- const metadata = await refreshClaudeRuntimeMetadata({
2395
+ await refreshClaudeRuntimeMetadata({
2546
2396
  cwd: workingDir,
2547
2397
  activeQuery: activeSessionQuery,
2548
2398
  }).catch((error) => {
2549
2399
  console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
2550
- return runtimeMetadata;
2551
- });
2552
- await publishAgentRuntime(agentId, runtimeDescriptor, rtdb).catch((error) => {
2553
- console.error('[canon-host] Failed to publish agent runtime:', error);
2554
2400
  });
2401
+ if (signal.aborted)
2402
+ return;
2555
2403
  await publishSessionSnapshots(Array.from(knownConversationIds));
2556
2404
  // Rebuild so the descriptor picks up native slash commands from the
2557
2405
  // metadata refresh above — the initial descriptor is built before the
@@ -2569,32 +2417,34 @@ export async function main() {
2569
2417
  }),
2570
2418
  };
2571
2419
  }
2572
- await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
2573
- const session = sessions.get(conversationId);
2574
- const workspaceId = session
2575
- ? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
2576
- : runtimeDescriptor.defaultWorkspaceId;
2577
- const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
2420
+ if (signal.aborted)
2421
+ return;
2422
+ const results = await Promise.allSettled(Array.from(knownConversationIds).map(async (conversationId) => {
2578
2423
  const descriptor = runtimeDescriptor.runtimeDescriptor;
2579
2424
  if (!descriptor)
2580
2425
  return;
2581
- const payload = buildClaudeRuntimeInfo({
2582
- descriptor,
2583
- metadata,
2584
- workspaceLabel: workspace?.label ?? workspaceId ?? null,
2585
- resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
2586
- workspaceRootId: workspace?.workspaceRootId ?? null,
2587
- workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
2588
- executionMode: session?.environment.mode ?? null,
2589
- executionBranch: session?.environment.branch ?? null,
2590
- worktreePath: session?.environment.worktreePath ?? null,
2591
- fallbackReason: resolveExecutionFallbackReason(session?.environment),
2426
+ await runtimeState.writeRuntimeInfo(conversationId, {
2427
+ surfaceMode: 'host',
2428
+ descriptor: {
2429
+ coreControls: [],
2430
+ supportsInterrupt: descriptor.supportsInterrupt,
2431
+ supportsInputInterrupt: descriptor.supportsInputInterrupt,
2432
+ streamingTextMode: descriptor.streamingTextMode,
2433
+ },
2592
2434
  });
2593
- await runtimeState.writeRuntimeInfo(conversationId, payload);
2594
- })).catch((error) => {
2595
- console.error('[canon-host] Failed to publish runtime info:', error);
2596
- });
2435
+ }));
2436
+ const failure = results.find((result) => result.status === 'rejected');
2437
+ if (failure?.status === 'rejected')
2438
+ throw failure.reason;
2597
2439
  };
2440
+ const runtimeHeartbeat = createRuntimeHeartbeat({
2441
+ publisher: runtimeState,
2442
+ getRuntime: () => runtimeDescriptor,
2443
+ refreshDetails: refreshRuntimeDetails,
2444
+ onError: (error, operation) => {
2445
+ console.error(`[canon-host] Runtime ${operation} failed:`, error);
2446
+ },
2447
+ });
2598
2448
  // ── Load conversations (informational — sessions created on demand) ──
2599
2449
  try {
2600
2450
  runtimeModels = await detectRuntimeModels(workingDir);
@@ -2706,9 +2556,6 @@ export async function main() {
2706
2556
  client,
2707
2557
  conversationCache,
2708
2558
  });
2709
- function resolveWorkspaceIdForBaseCwd(baseCwd) {
2710
- return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
2711
- }
2712
2559
  async function refreshKnownConversationIds(force = false) {
2713
2560
  if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
2714
2561
  return;
@@ -3004,7 +2851,7 @@ export async function main() {
3004
2851
  runtimeState.clearTurnState(convoId).catch(() => { });
3005
2852
  }, (descriptor) => {
3006
2853
  runtimeDescriptor = descriptor;
3007
- void publishRuntimeHeartbeat();
2854
+ runtimeHeartbeat.refresh();
3008
2855
  }, {
3009
2856
  model: runtimeModels[0]?.value,
3010
2857
  permissionMode: 'default',
@@ -3460,7 +3307,8 @@ export async function main() {
3460
3307
  },
3461
3308
  onConnected: () => {
3462
3309
  streamConnected = true;
3463
- void publishRuntimeHeartbeat();
3310
+ publishLocalHeartbeat();
3311
+ runtimeHeartbeat.connect();
3464
3312
  const recovery = reconnectRecovery.onConnected();
3465
3313
  if (recovery) {
3466
3314
  void recovery.catch((error) => {
@@ -3471,7 +3319,7 @@ export async function main() {
3471
3319
  },
3472
3320
  onDisconnected: () => {
3473
3321
  streamConnected = false;
3474
- runtimeState.clearAgentRuntime().catch(() => { });
3322
+ void runtimeHeartbeat.disconnect();
3475
3323
  console.error('[canon-host] SSE disconnected');
3476
3324
  },
3477
3325
  onError: (err) => console.error(`[canon-host] SSE error: ${err.message}`),
@@ -3571,7 +3419,7 @@ export async function main() {
3571
3419
  session.writeTurn();
3572
3420
  }
3573
3421
  }
3574
- void publishRuntimeHeartbeat();
3422
+ publishLocalHeartbeat();
3575
3423
  }, HEARTBEAT_MS);
3576
3424
  // ── Idle session cleanup ──
3577
3425
  const idleCheck = setInterval(() => {
@@ -3595,7 +3443,7 @@ export async function main() {
3595
3443
  stream.stop();
3596
3444
  approvalManager?.dispose();
3597
3445
  runtimeInputManager.dispose();
3598
- await runtimeState.clearAgentRuntime().catch(() => { });
3446
+ await runtimeHeartbeat.dispose();
3599
3447
  for (const convoId of [...sessions.keys()]) {
3600
3448
  closeSession(convoId);
3601
3449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.34.1",
3
+ "version": "0.34.3",
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,10 +31,10 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "0.3.228",
34
- "@canonmsg/agent-sdk": "^10.1.0",
34
+ "@canonmsg/agent-sdk": "^10.2.1",
35
35
  "@canonmsg/agent-tools": "^0.9.0",
36
36
  "@canonmsg/coding-agent-host": "^0.7.0",
37
- "@canonmsg/core": "^12.1.0",
37
+ "@canonmsg/core": "^12.3.0",
38
38
  "@canonmsg/rich-cards": "^0.10.4",
39
39
  "@modelcontextprotocol/sdk": "^1.30.0"
40
40
  },