@canonmsg/claude-code-plugin 0.9.6 → 0.10.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
@@ -32,6 +32,14 @@ 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 ~/dev/canon --workspace ~/dev/yumyumv2
39
+ ```
40
+
41
+ `--cwd` is the default workspace. Each `--workspace` value appears as a selectable workspace during session creation. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-workspace mode runs directly in the selected directory.
42
+
35
43
  ## Multiple agents
36
44
 
37
45
  ```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,9 +24,9 @@ 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, 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, 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';
@@ -34,6 +34,87 @@ import { runCli } from './cli-entry.js';
34
34
  let workingDir = process.cwd();
35
35
  let workspaceOptions = [];
36
36
  let runtimeModels = [];
37
+ const CLAUDE_METADATA_TTL_MS = 5 * 60 * 1000;
38
+ let runtimeMetadata = {
39
+ account: null,
40
+ mcpServers: [],
41
+ plugins: [],
42
+ loadedAt: 0,
43
+ };
44
+ let runtimeMetadataRefresh = null;
45
+ function buildClaudeRuntimeDescriptor(input) {
46
+ return {
47
+ coreControls: [
48
+ {
49
+ id: 'model',
50
+ label: 'Model',
51
+ options: input.models,
52
+ defaultValue: input.models[0]?.value ?? null,
53
+ availability: 'setup_and_live',
54
+ liveBehavior: 'immediate',
55
+ selectionPolicy: 'inherit',
56
+ },
57
+ {
58
+ id: 'workspace',
59
+ label: 'Workspace',
60
+ options: input.workspaces.map((workspace) => ({
61
+ value: workspace.id,
62
+ label: workspace.label,
63
+ })),
64
+ defaultValue: input.workspaces[0]?.id ?? null,
65
+ availability: 'setup',
66
+ liveBehavior: 'none',
67
+ selectionPolicy: 'inherit',
68
+ },
69
+ {
70
+ id: 'executionMode',
71
+ label: 'Execution mode',
72
+ options: input.executionModes.map((mode) => ({
73
+ value: mode,
74
+ label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared workspace',
75
+ description: mode === 'worktree'
76
+ ? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected workspace is a git repo.'
77
+ : 'Runs directly in the selected workspace. Canon records usage, but does not create a separate checkout.',
78
+ })),
79
+ defaultValue: null,
80
+ availability: 'setup',
81
+ liveBehavior: 'none',
82
+ selectionPolicy: 'required_explicit',
83
+ },
84
+ ],
85
+ runtimeControls: [
86
+ {
87
+ id: 'permissionMode',
88
+ label: 'Permission mode',
89
+ options: [...CLAUDE_PERMISSION_MODE_OPTIONS],
90
+ defaultValue: 'default',
91
+ availability: 'setup_and_live',
92
+ liveBehavior: 'immediate',
93
+ selectionPolicy: 'inherit',
94
+ },
95
+ {
96
+ id: 'effort',
97
+ label: 'Thinking level',
98
+ options: [
99
+ { value: 'low', label: 'Low' },
100
+ { value: 'medium', label: 'Medium' },
101
+ { value: 'high', label: 'High' },
102
+ ],
103
+ defaultValue: 'medium',
104
+ availability: 'setup_and_live',
105
+ liveBehavior: 'immediate',
106
+ selectionPolicy: 'inherit',
107
+ },
108
+ ],
109
+ supportsInterrupt: true,
110
+ streamingTextMode: 'delta',
111
+ };
112
+ }
113
+ function normalizeClaudeEffortLevel(value) {
114
+ return value === 'low' || value === 'medium' || value === 'high'
115
+ ? value
116
+ : undefined;
117
+ }
37
118
  let cachedClaudeCliPath;
38
119
  // The SDK bundles a Bun-compiled `claude` Mach-O binary next to
39
120
  // claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
@@ -83,11 +164,12 @@ async function loadSessionConfig(conversationId, agentId) {
83
164
  const config = await loadHostSessionConfig({
84
165
  conversationId,
85
166
  agentId,
86
- extraStringFields: ['permissionMode'],
167
+ extraStringFields: ['permissionMode', 'effort'],
87
168
  });
88
169
  return {
89
170
  ...config,
90
171
  permissionMode: config?.permissionMode,
172
+ effort: normalizeClaudeEffortLevel(config?.effort),
91
173
  };
92
174
  }
93
175
  // Default to 'locked' (shared workspace) when no mode has been picked. The
@@ -103,6 +185,14 @@ function resolveWorkspaceCwd(config) {
103
185
  defaultCwd: workingDir,
104
186
  });
105
187
  }
188
+ function resolveExecutionFallbackReason(environment) {
189
+ if (!environment?.reason || environment.mode !== 'locked') {
190
+ return null;
191
+ }
192
+ return environment.reason === 'Sharing the base workspace (locked mode)'
193
+ ? null
194
+ : environment.reason;
195
+ }
106
196
  async function detectRuntimeModels(cwd) {
107
197
  const emptyPrompt = {
108
198
  async *[Symbol.asyncIterator]() { },
@@ -130,6 +220,166 @@ async function detectRuntimeModels(cwd) {
130
220
  catch { }
131
221
  }
132
222
  }
223
+ async function inspectClaudeRuntimeMetadata(cwd, activeQuery) {
224
+ const emptyPrompt = {
225
+ async *[Symbol.asyncIterator]() { },
226
+ };
227
+ const claudeCliPath = resolveClaudeCliPath();
228
+ const probe = activeQuery ?? query({
229
+ prompt: emptyPrompt,
230
+ options: {
231
+ systemPrompt: { type: 'preset', preset: 'claude_code' },
232
+ tools: { type: 'preset', preset: 'claude_code' },
233
+ permissionMode: 'default',
234
+ cwd,
235
+ settings: { forceLoginMethod: 'claudeai' },
236
+ ...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
237
+ },
238
+ });
239
+ try {
240
+ const [init, mcpServers, pluginInfo] = await Promise.all([
241
+ probe.initializationResult(),
242
+ probe.mcpServerStatus().catch(() => []),
243
+ probe.reloadPlugins().catch(() => null),
244
+ ]);
245
+ return {
246
+ account: init.account ?? null,
247
+ mcpServers: pluginInfo?.mcpServers ?? mcpServers,
248
+ plugins: pluginInfo?.plugins ?? [],
249
+ loadedAt: Date.now(),
250
+ };
251
+ }
252
+ finally {
253
+ if (!activeQuery) {
254
+ try {
255
+ probe.close();
256
+ }
257
+ catch { }
258
+ }
259
+ }
260
+ }
261
+ async function refreshClaudeRuntimeMetadata(input) {
262
+ if (!input.force
263
+ && runtimeMetadata.loadedAt > 0
264
+ && Date.now() - runtimeMetadata.loadedAt < CLAUDE_METADATA_TTL_MS) {
265
+ return runtimeMetadata;
266
+ }
267
+ if (runtimeMetadataRefresh) {
268
+ return runtimeMetadataRefresh;
269
+ }
270
+ runtimeMetadataRefresh = inspectClaudeRuntimeMetadata(input.cwd, input.activeQuery ?? null)
271
+ .then((metadata) => {
272
+ runtimeMetadata = metadata;
273
+ return metadata;
274
+ })
275
+ .finally(() => {
276
+ runtimeMetadataRefresh = null;
277
+ });
278
+ return runtimeMetadataRefresh;
279
+ }
280
+ function mapClaudeMcpStatus(status) {
281
+ switch (status) {
282
+ case 'connected':
283
+ return 'ready';
284
+ case 'needs-auth':
285
+ return 'auth_needed';
286
+ case 'disabled':
287
+ return 'configured';
288
+ case 'failed':
289
+ return 'error';
290
+ default:
291
+ return 'unknown';
292
+ }
293
+ }
294
+ function buildClaudeRuntimeInfo(input) {
295
+ const { descriptor, metadata } = input;
296
+ const statusItems = [
297
+ metadata.account?.email
298
+ ? {
299
+ id: 'account',
300
+ label: 'Account',
301
+ value: metadata.account.email,
302
+ tone: 'success',
303
+ }
304
+ : null,
305
+ metadata.account?.subscriptionType
306
+ ? {
307
+ id: 'subscription',
308
+ label: 'Plan',
309
+ value: metadata.account.subscriptionType,
310
+ }
311
+ : null,
312
+ metadata.account?.apiProvider
313
+ ? {
314
+ id: 'provider',
315
+ label: 'Provider',
316
+ value: metadata.account.apiProvider,
317
+ }
318
+ : null,
319
+ metadata.account?.tokenSource || metadata.account?.apiKeySource
320
+ ? {
321
+ id: 'auth',
322
+ label: 'Auth',
323
+ value: metadata.account.tokenSource ?? metadata.account?.apiKeySource ?? 'connected',
324
+ }
325
+ : null,
326
+ runtimeModels[0]
327
+ ? {
328
+ id: 'fallbackModel',
329
+ label: 'Fallback model',
330
+ value: runtimeModels[0].label,
331
+ }
332
+ : null,
333
+ {
334
+ id: 'toolPolicy',
335
+ label: 'Tool policy',
336
+ value: 'Claude Code preset',
337
+ },
338
+ {
339
+ id: 'browser',
340
+ label: 'Browser tools',
341
+ value: 'Runtime policy',
342
+ },
343
+ ].filter((item) => Boolean(item));
344
+ return {
345
+ descriptor,
346
+ surfaceMode: 'host',
347
+ statusItems,
348
+ inventories: [
349
+ {
350
+ id: 'mcp',
351
+ label: 'MCP',
352
+ entries: metadata.mcpServers.map((server) => ({
353
+ id: server.name,
354
+ label: server.name,
355
+ status: mapClaudeMcpStatus(server.status),
356
+ description: server.error
357
+ ?? server.serverInfo?.version
358
+ ?? server.scope
359
+ ?? undefined,
360
+ })),
361
+ },
362
+ {
363
+ id: 'plugins',
364
+ label: 'Plugins',
365
+ entries: metadata.plugins.map((plugin) => ({
366
+ id: plugin.name,
367
+ label: plugin.name,
368
+ status: 'configured',
369
+ description: plugin.source ?? plugin.path,
370
+ })),
371
+ },
372
+ ],
373
+ execution: {
374
+ resolvedWorkspaceLabel: input.workspaceLabel,
375
+ resolvedCwd: input.resolvedCwd,
376
+ executionMode: input.executionMode,
377
+ executionBranch: input.executionBranch,
378
+ worktreePath: input.worktreePath,
379
+ fallbackReason: input.fallbackReason,
380
+ },
381
+ };
382
+ }
133
383
  // ── Constants ───────────────────────────────────────────────────────
134
384
  const MAX_SESSIONS = 5;
135
385
  const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
@@ -468,6 +718,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
468
718
  cwd,
469
719
  executionMode: session.environment.mode,
470
720
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
721
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
471
722
  hostMode: true,
472
723
  isActive: true,
473
724
  }).catch(() => { });
@@ -668,6 +919,15 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
668
919
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial model:`, err);
669
920
  }
670
921
  }
922
+ if (config.effort) {
923
+ try {
924
+ await q.applyFlagSettings({ effortLevel: config.effort });
925
+ session.state.effort = config.effort;
926
+ }
927
+ catch (err) {
928
+ console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial effort:`, err);
929
+ }
930
+ }
671
931
  if (!resumeSessionId) {
672
932
  // New session — send seed message to activate streaming input mode
673
933
  sendInput({
@@ -699,6 +959,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
699
959
  cwd,
700
960
  executionMode: session.environment.mode,
701
961
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
962
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
702
963
  hostMode: true,
703
964
  isActive: true,
704
965
  }).catch(() => { });
@@ -759,6 +1020,11 @@ export async function main() {
759
1020
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
760
1021
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
761
1022
  availableExecutionModes: hostAvailableExecutionModes,
1023
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1024
+ models: runtimeModels,
1025
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1026
+ executionModes: hostAvailableExecutionModes,
1027
+ }),
762
1028
  };
763
1029
  const conversationCache = new Map();
764
1030
  const knownConversationIds = new Set();
@@ -769,6 +1035,14 @@ export async function main() {
769
1035
  await refreshKnownConversationIds().catch((error) => {
770
1036
  console.error('[canon-host] Failed to refresh known conversations:', error);
771
1037
  });
1038
+ const activeSessionQuery = sessions.values().next().value?.query ?? null;
1039
+ const metadata = await refreshClaudeRuntimeMetadata({
1040
+ cwd: workingDir,
1041
+ activeQuery: activeSessionQuery,
1042
+ }).catch((error) => {
1043
+ console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
1044
+ return runtimeMetadata;
1045
+ });
772
1046
  await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
773
1047
  console.error('[canon-host] Failed to publish agent runtime:', error);
774
1048
  });
@@ -786,6 +1060,7 @@ export async function main() {
786
1060
  {
787
1061
  ...(session.state.model ? { model: session.state.model } : {}),
788
1062
  ...(session.state.permissionMode ? { permissionMode: session.state.permissionMode } : {}),
1063
+ ...(session.state.effort ? { effort: session.state.effort } : {}),
789
1064
  ...(workspaceId ? { workspaceId } : {}),
790
1065
  executionMode: session.environment.mode,
791
1066
  executionBranch: session.environment.branch ?? null,
@@ -795,6 +1070,30 @@ export async function main() {
795
1070
  }).catch((error) => {
796
1071
  console.error('[canon-host] Failed to publish session snapshots:', error);
797
1072
  });
1073
+ await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
1074
+ const session = sessions.get(conversationId);
1075
+ const workspaceId = session
1076
+ ? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
1077
+ : runtimeDescriptor.defaultWorkspaceId;
1078
+ const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
1079
+ const payload = buildClaudeRuntimeInfo({
1080
+ descriptor: runtimeDescriptor.runtimeDescriptor ?? buildClaudeRuntimeDescriptor({
1081
+ models: runtimeModels,
1082
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1083
+ executionModes: hostAvailableExecutionModes,
1084
+ }),
1085
+ metadata,
1086
+ workspaceLabel: workspace?.label ?? workspaceId ?? null,
1087
+ resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
1088
+ executionMode: session?.environment.mode ?? null,
1089
+ executionBranch: session?.environment.branch ?? null,
1090
+ worktreePath: session?.environment.worktreePath ?? null,
1091
+ fallbackReason: resolveExecutionFallbackReason(session?.environment),
1092
+ });
1093
+ await writeRuntimeInfo(conversationId, agentId, payload);
1094
+ })).catch((error) => {
1095
+ console.error('[canon-host] Failed to publish runtime info:', error);
1096
+ });
798
1097
  };
799
1098
  // ── Load conversations (informational — sessions created on demand) ──
800
1099
  try {
@@ -807,6 +1106,11 @@ export async function main() {
807
1106
  availableModels: runtimeModels,
808
1107
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
809
1108
  availableExecutionModes: hostAvailableExecutionModes,
1109
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1110
+ models: runtimeModels,
1111
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1112
+ executionModes: hostAvailableExecutionModes,
1113
+ }),
810
1114
  };
811
1115
  }
812
1116
  catch {
@@ -816,6 +1120,11 @@ export async function main() {
816
1120
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
817
1121
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
818
1122
  availableExecutionModes: hostAvailableExecutionModes,
1123
+ runtimeDescriptor: buildClaudeRuntimeDescriptor({
1124
+ models: runtimeModels,
1125
+ workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1126
+ executionModes: hostAvailableExecutionModes,
1127
+ }),
819
1128
  };
820
1129
  }
821
1130
  try {
@@ -958,6 +1267,7 @@ export async function main() {
958
1267
  }, {
959
1268
  model: config?.model ?? runtimeModels[0]?.value,
960
1269
  permissionMode: config?.permissionMode ?? 'default',
1270
+ effort: config?.effort,
961
1271
  availableModels: runtimeModels,
962
1272
  availableExecutionModes: hostAvailableExecutionModes,
963
1273
  }, resumeId);
@@ -1139,12 +1449,16 @@ export async function main() {
1139
1449
  console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
1140
1450
  }
1141
1451
  }
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}`);
1452
+ const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
1453
+ if (nextEffort && nextEffort !== session.state.effort) {
1454
+ try {
1455
+ await session.query.applyFlagSettings({ effortLevel: nextEffort });
1456
+ session.state.effort = nextEffort;
1457
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
1458
+ }
1459
+ catch (err) {
1460
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
1461
+ }
1148
1462
  }
1149
1463
  writeSessionState(convoId, agentId, {
1150
1464
  ...session.state,
@@ -1152,6 +1466,7 @@ export async function main() {
1152
1466
  cwd: session.cwd,
1153
1467
  executionMode: session.environment.mode,
1154
1468
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1469
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1155
1470
  hostMode: true,
1156
1471
  isActive: true,
1157
1472
  }).catch(() => { });
@@ -1213,6 +1528,7 @@ export async function main() {
1213
1528
  cwd: session.cwd,
1214
1529
  executionMode: session.environment.mode,
1215
1530
  ...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
1531
+ ...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
1216
1532
  hostMode: true,
1217
1533
  isActive: true,
1218
1534
  }).catch(() => { });
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.10.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.0",
35
+ "@canonmsg/core": "^0.8.0",
36
36
  "@modelcontextprotocol/sdk": "^1.12.0"
37
37
  },
38
38
  "engines": {