@canonmsg/claude-code-plugin 0.21.0 → 0.21.1
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 +64 -175
- package/dist/session-defaults.d.ts +1 -0
- package/dist/session-defaults.js +2 -1
- package/dist/session-state.d.ts +46 -4
- package/dist/session-state.js +173 -17
- package/package.json +1 -1
package/dist/host.js
CHANGED
|
@@ -36,8 +36,8 @@ import { ApprovalManager, readHostSessionConfig, resolveHostWorkspaceCwd, } from
|
|
|
36
36
|
import { runCli } from './cli-entry.js';
|
|
37
37
|
import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
|
|
38
38
|
import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
|
|
39
|
-
import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError,
|
|
40
|
-
import {
|
|
39
|
+
import { applyClaudeSessionControl, buildClaudeFinalMessageId, buildClaudeInitialSessionState, createClaudeInputEnvelope, formatClaudeControlError, normalizeClaudeEffortLevel, parseClaudePermissionMode, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
40
|
+
import { claudeThinkingToConfig, parseClaudeSessionDefaults, resolveEffectiveSessionSettings, } from './session-defaults.js';
|
|
41
41
|
import { mapClaudeSubagentActivity } from './subagent-activity.js';
|
|
42
42
|
function parseRuntimeVisibilityPreset(value) {
|
|
43
43
|
return value === 'normal' || value === 'minimal' || value === 'full' ? value : undefined;
|
|
@@ -295,26 +295,6 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
295
295
|
commands,
|
|
296
296
|
});
|
|
297
297
|
}
|
|
298
|
-
const CLAUDE_EFFORT_VALUES = new Set(CLAUDE_EFFORT_OPTIONS.map((option) => option.value));
|
|
299
|
-
function parseClaudePermissionMode(value) {
|
|
300
|
-
if (value === 'acceptEdits'
|
|
301
|
-
|| value === 'auto'
|
|
302
|
-
|| value === 'bypassPermissions'
|
|
303
|
-
|| value === 'default'
|
|
304
|
-
|| value === 'dontAsk'
|
|
305
|
-
|| value === 'plan') {
|
|
306
|
-
return value;
|
|
307
|
-
}
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
function normalizeClaudeEffortLevel(value) {
|
|
311
|
-
return typeof value === 'string' && CLAUDE_EFFORT_VALUES.has(value)
|
|
312
|
-
? value
|
|
313
|
-
: undefined;
|
|
314
|
-
}
|
|
315
|
-
async function applyClaudeEffortLevel(q, effort) {
|
|
316
|
-
await q.applyFlagSettings({ effortLevel: effort });
|
|
317
|
-
}
|
|
318
298
|
let cachedClaudeCliPath;
|
|
319
299
|
// The SDK bundles a Bun-compiled `claude` Mach-O binary next to
|
|
320
300
|
// claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
|
|
@@ -404,6 +384,7 @@ async function detectRuntimeModels(cwd) {
|
|
|
404
384
|
options: {
|
|
405
385
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
406
386
|
tools: { type: 'preset', preset: 'claude_code' },
|
|
387
|
+
model: 'default',
|
|
407
388
|
permissionMode: 'default',
|
|
408
389
|
cwd,
|
|
409
390
|
settings: { forceLoginMethod: 'claudeai' },
|
|
@@ -431,6 +412,7 @@ async function inspectClaudeRuntimeMetadata(cwd, activeQuery) {
|
|
|
431
412
|
options: {
|
|
432
413
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
433
414
|
tools: { type: 'preset', preset: 'claude_code' },
|
|
415
|
+
model: 'default',
|
|
434
416
|
permissionMode: 'default',
|
|
435
417
|
cwd,
|
|
436
418
|
settings: { forceLoginMethod: 'claudeai' },
|
|
@@ -871,6 +853,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
871
853
|
const queryOptions = {
|
|
872
854
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
873
855
|
tools: { type: 'preset', preset: 'claude_code' },
|
|
856
|
+
model: config.model ?? 'default',
|
|
874
857
|
permissionMode: parseClaudePermissionMode(config.permissionMode) ?? 'default',
|
|
875
858
|
allowDangerouslySkipPermissions: true,
|
|
876
859
|
cwd,
|
|
@@ -1092,16 +1075,16 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1092
1075
|
cwd,
|
|
1093
1076
|
environment,
|
|
1094
1077
|
query: q,
|
|
1078
|
+
controlApplySeq: new Map(),
|
|
1095
1079
|
sendInput,
|
|
1096
1080
|
enqueueInbound: (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, artifactRoutingMode = 'disabled') => enqueueInboundMessage(msg, intent, sourceMessageId, markAccepted, isOwnerTurn, artifactRoutingMode),
|
|
1097
|
-
state: {
|
|
1098
|
-
model:
|
|
1081
|
+
state: buildClaudeInitialSessionState({
|
|
1082
|
+
model: config.model,
|
|
1099
1083
|
permissionMode: config.permissionMode,
|
|
1100
|
-
effort:
|
|
1084
|
+
effort: config.effort,
|
|
1101
1085
|
fallbackModel: config.sessionSettings.fallbackModel,
|
|
1102
1086
|
runtimeControlValues: config.sessionSettings.runtimeControlValues,
|
|
1103
|
-
|
|
1104
|
-
},
|
|
1087
|
+
}),
|
|
1105
1088
|
availableModels: config.availableModels,
|
|
1106
1089
|
streamingText: '',
|
|
1107
1090
|
streamingTimer: null,
|
|
@@ -1140,12 +1123,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1140
1123
|
capabilities: CLAUDE_RUNTIME_CAPABILITIES,
|
|
1141
1124
|
fallbackTurnId: `claude-${conversationId}`,
|
|
1142
1125
|
streamingThrottleMs: STREAMING_THROTTLE_MS,
|
|
1143
|
-
requestedControls: {
|
|
1144
|
-
model: config.model,
|
|
1145
|
-
permissionMode: config.permissionMode,
|
|
1146
|
-
effort: config.effort,
|
|
1147
|
-
runtimeControlValues: config.sessionSettings.runtimeControlValues,
|
|
1148
|
-
},
|
|
1126
|
+
requestedControls: {},
|
|
1149
1127
|
}, {
|
|
1150
1128
|
session: () => session,
|
|
1151
1129
|
port,
|
|
@@ -1161,6 +1139,9 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1161
1139
|
formatControlError: formatClaudeControlError,
|
|
1162
1140
|
});
|
|
1163
1141
|
const { streamingOutput, flushStreaming, onStreamDelta, clearStreaming, getFinalTurnTrail, writeState, writeTurn, setRuntimeControlError, clearRuntimeControlError, refreshVisibleWorkSignal, startVisibleWorkSignal, stopVisibleWorkSignal, } = writers;
|
|
1142
|
+
// Initial values come from persisted session config and are effective for
|
|
1143
|
+
// this query. Publish immediately so controls do not linger as requested.
|
|
1144
|
+
writeState();
|
|
1164
1145
|
function isActiveTurnInterrupted() {
|
|
1165
1146
|
const turnKey = session.activeInput?.turnKey;
|
|
1166
1147
|
return Boolean(turnKey && session.interruptedTurnKeys.has(turnKey));
|
|
@@ -1485,24 +1466,6 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1485
1466
|
const init = async () => {
|
|
1486
1467
|
processMessages().catch((err) => console.error(`[canon-host] [${conversationId.slice(0, 8)}] processMessages crashed:`, err));
|
|
1487
1468
|
await q.initializationResult();
|
|
1488
|
-
if (config.model) {
|
|
1489
|
-
try {
|
|
1490
|
-
await q.setModel(config.model);
|
|
1491
|
-
session.state.model = config.model;
|
|
1492
|
-
}
|
|
1493
|
-
catch (err) {
|
|
1494
|
-
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial model:`, err);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
if (config.effort) {
|
|
1498
|
-
try {
|
|
1499
|
-
await applyClaudeEffortLevel(q, config.effort);
|
|
1500
|
-
session.state.effort = config.effort;
|
|
1501
|
-
}
|
|
1502
|
-
catch (err) {
|
|
1503
|
-
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial effort:`, err);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
1469
|
if (!resumeSessionId) {
|
|
1507
1470
|
// New session — send seed message to activate streaming input mode
|
|
1508
1471
|
sendInput(createClaudeInputEnvelope({
|
|
@@ -1517,16 +1480,22 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1517
1480
|
},
|
|
1518
1481
|
}));
|
|
1519
1482
|
}
|
|
1520
|
-
// Refresh the canonical runtime descriptor
|
|
1483
|
+
// Refresh the canonical runtime descriptor from a neutral probe. The live
|
|
1484
|
+
// session may be initialized with a selected model, which can narrow the
|
|
1485
|
+
// SDK's cached model list.
|
|
1521
1486
|
try {
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1487
|
+
const discoveredModels = await detectRuntimeModels(cwd).catch((error) => {
|
|
1488
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Model discovery failed:`, error);
|
|
1489
|
+
return runtimeModels;
|
|
1490
|
+
});
|
|
1491
|
+
if (discoveredModels.length > 0) {
|
|
1492
|
+
runtimeModels = discoveredModels;
|
|
1493
|
+
}
|
|
1494
|
+
const modelList = resolveClaudeModelOptions({
|
|
1495
|
+
discovered: runtimeModels,
|
|
1526
1496
|
selectedModel: session.state.model,
|
|
1527
1497
|
});
|
|
1528
1498
|
session.availableModels = modelList;
|
|
1529
|
-
runtimeModels = modelList;
|
|
1530
1499
|
onRuntimeDescriptorUpdate({
|
|
1531
1500
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
1532
1501
|
defaultModel: config.sessionSettings.model ?? modelList[0]?.value,
|
|
@@ -1544,7 +1513,7 @@ function createSession(conversationId, environment, agentId, port, typingSignals
|
|
|
1544
1513
|
}),
|
|
1545
1514
|
});
|
|
1546
1515
|
writeState();
|
|
1547
|
-
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Models: ${
|
|
1516
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Models: ${modelList.map((m) => m.label).join(', ')}`);
|
|
1548
1517
|
}
|
|
1549
1518
|
catch {
|
|
1550
1519
|
writeState();
|
|
@@ -1702,31 +1671,12 @@ export async function main() {
|
|
|
1702
1671
|
refreshFloorMs: HEARTBEAT_MS,
|
|
1703
1672
|
}, { port });
|
|
1704
1673
|
const { conversationCache, knownConversationIds, getConversationMeta, refreshKnownConversationIds, handleConversationUpdated, markGroupContextModeUsed, loadHydratedInboundContext, } = directory;
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
agentName: profileAgentName,
|
|
1709
|
-
cwd: workingDir,
|
|
1710
|
-
baseCwd: workingDir,
|
|
1711
|
-
});
|
|
1712
|
-
if (!streamConnected)
|
|
1674
|
+
const publishSessionSnapshotsForConversations = async (conversationIds) => {
|
|
1675
|
+
const ids = Array.from(new Set(conversationIds));
|
|
1676
|
+
if (!streamConnected || ids.length === 0)
|
|
1713
1677
|
return;
|
|
1714
|
-
await refreshKnownConversationIds().catch((error) => {
|
|
1715
|
-
console.error('[canon-host] Failed to refresh known conversations:', error);
|
|
1716
|
-
});
|
|
1717
|
-
const activeSessionQuery = sessions.values().next().value?.query ?? null;
|
|
1718
|
-
const metadata = await refreshClaudeRuntimeMetadata({
|
|
1719
|
-
cwd: workingDir,
|
|
1720
|
-
activeQuery: activeSessionQuery,
|
|
1721
|
-
}).catch((error) => {
|
|
1722
|
-
console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
|
|
1723
|
-
return runtimeMetadata;
|
|
1724
|
-
});
|
|
1725
|
-
await publishAgentRuntime(port, runtimeDescriptor).catch((error) => {
|
|
1726
|
-
console.error('[canon-host] Failed to publish agent runtime:', error);
|
|
1727
|
-
});
|
|
1728
1678
|
await port.publishHostSessionSnapshots({
|
|
1729
|
-
conversationIds:
|
|
1679
|
+
conversationIds: ids,
|
|
1730
1680
|
clientType: 'claude-code',
|
|
1731
1681
|
runtime: runtimeDescriptor,
|
|
1732
1682
|
workspaceOptions: workspaceOptions.map(({ id, cwd }) => ({ id, cwd })),
|
|
@@ -1752,6 +1702,31 @@ export async function main() {
|
|
|
1752
1702
|
}).catch((error) => {
|
|
1753
1703
|
console.error('[canon-host] Failed to publish session snapshots:', error);
|
|
1754
1704
|
});
|
|
1705
|
+
};
|
|
1706
|
+
const publishRuntimeHeartbeat = async () => {
|
|
1707
|
+
heartbeatLocalRuntimeEntry(runtimeId, {
|
|
1708
|
+
agentId,
|
|
1709
|
+
agentName: profileAgentName,
|
|
1710
|
+
cwd: workingDir,
|
|
1711
|
+
baseCwd: workingDir,
|
|
1712
|
+
});
|
|
1713
|
+
if (!streamConnected)
|
|
1714
|
+
return;
|
|
1715
|
+
await refreshKnownConversationIds().catch((error) => {
|
|
1716
|
+
console.error('[canon-host] Failed to refresh known conversations:', error);
|
|
1717
|
+
});
|
|
1718
|
+
const activeSessionQuery = sessions.values().next().value?.query ?? null;
|
|
1719
|
+
const metadata = await refreshClaudeRuntimeMetadata({
|
|
1720
|
+
cwd: workingDir,
|
|
1721
|
+
activeQuery: activeSessionQuery,
|
|
1722
|
+
}).catch((error) => {
|
|
1723
|
+
console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
|
|
1724
|
+
return runtimeMetadata;
|
|
1725
|
+
});
|
|
1726
|
+
await publishAgentRuntime(port, runtimeDescriptor).catch((error) => {
|
|
1727
|
+
console.error('[canon-host] Failed to publish agent runtime:', error);
|
|
1728
|
+
});
|
|
1729
|
+
await publishSessionSnapshotsForConversations(knownConversationIds);
|
|
1755
1730
|
// Rebuild so the descriptor picks up native slash commands from the
|
|
1756
1731
|
// metadata refresh above — the initial descriptor is built before the
|
|
1757
1732
|
// command inventory has loaded.
|
|
@@ -2373,105 +2348,19 @@ export async function main() {
|
|
|
2373
2348
|
},
|
|
2374
2349
|
});
|
|
2375
2350
|
// ── Control signal watcher ──
|
|
2376
|
-
// Session control signals (model, mode, effort).
|
|
2377
|
-
//
|
|
2378
|
-
// never replayed once a session exists.
|
|
2351
|
+
// Session control signals (model, mode, effort). The bridge has already
|
|
2352
|
+
// consumed the control notification, so every path must publish an answer.
|
|
2379
2353
|
async function applyRuntimeSessionControl(event) {
|
|
2380
2354
|
const convoId = event.conversationId;
|
|
2381
2355
|
const session = sessions.get(convoId);
|
|
2382
|
-
if (!session || session.closed)
|
|
2383
|
-
|
|
2356
|
+
if (!session || session.closed) {
|
|
2357
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Control received without live session; publishing persisted snapshot`);
|
|
2358
|
+
void publishSessionSnapshotsForConversations([convoId]);
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2384
2361
|
const ctrl = event.control;
|
|
2385
2362
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Control:`, ctrl);
|
|
2386
|
-
|
|
2387
|
-
try {
|
|
2388
|
-
await session.query.setModel(ctrl.model);
|
|
2389
|
-
session.state.model = ctrl.model;
|
|
2390
|
-
session.clearRuntimeControlError('model');
|
|
2391
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Model → ${ctrl.model}`);
|
|
2392
|
-
}
|
|
2393
|
-
catch (err) {
|
|
2394
|
-
session.setRuntimeControlError('model', ctrl.model, err);
|
|
2395
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set model:`, err);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
const nextPermissionMode = parseClaudePermissionMode(ctrl.permissionMode);
|
|
2399
|
-
if (ctrl.permissionMode && !nextPermissionMode) {
|
|
2400
|
-
session.setRuntimeControlError('permissionMode', ctrl.permissionMode, new Error(`Unsupported Claude permission mode: ${ctrl.permissionMode}`));
|
|
2401
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Unsupported mode: ${ctrl.permissionMode}`);
|
|
2402
|
-
}
|
|
2403
|
-
else if (nextPermissionMode && nextPermissionMode !== session.state.permissionMode) {
|
|
2404
|
-
try {
|
|
2405
|
-
await session.query.setPermissionMode(nextPermissionMode);
|
|
2406
|
-
session.state.permissionMode = nextPermissionMode;
|
|
2407
|
-
session.clearRuntimeControlError('permissionMode');
|
|
2408
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Mode → ${nextPermissionMode}`);
|
|
2409
|
-
}
|
|
2410
|
-
catch (err) {
|
|
2411
|
-
session.setRuntimeControlError('permissionMode', nextPermissionMode, err);
|
|
2412
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
|
|
2416
|
-
if (nextEffort && nextEffort !== session.state.effort) {
|
|
2417
|
-
try {
|
|
2418
|
-
await applyClaudeEffortLevel(session.query, nextEffort);
|
|
2419
|
-
session.state.effort = nextEffort;
|
|
2420
|
-
session.clearRuntimeControlError('effort');
|
|
2421
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
|
|
2422
|
-
}
|
|
2423
|
-
catch (err) {
|
|
2424
|
-
session.setRuntimeControlError('effort', nextEffort, err);
|
|
2425
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
else if (ctrl.effort && !nextEffort) {
|
|
2429
|
-
session.setRuntimeControlError('effort', String(ctrl.effort), new Error('Unsupported Claude thinking level.'));
|
|
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
|
-
}
|
|
2474
|
-
session.writeState();
|
|
2363
|
+
applyClaudeSessionControl(session, ctrl);
|
|
2475
2364
|
}
|
|
2476
2365
|
// ── Heartbeat + idle cleanup + graceful shutdown (agent-host lifecycle) ──
|
|
2477
2366
|
lifecycle.startTimers();
|
|
@@ -18,6 +18,7 @@ export interface ClaudeSessionControlValues {
|
|
|
18
18
|
export interface ClaudeEffectiveSessionSettings extends ClaudeSessionDefaults {
|
|
19
19
|
runtimeControlValues: Record<string, string>;
|
|
20
20
|
}
|
|
21
|
+
export declare const CLAUDE_EFFORT_LEVELS: readonly ClaudeEffortLevel[];
|
|
21
22
|
export declare function parseClaudeThinkingMode(value: unknown): ClaudeThinkingMode | undefined;
|
|
22
23
|
export declare function parseClaudeUltracodeMode(value: unknown): ClaudeUltracodeMode | undefined;
|
|
23
24
|
export declare function parseClaudeSessionDefaults(raw: unknown): ClaudeSessionDefaults;
|
package/dist/session-defaults.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
export const CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
2
|
+
const CLAUDE_EFFORT_VALUES = new Set(CLAUDE_EFFORT_LEVELS);
|
|
2
3
|
function optionalString(value) {
|
|
3
4
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
4
5
|
}
|
package/dist/session-state.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
1
|
+
import type { Query, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import type { DeliveryIntent, ModelOption, TurnLifecycleState } from '@canonmsg/core/contract';
|
|
3
3
|
import type { TurnArtifactSnapshot } from '@canonmsg/agent-host';
|
|
4
|
+
import { type ClaudeEffortLevel } from './session-defaults.js';
|
|
5
|
+
export type { ClaudeEffortLevel } from './session-defaults.js';
|
|
4
6
|
export type ClaudeInputKind = 'seed' | 'canon';
|
|
5
7
|
export type ClaudeArtifactRoutingMode = 'workspace-generated' | 'disabled';
|
|
6
8
|
export interface ClaudeInputEnvelope {
|
|
@@ -19,6 +21,47 @@ export interface ClaudeRuntimeControlError {
|
|
|
19
21
|
message: string;
|
|
20
22
|
updatedAt: number;
|
|
21
23
|
}
|
|
24
|
+
export type ClaudePermissionMode = Parameters<Query['setPermissionMode']>[0];
|
|
25
|
+
export interface ClaudeSessionControl {
|
|
26
|
+
model?: string;
|
|
27
|
+
permissionMode?: string;
|
|
28
|
+
effort?: string;
|
|
29
|
+
runtimeControlValues?: Record<string, string>;
|
|
30
|
+
updatedAt?: number;
|
|
31
|
+
updatedBy?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ClaudeHostSessionState {
|
|
34
|
+
model?: string;
|
|
35
|
+
permissionMode?: string;
|
|
36
|
+
effort?: string;
|
|
37
|
+
fallbackModel?: string;
|
|
38
|
+
runtimeControlValues?: Record<string, string>;
|
|
39
|
+
state?: 'idle' | 'running' | 'requires_action';
|
|
40
|
+
contextUsage?: {
|
|
41
|
+
percentage: number;
|
|
42
|
+
totalTokens: number;
|
|
43
|
+
maxTokens: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function parseClaudePermissionMode(value: unknown): ClaudePermissionMode | null;
|
|
47
|
+
export declare function normalizeClaudeEffortLevel(value: unknown): ClaudeEffortLevel | undefined;
|
|
48
|
+
export declare function buildClaudeInitialSessionState(input: {
|
|
49
|
+
model?: string;
|
|
50
|
+
permissionMode?: string;
|
|
51
|
+
effort?: string;
|
|
52
|
+
fallbackModel?: string;
|
|
53
|
+
runtimeControlValues?: Record<string, string>;
|
|
54
|
+
}): ClaudeHostSessionState;
|
|
55
|
+
export interface ClaudeSessionControlTarget {
|
|
56
|
+
state: ClaudeHostSessionState;
|
|
57
|
+
controlApplySeq: Map<string, number>;
|
|
58
|
+
closed: boolean;
|
|
59
|
+
query: Pick<Query, 'setModel' | 'setPermissionMode' | 'applyFlagSettings'>;
|
|
60
|
+
writeState: () => void;
|
|
61
|
+
setRuntimeControlError: (controlId: string, value: string, error: unknown) => void;
|
|
62
|
+
clearRuntimeControlError: (controlId: string) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare function applyClaudeSessionControl(target: ClaudeSessionControlTarget, ctrl: ClaudeSessionControl): void;
|
|
22
65
|
export interface ClaudePendingFinalDelivery {
|
|
23
66
|
turnKey: string;
|
|
24
67
|
text: string;
|
|
@@ -60,9 +103,8 @@ export declare function shouldDeliverClaudeFinal(input: {
|
|
|
60
103
|
interruptedTurnKeys: ReadonlySet<string>;
|
|
61
104
|
}): boolean;
|
|
62
105
|
export declare function resetClaudeCompletedTurnState(session: ClaudeCompletedTurnState): void;
|
|
63
|
-
export declare function
|
|
64
|
-
|
|
65
|
-
next: ReadonlyArray<ModelOption>;
|
|
106
|
+
export declare function resolveClaudeModelOptions(input: {
|
|
107
|
+
discovered: ReadonlyArray<ModelOption>;
|
|
66
108
|
selectedModel?: string | null;
|
|
67
109
|
}): ModelOption[];
|
|
68
110
|
export declare function formatClaudeControlError(error: unknown): string;
|
package/dist/session-state.js
CHANGED
|
@@ -1,4 +1,173 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { CLAUDE_EFFORT_LEVELS, claudeThinkingToFlagValue, parseClaudeThinkingMode, parseClaudeUltracodeMode, } from './session-defaults.js';
|
|
3
|
+
const CLAUDE_PERMISSION_MODE_VALUES = new Set([
|
|
4
|
+
'acceptEdits',
|
|
5
|
+
'auto',
|
|
6
|
+
'bypassPermissions',
|
|
7
|
+
'default',
|
|
8
|
+
'dontAsk',
|
|
9
|
+
'plan',
|
|
10
|
+
]);
|
|
11
|
+
const CLAUDE_EFFORT_VALUES = new Set(CLAUDE_EFFORT_LEVELS);
|
|
12
|
+
export function parseClaudePermissionMode(value) {
|
|
13
|
+
return typeof value === 'string' && CLAUDE_PERMISSION_MODE_VALUES.has(value)
|
|
14
|
+
? value
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
export function normalizeClaudeEffortLevel(value) {
|
|
18
|
+
return typeof value === 'string' && CLAUDE_EFFORT_VALUES.has(value)
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
export function buildClaudeInitialSessionState(input) {
|
|
23
|
+
return {
|
|
24
|
+
...(input.model ? { model: input.model } : {}),
|
|
25
|
+
...(input.permissionMode ? { permissionMode: input.permissionMode } : {}),
|
|
26
|
+
...(input.effort ? { effort: input.effort } : {}),
|
|
27
|
+
...(input.fallbackModel ? { fallbackModel: input.fallbackModel } : {}),
|
|
28
|
+
...(input.runtimeControlValues ? { runtimeControlValues: input.runtimeControlValues } : {}),
|
|
29
|
+
state: 'idle',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function nextControlApplySeq(target, controlId) {
|
|
33
|
+
const nextSeq = (target.controlApplySeq.get(controlId) ?? 0) + 1;
|
|
34
|
+
target.controlApplySeq.set(controlId, nextSeq);
|
|
35
|
+
return nextSeq;
|
|
36
|
+
}
|
|
37
|
+
function applyOptimisticControl(input) {
|
|
38
|
+
const seq = nextControlApplySeq(input.target, input.controlId);
|
|
39
|
+
const applyPromise = (async () => input.apply())();
|
|
40
|
+
void applyPromise
|
|
41
|
+
.catch((error) => {
|
|
42
|
+
if (input.target.closed || input.target.controlApplySeq.get(input.controlId) !== seq) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
input.revert();
|
|
46
|
+
input.target.setRuntimeControlError(input.controlId, input.value, error);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function setRuntimeControlValue(state, controlId, value) {
|
|
50
|
+
state.runtimeControlValues = {
|
|
51
|
+
...(state.runtimeControlValues ?? {}),
|
|
52
|
+
[controlId]: value,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function applyClaudeSessionControl(target, ctrl) {
|
|
56
|
+
const nextModel = typeof ctrl.model === 'string' && ctrl.model.trim() ? ctrl.model.trim() : null;
|
|
57
|
+
if (ctrl.model !== undefined && !nextModel) {
|
|
58
|
+
target.setRuntimeControlError('model', String(ctrl.model), new Error('Unsupported Claude model.'));
|
|
59
|
+
}
|
|
60
|
+
else if (nextModel && nextModel !== target.state.model) {
|
|
61
|
+
const previous = target.state.model;
|
|
62
|
+
target.state.model = nextModel;
|
|
63
|
+
target.clearRuntimeControlError('model');
|
|
64
|
+
applyOptimisticControl({
|
|
65
|
+
target,
|
|
66
|
+
controlId: 'model',
|
|
67
|
+
value: nextModel,
|
|
68
|
+
apply: () => target.query.setModel(nextModel),
|
|
69
|
+
revert: () => {
|
|
70
|
+
if (previous === undefined)
|
|
71
|
+
delete target.state.model;
|
|
72
|
+
else
|
|
73
|
+
target.state.model = previous;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const nextPermissionMode = parseClaudePermissionMode(ctrl.permissionMode);
|
|
78
|
+
if (ctrl.permissionMode !== undefined && !nextPermissionMode) {
|
|
79
|
+
target.setRuntimeControlError('permissionMode', ctrl.permissionMode, new Error(`Unsupported Claude permission mode: ${ctrl.permissionMode}`));
|
|
80
|
+
}
|
|
81
|
+
else if (nextPermissionMode && nextPermissionMode !== target.state.permissionMode) {
|
|
82
|
+
// Claude Code applies permission-mode changes asynchronously. We publish
|
|
83
|
+
// the requested value immediately so Canon does not show a stale "requested"
|
|
84
|
+
// state while the SDK catches up, then revert if the current apply fails.
|
|
85
|
+
const previous = target.state.permissionMode;
|
|
86
|
+
target.state.permissionMode = nextPermissionMode;
|
|
87
|
+
target.clearRuntimeControlError('permissionMode');
|
|
88
|
+
applyOptimisticControl({
|
|
89
|
+
target,
|
|
90
|
+
controlId: 'permissionMode',
|
|
91
|
+
value: nextPermissionMode,
|
|
92
|
+
apply: () => target.query.setPermissionMode(nextPermissionMode),
|
|
93
|
+
revert: () => {
|
|
94
|
+
if (previous === undefined)
|
|
95
|
+
delete target.state.permissionMode;
|
|
96
|
+
else
|
|
97
|
+
target.state.permissionMode = previous;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
|
|
102
|
+
if (nextEffort && nextEffort !== target.state.effort) {
|
|
103
|
+
const previous = target.state.effort;
|
|
104
|
+
target.state.effort = nextEffort;
|
|
105
|
+
target.clearRuntimeControlError('effort');
|
|
106
|
+
applyOptimisticControl({
|
|
107
|
+
target,
|
|
108
|
+
controlId: 'effort',
|
|
109
|
+
value: nextEffort,
|
|
110
|
+
apply: () => target.query.applyFlagSettings({ effortLevel: nextEffort }),
|
|
111
|
+
revert: () => {
|
|
112
|
+
if (previous === undefined)
|
|
113
|
+
delete target.state.effort;
|
|
114
|
+
else
|
|
115
|
+
target.state.effort = previous;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else if (ctrl.effort !== undefined && !nextEffort) {
|
|
120
|
+
target.setRuntimeControlError('effort', String(ctrl.effort), new Error('Unsupported Claude thinking level.'));
|
|
121
|
+
}
|
|
122
|
+
const runtimeControlValues = ctrl.runtimeControlValues ?? {};
|
|
123
|
+
if ('thinking' in runtimeControlValues) {
|
|
124
|
+
const nextThinking = parseClaudeThinkingMode(runtimeControlValues.thinking);
|
|
125
|
+
if (!nextThinking) {
|
|
126
|
+
target.setRuntimeControlError('thinking', String(runtimeControlValues.thinking), new Error('Unsupported Claude thinking mode.'));
|
|
127
|
+
}
|
|
128
|
+
else if (nextThinking !== target.state.runtimeControlValues?.thinking) {
|
|
129
|
+
const previous = target.state.runtimeControlValues?.thinking;
|
|
130
|
+
setRuntimeControlValue(target.state, 'thinking', nextThinking);
|
|
131
|
+
target.clearRuntimeControlError('thinking');
|
|
132
|
+
applyOptimisticControl({
|
|
133
|
+
target,
|
|
134
|
+
controlId: 'thinking',
|
|
135
|
+
value: nextThinking,
|
|
136
|
+
apply: () => target.query.applyFlagSettings({ alwaysThinkingEnabled: claudeThinkingToFlagValue(nextThinking) }),
|
|
137
|
+
revert: () => {
|
|
138
|
+
if (previous === undefined)
|
|
139
|
+
delete target.state.runtimeControlValues?.thinking;
|
|
140
|
+
else
|
|
141
|
+
setRuntimeControlValue(target.state, 'thinking', previous);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if ('ultracode' in runtimeControlValues) {
|
|
147
|
+
const nextUltracode = parseClaudeUltracodeMode(runtimeControlValues.ultracode);
|
|
148
|
+
if (!nextUltracode) {
|
|
149
|
+
target.setRuntimeControlError('ultracode', String(runtimeControlValues.ultracode), new Error('Unsupported Claude ultracode mode.'));
|
|
150
|
+
}
|
|
151
|
+
else if (nextUltracode !== target.state.runtimeControlValues?.ultracode) {
|
|
152
|
+
const previous = target.state.runtimeControlValues?.ultracode;
|
|
153
|
+
setRuntimeControlValue(target.state, 'ultracode', nextUltracode);
|
|
154
|
+
target.clearRuntimeControlError('ultracode');
|
|
155
|
+
applyOptimisticControl({
|
|
156
|
+
target,
|
|
157
|
+
controlId: 'ultracode',
|
|
158
|
+
value: nextUltracode,
|
|
159
|
+
apply: () => target.query.applyFlagSettings({ ultracode: nextUltracode === 'on' }),
|
|
160
|
+
revert: () => {
|
|
161
|
+
if (previous === undefined)
|
|
162
|
+
delete target.state.runtimeControlValues?.ultracode;
|
|
163
|
+
else
|
|
164
|
+
setRuntimeControlValue(target.state, 'ultracode', previous);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
target.writeState();
|
|
170
|
+
}
|
|
2
171
|
export function createClaudeInputEnvelope(input) {
|
|
3
172
|
const sourceMessageId = input.sourceMessageId ?? null;
|
|
4
173
|
const fallbackId = randomUUID();
|
|
@@ -46,32 +215,19 @@ export function resetClaudeCompletedTurnState(session) {
|
|
|
46
215
|
session.toolInProgress = false;
|
|
47
216
|
session.turnState = 'idle';
|
|
48
217
|
}
|
|
49
|
-
export function
|
|
218
|
+
export function resolveClaudeModelOptions(input) {
|
|
50
219
|
const byValue = new Map();
|
|
51
|
-
for (const option of input.
|
|
52
|
-
byValue.set(option.value, { ...option });
|
|
53
|
-
}
|
|
54
|
-
for (const option of input.next) {
|
|
220
|
+
for (const option of input.discovered) {
|
|
55
221
|
byValue.set(option.value, { ...option });
|
|
56
222
|
}
|
|
57
223
|
if (input.selectedModel && !byValue.has(input.selectedModel)) {
|
|
58
224
|
byValue.set(input.selectedModel, {
|
|
59
225
|
value: input.selectedModel,
|
|
60
226
|
label: input.selectedModel,
|
|
61
|
-
description: 'Selected for this Canon session but not returned by
|
|
227
|
+
description: 'Selected for this Canon session but not returned by Claude model discovery.',
|
|
62
228
|
});
|
|
63
229
|
}
|
|
64
|
-
|
|
65
|
-
return [...byValue.values()].map((option) => {
|
|
66
|
-
if (nextValues.has(option.value))
|
|
67
|
-
return option;
|
|
68
|
-
return {
|
|
69
|
-
...option,
|
|
70
|
-
description: option.description
|
|
71
|
-
? `${option.description} Previously advertised; not returned by the latest Claude model probe.`
|
|
72
|
-
: 'Previously advertised; not returned by the latest Claude model probe.',
|
|
73
|
-
};
|
|
74
|
-
});
|
|
230
|
+
return [...byValue.values()];
|
|
75
231
|
}
|
|
76
232
|
export function formatClaudeControlError(error) {
|
|
77
233
|
if (error instanceof Error && error.message.trim()) {
|
package/package.json
CHANGED