@canonmsg/claude-code-plugin 0.25.6 → 0.26.0

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.25.6",
4
+ "version": "0.26.0",
5
5
  "channels": [
6
6
  {
7
7
  "server": "canon-channel",
package/dist/host.js CHANGED
@@ -31,6 +31,7 @@ import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/co
31
31
  import { CLAUDE_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, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, readLocalRuntimeEntry, resolveCanonAgent, initRTDBAuth, sendMessageWithRetry, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
32
32
  import { decideAutoReply, } from './inbound-policy.js';
33
33
  import { runCli } from './cli-entry.js';
34
+ import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
34
35
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
35
36
  import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
36
37
  import { applyClaudeSessionControl, buildClaudeFinalMessageId, claudeModelInfoToOption, confirmClaudeInterrupt, createClaudeInputEnvelope, formatClaudeControlError, isClaudeCustomModelOption, mergeClaudeDiscoveredModelOptions, resolveClaudeTurnResponseRouting, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
@@ -867,6 +868,28 @@ function createSession(conversationId, environment, agentId, client, typingSigna
867
868
  ...(config.effort ? { effort: config.effort } : {}),
868
869
  settings: { forceLoginMethod: 'claudeai' },
869
870
  settingSources: ['project', 'local'],
871
+ // Canonical Canon verbs, in-process (projection 2 of canon.verbs.v1).
872
+ // Fresh instance per session: the SDK connects the instance to its own
873
+ // in-process transport, so instances are not shared across queries.
874
+ // The context getter reads the session lazily (tool calls only happen
875
+ // once the query is running, after `session` is assigned — same pattern
876
+ // as canUseTool): host sessions are conversation-scoped, so the binding
877
+ // supplies the active conversation, turn, and turn responder that the
878
+ // semantic schemas leave to it.
879
+ mcpServers: {
880
+ [CANON_VERB_MCP_SERVER_NAME]: {
881
+ type: 'sdk',
882
+ name: CANON_VERB_MCP_SERVER_NAME,
883
+ instance: createCanonVerbMcpServer(() => client, () => {
884
+ const routing = resolveClaudeTurnResponseRouting(session.activeInput, config.ownerId);
885
+ return {
886
+ conversationId,
887
+ ...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
888
+ ...(routing.responseUserId ? { responseUserId: routing.responseUserId } : {}),
889
+ };
890
+ }),
891
+ },
892
+ },
870
893
  ...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
871
894
  stderr: (data) => {
872
895
  const trimmed = data.toString().trim();
@@ -1,4 +1,3 @@
1
- import type { DirectSessionSelection, SendContextualMessageOptions } from '@canonmsg/core';
2
1
  export type McpParseResult<T> = {
3
2
  ok: true;
4
3
  value: T;
@@ -26,16 +25,6 @@ export interface SendMessageArgs {
26
25
  durationMs?: number;
27
26
  }
28
27
  export declare function parseSendMessageArgs(value: unknown): McpParseResult<SendMessageArgs>;
29
- export interface SendContextualMessageArgs {
30
- sourceConversationId: string;
31
- targetConversationId?: string;
32
- targetUserId?: string;
33
- text: string;
34
- selfContext: SendContextualMessageOptions['selfContext'];
35
- sessionSelection?: DirectSessionSelection;
36
- messageOptions?: SendContextualMessageOptions['messageOptions'];
37
- }
38
- export declare function parseSendContextualMessageArgs(value: unknown): McpParseResult<SendContextualMessageArgs>;
39
28
  export interface SetTypingArgs {
40
29
  conversationId: string;
41
30
  typing: boolean;
package/dist/mcp-args.js CHANGED
@@ -87,74 +87,6 @@ export function parseSendMessageArgs(value) {
87
87
  },
88
88
  };
89
89
  }
90
- function parseSessionSelection(value) {
91
- if (value == null)
92
- return { ok: true, value: undefined };
93
- const raw = typeof value === 'string'
94
- ? value
95
- : isRecord(value) && typeof value.mode === 'string'
96
- ? value.mode
97
- : null;
98
- const mode = raw?.trim().toLowerCase().replace(/-/g, '_');
99
- if (!mode)
100
- return { ok: false, error: 'Invalid session_selection' };
101
- if (mode === 'new' || mode === 'fresh' || mode === 'start_new') {
102
- return { ok: true, value: { mode: 'new' } };
103
- }
104
- if (mode === 'continue_latest' || mode === 'latest' || mode === 'continue') {
105
- return { ok: true, value: { mode: 'continue_latest' } };
106
- }
107
- if (mode === 'continue_or_create' || mode === 'auto' || mode === 'reuse_or_create') {
108
- return { ok: true, value: { mode: 'continue_or_create' } };
109
- }
110
- return { ok: false, error: 'session_selection must be new, continue_latest, or continue_or_create' };
111
- }
112
- export function parseSendContextualMessageArgs(value) {
113
- if (!isRecord(value))
114
- return { ok: false, error: 'Arguments must be an object' };
115
- const sourceConversationId = requiredString(value, 'source_conversation_id', 256);
116
- if (!sourceConversationId.ok)
117
- return sourceConversationId;
118
- const text = requiredString(value, 'text');
119
- if (!text.ok)
120
- return text;
121
- const targetConversationId = optionalString(value, 'target_conversation_id', 256);
122
- const targetUserId = optionalString(value, 'target_user_id', 256);
123
- if (Boolean(targetConversationId) === Boolean(targetUserId)) {
124
- return { ok: false, error: 'Provide exactly one target_conversation_id or target_user_id' };
125
- }
126
- if (!isRecord(value.self_context)) {
127
- return { ok: false, error: 'Missing self_context' };
128
- }
129
- const type = value.self_context.type;
130
- const context = requiredString(value.self_context, 'context');
131
- if (!context.ok)
132
- return context;
133
- if (type !== 'cross_session') {
134
- return { ok: false, error: 'self_context.type must be cross_session' };
135
- }
136
- const sessionSelection = parseSessionSelection(value.session_selection);
137
- if (!sessionSelection.ok)
138
- return sessionSelection;
139
- const messageOptions = isRecord(value.message_options)
140
- ? value.message_options
141
- : undefined;
142
- return {
143
- ok: true,
144
- value: {
145
- sourceConversationId: sourceConversationId.value,
146
- ...(targetConversationId ? { targetConversationId } : {}),
147
- ...(targetUserId ? { targetUserId } : {}),
148
- text: text.value,
149
- selfContext: {
150
- type: 'cross_session',
151
- context: context.value,
152
- },
153
- ...(sessionSelection.value ? { sessionSelection: sessionSelection.value } : {}),
154
- ...(messageOptions ? { messageOptions } : {}),
155
- },
156
- };
157
- }
158
90
  export function parseSetTypingArgs(value) {
159
91
  if (!isRecord(value))
160
92
  return { ok: false, error: 'Arguments must be an object' };
package/dist/server.js CHANGED
@@ -14,7 +14,8 @@ import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, getActi
14
14
  import { materializeMessageMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
15
15
  import { ApprovalHttpServer } from './approval-server.js';
16
16
  import { runCli } from './cli-entry.js';
17
- import { parseReplyArgs, parseSendContextualMessageArgs, parseSendMessageArgs, parseSetTypingArgs, } from './mcp-args.js';
17
+ import { parseReplyArgs, parseSendMessageArgs, parseSetTypingArgs, } from './mcp-args.js';
18
+ import { canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, stampSendToTurnComplete, } from '@canonmsg/agent-tools';
18
19
  const HELP = `canon-channel-server — Claude Code MCP channel server for Canon
19
20
 
20
21
  USAGE
@@ -137,58 +138,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
137
138
  required: ['conversation_id'],
138
139
  },
139
140
  },
140
- {
141
- name: 'send_contextual_message',
142
- description: 'Send a message into another Canon conversation or contact with private cross-session self-context',
143
- inputSchema: {
144
- type: 'object',
145
- properties: {
146
- source_conversation_id: {
147
- type: 'string',
148
- description: 'The Canon conversation where this cross-session intent originated',
149
- },
150
- target_conversation_id: {
151
- type: 'string',
152
- description: 'The destination Canon conversation to send into',
153
- },
154
- target_user_id: {
155
- type: 'string',
156
- description: 'The destination Canon user/contact to reach out to',
157
- },
158
- text: {
159
- type: 'string',
160
- description: 'Message text to send',
161
- },
162
- self_context: {
163
- type: 'object',
164
- description: 'Required { type: "cross_session", context: "why I am saying this here" }',
165
- },
166
- session_selection: {
167
- type: 'string',
168
- enum: ['new', 'continue_latest', 'continue_or_create'],
169
- description: 'Optional direct-agent session behavior when target_user_id is used',
170
- },
171
- message_options: {
172
- type: 'object',
173
- description: 'Optional Canon send options for the contextual outbound message',
174
- },
175
- },
176
- required: ['source_conversation_id', 'text', 'self_context'],
177
- },
178
- },
179
- {
180
- name: 'list_conversations',
181
- description: "List Canon conversations this agent participates in",
182
- inputSchema: {
183
- type: 'object',
184
- properties: {
185
- limit: {
186
- type: 'number',
187
- description: 'Max conversations to return (default: 20)',
188
- },
189
- },
190
- },
191
- },
192
141
  {
193
142
  name: 'list_agents',
194
143
  description: 'List registered Canon agent profiles and their lock status',
@@ -220,6 +169,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
220
169
  required: ['conversation_id', 'typing'],
221
170
  },
222
171
  },
172
+ // Canonical verbs — projections of canon.verbs.v1 (@canonmsg/agent-tools).
173
+ ...canonVerbToolDefinitions(),
223
174
  ],
224
175
  }));
225
176
  // ── Tool handlers ──────────────────────────────────────────────────────
@@ -322,62 +273,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
322
273
  ],
323
274
  };
324
275
  }
325
- case 'send_contextual_message': {
326
- const parsed = parseSendContextualMessageArgs(args);
327
- if (!parsed.ok)
328
- return toolArgumentError(parsed.error);
329
- const { sourceConversationId, targetConversationId, targetUserId, text, selfContext, sessionSelection, messageOptions, } = parsed.value;
330
- const contextualMessageOptions = messageOptions;
331
- const result = await client.sendContextualMessage({
332
- sourceConversationId,
333
- ...(targetConversationId ? { targetConversationId } : {}),
334
- ...(targetUserId ? { targetUserId } : {}),
335
- text,
336
- selfContext,
337
- ...(sessionSelection ? { sessionSelection } : {}),
338
- ...(contextualMessageOptions
339
- ? {
340
- messageOptions: {
341
- ...contextualMessageOptions,
342
- metadata: {
343
- ...(contextualMessageOptions.metadata ?? {}),
344
- ...TURN_COMPLETE_METADATA,
345
- },
346
- },
347
- }
348
- : { messageOptions: { metadata: TURN_COMPLETE_METADATA } }),
349
- });
350
- return {
351
- content: [
352
- {
353
- type: 'text',
354
- text: result.status === 'messaged'
355
- ? `Contextual message sent (${result.messageId}) via self-context ${result.selfContextId}`
356
- : `Contextual message not sent: ${result.status}`,
357
- },
358
- ],
359
- };
360
- }
361
- case 'list_conversations': {
362
- const rawArgs = args && typeof args === 'object' ? args : {};
363
- const limit = typeof rawArgs.limit === 'number' && Number.isFinite(rawArgs.limit)
364
- ? Math.max(1, Math.min(100, Math.floor(rawArgs.limit)))
365
- : 20;
366
- const convos = await client.getConversations();
367
- const slice = convos.slice(0, limit);
368
- const summary = slice.map((c) => ({
369
- id: c.id,
370
- type: c.type,
371
- name: c.name,
372
- members: c.memberIds.length,
373
- lastMessage: c.lastMessage?.text?.slice(0, 100) || null,
374
- }));
375
- return {
376
- content: [
377
- { type: 'text', text: JSON.stringify(summary, null, 2) },
378
- ],
379
- };
380
- }
381
276
  case 'list_agents': {
382
277
  const profiles = loadProfiles();
383
278
  const entries = Object.entries(profiles).map(([name, p]) => {
@@ -413,13 +308,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
413
308
  ],
414
309
  };
415
310
  }
416
- default:
311
+ default: {
312
+ const name = request.params.name;
313
+ if (isCanonToolVerb(name)) {
314
+ const rawArgs = args && typeof args === 'object' ? { ...args } : {};
315
+ const verbArgs = name === 'send_to' ? stampSendToTurnComplete(rawArgs) : rawArgs;
316
+ const verbResult = await executeCanonVerbTool(client, name, verbArgs);
317
+ return {
318
+ content: verbResult.content.map((item) => ({ type: 'text', text: item.text })),
319
+ ...(verbResult.isError ? { isError: true } : {}),
320
+ };
321
+ }
417
322
  return {
418
323
  content: [
419
- { type: 'text', text: `Unknown tool: ${request.params.name}` },
324
+ { type: 'text', text: `Unknown tool: ${name}` },
420
325
  ],
421
326
  isError: true,
422
327
  };
328
+ }
423
329
  }
424
330
  });
425
331
  // ── Inbound message handler ────────────────────────────────────────────
@@ -1,11 +1,14 @@
1
1
  import type { ApprovalManager, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core';
2
2
  export type ClaudeToolPolicyDecision = {
3
3
  behavior: 'allow';
4
+ /** Replacement tool input (the SDK dispatches this instead). */
5
+ updatedInput?: Record<string, unknown>;
4
6
  } | {
5
7
  behavior: 'deny';
6
8
  message: string;
7
9
  };
8
10
  export declare const CLAUDE_NATIVE_APPROVAL_PERMISSION_MODE = "default";
11
+ export declare function canonVerbFromToolName(toolName: string): string | null;
9
12
  export declare function parseAllowedNonOwnerClaudeTools(value: string | null | undefined): string[];
10
13
  export declare function isSensitiveNonOwnerClaudeTool(toolName: string, allowedNonOwnerTools?: Iterable<string>): boolean;
11
14
  export declare function decideClaudeToolPermission(input: {
@@ -1,4 +1,5 @@
1
1
  const NON_OWNER_DENY_MESSAGE = 'Only the agent owner can authorize runtime tool use in this chat.';
2
+ const NON_OWNER_CROSS_CONVERSATION_DENY_MESSAGE = 'Non-owner turns may only target the conversation they came from.';
2
3
  const NATIVE_APPROVAL_UNAVAILABLE_MESSAGE = 'Runtime action approval is not available for this conversation.';
3
4
  const NATIVE_APPROVAL_DENIED_MESSAGE = 'The user denied this action.';
4
5
  export const CLAUDE_NATIVE_APPROVAL_PERMISSION_MODE = 'default';
@@ -36,9 +37,52 @@ function isCanonOutboundTool(normalizedToolName) {
36
37
  return normalizedToolName.endsWith('__reply')
37
38
  || normalizedToolName.endsWith('__send_message')
38
39
  || normalizedToolName.endsWith('__send_contextual_message')
40
+ || normalizedToolName.endsWith('__send_to')
41
+ || normalizedToolName.endsWith('__share_contact')
39
42
  || normalizedToolName === 'reply'
40
43
  || normalizedToolName === 'send_message'
41
- || normalizedToolName === 'send_contextual_message';
44
+ || normalizedToolName === 'send_contextual_message'
45
+ || normalizedToolName === 'send_to'
46
+ || normalizedToolName === 'share_contact';
47
+ }
48
+ /**
49
+ * Canon's own verb tools (the in-process `canon` MCP mount): `mcp__canon__<verb>`.
50
+ * They need their own policy lane — the HITL verbs ARE the approval/input
51
+ * mechanism, so routing them through approvalManager.requestApproval would
52
+ * gate an approval card behind an approval card.
53
+ */
54
+ const CANON_VERB_TOOL_PREFIX = 'mcp__canon__';
55
+ const CANON_HITL_OR_READ_VERBS = new Set([
56
+ 'react',
57
+ 'request_input',
58
+ 'request_approval',
59
+ 'check_approval',
60
+ 'send_card',
61
+ 'request_card',
62
+ 'list_contacts',
63
+ 'list_contact_requests',
64
+ 'list_conversations',
65
+ ]);
66
+ /**
67
+ * Verbs a NON-owner conversation member may trigger: same-conversation HITL
68
+ * and display surfaces, whose responder routing and owner-only escalation
69
+ * (secret/sudo, session rules) are enforced server-side. Cross-conversation
70
+ * verbs (send_to, share_contact) and the reads (contacts, conversation
71
+ * lists — they expose the agent's other relationships to this conversation)
72
+ * stay owner-only until the provenance trigger-token primitive lands.
73
+ */
74
+ const CANON_NON_OWNER_ALLOWED_VERBS = new Set([
75
+ 'request_input',
76
+ 'request_approval',
77
+ 'check_approval',
78
+ 'send_card',
79
+ 'request_card',
80
+ ]);
81
+ export function canonVerbFromToolName(toolName) {
82
+ const normalized = normalizeToolName(toolName);
83
+ if (!normalized.startsWith(CANON_VERB_TOOL_PREFIX))
84
+ return null;
85
+ return normalized.slice(CANON_VERB_TOOL_PREFIX.length) || null;
42
86
  }
43
87
  function isMcpTool(normalizedToolName) {
44
88
  return normalizedToolName.startsWith('mcp__');
@@ -100,12 +144,12 @@ export function classifyClaudeApprovalCategory(toolName) {
100
144
  if (normalized === 'webfetch' || normalized === 'websearch') {
101
145
  return 'network';
102
146
  }
147
+ if (isCanonOutboundTool(normalized) || canonVerbFromToolName(normalized)) {
148
+ return 'canon';
149
+ }
103
150
  if (isMcpTool(normalized) || normalized === 'listmcpresources' || normalized === 'readmcpresource') {
104
151
  return 'mcp';
105
152
  }
106
- if (isCanonOutboundTool(normalized)) {
107
- return 'canon';
108
- }
109
153
  return 'tool';
110
154
  }
111
155
  export function classifyClaudeApprovalRisk(toolName, toolInput) {
@@ -161,6 +205,39 @@ export function buildClaudeApprovalDetails(toolName, toolInput) {
161
205
  return details;
162
206
  }
163
207
  export async function decideClaudeToolPermissionForMode(input) {
208
+ const canonVerb = canonVerbFromToolName(input.toolName);
209
+ if (canonVerb) {
210
+ // Non-owner turns: same-conversation HITL/display verbs are allowed
211
+ // (members may instruct the agent; the server enforces responder
212
+ // membership and owner-only escalation); everything cross-conversation
213
+ // stays owner-only. "Same-conversation" is ENFORCED here, not assumed:
214
+ // the server only checks agent membership in the target, so without this
215
+ // pin a member of conversation A could aim cards/questions at any other
216
+ // conversation the agent belongs to. A mismatching explicit target is
217
+ // denied; otherwise the turn's own conversation is stamped into the
218
+ // input (updatedInput is what the SDK actually dispatches).
219
+ if (!input.isOwnerTurn) {
220
+ if (CANON_NON_OWNER_ALLOWED_VERBS.has(canonVerb)) {
221
+ const target = input.toolInput.conversationId;
222
+ if (typeof target === 'string' && target !== input.conversationId) {
223
+ return { behavior: 'deny', message: NON_OWNER_CROSS_CONVERSATION_DENY_MESSAGE };
224
+ }
225
+ return {
226
+ behavior: 'allow',
227
+ updatedInput: { ...input.toolInput, conversationId: input.conversationId },
228
+ };
229
+ }
230
+ return { behavior: 'deny', message: NON_OWNER_DENY_MESSAGE };
231
+ }
232
+ // HITL + read verbs are allowed without an approval card: the HITL verbs
233
+ // themselves render the human-facing card (recursion otherwise), and the
234
+ // reads are side-effect free. Outbound verbs (send_to, share_contact)
235
+ // fall through to the mode policy — in native-approval mode they gate
236
+ // like any other side effect; in default mode owner turns allow.
237
+ if (CANON_HITL_OR_READ_VERBS.has(canonVerb)) {
238
+ return { behavior: 'allow' };
239
+ }
240
+ }
164
241
  if (input.permissionMode !== CLAUDE_NATIVE_APPROVAL_PERMISSION_MODE) {
165
242
  return decideClaudeToolPermission({
166
243
  toolName: input.toolName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.25.6",
3
+ "version": "0.26.0",
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",
@@ -23,7 +23,7 @@
23
23
  ".claude-plugin"
24
24
  ],
25
25
  "scripts": {
26
- "prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../core ../agent-sdk ../coding-agent-host",
26
+ "prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../core ../rich-cards ../agent-sdk ../coding-agent-host ../agent-tools",
27
27
  "build": "npm run prepare:workspace-deps && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
28
28
  "dev": "npm run prepare:workspace-deps && tsc --watch",
29
29
  "prepack": "npm run build",
@@ -31,9 +31,11 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "0.3.204",
34
- "@canonmsg/agent-sdk": "^5.1.1",
34
+ "@canonmsg/agent-sdk": "^5.1.2",
35
35
  "@canonmsg/coding-agent-host": "^0.2.2",
36
- "@canonmsg/core": "^4.2.3",
36
+ "@canonmsg/agent-tools": "^0.2.0",
37
+ "@canonmsg/core": "^4.3.0",
38
+ "@canonmsg/rich-cards": "^0.8.1",
37
39
  "@modelcontextprotocol/sdk": "^1.29.0"
38
40
  },
39
41
  "engines": {