@canonmsg/claude-code-plugin 0.14.14 → 0.14.16
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 -38
- package/dist/session-state.d.ts +1 -0
- package/package.json +3 -3
package/dist/host.js
CHANGED
|
@@ -26,7 +26,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
26
26
|
import { existsSync } from 'node:fs';
|
|
27
27
|
import { query, } from '@anthropic-ai/claude-agent-sdk';
|
|
28
28
|
import { isAnthropicImageAttachment, materializeMessageMedia, materializeReplyContextMedia, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
|
|
29
|
-
import { CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, rtdbRead, rtdbWrite, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
29
|
+
import { CLAUDE_EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, rtdbRead, rtdbWrite, sendMessageWithRetry, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
30
30
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
31
31
|
import { runCli } from './cli-entry.js';
|
|
32
32
|
import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
|
|
@@ -167,11 +167,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
167
167
|
label: 'Level',
|
|
168
168
|
kind: 'enum',
|
|
169
169
|
required: true,
|
|
170
|
-
choices: [
|
|
171
|
-
{ value: 'low', label: 'Low' },
|
|
172
|
-
{ value: 'medium', label: 'Medium' },
|
|
173
|
-
{ value: 'high', label: 'High' },
|
|
174
|
-
],
|
|
170
|
+
choices: [...CLAUDE_EFFORT_OPTIONS],
|
|
175
171
|
}],
|
|
176
172
|
dispatch: {
|
|
177
173
|
kind: 'control',
|
|
@@ -199,6 +195,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
199
195
|
commands,
|
|
200
196
|
});
|
|
201
197
|
}
|
|
198
|
+
const CLAUDE_EFFORT_VALUES = new Set(CLAUDE_EFFORT_OPTIONS.map((option) => option.value));
|
|
202
199
|
function parseClaudePermissionMode(value) {
|
|
203
200
|
if (value === 'acceptEdits'
|
|
204
201
|
|| value === 'auto'
|
|
@@ -211,10 +208,13 @@ function parseClaudePermissionMode(value) {
|
|
|
211
208
|
return null;
|
|
212
209
|
}
|
|
213
210
|
function normalizeClaudeEffortLevel(value) {
|
|
214
|
-
return
|
|
211
|
+
return typeof value === 'string' && CLAUDE_EFFORT_VALUES.has(value)
|
|
215
212
|
? value
|
|
216
213
|
: undefined;
|
|
217
214
|
}
|
|
215
|
+
async function applyClaudeEffortLevel(q, effort) {
|
|
216
|
+
await q.applyFlagSettings({ effortLevel: effort });
|
|
217
|
+
}
|
|
218
218
|
let cachedClaudeCliPath;
|
|
219
219
|
// The SDK bundles a Bun-compiled `claude` Mach-O binary next to
|
|
220
220
|
// claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
|
|
@@ -522,7 +522,10 @@ const STREAMING_THROTTLE_MS = 250;
|
|
|
522
522
|
const HEARTBEAT_MS = 30_000;
|
|
523
523
|
const IDLE_CHECK_MS = 60_000;
|
|
524
524
|
const CONTROL_POLL_MS = 2000;
|
|
525
|
-
const
|
|
525
|
+
const IDLE_CONTROL_POLL_MS = 10_000;
|
|
526
|
+
const CONTROL_POLL_JITTER_MS = 1_000;
|
|
527
|
+
const FINAL_DELIVERY_RETRY_MS = 30_000;
|
|
528
|
+
const MAX_FINAL_DELIVERY_RETRIES = 1;
|
|
526
529
|
const CLAUDE_RUNTIME_CAPABILITIES = {
|
|
527
530
|
...DEFAULT_RUNTIME_CAPABILITIES,
|
|
528
531
|
supportsInterrupt: true,
|
|
@@ -532,6 +535,10 @@ const CLAUDE_RUNTIME_CAPABILITIES = {
|
|
|
532
535
|
supportsRequiresAction: true,
|
|
533
536
|
supportsNonFinalPermanentMessages: false,
|
|
534
537
|
};
|
|
538
|
+
function controlPollDelayMs(hasActiveWork) {
|
|
539
|
+
const base = hasActiveWork ? CONTROL_POLL_MS : IDLE_CONTROL_POLL_MS;
|
|
540
|
+
return base + Math.floor(Math.random() * CONTROL_POLL_JITTER_MS);
|
|
541
|
+
}
|
|
535
542
|
function buildCanonPrompt(input) {
|
|
536
543
|
const { session, ...promptInput } = input;
|
|
537
544
|
return buildCanonHostPrompt({
|
|
@@ -550,11 +557,7 @@ function optionLabel(value, options) {
|
|
|
550
557
|
function buildClaudeSessionContextLines(session) {
|
|
551
558
|
const workspace = workspaceOptions.find((option) => option.cwd === session.environment.baseCwd);
|
|
552
559
|
const permissionMode = optionLabel(session.state.permissionMode, CLAUDE_PERMISSION_MODE_OPTIONS);
|
|
553
|
-
const effort = optionLabel(session.state.effort,
|
|
554
|
-
{ value: 'low', label: 'Low' },
|
|
555
|
-
{ value: 'medium', label: 'Medium' },
|
|
556
|
-
{ value: 'high', label: 'High' },
|
|
557
|
-
]);
|
|
560
|
+
const effort = optionLabel(session.state.effort, CLAUDE_EFFORT_OPTIONS);
|
|
558
561
|
const model = optionLabel(session.state.model, session.availableModels);
|
|
559
562
|
return [
|
|
560
563
|
model ? `Selected model: ${model}` : null,
|
|
@@ -606,7 +609,7 @@ async function buildCanonUserContent(input) {
|
|
|
606
609
|
];
|
|
607
610
|
}
|
|
608
611
|
// ── Session factory ─────────────────────────────────────────────────
|
|
609
|
-
function createSession(conversationId, environment, agentId, client, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
612
|
+
function createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
610
613
|
const { cwd } = environment;
|
|
611
614
|
const cardResponseUserId = config.ownerId ?? undefined;
|
|
612
615
|
const cardResponseUser = cardResponseUserId ? { responseUserId: cardResponseUserId } : undefined;
|
|
@@ -692,6 +695,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
692
695
|
cwd,
|
|
693
696
|
includePartialMessages: true,
|
|
694
697
|
maxTurns: 200,
|
|
698
|
+
...(config.effort ? { effort: config.effort } : {}),
|
|
695
699
|
settings: { forceLoginMethod: 'claudeai' },
|
|
696
700
|
settingSources: ['project', 'local'],
|
|
697
701
|
...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
|
|
@@ -707,7 +711,8 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
707
711
|
const questionId = randomUUID();
|
|
708
712
|
const questions = input.questions ?? [];
|
|
709
713
|
const questionCard = buildQuestionRequest(questionId, questions, cardResponseUser);
|
|
710
|
-
await client
|
|
714
|
+
await sendMessageWithRetry(client, conversationId, questionCard.text, {
|
|
715
|
+
messageId: `claude-question-${questionId}`,
|
|
711
716
|
...(session.activeSelfContextId
|
|
712
717
|
? { selfContextId: session.activeSelfContextId }
|
|
713
718
|
: {}),
|
|
@@ -757,7 +762,8 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
757
762
|
summary: 'Claude has prepared a plan and is asking permission to continue.',
|
|
758
763
|
...(cardResponseUser ?? {}),
|
|
759
764
|
});
|
|
760
|
-
await client
|
|
765
|
+
await sendMessageWithRetry(client, conversationId, planApproval.text, {
|
|
766
|
+
messageId: `claude-plan-${planId}`,
|
|
761
767
|
...(session.activeSelfContextId
|
|
762
768
|
? { selfContextId: session.activeSelfContextId }
|
|
763
769
|
: {}),
|
|
@@ -915,17 +921,21 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
915
921
|
clearIdleResetTimer();
|
|
916
922
|
session.idleResetTimer = setTimeout(() => {
|
|
917
923
|
session.idleResetTimer = null;
|
|
918
|
-
|
|
924
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
919
925
|
clearStreaming();
|
|
920
926
|
resetTurnToIdle();
|
|
921
927
|
}, FINAL_MESSAGE_HANDOFF_MS);
|
|
922
928
|
}
|
|
923
929
|
function markPendingFinalDelivery(finalText, turn) {
|
|
924
930
|
clearIdleResetTimer();
|
|
931
|
+
const retryCount = session.pendingFinalDelivery?.turnKey === turn.turnKey
|
|
932
|
+
? session.pendingFinalDelivery.retryCount
|
|
933
|
+
: 0;
|
|
925
934
|
session.pendingFinalText = finalText;
|
|
926
935
|
session.pendingFinalDelivery = {
|
|
927
936
|
turnKey: turn.turnKey,
|
|
928
937
|
text: finalText,
|
|
938
|
+
retryCount,
|
|
929
939
|
messageId: buildClaudeFinalMessageId({
|
|
930
940
|
agentId,
|
|
931
941
|
conversationId,
|
|
@@ -938,7 +948,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
938
948
|
streamingOutput.replaceSnapshot(finalText, 'streaming').catch(() => { });
|
|
939
949
|
writeState();
|
|
940
950
|
writeTurn();
|
|
941
|
-
|
|
951
|
+
typingSignals.start(conversationId, 'typing').catch(() => { });
|
|
942
952
|
}
|
|
943
953
|
async function deliverFinalReply(finalText, turn = session.activeInput) {
|
|
944
954
|
if (!shouldDeliverClaudeFinal({
|
|
@@ -957,7 +967,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
957
967
|
turnKey: deliverTurn.turnKey,
|
|
958
968
|
});
|
|
959
969
|
try {
|
|
960
|
-
await client
|
|
970
|
+
await sendMessageWithRetry(client, conversationId, finalText, {
|
|
961
971
|
messageId: finalMessageId,
|
|
962
972
|
...(session.activeSelfContextId
|
|
963
973
|
? { selfContextId: session.activeSelfContextId }
|
|
@@ -990,6 +1000,14 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
990
1000
|
function scheduleFinalDeliveryRetry(pending = session.pendingFinalDelivery) {
|
|
991
1001
|
if (!pending || session.closed || session.pendingFinalDelivery?.turnKey !== pending.turnKey)
|
|
992
1002
|
return;
|
|
1003
|
+
if (pending.retryCount >= MAX_FINAL_DELIVERY_RETRIES) {
|
|
1004
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Final reply delivery exhausted after bounded retries`);
|
|
1005
|
+
session.pendingFinalDelivery = null;
|
|
1006
|
+
resetTurnToIdle();
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
pending.retryCount += 1;
|
|
1010
|
+
session.pendingFinalDelivery = pending;
|
|
993
1011
|
clearFinalDeliveryTimer();
|
|
994
1012
|
session.finalDeliveryTimer = setTimeout(() => {
|
|
995
1013
|
session.finalDeliveryTimer = null;
|
|
@@ -1094,19 +1112,17 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1094
1112
|
return;
|
|
1095
1113
|
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
1096
1114
|
return;
|
|
1097
|
-
|
|
1115
|
+
typingSignals.start(conversationId, 'thinking').catch(() => { });
|
|
1098
1116
|
}
|
|
1099
1117
|
function startVisibleWorkSignal() {
|
|
1100
1118
|
refreshVisibleWorkSignal();
|
|
1101
|
-
if (session.typingKeepaliveTimer)
|
|
1102
|
-
return;
|
|
1103
|
-
session.typingKeepaliveTimer = setInterval(refreshVisibleWorkSignal, 3500);
|
|
1104
1119
|
}
|
|
1105
1120
|
function stopVisibleWorkSignal() {
|
|
1106
1121
|
if (session.typingKeepaliveTimer) {
|
|
1107
1122
|
clearInterval(session.typingKeepaliveTimer);
|
|
1108
1123
|
session.typingKeepaliveTimer = null;
|
|
1109
1124
|
}
|
|
1125
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
1110
1126
|
}
|
|
1111
1127
|
async function markQueuedMessageAccepted(sourceMessageId, markAccepted) {
|
|
1112
1128
|
if (!markAccepted || !sourceMessageId)
|
|
@@ -1152,7 +1168,6 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1152
1168
|
session.turnState = 'interrupted';
|
|
1153
1169
|
stopVisibleWorkSignal();
|
|
1154
1170
|
clearStreaming().catch(() => { });
|
|
1155
|
-
client.setTyping(conversationId, false).catch(() => { });
|
|
1156
1171
|
writeTurn();
|
|
1157
1172
|
void session.query.interrupt().catch(() => { });
|
|
1158
1173
|
return;
|
|
@@ -1192,7 +1207,6 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1192
1207
|
const event = msg.event;
|
|
1193
1208
|
if (isActiveTurnInterrupted()) {
|
|
1194
1209
|
stopVisibleWorkSignal();
|
|
1195
|
-
client.setTyping(conversationId, false).catch(() => { });
|
|
1196
1210
|
break;
|
|
1197
1211
|
}
|
|
1198
1212
|
if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
@@ -1222,7 +1236,6 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1222
1236
|
session.toolInProgress = false;
|
|
1223
1237
|
session.turnState = 'streaming';
|
|
1224
1238
|
stopVisibleWorkSignal();
|
|
1225
|
-
client.setTyping(conversationId, false).catch(() => { });
|
|
1226
1239
|
writeTurn();
|
|
1227
1240
|
onStreamDelta(event.delta.text);
|
|
1228
1241
|
}
|
|
@@ -1247,7 +1260,6 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1247
1260
|
}
|
|
1248
1261
|
else if (state === 'idle') {
|
|
1249
1262
|
stopVisibleWorkSignal();
|
|
1250
|
-
client.setTyping(conversationId, false).catch(() => { });
|
|
1251
1263
|
}
|
|
1252
1264
|
else if (state === 'requires_action') {
|
|
1253
1265
|
clearIdleResetTimer();
|
|
@@ -1258,7 +1270,6 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1258
1270
|
session.toolInProgress = false;
|
|
1259
1271
|
session.turnState = 'waiting_input';
|
|
1260
1272
|
stopVisibleWorkSignal();
|
|
1261
|
-
client.setTyping(conversationId, false).catch(() => { });
|
|
1262
1273
|
}
|
|
1263
1274
|
writeState();
|
|
1264
1275
|
writeTurn();
|
|
@@ -1345,7 +1356,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1345
1356
|
}
|
|
1346
1357
|
if (config.effort) {
|
|
1347
1358
|
try {
|
|
1348
|
-
await q
|
|
1359
|
+
await applyClaudeEffortLevel(q, config.effort);
|
|
1349
1360
|
session.state.effort = config.effort;
|
|
1350
1361
|
}
|
|
1351
1362
|
catch (err) {
|
|
@@ -1442,6 +1453,11 @@ export async function main() {
|
|
|
1442
1453
|
// ── Canon API client + RTDB auth ──
|
|
1443
1454
|
const client = new CanonClient(apiKey, baseUrl);
|
|
1444
1455
|
initRTDBAuth(client);
|
|
1456
|
+
const typingSignals = createTypingStatusPublisher({
|
|
1457
|
+
setTyping: (conversationId, typing, status) => status
|
|
1458
|
+
? client.setTyping(conversationId, typing, status)
|
|
1459
|
+
: client.setTyping(conversationId, typing),
|
|
1460
|
+
});
|
|
1445
1461
|
async function markQueuedMessageAcceptedForConversation(conversationId, sourceMessageId, markAccepted) {
|
|
1446
1462
|
if (!markAccepted || !sourceMessageId)
|
|
1447
1463
|
return;
|
|
@@ -1648,7 +1664,7 @@ export async function main() {
|
|
|
1648
1664
|
runtimeState.clearStreaming(conversation.id).catch(() => { });
|
|
1649
1665
|
runtimeState.clearSessionState(conversation.id).catch(() => { });
|
|
1650
1666
|
runtimeState.clearTurnState(conversation.id).catch(() => { });
|
|
1651
|
-
|
|
1667
|
+
typingSignals.clear(conversation.id).catch(() => { });
|
|
1652
1668
|
}
|
|
1653
1669
|
console.error(`[canon-host] ${convos.length} conversations`);
|
|
1654
1670
|
}
|
|
@@ -1806,7 +1822,8 @@ export async function main() {
|
|
|
1806
1822
|
}
|
|
1807
1823
|
runtimeState.clearSessionState(conversationId).catch(() => { });
|
|
1808
1824
|
runtimeState.clearTurnState(conversationId).catch(() => { });
|
|
1809
|
-
|
|
1825
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
1826
|
+
typingSignals.dispose(conversationId);
|
|
1810
1827
|
try {
|
|
1811
1828
|
session.query.close();
|
|
1812
1829
|
}
|
|
@@ -1825,7 +1842,7 @@ export async function main() {
|
|
|
1825
1842
|
session.pendingFinalDelivery = null;
|
|
1826
1843
|
session.turnState = 'interrupted';
|
|
1827
1844
|
session.clearStreaming().catch(() => { });
|
|
1828
|
-
|
|
1845
|
+
typingSignals.clear(conversationId).catch(() => { });
|
|
1829
1846
|
if (session.state.state === 'running') {
|
|
1830
1847
|
await session.query.interrupt().catch(() => { });
|
|
1831
1848
|
}
|
|
@@ -1893,7 +1910,7 @@ export async function main() {
|
|
|
1893
1910
|
executionMode: environment.mode,
|
|
1894
1911
|
});
|
|
1895
1912
|
const resumeId = savedSessionIds.get(resumeKey) ?? persisted?.claudeSessionId;
|
|
1896
|
-
const session = createSession(conversationId, environment, agentId, client, runtimeState, (convoId, sdkSessionId) => {
|
|
1913
|
+
const session = createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, (convoId, sdkSessionId) => {
|
|
1897
1914
|
// Called when session processing loop ends — save session ID for resume
|
|
1898
1915
|
const existing = sessions.get(convoId);
|
|
1899
1916
|
if (existing) {
|
|
@@ -1916,6 +1933,8 @@ export async function main() {
|
|
|
1916
1933
|
}
|
|
1917
1934
|
}
|
|
1918
1935
|
existing?.clearStreaming().catch(() => { });
|
|
1936
|
+
typingSignals.clear(convoId).catch(() => { });
|
|
1937
|
+
typingSignals.dispose(convoId);
|
|
1919
1938
|
sessions.delete(convoId);
|
|
1920
1939
|
runtimeState.clearSessionState(convoId).catch(() => { });
|
|
1921
1940
|
runtimeState.clearTurnState(convoId).catch(() => { });
|
|
@@ -2031,7 +2050,8 @@ export async function main() {
|
|
|
2031
2050
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
2032
2051
|
console.error(`[canon-host] [${input.conversationId.slice(0, 8)}] Failed to create recovered session: ${message}`);
|
|
2033
2052
|
await markQueuedMessageAcceptedForConversation(input.conversationId, m.id ?? null, shouldMarkAccepted);
|
|
2034
|
-
await client
|
|
2053
|
+
await sendMessageWithRetry(client, input.conversationId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
2054
|
+
messageId: `claude-start-failed-${m.id}`,
|
|
2035
2055
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
2036
2056
|
metadata: {
|
|
2037
2057
|
turnSemantics: 'turn_complete',
|
|
@@ -2208,7 +2228,8 @@ export async function main() {
|
|
|
2208
2228
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
2209
2229
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
2210
2230
|
await markQueuedMessageAcceptedForConversation(convoId, m.id ?? null, shouldMarkAccepted);
|
|
2211
|
-
await client
|
|
2231
|
+
await sendMessageWithRetry(client, convoId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
2232
|
+
messageId: `claude-start-failed-${m.id}`,
|
|
2212
2233
|
...(activeSelfContextId ? { selfContextId: activeSelfContextId } : {}),
|
|
2213
2234
|
metadata: {
|
|
2214
2235
|
turnSemantics: 'turn_complete',
|
|
@@ -2273,6 +2294,11 @@ export async function main() {
|
|
|
2273
2294
|
// ── Control signal watcher ──
|
|
2274
2295
|
const pollControl = async () => {
|
|
2275
2296
|
while (!controlStopped) {
|
|
2297
|
+
const hadActiveWork = [...sessions.values()].some((session) => !session.closed
|
|
2298
|
+
&& (session.state.state === 'running'
|
|
2299
|
+
|| session.state.state === 'requires_action'
|
|
2300
|
+
|| session.pendingInputs.length > 0
|
|
2301
|
+
|| Boolean(session.pendingFinalDelivery)));
|
|
2276
2302
|
for (const convoId of [...sessions.keys()]) {
|
|
2277
2303
|
try {
|
|
2278
2304
|
// Session control signals (model, mode)
|
|
@@ -2317,7 +2343,7 @@ export async function main() {
|
|
|
2317
2343
|
const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
|
|
2318
2344
|
if (nextEffort && nextEffort !== session.state.effort) {
|
|
2319
2345
|
try {
|
|
2320
|
-
await session.query
|
|
2346
|
+
await applyClaudeEffortLevel(session.query, nextEffort);
|
|
2321
2347
|
session.state.effort = nextEffort;
|
|
2322
2348
|
session.clearRuntimeControlError('effort');
|
|
2323
2349
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
|
|
@@ -2368,7 +2394,7 @@ export async function main() {
|
|
|
2368
2394
|
session.pendingFinalText = null;
|
|
2369
2395
|
session.turnState = 'interrupted';
|
|
2370
2396
|
session.clearStreaming().catch(() => { });
|
|
2371
|
-
|
|
2397
|
+
typingSignals.clear(convoId).catch(() => { });
|
|
2372
2398
|
runtimeState.writeTurnState(convoId, {
|
|
2373
2399
|
turnId: session.currentTurnId,
|
|
2374
2400
|
state: 'interrupted',
|
|
@@ -2395,7 +2421,7 @@ export async function main() {
|
|
|
2395
2421
|
// Transient RTDB errors — ignore and retry next cycle
|
|
2396
2422
|
}
|
|
2397
2423
|
}
|
|
2398
|
-
await new Promise((r) => setTimeout(r,
|
|
2424
|
+
await new Promise((r) => setTimeout(r, controlPollDelayMs(hadActiveWork)));
|
|
2399
2425
|
}
|
|
2400
2426
|
};
|
|
2401
2427
|
pollControl().catch((err) => console.error('[canon-host] Control poll crashed:', err));
|
package/dist/session-state.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.16",
|
|
4
4
|
"description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "^0.2.141",
|
|
34
|
-
"@canonmsg/agent-sdk": "^2.
|
|
35
|
-
"@canonmsg/core": "^1.
|
|
34
|
+
"@canonmsg/agent-sdk": "^2.1.1",
|
|
35
|
+
"@canonmsg/core": "^1.3.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|