@canonmsg/claude-code-plugin 0.9.6 → 0.12.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.
package/README.md CHANGED
@@ -20,8 +20,8 @@ Send a message to your agent in the Canon app. Claude Code responds with full ca
20
20
  ## Features
21
21
 
22
22
  - **Two-way messaging** — Messages from Canon flow to Claude Code and back
23
- - **Session controls** — Switch model (Haiku/Sonnet/Opus) and permission mode from the app
24
- - **Live streaming** — See Claude's response as it types
23
+ - **Session controls** — Canon renders setup and live controls from the runtime descriptor Claude publishes
24
+ - **Live preview** — See Claude's current live preview/status in the app
25
25
  - **Interrupt** — Stop Claude mid-response from the app
26
26
  - **Context meter** — See context window usage in the app
27
27
  - **Max plan auth** — Uses your Claude subscription, no API key billing
@@ -32,6 +32,23 @@ Send a message to your agent in the Canon app. Claude Code responds with full ca
32
32
  canon-claude --cwd /path/to/project
33
33
  ```
34
34
 
35
+ Advertise multiple project choices to the Canon app:
36
+
37
+ ```bash
38
+ canon-claude --cwd ~/dev --workspace-root ~/dev
39
+ ```
40
+
41
+ `--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
42
+
43
+ Current Canon truth for Claude host mode:
44
+
45
+ - model is live-editable
46
+ - thinking level / effort is live-editable when the host reports it
47
+ - workspace selection is setup-only
48
+ - execution mode selection is setup-only
49
+ - permission modes are rendered exactly as the Claude runtime reports them; Canon does not flatten them to a smaller hardcoded set
50
+ - if worktree creation is unavailable for the selected project, Canon may fall back to shared-project execution and surface the fallback reason in session details
51
+
35
52
  ## Multiple agents
36
53
 
37
54
  ```bash
@@ -98,17 +98,23 @@ export declare function publishHostSessionSnapshots(input: {
98
98
  liveSessionConfigByConversation?: ReadonlyMap<string, {
99
99
  model?: string;
100
100
  permissionMode?: string;
101
+ effort?: string;
102
+ runtimeControlValues?: Record<string, string>;
101
103
  workspaceId?: string;
102
104
  executionMode?: SessionWorkspaceConfig['executionMode'];
103
105
  executionBranch?: string | null;
104
106
  }>;
105
107
  }): Promise<void>;
106
- export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>>) | null;
108
+ export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
109
+ runtimeControlValues?: Record<string, string>;
110
+ }) | null;
107
111
  export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
108
112
  conversationId: string;
109
113
  agentId: string;
110
114
  extraStringFields?: readonly TExtra[];
111
- }): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>>) | null>;
115
+ }): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
116
+ runtimeControlValues?: Record<string, string>;
117
+ }) | null>;
112
118
  export declare function resolveHostWorkspaceCwd(input: {
113
119
  workspaceOptions: HostWorkspaceResolverOption[];
114
120
  config: {
@@ -145,7 +145,7 @@ export async function publishHostSessionSnapshots(input) {
145
145
  const persistedConfig = await loadHostSessionConfig({
146
146
  conversationId,
147
147
  agentId: input.agentId,
148
- extraStringFields: ['permissionMode'],
148
+ extraStringFields: ['permissionMode', 'effort'],
149
149
  });
150
150
  const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
151
151
  const mergedConfig = {
@@ -163,6 +163,10 @@ export async function publishHostSessionSnapshots(input) {
163
163
  sessionConfig: {
164
164
  ...(mergedConfig.model ? { model: mergedConfig.model } : {}),
165
165
  ...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
166
+ ...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
167
+ ...(mergedConfig.runtimeControlValues
168
+ ? { runtimeControlValues: mergedConfig.runtimeControlValues }
169
+ : {}),
166
170
  ...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
167
171
  ...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
168
172
  },
@@ -184,6 +188,8 @@ export async function publishHostSessionSnapshots(input) {
184
188
  hostMode: true,
185
189
  model: snapshot.model ?? null,
186
190
  permissionMode: snapshot.permissionMode ?? null,
191
+ effort: snapshot.effort ?? null,
192
+ runtimeControlValues: snapshot.runtimeControlValues ?? null,
187
193
  workspaceId: snapshot.workspaceId ?? null,
188
194
  executionMode: snapshot.executionMode ?? null,
189
195
  executionBranch,
@@ -205,9 +211,16 @@ export function readHostSessionConfig(raw, extraStringFields = []) {
205
211
  const value = normalizeOptionalString(data[field]);
206
212
  return value ? [[field, value]] : [];
207
213
  }));
214
+ const runtimeControlValues = Object.fromEntries(Object.entries(data.runtimeControlValues && typeof data.runtimeControlValues === 'object'
215
+ ? data.runtimeControlValues
216
+ : {}).flatMap(([key, value]) => {
217
+ const normalizedValue = normalizeOptionalString(value);
218
+ return normalizedValue ? [[key, normalizedValue]] : [];
219
+ }));
208
220
  return {
209
221
  ...(baseConfig ?? {}),
210
222
  ...extraConfig,
223
+ ...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
211
224
  };
212
225
  }
213
226
  export async function loadHostSessionConfig(input) {
package/dist/host.js CHANGED
@@ -24,16 +24,196 @@ import { parseArgs } from 'node:util';
24
24
  import { randomUUID } from 'node:crypto';
25
25
  import { execFileSync } from 'node:child_process';
26
26
  import { existsSync } from 'node:fs';
27
- import { query } from '@anthropic-ai/claude-agent-sdk';
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, buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, getActiveProfile, releaseLock, initRTDBAuth, rtdbRead, rtdbWrite, writeSessionState, clearSessionState, writeTurnState, releaseConversationEnvironment, } from '@canonmsg/core';
29
+ import { CLAUDE_PERMISSION_MODE_OPTIONS, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, getActiveProfile, releaseLock, initRTDBAuth, rtdbRead, rtdbWrite, writeRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, releaseConversationEnvironment, } from '@canonmsg/core';
30
30
  import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
31
31
  import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
32
32
  import { createStreamingBuffer } from './streaming-buffer.js';
33
33
  import { runCli } from './cli-entry.js';
34
34
  let workingDir = process.cwd();
35
35
  let workspaceOptions = [];
36
+ let workspaceRoots = [];
37
+ let workspaceRootMetadata = [];
36
38
  let runtimeModels = [];
39
+ const CLAUDE_METADATA_TTL_MS = 5 * 60 * 1000;
40
+ let runtimeMetadata = {
41
+ account: null,
42
+ mcpServers: [],
43
+ commands: [],
44
+ agents: [],
45
+ plugins: [],
46
+ loadedAt: 0,
47
+ };
48
+ let runtimeMetadataRefresh = null;
49
+ function buildClaudeRuntimeDescriptor(input) {
50
+ return {
51
+ coreControls: [
52
+ {
53
+ id: 'model',
54
+ label: 'Model',
55
+ options: input.models,
56
+ defaultValue: input.models[0]?.value ?? null,
57
+ availability: 'setup_and_live',
58
+ liveBehavior: 'immediate',
59
+ selectionPolicy: 'inherit',
60
+ },
61
+ {
62
+ id: 'workspace',
63
+ label: 'Project',
64
+ options: input.workspaces.map((workspace) => ({
65
+ value: workspace.id,
66
+ label: workspace.label,
67
+ ...(workspace.description ? { description: workspace.description } : {}),
68
+ ...(workspace.workspaceRootId ? { workspaceRootId: workspace.workspaceRootId } : {}),
69
+ ...(workspace.workspaceRelativePath ? { workspaceRelativePath: workspace.workspaceRelativePath } : {}),
70
+ ...(workspace.source ? { source: workspace.source } : {}),
71
+ })),
72
+ defaultValue: input.workspaces[0]?.id ?? null,
73
+ availability: 'setup',
74
+ liveBehavior: 'none',
75
+ selectionPolicy: 'inherit',
76
+ description: input.workspaceRoots?.length
77
+ ? 'Choose one of the projects discovered inside the approved local roots for this host.'
78
+ : 'Choose one of the local projects advertised by this host.',
79
+ },
80
+ {
81
+ id: 'executionMode',
82
+ label: 'Execution mode',
83
+ options: input.executionModes.map((mode) => ({
84
+ value: mode,
85
+ label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared project',
86
+ description: mode === 'worktree'
87
+ ? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected project is a git repo.'
88
+ : 'Runs directly in the selected project folder. Changes happen there.',
89
+ })),
90
+ defaultValue: null,
91
+ availability: 'setup',
92
+ liveBehavior: 'none',
93
+ selectionPolicy: 'required_explicit',
94
+ },
95
+ ],
96
+ runtimeControls: [
97
+ {
98
+ id: 'permissionMode',
99
+ label: 'Permission mode',
100
+ options: [...CLAUDE_PERMISSION_MODE_OPTIONS],
101
+ defaultValue: 'default',
102
+ availability: 'setup_and_live',
103
+ liveBehavior: 'immediate',
104
+ selectionPolicy: 'inherit',
105
+ },
106
+ {
107
+ id: 'effort',
108
+ label: 'Thinking level',
109
+ options: [
110
+ { value: 'low', label: 'Low' },
111
+ { value: 'medium', label: 'Medium' },
112
+ { value: 'high', label: 'High' },
113
+ ],
114
+ defaultValue: 'medium',
115
+ availability: 'setup_and_live',
116
+ liveBehavior: 'immediate',
117
+ selectionPolicy: 'inherit',
118
+ },
119
+ ],
120
+ actions: [
121
+ {
122
+ id: 'plan',
123
+ label: 'Plan first',
124
+ description: 'Switch Claude into Plan mode. Text after /plan is kept as a ready prompt while the mode applies.',
125
+ aliases: ['plan'],
126
+ category: 'plan',
127
+ placements: ['composer_slash', 'command_palette'],
128
+ availability: ['always'],
129
+ trailingTextBehavior: 'send_as_prompt',
130
+ dispatch: {
131
+ kind: 'control',
132
+ controlId: 'permissionMode',
133
+ value: 'plan',
134
+ },
135
+ },
136
+ {
137
+ id: 'runtime-status',
138
+ label: 'Runtime status',
139
+ description: 'Open Claude runtime details, including account, project, and policy status.',
140
+ aliases: ['status'],
141
+ category: 'details',
142
+ placements: ['composer_slash', 'command_palette'],
143
+ availability: ['always'],
144
+ dispatch: { kind: 'open_details', target: 'status' },
145
+ },
146
+ {
147
+ id: 'mcp-inventory',
148
+ label: 'MCP servers',
149
+ description: 'Open the Claude MCP inventory published by the host.',
150
+ aliases: ['mcp'],
151
+ category: 'details',
152
+ placements: ['composer_slash', 'command_palette'],
153
+ availability: ['always'],
154
+ dispatch: { kind: 'open_details', target: 'mcp' },
155
+ },
156
+ {
157
+ id: 'plugin-inventory',
158
+ label: 'Plugins',
159
+ description: 'Open the Claude plugin and command inventory published by the host.',
160
+ aliases: ['plugins'],
161
+ category: 'details',
162
+ placements: ['composer_slash', 'command_palette'],
163
+ availability: ['always'],
164
+ dispatch: { kind: 'open_details', target: 'plugins' },
165
+ },
166
+ {
167
+ id: 'model-details',
168
+ label: 'Model control',
169
+ description: 'Open runtime details and use the live Model control.',
170
+ aliases: ['model'],
171
+ category: 'details',
172
+ placements: ['composer_slash', 'command_palette'],
173
+ availability: ['always'],
174
+ dispatch: { kind: 'open_details', target: 'model' },
175
+ },
176
+ {
177
+ id: 'permission-details',
178
+ label: 'Permission mode',
179
+ description: 'Open runtime details and use the live Permission mode control.',
180
+ aliases: ['permission'],
181
+ category: 'details',
182
+ placements: ['composer_slash', 'command_palette'],
183
+ availability: ['always'],
184
+ dispatch: { kind: 'open_details', target: 'permissionMode' },
185
+ },
186
+ {
187
+ id: 'effort-details',
188
+ label: 'Thinking level',
189
+ description: 'Open runtime details and use the live Thinking level control.',
190
+ aliases: ['effort'],
191
+ category: 'details',
192
+ placements: ['composer_slash', 'command_palette'],
193
+ availability: ['always'],
194
+ dispatch: { kind: 'open_details', target: 'effort' },
195
+ },
196
+ {
197
+ id: 'stop',
198
+ label: 'Stop',
199
+ description: 'Interrupt the current Claude turn.',
200
+ aliases: ['stop'],
201
+ category: 'turn',
202
+ placements: ['composer_slash', 'command_palette'],
203
+ availability: ['busy'],
204
+ dispatch: { kind: 'signal', signal: 'interrupt' },
205
+ },
206
+ ],
207
+ workspaceRoots: input.workspaceRoots,
208
+ supportsInterrupt: true,
209
+ streamingTextMode: 'delta',
210
+ };
211
+ }
212
+ function normalizeClaudeEffortLevel(value) {
213
+ return value === 'low' || value === 'medium' || value === 'high'
214
+ ? value
215
+ : undefined;
216
+ }
37
217
  let cachedClaudeCliPath;
38
218
  // The SDK bundles a Bun-compiled `claude` Mach-O binary next to
39
219
  // claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
@@ -83,18 +263,18 @@ async function loadSessionConfig(conversationId, agentId) {
83
263
  const config = await loadHostSessionConfig({
84
264
  conversationId,
85
265
  agentId,
86
- extraStringFields: ['permissionMode'],
266
+ extraStringFields: ['permissionMode', 'effort'],
87
267
  });
88
268
  return {
89
269
  ...config,
90
270
  permissionMode: config?.permissionMode,
271
+ effort: normalizeClaudeEffortLevel(config?.effort),
91
272
  };
92
273
  }
93
- // Default to 'locked' (shared workspace) when no mode has been picked. The
94
- // UI still lets owners flip to 'worktree'; this just stops sessions from
95
- // failing closed when the mode has never been written.
96
274
  function resolveSessionExecutionMode(config) {
97
- return config?.executionMode ?? 'locked';
275
+ if (config?.executionMode)
276
+ return config.executionMode;
277
+ throw new ExecutionEnvironmentError('Session config is missing an execution mode.', 'Choose Isolated worktree or Use shared project before starting this coding session.');
98
278
  }
99
279
  function resolveWorkspaceCwd(config) {
100
280
  return resolveHostWorkspaceCwd({
@@ -103,6 +283,14 @@ function resolveWorkspaceCwd(config) {
103
283
  defaultCwd: workingDir,
104
284
  });
105
285
  }
286
+ function resolveExecutionFallbackReason(environment) {
287
+ if (!environment?.reason || environment.mode !== 'locked') {
288
+ return null;
289
+ }
290
+ return environment.reason === 'Sharing the base workspace (locked mode)'
291
+ ? null
292
+ : environment.reason;
293
+ }
106
294
  async function detectRuntimeModels(cwd) {
107
295
  const emptyPrompt = {
108
296
  async *[Symbol.asyncIterator]() { },
@@ -130,6 +318,190 @@ async function detectRuntimeModels(cwd) {
130
318
  catch { }
131
319
  }
132
320
  }
321
+ async function inspectClaudeRuntimeMetadata(cwd, activeQuery) {
322
+ const emptyPrompt = {
323
+ async *[Symbol.asyncIterator]() { },
324
+ };
325
+ const claudeCliPath = resolveClaudeCliPath();
326
+ const probe = activeQuery ?? query({
327
+ prompt: emptyPrompt,
328
+ options: {
329
+ systemPrompt: { type: 'preset', preset: 'claude_code' },
330
+ tools: { type: 'preset', preset: 'claude_code' },
331
+ permissionMode: 'default',
332
+ cwd,
333
+ settings: { forceLoginMethod: 'claudeai' },
334
+ ...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
335
+ },
336
+ });
337
+ try {
338
+ const [init, mcpServers, pluginInfo] = await Promise.all([
339
+ probe.initializationResult(),
340
+ probe.mcpServerStatus().catch(() => []),
341
+ probe.reloadPlugins().catch(() => null),
342
+ ]);
343
+ return {
344
+ account: init.account ?? null,
345
+ mcpServers: pluginInfo?.mcpServers ?? mcpServers,
346
+ commands: pluginInfo?.commands ?? init.commands ?? [],
347
+ agents: pluginInfo?.agents ?? init.agents ?? [],
348
+ plugins: pluginInfo?.plugins ?? [],
349
+ loadedAt: Date.now(),
350
+ };
351
+ }
352
+ finally {
353
+ if (!activeQuery) {
354
+ try {
355
+ probe.close();
356
+ }
357
+ catch { }
358
+ }
359
+ }
360
+ }
361
+ async function refreshClaudeRuntimeMetadata(input) {
362
+ if (!input.force
363
+ && runtimeMetadata.loadedAt > 0
364
+ && Date.now() - runtimeMetadata.loadedAt < CLAUDE_METADATA_TTL_MS) {
365
+ return runtimeMetadata;
366
+ }
367
+ if (runtimeMetadataRefresh) {
368
+ return runtimeMetadataRefresh;
369
+ }
370
+ runtimeMetadataRefresh = inspectClaudeRuntimeMetadata(input.cwd, input.activeQuery ?? null)
371
+ .then((metadata) => {
372
+ runtimeMetadata = metadata;
373
+ return metadata;
374
+ })
375
+ .finally(() => {
376
+ runtimeMetadataRefresh = null;
377
+ });
378
+ return runtimeMetadataRefresh;
379
+ }
380
+ function mapClaudeMcpStatus(status) {
381
+ switch (status) {
382
+ case 'connected':
383
+ return 'ready';
384
+ case 'needs-auth':
385
+ return 'auth_needed';
386
+ case 'disabled':
387
+ return 'configured';
388
+ case 'failed':
389
+ return 'error';
390
+ default:
391
+ return 'unknown';
392
+ }
393
+ }
394
+ function buildClaudeRuntimeInfo(input) {
395
+ const { descriptor, metadata } = input;
396
+ const statusItems = [
397
+ metadata.account?.email
398
+ ? {
399
+ id: 'account',
400
+ label: 'Account',
401
+ value: metadata.account.email,
402
+ tone: 'success',
403
+ }
404
+ : null,
405
+ metadata.account?.subscriptionType
406
+ ? {
407
+ id: 'subscription',
408
+ label: 'Plan',
409
+ value: metadata.account.subscriptionType,
410
+ }
411
+ : null,
412
+ metadata.account?.apiProvider
413
+ ? {
414
+ id: 'provider',
415
+ label: 'Provider',
416
+ value: metadata.account.apiProvider,
417
+ }
418
+ : null,
419
+ metadata.account?.tokenSource || metadata.account?.apiKeySource
420
+ ? {
421
+ id: 'auth',
422
+ label: 'Auth',
423
+ value: metadata.account.tokenSource ?? metadata.account?.apiKeySource ?? 'connected',
424
+ }
425
+ : null,
426
+ runtimeModels[0]
427
+ ? {
428
+ id: 'fallbackModel',
429
+ label: 'Fallback model',
430
+ value: runtimeModels[0].label,
431
+ }
432
+ : null,
433
+ {
434
+ id: 'toolPolicy',
435
+ label: 'Tool policy',
436
+ value: 'Claude Code preset',
437
+ },
438
+ {
439
+ id: 'browser',
440
+ label: 'Browser tools',
441
+ value: 'Runtime policy',
442
+ },
443
+ ].filter((item) => Boolean(item));
444
+ return {
445
+ descriptor,
446
+ surfaceMode: 'host',
447
+ statusItems,
448
+ inventories: [
449
+ {
450
+ id: 'mcp',
451
+ label: 'MCP',
452
+ entries: metadata.mcpServers.map((server) => ({
453
+ id: server.name,
454
+ label: server.name,
455
+ status: mapClaudeMcpStatus(server.status),
456
+ description: server.error
457
+ ?? server.serverInfo?.version
458
+ ?? server.scope
459
+ ?? undefined,
460
+ })),
461
+ },
462
+ {
463
+ id: 'plugins',
464
+ label: 'Plugins',
465
+ entries: metadata.plugins.map((plugin) => ({
466
+ id: plugin.name,
467
+ label: plugin.name,
468
+ status: 'configured',
469
+ description: plugin.source ?? plugin.path,
470
+ })),
471
+ },
472
+ {
473
+ id: 'commands',
474
+ label: 'Commands',
475
+ entries: metadata.commands.map((command) => ({
476
+ id: command.name,
477
+ label: command.name.startsWith('/') ? command.name : `/${command.name}`,
478
+ status: 'configured',
479
+ description: command.description ?? command.argumentHint ?? undefined,
480
+ })),
481
+ },
482
+ {
483
+ id: 'agents',
484
+ label: 'Agents',
485
+ entries: metadata.agents.map((agent) => ({
486
+ id: agent.name,
487
+ label: agent.name,
488
+ status: 'configured',
489
+ description: agent.description ?? agent.model ?? undefined,
490
+ })),
491
+ },
492
+ ],
493
+ execution: {
494
+ resolvedWorkspaceLabel: input.workspaceLabel,
495
+ resolvedCwd: input.resolvedCwd,
496
+ workspaceRootId: input.workspaceRootId ?? null,
497
+ workspaceRelativePath: input.workspaceRelativePath ?? null,
498
+ executionMode: input.executionMode,
499
+ executionBranch: input.executionBranch,
500
+ worktreePath: input.worktreePath,
501
+ fallbackReason: input.fallbackReason,
502
+ },
503
+ };
504
+ }
133
505
  // ── Constants ───────────────────────────────────────────────────────
134
506
  const MAX_SESSIONS = 5;
135
507
  const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
@@ -245,6 +617,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
245
617
  systemPrompt: { type: 'preset', preset: 'claude_code' },
246
618
  tools: { type: 'preset', preset: 'claude_code' },
247
619
  permissionMode: config.permissionMode,
620
+ allowDangerouslySkipPermissions: true,
248
621
  cwd,
249
622
  includePartialMessages: true,
250
623
  maxTurns: 200,
@@ -284,7 +657,30 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
284
657
  if (toolName === 'ExitPlanMode') {
285
658
  try {
286
659
  const planId = randomUUID();
287
- const planApproval = buildPlanApprovalRequest(planId);
660
+ const allowedPrompts = Array.isArray(input.allowedPrompts)
661
+ ? input.allowedPrompts
662
+ .map((entry) => {
663
+ if (!entry || typeof entry !== 'object') {
664
+ return null;
665
+ }
666
+ const prompt = entry.prompt;
667
+ const tool = entry.tool;
668
+ if (typeof prompt !== 'string' || !prompt.trim()) {
669
+ return null;
670
+ }
671
+ return {
672
+ tool: typeof tool === 'string' && tool.trim() ? tool : 'Tool',
673
+ prompt,
674
+ };
675
+ })
676
+ .filter((entry) => Boolean(entry))
677
+ : [];
678
+ const planApproval = buildPlanApprovalRequest(planId, 'Plan ready for review.', allowedPrompts.length > 0 ? {
679
+ summary: 'Claude has prepared a plan and is asking permission to continue.',
680
+ allowedPrompts,
681
+ } : {
682
+ summary: 'Claude has prepared a plan and is asking permission to continue.',
683
+ });
288
684
  await client.sendMessage(conversationId, planApproval.text, {
289
685
  metadata: {
290
686
  ...planApproval.metadata,
@@ -344,6 +740,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
344
740
  currentTurnOpenedAt: null,
345
741
  lastAcceptedIntent: null,
346
742
  lastActivity: Date.now(),
743
+ typingKeepaliveTimer: null,
347
744
  closed: false,
348
745
  sdkSessionId: resumeSessionId || null,
349
746
  pendingReply: null,
@@ -400,6 +797,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
400
797
  session.pendingFinalDeliveryText = null;
401
798
  session.toolInProgress = false;
402
799
  session.turnState = 'idle';
800
+ stopVisibleWorkSignal();
403
801
  writeTurn();
404
802
  drainPendingInput();
405
803
  }
@@ -418,6 +816,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
418
816
  session.pendingFinalDeliveryText = finalText;
419
817
  session.state.state = 'running';
420
818
  session.turnState = 'streaming';
819
+ stopVisibleWorkSignal();
421
820
  streamingBuffer.replace(finalText);
422
821
  writeState();
423
822
  writeTurn();
@@ -468,6 +867,10 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
468
867
  cwd,
469
868
  executionMode: session.environment.mode,
470
869
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
870
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
871
+ ...(resolveExecutionFallbackReason(session.environment)
872
+ ? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
873
+ : {}),
471
874
  hostMode: true,
472
875
  isActive: true,
473
876
  }).catch(() => { });
@@ -486,6 +889,25 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
486
889
  : {}),
487
890
  }).catch(() => { });
488
891
  }
892
+ function refreshVisibleWorkSignal() {
893
+ if (session.closed)
894
+ return;
895
+ if (session.turnState !== 'thinking' && session.turnState !== 'tool')
896
+ return;
897
+ client.setTyping(conversationId, true, 'thinking').catch(() => { });
898
+ }
899
+ function startVisibleWorkSignal() {
900
+ refreshVisibleWorkSignal();
901
+ if (session.typingKeepaliveTimer)
902
+ return;
903
+ session.typingKeepaliveTimer = setInterval(refreshVisibleWorkSignal, 3500);
904
+ }
905
+ function stopVisibleWorkSignal() {
906
+ if (session.typingKeepaliveTimer) {
907
+ clearInterval(session.typingKeepaliveTimer);
908
+ session.typingKeepaliveTimer = null;
909
+ }
910
+ }
489
911
  async function markQueuedMessageAccepted(sourceMessageId, markAccepted) {
490
912
  if (!markAccepted || !sourceMessageId)
491
913
  return;
@@ -517,6 +939,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
517
939
  if (session.state.state === 'running' && intent === 'interrupt') {
518
940
  session.pendingInputs.unshift({ msg, intent, sourceMessageId, markAccepted });
519
941
  session.turnState = 'interrupted';
942
+ stopVisibleWorkSignal();
520
943
  writeTurn();
521
944
  void session.query.interrupt().catch(() => { });
522
945
  return;
@@ -557,18 +980,22 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
557
980
  if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
558
981
  session.toolInProgress = true;
559
982
  session.turnState = 'tool';
983
+ startVisibleWorkSignal();
560
984
  writeTurn();
561
985
  }
562
986
  else if (event?.type === 'content_block_stop' && session.toolInProgress) {
563
987
  session.toolInProgress = false;
564
988
  if (session.state.state === 'running') {
565
989
  session.turnState = 'thinking';
990
+ startVisibleWorkSignal();
566
991
  writeTurn();
567
992
  }
568
993
  }
569
994
  else if (event?.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
570
995
  session.toolInProgress = false;
571
996
  session.turnState = 'streaming';
997
+ stopVisibleWorkSignal();
998
+ client.setTyping(conversationId, false).catch(() => { });
572
999
  writeTurn();
573
1000
  onStreamDelta(event.delta.text);
574
1001
  }
@@ -589,9 +1016,10 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
589
1016
  session.pendingFinalText = null;
590
1017
  session.toolInProgress = false;
591
1018
  session.turnState = 'thinking';
592
- client.setTyping(conversationId, true, 'thinking').catch(() => { });
1019
+ startVisibleWorkSignal();
593
1020
  }
594
1021
  else if (state === 'idle') {
1022
+ stopVisibleWorkSignal();
595
1023
  client.setTyping(conversationId, false).catch(() => { });
596
1024
  }
597
1025
  else if (state === 'requires_action') {
@@ -602,6 +1030,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
602
1030
  }
603
1031
  session.toolInProgress = false;
604
1032
  session.turnState = 'waiting_input';
1033
+ stopVisibleWorkSignal();
605
1034
  client.setTyping(conversationId, false).catch(() => { });
606
1035
  }
607
1036
  writeState();
@@ -668,6 +1097,15 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
668
1097
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial model:`, err);
669
1098
  }
670
1099
  }
1100
+ if (config.effort) {
1101
+ try {
1102
+ await q.applyFlagSettings({ effortLevel: config.effort });
1103
+ session.state.effort = config.effort;
1104
+ }
1105
+ catch (err) {
1106
+ console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial effort:`, err);
1107
+ }
1108
+ }
671
1109
  if (!resumeSessionId) {
672
1110
  // New session — send seed message to activate streaming input mode
673
1111
  sendInput({
@@ -692,6 +1130,12 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
692
1130
  availableModels: modelList,
693
1131
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
694
1132
  availableExecutionModes: config.availableExecutionModes,
1133
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1134
+ models: modelList,
1135
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1136
+ workspaceRoots: workspaceRootMetadata,
1137
+ executionModes: config.availableExecutionModes,
1138
+ }),
695
1139
  });
696
1140
  writeSessionState(conversationId, agentId, {
697
1141
  ...session.state,
@@ -699,6 +1143,10 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
699
1143
  cwd,
700
1144
  executionMode: session.environment.mode,
701
1145
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1146
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1147
+ ...(resolveExecutionFallbackReason(session.environment)
1148
+ ? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
1149
+ : {}),
702
1150
  hostMode: true,
703
1151
  isActive: true,
704
1152
  }).catch(() => { });
@@ -720,12 +1168,24 @@ export async function main() {
720
1168
  options: {
721
1169
  cwd: { type: 'string' },
722
1170
  workspace: { type: 'string', multiple: true },
1171
+ 'workspace-root': { type: 'string', multiple: true },
723
1172
  },
724
1173
  strict: false,
725
1174
  });
726
1175
  workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
727
1176
  const configuredWorkspaces = (args.workspace ?? []).filter((value) => typeof value === 'string');
728
- workspaceOptions = buildConfiguredWorkspaceOptions(workingDir, configuredWorkspaces);
1177
+ const configuredWorkspaceRoots = (args['workspace-root'] ?? []).filter((value) => typeof value === 'string');
1178
+ const workspaceDiscovery = buildConfiguredWorkspaceOptionsWithRoots({
1179
+ primaryCwd: workingDir,
1180
+ configuredWorkspaces,
1181
+ workspaceRoots: configuredWorkspaceRoots,
1182
+ });
1183
+ workspaceOptions = workspaceDiscovery.workspaceOptions;
1184
+ workspaceRoots = workspaceDiscovery.workspaceRoots;
1185
+ workspaceRootMetadata = buildPublicWorkspaceRoots(workspaceRoots);
1186
+ for (const warning of workspaceDiscovery.warnings) {
1187
+ console.error(`[canon-host] ${warning}`);
1188
+ }
729
1189
  const { apiKey, agentId: profileAgentId, profile } = resolveCanonAgent({ logPrefix: '[canon-host]' });
730
1190
  console.error(`[canon-host] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
731
1191
  // ── Canon API client + RTDB auth ──
@@ -759,6 +1219,12 @@ export async function main() {
759
1219
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
760
1220
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
761
1221
  availableExecutionModes: hostAvailableExecutionModes,
1222
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1223
+ models: runtimeModels,
1224
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1225
+ workspaceRoots: workspaceRootMetadata,
1226
+ executionModes: hostAvailableExecutionModes,
1227
+ }),
762
1228
  };
763
1229
  const conversationCache = new Map();
764
1230
  const knownConversationIds = new Set();
@@ -769,6 +1235,14 @@ export async function main() {
769
1235
  await refreshKnownConversationIds().catch((error) => {
770
1236
  console.error('[canon-host] Failed to refresh known conversations:', error);
771
1237
  });
1238
+ const activeSessionQuery = sessions.values().next().value?.query ?? null;
1239
+ const metadata = await refreshClaudeRuntimeMetadata({
1240
+ cwd: workingDir,
1241
+ activeQuery: activeSessionQuery,
1242
+ }).catch((error) => {
1243
+ console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
1244
+ return runtimeMetadata;
1245
+ });
772
1246
  await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
773
1247
  console.error('[canon-host] Failed to publish agent runtime:', error);
774
1248
  });
@@ -786,6 +1260,7 @@ export async function main() {
786
1260
  {
787
1261
  ...(session.state.model ? { model: session.state.model } : {}),
788
1262
  ...(session.state.permissionMode ? { permissionMode: session.state.permissionMode } : {}),
1263
+ ...(session.state.effort ? { effort: session.state.effort } : {}),
789
1264
  ...(workspaceId ? { workspaceId } : {}),
790
1265
  executionMode: session.environment.mode,
791
1266
  executionBranch: session.environment.branch ?? null,
@@ -795,6 +1270,33 @@ export async function main() {
795
1270
  }).catch((error) => {
796
1271
  console.error('[canon-host] Failed to publish session snapshots:', error);
797
1272
  });
1273
+ await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
1274
+ const session = sessions.get(conversationId);
1275
+ const workspaceId = session
1276
+ ? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
1277
+ : runtimeDescriptor.defaultWorkspaceId;
1278
+ const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
1279
+ const payload = buildClaudeRuntimeInfo({
1280
+ descriptor: runtimeDescriptor.runtimeDescriptor ?? buildClaudeRuntimeDescriptor({
1281
+ models: runtimeModels,
1282
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1283
+ workspaceRoots: workspaceRootMetadata,
1284
+ executionModes: hostAvailableExecutionModes,
1285
+ }),
1286
+ metadata,
1287
+ workspaceLabel: workspace?.label ?? workspaceId ?? null,
1288
+ resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
1289
+ workspaceRootId: workspace?.workspaceRootId ?? null,
1290
+ workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
1291
+ executionMode: session?.environment.mode ?? null,
1292
+ executionBranch: session?.environment.branch ?? null,
1293
+ worktreePath: session?.environment.worktreePath ?? null,
1294
+ fallbackReason: resolveExecutionFallbackReason(session?.environment),
1295
+ });
1296
+ await writeRuntimeInfo(conversationId, agentId, payload);
1297
+ })).catch((error) => {
1298
+ console.error('[canon-host] Failed to publish runtime info:', error);
1299
+ });
798
1300
  };
799
1301
  // ── Load conversations (informational — sessions created on demand) ──
800
1302
  try {
@@ -807,6 +1309,12 @@ export async function main() {
807
1309
  availableModels: runtimeModels,
808
1310
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
809
1311
  availableExecutionModes: hostAvailableExecutionModes,
1312
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1313
+ models: runtimeModels,
1314
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1315
+ workspaceRoots: workspaceRootMetadata,
1316
+ executionModes: hostAvailableExecutionModes,
1317
+ }),
810
1318
  };
811
1319
  }
812
1320
  catch {
@@ -816,6 +1324,12 @@ export async function main() {
816
1324
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
817
1325
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
818
1326
  availableExecutionModes: hostAvailableExecutionModes,
1327
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1328
+ models: runtimeModels,
1329
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1330
+ workspaceRoots: workspaceRootMetadata,
1331
+ executionModes: hostAvailableExecutionModes,
1332
+ }),
819
1333
  };
820
1334
  }
821
1335
  try {
@@ -873,6 +1387,10 @@ export async function main() {
873
1387
  if (!session)
874
1388
  return;
875
1389
  session.closed = true;
1390
+ if (session.typingKeepaliveTimer) {
1391
+ clearInterval(session.typingKeepaliveTimer);
1392
+ session.typingKeepaliveTimer = null;
1393
+ }
876
1394
  releaseConversationEnvironment(session.environment);
877
1395
  // Save SDK session ID for future resume
878
1396
  if (session.sdkSessionId) {
@@ -958,6 +1476,7 @@ export async function main() {
958
1476
  }, {
959
1477
  model: config?.model ?? runtimeModels[0]?.value,
960
1478
  permissionMode: config?.permissionMode ?? 'default',
1479
+ effort: config?.effort,
961
1480
  availableModels: runtimeModels,
962
1481
  availableExecutionModes: hostAvailableExecutionModes,
963
1482
  }, resumeId);
@@ -1060,7 +1579,12 @@ export async function main() {
1060
1579
  const message = error instanceof Error ? error.message : String(error);
1061
1580
  const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
1062
1581
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to create session: ${message}`);
1063
- await client.sendMessage(convoId, `I couldn't start a coding session for this workspace: ${userMessage}`).catch(() => { });
1582
+ await client.sendMessage(convoId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
1583
+ metadata: {
1584
+ turnSemantics: 'turn_complete',
1585
+ turnComplete: true,
1586
+ },
1587
+ }).catch(() => { });
1064
1588
  return;
1065
1589
  }
1066
1590
  const turnMetadata = normalizeTurnMetadata(m.metadata);
@@ -1139,12 +1663,16 @@ export async function main() {
1139
1663
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
1140
1664
  }
1141
1665
  }
1142
- if (ctrl.effort && ctrl.effort !== session.state.effort) {
1143
- session.state.effort = ctrl.effort;
1144
- session.query.applyFlagSettings({
1145
- effortLevel: ctrl.effort,
1146
- }).catch((err) => console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err));
1147
- console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${ctrl.effort}`);
1666
+ const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
1667
+ if (nextEffort && nextEffort !== session.state.effort) {
1668
+ try {
1669
+ await session.query.applyFlagSettings({ effortLevel: nextEffort });
1670
+ session.state.effort = nextEffort;
1671
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
1672
+ }
1673
+ catch (err) {
1674
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
1675
+ }
1148
1676
  }
1149
1677
  writeSessionState(convoId, agentId, {
1150
1678
  ...session.state,
@@ -1152,6 +1680,10 @@ export async function main() {
1152
1680
  cwd: session.cwd,
1153
1681
  executionMode: session.environment.mode,
1154
1682
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1683
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1684
+ ...(resolveExecutionFallbackReason(session.environment)
1685
+ ? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
1686
+ : {}),
1155
1687
  hostMode: true,
1156
1688
  isActive: true,
1157
1689
  }).catch(() => { });
@@ -1213,6 +1745,7 @@ export async function main() {
1213
1745
  cwd: session.cwd,
1214
1746
  executionMode: session.environment.mode,
1215
1747
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1748
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1216
1749
  hostMode: true,
1217
1750
  isActive: true,
1218
1751
  }).catch(() => { });
package/dist/server.js CHANGED
@@ -26,6 +26,10 @@ let agentContext = null;
26
26
  let approvalManager = null;
27
27
  let approvalServer = null;
28
28
  const conversationCache = new Map();
29
+ const TURN_COMPLETE_METADATA = {
30
+ turnSemantics: 'turn_complete',
31
+ turnComplete: true,
32
+ };
29
33
  /** The conversation that most recently triggered work (for approval routing) */
30
34
  let triggerConversationId = null;
31
35
  function getPrimaryAttachment(message) {
@@ -221,7 +225,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
221
225
  case 'reply': {
222
226
  const conversationId = args.conversation_id;
223
227
  const text = args.text;
224
- const result = await client.sendMessage(conversationId, text);
228
+ const result = await client.sendMessage(conversationId, text, {
229
+ metadata: TURN_COMPLETE_METADATA,
230
+ });
225
231
  return {
226
232
  content: [
227
233
  {
@@ -261,6 +267,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
261
267
  else {
262
268
  opts.contentType = contentType;
263
269
  }
270
+ opts.metadata = {
271
+ ...(opts.metadata ?? {}),
272
+ ...TURN_COMPLETE_METADATA,
273
+ };
264
274
  const result = await client.sendMessage(conversationId, text, opts);
265
275
  return {
266
276
  content: [
@@ -288,6 +298,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
288
298
  const messageOptions = typeof args.message_options === 'object' && args.message_options != null
289
299
  ? args.message_options
290
300
  : undefined;
301
+ const linkedMessageOptions = messageOptions;
291
302
  const result = await client.sendLinkedMessage({
292
303
  sourceConversationId,
293
304
  targetConversationId,
@@ -308,11 +319,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
308
319
  targetContext: targetContext,
309
320
  }
310
321
  : {}),
311
- ...(messageOptions
322
+ ...(linkedMessageOptions
312
323
  ? {
313
- messageOptions: messageOptions,
324
+ messageOptions: {
325
+ ...linkedMessageOptions,
326
+ metadata: {
327
+ ...(linkedMessageOptions.metadata ?? {}),
328
+ ...TURN_COMPLETE_METADATA,
329
+ },
330
+ },
314
331
  }
315
- : {}),
332
+ : { messageOptions: { metadata: TURN_COMPLETE_METADATA } }),
316
333
  });
317
334
  return {
318
335
  content: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.9.6",
3
+ "version": "0.12.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",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "^0.2.101",
34
- "@canonmsg/agent-sdk": "^0.8.3",
35
- "@canonmsg/core": "^0.7.5",
34
+ "@canonmsg/agent-sdk": "^0.9.2",
35
+ "@canonmsg/core": "^0.10.0",
36
36
  "@modelcontextprotocol/sdk": "^1.12.0"
37
37
  },
38
38
  "engines": {