@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 +175 -11
- package/dist/session-defaults.d.ts +34 -0
- package/dist/session-defaults.js +123 -0
- package/dist/subagent-activity.d.ts +11 -0
- package/dist/subagent-activity.js +51 -0
- package/package.json +4 -4
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
|
-
|
|
614
|
+
input.fallbackModel
|
|
565
615
|
? {
|
|
566
616
|
id: 'fallbackModel',
|
|
567
617
|
label: 'Fallback model',
|
|
568
|
-
value:
|
|
618
|
+
value: input.fallbackModel,
|
|
569
619
|
tier: 'diagnostic',
|
|
570
|
-
source: '
|
|
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
|
-
|
|
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 === '
|
|
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
|
-
|
|
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:
|
|
2017
|
+
model: sessionSettings.model ?? runtimeModels[0]?.value,
|
|
1898
2018
|
permissionMode: config?.permissionMode ?? 'default',
|
|
1899
|
-
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.
|
|
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.
|
|
35
|
-
"@canonmsg/agent-sdk": "^3.4.
|
|
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.
|
|
37
|
+
"@canonmsg/core": "^3.2.0",
|
|
38
38
|
"@canonmsg/framework": "^0.2.0",
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
40
40
|
},
|