@canonmsg/claude-code-plugin 0.20.0 → 0.21.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/dist/host.js CHANGED
@@ -37,6 +37,8 @@ import { runCli } from './cli-entry.js';
37
37
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
38
38
  import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
39
39
  import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError, mergeClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
40
+ import { claudeThinkingToFlagValue, claudeThinkingToConfig, parseClaudeSessionDefaults, parseClaudeThinkingMode, parseClaudeUltracodeMode, resolveEffectiveSessionSettings, } from './session-defaults.js';
41
+ import { mapClaudeSubagentActivity } from './subagent-activity.js';
40
42
  function parseRuntimeVisibilityPreset(value) {
41
43
  return value === 'normal' || value === 'minimal' || value === 'full' ? value : undefined;
42
44
  }
@@ -87,6 +89,29 @@ let workspaceRootMetadata = [];
87
89
  let runtimeModels = [];
88
90
  const CLAUDE_METADATA_TTL_MS = 5 * 60 * 1000;
89
91
  const allowedNonOwnerClaudeTools = parseAllowedNonOwnerClaudeTools(process.env.CANON_CLAUDE_NON_OWNER_ALLOWED_TOOLS);
92
+ const CLAUDE_THINKING_CONTROL_OPTIONS = [
93
+ {
94
+ value: 'adaptive',
95
+ label: 'Adaptive',
96
+ description: 'Let Claude decide when and how much to think.',
97
+ },
98
+ {
99
+ value: 'off',
100
+ label: 'Off',
101
+ description: 'Disable extended thinking for this session.',
102
+ },
103
+ ];
104
+ const CLAUDE_ULTRACODE_CONTROL_OPTIONS = [
105
+ {
106
+ value: 'off',
107
+ label: 'Off',
108
+ },
109
+ {
110
+ value: 'on',
111
+ label: 'On',
112
+ description: 'Enable Claude Code ultracode mode when the selected model supports it.',
113
+ },
114
+ ];
90
115
  let runtimeMetadata = {
91
116
  account: null,
92
117
  mcpServers: [],
@@ -211,15 +236,40 @@ function buildClaudeRuntimeDescriptor(input) {
211
236
  RUNTIME_STOP_AND_DROP_ACTION,
212
237
  ];
213
238
  commands.push(...buildNativeSlashCommandDescriptors(runtimeMetadata.commands, new Set(commands.flatMap((command) => [command.id, ...(command.aliases ?? [])]))));
239
+ const extraRuntimeControls = [
240
+ {
241
+ id: 'thinking',
242
+ label: 'Thinking',
243
+ description: 'Claude SDK thinking mode. Mid-session changes toggle thinking on or off; display/budget stay session-start scoped.',
244
+ options: CLAUDE_THINKING_CONTROL_OPTIONS,
245
+ defaultValue: input.defaults?.thinking ?? 'adaptive',
246
+ availability: 'setup_and_live',
247
+ liveBehavior: 'immediate',
248
+ selectionPolicy: 'inherit',
249
+ },
250
+ {
251
+ id: 'ultracode',
252
+ label: 'Ultracode',
253
+ description: 'Enable Claude Code ultracode mode when the selected model supports it.',
254
+ options: CLAUDE_ULTRACODE_CONTROL_OPTIONS,
255
+ defaultValue: input.defaults?.ultracode ?? 'off',
256
+ availability: 'setup_and_live',
257
+ liveBehavior: 'immediate',
258
+ selectionPolicy: 'inherit',
259
+ },
260
+ ];
214
261
  return buildFirstPartyCodingRuntimeDescriptor({
215
262
  clientType: 'claude-code',
216
263
  models: input.models,
264
+ defaultModel: input.defaults?.model ?? input.models[0]?.value ?? null,
217
265
  workspaces: input.workspaces,
218
266
  workspaceRoots: input.workspaceRoots,
219
267
  executionModes: input.executionModes,
220
268
  permissionModes: CLAUDE_PERMISSION_MODE_OPTIONS,
221
269
  defaultPermissionMode: 'default',
222
270
  effortOptions: [...CLAUDE_EFFORT_OPTIONS],
271
+ defaultEffort: input.defaults?.effort,
272
+ extraRuntimeControls,
223
273
  streamingTextMode: 'delta',
224
274
  turnModes: [
225
275
  {
@@ -561,13 +611,22 @@ function buildClaudeRuntimeInfo(input) {
561
611
  tier: 'diagnostic',
562
612
  }
563
613
  : null,
564
- runtimeModels[0]
614
+ input.fallbackModel
565
615
  ? {
566
616
  id: 'fallbackModel',
567
617
  label: 'Fallback model',
568
- value: runtimeModels[0].label,
618
+ value: input.fallbackModel,
569
619
  tier: 'diagnostic',
570
- source: 'host-default',
620
+ source: 'applied',
621
+ }
622
+ : null,
623
+ input.ultracodeEnabled
624
+ ? {
625
+ id: 'ultracode',
626
+ label: 'Ultracode',
627
+ value: 'Enabled',
628
+ tier: 'detail',
629
+ source: 'applied',
571
630
  }
572
631
  : null,
573
632
  {
@@ -808,6 +867,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
808
867
  };
809
868
  }
810
869
  const claudeCliPath = resolveClaudeCliPath();
870
+ const initialThinkingConfig = claudeThinkingToConfig(config.sessionSettings.thinking);
811
871
  const queryOptions = {
812
872
  systemPrompt: { type: 'preset', preset: 'claude_code' },
813
873
  tools: { type: 'preset', preset: 'claude_code' },
@@ -817,7 +877,16 @@ function createSession(conversationId, environment, agentId, port, typingSignals
817
877
  includePartialMessages: true,
818
878
  maxTurns: 200,
819
879
  ...(config.effort ? { effort: config.effort } : {}),
820
- settings: { forceLoginMethod: 'claudeai' },
880
+ ...(initialThinkingConfig ? { thinking: initialThinkingConfig } : {}),
881
+ ...(config.sessionSettings.fallbackModel ? { fallbackModel: config.sessionSettings.fallbackModel } : {}),
882
+ agentProgressSummaries: config.sessionSettings.agentProgressSummaries ?? true,
883
+ ...(config.sessionSettings.agents ? { agents: config.sessionSettings.agents } : {}),
884
+ settings: {
885
+ forceLoginMethod: 'claudeai',
886
+ ...(config.sessionSettings.ultracode
887
+ ? { ultracode: config.sessionSettings.ultracode === 'on' }
888
+ : {}),
889
+ },
821
890
  settingSources: ['project', 'local'],
822
891
  ...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
823
892
  stderr: (data) => {
@@ -1029,6 +1098,8 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1029
1098
  model: undefined,
1030
1099
  permissionMode: config.permissionMode,
1031
1100
  effort: undefined,
1101
+ fallbackModel: config.sessionSettings.fallbackModel,
1102
+ runtimeControlValues: config.sessionSettings.runtimeControlValues,
1032
1103
  state: 'idle',
1033
1104
  },
1034
1105
  availableModels: config.availableModels,
@@ -1073,6 +1144,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1073
1144
  model: config.model,
1074
1145
  permissionMode: config.permissionMode,
1075
1146
  effort: config.effort,
1147
+ runtimeControlValues: config.sessionSettings.runtimeControlValues,
1076
1148
  },
1077
1149
  }, {
1078
1150
  session: () => session,
@@ -1213,6 +1285,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1213
1285
  };
1214
1286
  writeTurn();
1215
1287
  // ── Process SDK output messages ──
1288
+ let assistantStreamOrdinal = 0;
1216
1289
  const processMessages = async () => {
1217
1290
  try {
1218
1291
  for await (const msg of q) {
@@ -1243,7 +1316,10 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1243
1316
  stopVisibleWorkSignal();
1244
1317
  break;
1245
1318
  }
1246
- if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
1319
+ if (event?.type === 'message_start') {
1320
+ assistantStreamOrdinal += 1;
1321
+ }
1322
+ else if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
1247
1323
  session.toolInProgress = true;
1248
1324
  session.turnState = 'tool';
1249
1325
  streamingOutput.addBlock({
@@ -1272,12 +1348,35 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1272
1348
  stopVisibleWorkSignal();
1273
1349
  writeTurn();
1274
1350
  const blockIndex = typeof event.index === 'number' ? event.index : 'latest';
1275
- onStreamDelta(event.delta.text, `text:${session.currentTurnId ?? conversationId}:${blockIndex}`);
1351
+ const messageOrdinal = assistantStreamOrdinal || 1;
1352
+ onStreamDelta(event.delta.text, `text:${session.currentTurnId ?? conversationId}:${messageOrdinal}:${blockIndex}`);
1276
1353
  }
1277
1354
  break;
1278
1355
  }
1279
1356
  case 'system': {
1280
1357
  const subtype = msg.subtype;
1358
+ if (subtype === 'task_started'
1359
+ || subtype === 'task_progress'
1360
+ || subtype === 'task_updated'
1361
+ || subtype === 'task_notification') {
1362
+ const activity = mapClaudeSubagentActivity(msg);
1363
+ if (activity && !isActiveTurnInterrupted()) {
1364
+ if (!session.currentTurnId) {
1365
+ session.currentTurnId = randomUUID();
1366
+ session.currentTurnOpenedAt = Date.now();
1367
+ }
1368
+ if (session.state.state === 'running' && session.turnState !== 'streaming') {
1369
+ session.turnState = 'thinking';
1370
+ startVisibleWorkSignal();
1371
+ writeTurn();
1372
+ }
1373
+ await streamingOutput.addBlock(activity).catch(() => { });
1374
+ }
1375
+ }
1376
+ if (subtype === 'model_refusal_fallback') {
1377
+ console.error(`[canon-host] [${conversationId.slice(0, 8)}] Model refusal fallback: `
1378
+ + `${msg.original_model} → ${msg.fallback_model}`);
1379
+ }
1281
1380
  if (subtype === 'session_state_changed') {
1282
1381
  const state = msg.state;
1283
1382
  session.state.state = state;
@@ -1289,6 +1388,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1289
1388
  }
1290
1389
  session.state.contextUsage = undefined;
1291
1390
  session.pendingFinalText = null;
1391
+ assistantStreamOrdinal = 0;
1292
1392
  session.toolInProgress = false;
1293
1393
  session.turnState = 'thinking';
1294
1394
  startVisibleWorkSignal();
@@ -1429,13 +1529,14 @@ function createSession(conversationId, environment, agentId, port, typingSignals
1429
1529
  runtimeModels = modelList;
1430
1530
  onRuntimeDescriptorUpdate({
1431
1531
  defaultWorkspaceId: workspaceOptions[0]?.id,
1432
- defaultModel: modelList[0]?.value,
1532
+ defaultModel: config.sessionSettings.model ?? modelList[0]?.value,
1433
1533
  defaultPermissionMode: 'default',
1434
1534
  availableModels: modelList,
1435
1535
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
1436
1536
  availableExecutionModes: config.availableExecutionModes,
1437
1537
  runtimeDescriptor: buildClaudeRuntimeDescriptor({
1438
1538
  models: modelList,
1539
+ defaults: config.sessionSettings,
1439
1540
  workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1440
1541
  workspaceRoots: workspaceRootMetadata,
1441
1542
  executionModes: config.availableExecutionModes,
@@ -1488,7 +1589,8 @@ export async function main() {
1488
1589
  for (const warning of workspaceDiscovery.warnings) {
1489
1590
  console.error(`[canon-host] ${warning}`);
1490
1591
  }
1491
- const { agentName: profileAgentName, profile, lockHandle, } = resolveCanonAgent({ logPrefix: '[canon-host]', expectedClientType: 'claude-code' });
1592
+ const { agentName: profileAgentName, profile, sessionDefaults: rawSessionDefaults, lockHandle, } = resolveCanonAgent({ logPrefix: '[canon-host]', expectedClientType: 'claude-code' });
1593
+ const profileSessionDefaults = parseClaudeSessionDefaults(rawSessionDefaults);
1492
1594
  activeLockHandle = lockHandle ?? null;
1493
1595
  console.error(`[canon-host] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
1494
1596
  // ── Canon via the Bridge (bridge-execution-plan Phase 6) ──
@@ -1578,12 +1680,14 @@ export async function main() {
1578
1680
  });
1579
1681
  let runtimeDescriptor = {
1580
1682
  defaultWorkspaceId: workspaceOptions[0]?.id,
1683
+ defaultModel: profileSessionDefaults.model ?? runtimeModels[0]?.value,
1581
1684
  defaultPermissionMode: 'default',
1582
1685
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
1583
1686
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
1584
1687
  availableExecutionModes: hostAvailableExecutionModes,
1585
1688
  runtimeDescriptor: buildClaudeRuntimeDescriptor({
1586
1689
  models: runtimeModels,
1690
+ defaults: profileSessionDefaults,
1587
1691
  workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1588
1692
  workspaceRoots: workspaceRootMetadata,
1589
1693
  executionModes: hostAvailableExecutionModes,
@@ -1636,6 +1740,9 @@ export async function main() {
1636
1740
  ...(session.state.model ? { model: session.state.model } : {}),
1637
1741
  ...(session.state.permissionMode ? { permissionMode: session.state.permissionMode } : {}),
1638
1742
  ...(session.state.effort ? { effort: session.state.effort } : {}),
1743
+ ...(session.state.runtimeControlValues
1744
+ ? { runtimeControlValues: session.state.runtimeControlValues }
1745
+ : {}),
1639
1746
  ...(workspaceId ? { workspaceId } : {}),
1640
1747
  executionMode: session.environment.mode,
1641
1748
  executionBranch: session.environment.branch ?? null,
@@ -1651,8 +1758,10 @@ export async function main() {
1651
1758
  if (runtimeDescriptor.runtimeDescriptor) {
1652
1759
  runtimeDescriptor = {
1653
1760
  ...runtimeDescriptor,
1761
+ defaultModel: profileSessionDefaults.model ?? runtimeModels[0]?.value,
1654
1762
  runtimeDescriptor: buildClaudeRuntimeDescriptor({
1655
1763
  models: runtimeModels,
1764
+ defaults: profileSessionDefaults,
1656
1765
  workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1657
1766
  workspaceRoots: workspaceRootMetadata,
1658
1767
  executionModes: hostAvailableExecutionModes,
@@ -1680,6 +1789,10 @@ export async function main() {
1680
1789
  executionBranch: session?.environment.branch ?? null,
1681
1790
  worktreePath: session?.environment.worktreePath ?? null,
1682
1791
  fallbackReason: resolveExecutionFallbackReason(session?.environment),
1792
+ fallbackModel: session?.state.fallbackModel
1793
+ ?? profileSessionDefaults.fallbackModel
1794
+ ?? null,
1795
+ ultracodeEnabled: (session?.state.runtimeControlValues?.ultracode ?? profileSessionDefaults.ultracode) === 'on',
1683
1796
  });
1684
1797
  await port.publishRuntimeStatus({
1685
1798
  conversationId,
@@ -1694,7 +1807,7 @@ export async function main() {
1694
1807
  runtimeModels = await detectRuntimeModels(workingDir);
1695
1808
  runtimeDescriptor = {
1696
1809
  defaultWorkspaceId: workspaceOptions[0]?.id,
1697
- defaultModel: runtimeModels[0]?.value,
1810
+ defaultModel: profileSessionDefaults.model ?? runtimeModels[0]?.value,
1698
1811
  defaultPermissionMode: 'default',
1699
1812
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
1700
1813
  availableModels: runtimeModels,
@@ -1702,6 +1815,7 @@ export async function main() {
1702
1815
  availableExecutionModes: hostAvailableExecutionModes,
1703
1816
  runtimeDescriptor: buildClaudeRuntimeDescriptor({
1704
1817
  models: runtimeModels,
1818
+ defaults: profileSessionDefaults,
1705
1819
  workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1706
1820
  workspaceRoots: workspaceRootMetadata,
1707
1821
  executionModes: hostAvailableExecutionModes,
@@ -1712,12 +1826,14 @@ export async function main() {
1712
1826
  catch {
1713
1827
  runtimeDescriptor = {
1714
1828
  defaultWorkspaceId: workspaceOptions[0]?.id,
1829
+ defaultModel: profileSessionDefaults.model ?? runtimeModels[0]?.value,
1715
1830
  defaultPermissionMode: 'default',
1716
1831
  availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
1717
1832
  availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
1718
1833
  availableExecutionModes: hostAvailableExecutionModes,
1719
1834
  runtimeDescriptor: buildClaudeRuntimeDescriptor({
1720
1835
  models: runtimeModels,
1836
+ defaults: profileSessionDefaults,
1721
1837
  workspaces: buildPublicWorkspaceOptions(workspaceOptions),
1722
1838
  workspaceRoots: workspaceRootMetadata,
1723
1839
  executionModes: hostAvailableExecutionModes,
@@ -1847,6 +1963,10 @@ export async function main() {
1847
1963
  function createSessionForConversation(conversationId) {
1848
1964
  return (async () => {
1849
1965
  const config = await loadSessionConfig(port, conversationId);
1966
+ const sessionSettings = resolveEffectiveSessionSettings({
1967
+ profileDefaults: profileSessionDefaults,
1968
+ sessionConfig: config,
1969
+ });
1850
1970
  const sessionExecutionMode = resolveSessionExecutionMode(config);
1851
1971
  const workspaceCwd = resolveWorkspaceCwd(config);
1852
1972
  const environment = prepareConversationEnvironment({
@@ -1894,9 +2014,10 @@ export async function main() {
1894
2014
  runtimeDescriptor = descriptor;
1895
2015
  void publishRuntimeHeartbeat();
1896
2016
  }, {
1897
- model: config?.model ?? runtimeModels[0]?.value,
2017
+ model: sessionSettings.model ?? runtimeModels[0]?.value,
1898
2018
  permissionMode: config?.permissionMode ?? 'default',
1899
- effort: config?.effort,
2019
+ effort: sessionSettings.effort,
2020
+ sessionSettings,
1900
2021
  availableModels: runtimeModels,
1901
2022
  availableExecutionModes: hostAvailableExecutionModes,
1902
2023
  runtimePresentation,
@@ -2307,6 +2428,49 @@ export async function main() {
2307
2428
  else if (ctrl.effort && !nextEffort) {
2308
2429
  session.setRuntimeControlError('effort', String(ctrl.effort), new Error('Unsupported Claude thinking level.'));
2309
2430
  }
2431
+ const runtimeControlValues = ctrl.runtimeControlValues ?? {};
2432
+ if ('thinking' in runtimeControlValues) {
2433
+ const nextThinking = parseClaudeThinkingMode(runtimeControlValues.thinking);
2434
+ if (!nextThinking) {
2435
+ session.setRuntimeControlError('thinking', String(runtimeControlValues.thinking), new Error('Unsupported Claude thinking mode.'));
2436
+ }
2437
+ else if (nextThinking !== session.state.runtimeControlValues?.thinking) {
2438
+ try {
2439
+ await session.query.applyFlagSettings({ alwaysThinkingEnabled: claudeThinkingToFlagValue(nextThinking) });
2440
+ session.state.runtimeControlValues = {
2441
+ ...(session.state.runtimeControlValues ?? {}),
2442
+ thinking: nextThinking,
2443
+ };
2444
+ session.clearRuntimeControlError('thinking');
2445
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Thinking → ${nextThinking}`);
2446
+ }
2447
+ catch (err) {
2448
+ session.setRuntimeControlError('thinking', nextThinking, err);
2449
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set thinking:`, err);
2450
+ }
2451
+ }
2452
+ }
2453
+ if ('ultracode' in runtimeControlValues) {
2454
+ const nextUltracode = parseClaudeUltracodeMode(runtimeControlValues.ultracode);
2455
+ if (!nextUltracode) {
2456
+ session.setRuntimeControlError('ultracode', String(runtimeControlValues.ultracode), new Error('Unsupported Claude ultracode mode.'));
2457
+ }
2458
+ else if (nextUltracode !== session.state.runtimeControlValues?.ultracode) {
2459
+ try {
2460
+ await session.query.applyFlagSettings({ ultracode: nextUltracode === 'on' });
2461
+ session.state.runtimeControlValues = {
2462
+ ...(session.state.runtimeControlValues ?? {}),
2463
+ ultracode: nextUltracode,
2464
+ };
2465
+ session.clearRuntimeControlError('ultracode');
2466
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Ultracode → ${nextUltracode}`);
2467
+ }
2468
+ catch (err) {
2469
+ session.setRuntimeControlError('ultracode', nextUltracode, err);
2470
+ console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set ultracode:`, err);
2471
+ }
2472
+ }
2473
+ }
2310
2474
  session.writeState();
2311
2475
  }
2312
2476
  // ── Heartbeat + idle cleanup + graceful shutdown (agent-host lifecycle) ──
@@ -0,0 +1,34 @@
1
+ import type { AgentDefinition, EffortLevel, ThinkingConfig } from '@anthropic-ai/claude-agent-sdk';
2
+ export type ClaudeThinkingMode = 'adaptive' | 'off';
3
+ export type ClaudeUltracodeMode = 'on' | 'off';
4
+ export type ClaudeEffortLevel = Extract<EffortLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'>;
5
+ export interface ClaudeSessionDefaults {
6
+ model?: string;
7
+ effort?: ClaudeEffortLevel;
8
+ thinking?: ClaudeThinkingMode;
9
+ ultracode?: ClaudeUltracodeMode;
10
+ fallbackModel?: string;
11
+ agentProgressSummaries?: boolean;
12
+ agents?: Record<string, AgentDefinition>;
13
+ }
14
+ export interface ClaudeSessionControlValues {
15
+ thinking?: ClaudeThinkingMode;
16
+ ultracode?: ClaudeUltracodeMode;
17
+ }
18
+ export interface ClaudeEffectiveSessionSettings extends ClaudeSessionDefaults {
19
+ runtimeControlValues: Record<string, string>;
20
+ }
21
+ export declare function parseClaudeThinkingMode(value: unknown): ClaudeThinkingMode | undefined;
22
+ export declare function parseClaudeUltracodeMode(value: unknown): ClaudeUltracodeMode | undefined;
23
+ export declare function parseClaudeSessionDefaults(raw: unknown): ClaudeSessionDefaults;
24
+ export declare function parseClaudeSessionControlValues(values: Record<string, string> | null | undefined): ClaudeSessionControlValues;
25
+ export declare function claudeThinkingToConfig(mode: ClaudeThinkingMode | undefined): ThinkingConfig | undefined;
26
+ export declare function claudeThinkingToFlagValue(mode: ClaudeThinkingMode): boolean;
27
+ export declare function resolveEffectiveSessionSettings(input: {
28
+ profileDefaults?: ClaudeSessionDefaults;
29
+ sessionConfig?: {
30
+ model?: string;
31
+ effort?: ClaudeEffortLevel;
32
+ runtimeControlValues?: Record<string, string>;
33
+ } | null;
34
+ }): ClaudeEffectiveSessionSettings;
@@ -0,0 +1,123 @@
1
+ const CLAUDE_EFFORT_VALUES = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
2
+ function optionalString(value) {
3
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
4
+ }
5
+ function optionalBoolean(value) {
6
+ return typeof value === 'boolean' ? value : undefined;
7
+ }
8
+ export function parseClaudeThinkingMode(value) {
9
+ if (value === false || value === 'off' || value === 'disabled')
10
+ return 'off';
11
+ if (value === true || value === 'on' || value === 'adaptive' || value === 'enabled')
12
+ return 'adaptive';
13
+ return undefined;
14
+ }
15
+ export function parseClaudeUltracodeMode(value) {
16
+ if (value === true || value === 'on' || value === 'enabled')
17
+ return 'on';
18
+ if (value === false || value === 'off' || value === 'disabled')
19
+ return 'off';
20
+ return undefined;
21
+ }
22
+ function parseEffort(value) {
23
+ const normalized = optionalString(value);
24
+ if (!normalized)
25
+ return undefined;
26
+ return CLAUDE_EFFORT_VALUES.has(normalized)
27
+ ? normalized
28
+ : undefined;
29
+ }
30
+ function parseStringArray(value) {
31
+ if (!Array.isArray(value))
32
+ return undefined;
33
+ const out = value
34
+ .map((entry) => optionalString(entry))
35
+ .filter((entry) => Boolean(entry));
36
+ return out.length > 0 ? out : undefined;
37
+ }
38
+ function parseAgentDefinition(value) {
39
+ if (!value || typeof value !== 'object' || Array.isArray(value))
40
+ return null;
41
+ const data = value;
42
+ const description = optionalString(data.description);
43
+ const prompt = optionalString(data.prompt);
44
+ if (!description || !prompt)
45
+ return null;
46
+ return {
47
+ description,
48
+ prompt,
49
+ ...(parseStringArray(data.tools) ? { tools: parseStringArray(data.tools) } : {}),
50
+ ...(parseStringArray(data.disallowedTools) ? { disallowedTools: parseStringArray(data.disallowedTools) } : {}),
51
+ ...(optionalString(data.model) ? { model: optionalString(data.model) } : {}),
52
+ ...(parseStringArray(data.skills) ? { skills: parseStringArray(data.skills) } : {}),
53
+ ...(typeof data.maxTurns === 'number' && Number.isFinite(data.maxTurns) && data.maxTurns > 0
54
+ ? { maxTurns: Math.floor(data.maxTurns) }
55
+ : {}),
56
+ ...(typeof data.background === 'boolean' ? { background: data.background } : {}),
57
+ ...(parseEffort(data.effort) ? { effort: parseEffort(data.effort) } : {}),
58
+ };
59
+ }
60
+ function parseAgents(value) {
61
+ if (!value || typeof value !== 'object' || Array.isArray(value))
62
+ return undefined;
63
+ const out = {};
64
+ for (const [rawName, rawAgent] of Object.entries(value)) {
65
+ const name = rawName.trim();
66
+ if (!name)
67
+ continue;
68
+ const agent = parseAgentDefinition(rawAgent);
69
+ if (agent)
70
+ out[name] = agent;
71
+ }
72
+ return Object.keys(out).length > 0 ? out : undefined;
73
+ }
74
+ export function parseClaudeSessionDefaults(raw) {
75
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
76
+ return {};
77
+ const data = raw;
78
+ return {
79
+ ...(optionalString(data.model) ? { model: optionalString(data.model) } : {}),
80
+ ...(parseEffort(data.effort) ? { effort: parseEffort(data.effort) } : {}),
81
+ ...(parseClaudeThinkingMode(data.thinking) ? { thinking: parseClaudeThinkingMode(data.thinking) } : {}),
82
+ ...(parseClaudeUltracodeMode(data.ultracode) ? { ultracode: parseClaudeUltracodeMode(data.ultracode) } : {}),
83
+ ...(optionalString(data.fallbackModel) ? { fallbackModel: optionalString(data.fallbackModel) } : {}),
84
+ ...(optionalBoolean(data.agentProgressSummaries) !== undefined
85
+ ? { agentProgressSummaries: optionalBoolean(data.agentProgressSummaries) }
86
+ : {}),
87
+ ...(parseAgents(data.agents) ? { agents: parseAgents(data.agents) } : {}),
88
+ };
89
+ }
90
+ export function parseClaudeSessionControlValues(values) {
91
+ return {
92
+ ...(parseClaudeThinkingMode(values?.thinking) ? { thinking: parseClaudeThinkingMode(values?.thinking) } : {}),
93
+ ...(parseClaudeUltracodeMode(values?.ultracode) ? { ultracode: parseClaudeUltracodeMode(values?.ultracode) } : {}),
94
+ };
95
+ }
96
+ export function claudeThinkingToConfig(mode) {
97
+ if (mode === 'adaptive')
98
+ return { type: 'adaptive' };
99
+ if (mode === 'off')
100
+ return { type: 'disabled' };
101
+ return undefined;
102
+ }
103
+ export function claudeThinkingToFlagValue(mode) {
104
+ return mode !== 'off';
105
+ }
106
+ export function resolveEffectiveSessionSettings(input) {
107
+ const controlValues = parseClaudeSessionControlValues(input.sessionConfig?.runtimeControlValues);
108
+ const runtimeControlValues = {
109
+ ...(input.profileDefaults?.thinking ? { thinking: input.profileDefaults.thinking } : {}),
110
+ ...(input.profileDefaults?.ultracode ? { ultracode: input.profileDefaults.ultracode } : {}),
111
+ ...(input.sessionConfig?.runtimeControlValues ?? {}),
112
+ ...(controlValues.thinking ? { thinking: controlValues.thinking } : {}),
113
+ ...(controlValues.ultracode ? { ultracode: controlValues.ultracode } : {}),
114
+ };
115
+ return {
116
+ ...(input.profileDefaults ?? {}),
117
+ ...(input.sessionConfig?.model ? { model: input.sessionConfig.model } : {}),
118
+ ...(input.sessionConfig?.effort ? { effort: input.sessionConfig.effort } : {}),
119
+ ...(controlValues.thinking ? { thinking: controlValues.thinking } : {}),
120
+ ...(controlValues.ultracode ? { ultracode: controlValues.ultracode } : {}),
121
+ runtimeControlValues,
122
+ };
123
+ }
@@ -0,0 +1,11 @@
1
+ import type { SDKTaskNotificationMessage, SDKTaskProgressMessage, SDKTaskStartedMessage, SDKTaskUpdatedMessage } from '@anthropic-ai/claude-agent-sdk';
2
+ type ClaudeTaskMessage = SDKTaskNotificationMessage | SDKTaskProgressMessage | SDKTaskStartedMessage | SDKTaskUpdatedMessage;
3
+ export interface ClaudeSubagentActivityBlock {
4
+ id: string;
5
+ kind: 'status';
6
+ status: 'running' | 'completed' | 'failed';
7
+ title: string;
8
+ text?: string;
9
+ }
10
+ export declare function mapClaudeSubagentActivity(message: ClaudeTaskMessage): ClaudeSubagentActivityBlock | null;
11
+ export {};
@@ -0,0 +1,51 @@
1
+ function titleForMessage(message) {
2
+ const subtype = message.subtype;
3
+ if (subtype === 'task_started') {
4
+ return message.subagent_type
5
+ ? `Subagent started: ${message.subagent_type}`
6
+ : 'Subagent started';
7
+ }
8
+ if (subtype === 'task_progress') {
9
+ return message.subagent_type
10
+ ? `Subagent progress: ${message.subagent_type}`
11
+ : 'Subagent progress';
12
+ }
13
+ if (subtype === 'task_updated')
14
+ return 'Subagent updated';
15
+ return `Subagent ${message.status}`;
16
+ }
17
+ function textForMessage(message) {
18
+ if (message.subtype === 'task_started')
19
+ return message.description;
20
+ if (message.subtype === 'task_progress')
21
+ return message.summary ?? message.description;
22
+ if (message.subtype === 'task_updated')
23
+ return message.patch.description ?? message.patch.error;
24
+ return message.summary;
25
+ }
26
+ function statusForMessage(message) {
27
+ if (message.subtype === 'task_notification') {
28
+ if (message.status === 'completed')
29
+ return 'completed';
30
+ return 'failed';
31
+ }
32
+ if (message.subtype === 'task_updated') {
33
+ if (message.patch.status === 'completed')
34
+ return 'completed';
35
+ if (message.patch.status === 'failed' || message.patch.status === 'killed')
36
+ return 'failed';
37
+ }
38
+ return 'running';
39
+ }
40
+ export function mapClaudeSubagentActivity(message) {
41
+ if ('skip_transcript' in message && message.skip_transcript)
42
+ return null;
43
+ const text = textForMessage(message);
44
+ return {
45
+ id: `task:${message.task_id}`,
46
+ kind: 'status',
47
+ status: statusForMessage(message),
48
+ title: titleForMessage(message),
49
+ ...(text ? { text } : {}),
50
+ };
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.20.0",
3
+ "version": "0.21.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,10 +31,10 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "^0.3.200",
34
- "@canonmsg/agent-host": "^0.3.0",
35
- "@canonmsg/agent-sdk": "^3.4.3",
34
+ "@canonmsg/agent-host": "^0.4.0",
35
+ "@canonmsg/agent-sdk": "^3.4.4",
36
36
  "@canonmsg/bridge": "^0.2.0",
37
- "@canonmsg/core": "^3.1.0",
37
+ "@canonmsg/core": "^3.2.0",
38
38
  "@canonmsg/framework": "^0.2.0",
39
39
  "@modelcontextprotocol/sdk": "^1.29.0"
40
40
  },