@canonmsg/claude-code-plugin 0.12.8 → 0.13.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 +57 -36
- package/dist/session-state.d.ts +15 -1
- package/dist/session-state.js +11 -0
- package/package.json +3 -3
package/dist/host.js
CHANGED
|
@@ -26,11 +26,11 @@ 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, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
|
|
29
|
-
import { CLAUDE_PERMISSION_MODE_OPTIONS, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, 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_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonHostPrompt, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, createConversationMetadataLoader, createRuntimeStatePublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, 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';
|
|
30
30
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
31
31
|
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
32
32
|
import { runCli } from './cli-entry.js';
|
|
33
|
-
import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError, mergeClaudeModelOptions, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
33
|
+
import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError, mergeClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
34
34
|
const HELP = `canon-claude — run a local Claude Code agent host for Canon
|
|
35
35
|
|
|
36
36
|
USAGE
|
|
@@ -154,26 +154,9 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
154
154
|
availability: ['always'],
|
|
155
155
|
dispatch: { kind: 'open_details', target: 'effort' },
|
|
156
156
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
description: 'Interrupt the current Claude turn.',
|
|
161
|
-
aliases: ['stop'],
|
|
162
|
-
category: 'turn',
|
|
163
|
-
placements: ['composer_slash', 'command_palette'],
|
|
164
|
-
availability: ['busy'],
|
|
165
|
-
dispatch: { kind: 'signal', signal: 'interrupt' },
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: 'stop-and-clear-queue',
|
|
169
|
-
label: 'Stop & clear queue',
|
|
170
|
-
description: 'Interrupt the current Claude turn and drop queued Canon messages.',
|
|
171
|
-
aliases: ['stop-clear', 'clear-queue'],
|
|
172
|
-
category: 'turn',
|
|
173
|
-
placements: ['composer_slash', 'command_palette', 'session_strip'],
|
|
174
|
-
availability: ['busy_with_queue'],
|
|
175
|
-
dispatch: { kind: 'signal', signal: 'stop_and_drop' },
|
|
176
|
-
},
|
|
157
|
+
RUNTIME_STOP_ACTION,
|
|
158
|
+
RUNTIME_STOP_AND_DROP_ACTION,
|
|
159
|
+
RUNTIME_NEW_SESSION_ACTION,
|
|
177
160
|
],
|
|
178
161
|
});
|
|
179
162
|
}
|
|
@@ -822,15 +805,9 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
822
805
|
if (session.closed)
|
|
823
806
|
return;
|
|
824
807
|
clearFinalDeliveryTimer();
|
|
825
|
-
session
|
|
826
|
-
session.currentTurnOpenedAt = null;
|
|
827
|
-
session.lastAcceptedIntent = null;
|
|
828
|
-
session.pendingFinalText = null;
|
|
829
|
-
session.pendingFinalDelivery = null;
|
|
830
|
-
session.activeInput = null;
|
|
831
|
-
session.toolInProgress = false;
|
|
832
|
-
session.turnState = 'idle';
|
|
808
|
+
resetClaudeCompletedTurnState(session);
|
|
833
809
|
stopVisibleWorkSignal();
|
|
810
|
+
writeState();
|
|
834
811
|
writeTurn();
|
|
835
812
|
drainPendingInput();
|
|
836
813
|
}
|
|
@@ -1570,19 +1547,20 @@ export async function main() {
|
|
|
1570
1547
|
isOwner: input.isOwner,
|
|
1571
1548
|
});
|
|
1572
1549
|
}
|
|
1573
|
-
function closeSession(conversationId) {
|
|
1550
|
+
function closeSession(conversationId, options = {}) {
|
|
1574
1551
|
const session = sessions.get(conversationId);
|
|
1575
1552
|
if (!session)
|
|
1576
1553
|
return;
|
|
1554
|
+
const saveResume = options.saveResume !== false;
|
|
1577
1555
|
session.closed = true;
|
|
1578
1556
|
if (session.typingKeepaliveTimer) {
|
|
1579
1557
|
clearInterval(session.typingKeepaliveTimer);
|
|
1580
1558
|
session.typingKeepaliveTimer = null;
|
|
1581
1559
|
}
|
|
1582
1560
|
releaseConversationEnvironment(session.environment);
|
|
1583
|
-
|
|
1584
|
-
if (session.sdkSessionId) {
|
|
1585
|
-
savedSessionIds.set(
|
|
1561
|
+
const resumeKey = buildConversationEnvironmentKey(conversationId, session.environment.baseCwd);
|
|
1562
|
+
if (saveResume && session.sdkSessionId) {
|
|
1563
|
+
savedSessionIds.set(resumeKey, session.sdkSessionId);
|
|
1586
1564
|
saveRuntimeSessionState(runtimeId, {
|
|
1587
1565
|
conversationId,
|
|
1588
1566
|
baseCwd: session.environment.baseCwd,
|
|
@@ -1590,6 +1568,27 @@ export async function main() {
|
|
|
1590
1568
|
claudeSessionId: session.sdkSessionId,
|
|
1591
1569
|
});
|
|
1592
1570
|
}
|
|
1571
|
+
else {
|
|
1572
|
+
const existingState = loadRuntimeSessionState(runtimeId, {
|
|
1573
|
+
conversationId,
|
|
1574
|
+
baseCwd: session.environment.baseCwd,
|
|
1575
|
+
executionMode: session.environment.mode,
|
|
1576
|
+
});
|
|
1577
|
+
savedSessionIds.delete(resumeKey);
|
|
1578
|
+
clearRuntimeSessionState(runtimeId, {
|
|
1579
|
+
conversationId,
|
|
1580
|
+
baseCwd: session.environment.baseCwd,
|
|
1581
|
+
executionMode: session.environment.mode,
|
|
1582
|
+
});
|
|
1583
|
+
if (existingState?.lastInboundMessageId) {
|
|
1584
|
+
saveRuntimeSessionState(runtimeId, {
|
|
1585
|
+
conversationId,
|
|
1586
|
+
baseCwd: session.environment.baseCwd,
|
|
1587
|
+
executionMode: session.environment.mode,
|
|
1588
|
+
lastInboundMessageId: existingState.lastInboundMessageId,
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1593
1592
|
if (session.streamingTimer)
|
|
1594
1593
|
clearTimeout(session.streamingTimer);
|
|
1595
1594
|
session.streamingText = '';
|
|
@@ -1610,7 +1609,22 @@ export async function main() {
|
|
|
1610
1609
|
}
|
|
1611
1610
|
catch { }
|
|
1612
1611
|
sessions.delete(conversationId);
|
|
1613
|
-
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Session suspended (sessionId saved for resume)`);
|
|
1612
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Session ${options.reason ?? (saveResume ? 'suspended (sessionId saved for resume)' : 'cleared')}`);
|
|
1613
|
+
}
|
|
1614
|
+
async function resetRuntimeSession(session) {
|
|
1615
|
+
const conversationId = session.conversationId;
|
|
1616
|
+
const droppedInputs = session.pendingInputs.splice(0);
|
|
1617
|
+
await markSessionPendingInputsRejected(conversationId, droppedInputs);
|
|
1618
|
+
if (session.activeInput) {
|
|
1619
|
+
session.interruptedTurnKeys.add(session.activeInput.turnKey);
|
|
1620
|
+
}
|
|
1621
|
+
session.pendingFinalText = null;
|
|
1622
|
+
session.pendingFinalDelivery = null;
|
|
1623
|
+
session.turnState = 'interrupted';
|
|
1624
|
+
if (session.state.state === 'running') {
|
|
1625
|
+
await session.query.interrupt().catch(() => { });
|
|
1626
|
+
}
|
|
1627
|
+
closeSession(conversationId, { saveResume: false, reason: 'reset for new Canon session' });
|
|
1614
1628
|
}
|
|
1615
1629
|
async function markSessionPendingInputsRejected(conversationId, inputs) {
|
|
1616
1630
|
await Promise.all(inputs.map((input) => {
|
|
@@ -2054,10 +2068,17 @@ export async function main() {
|
|
|
2054
2068
|
if (sig && typeof sig === 'object') {
|
|
2055
2069
|
const s = sig;
|
|
2056
2070
|
const ts = s.updatedAt ?? 0;
|
|
2057
|
-
if ((s.type === 'interrupt' || s.type === 'stop_and_drop'
|
|
2071
|
+
if ((s.type === 'interrupt' || s.type === 'stop_and_drop' || s.type === 'new_session')
|
|
2072
|
+
&& ts > (lastSeenSignal.get(convoId) ?? 0)) {
|
|
2058
2073
|
lastSeenSignal.set(convoId, ts);
|
|
2059
2074
|
const session = sessions.get(convoId);
|
|
2060
2075
|
if (session && !session.closed) {
|
|
2076
|
+
if (s.type === 'new_session') {
|
|
2077
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] new_session signal`);
|
|
2078
|
+
await resetRuntimeSession(session);
|
|
2079
|
+
await rtdbWrite(`/control/${convoId}/${agentId}/signal`, null).catch(() => { });
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2061
2082
|
const isActive = session.state.state === 'running';
|
|
2062
2083
|
if (!isActive && (s.type !== 'stop_and_drop' || session.pendingInputs.length === 0)) {
|
|
2063
2084
|
await rtdbWrite(`/control/${convoId}/${agentId}/signal`, null).catch(() => { });
|
package/dist/session-state.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import type { DeliveryIntent, ModelOption } from '@canonmsg/core';
|
|
2
|
+
import type { DeliveryIntent, ModelOption, TurnLifecycleState } from '@canonmsg/core';
|
|
3
3
|
export type ClaudeInputKind = 'seed' | 'canon';
|
|
4
4
|
export interface ClaudeInputEnvelope {
|
|
5
5
|
kind: ClaudeInputKind;
|
|
@@ -19,6 +19,19 @@ export interface ClaudePendingFinalDelivery {
|
|
|
19
19
|
text: string;
|
|
20
20
|
messageId: string;
|
|
21
21
|
}
|
|
22
|
+
export interface ClaudeCompletedTurnState {
|
|
23
|
+
state: {
|
|
24
|
+
state?: 'idle' | 'running' | 'requires_action';
|
|
25
|
+
};
|
|
26
|
+
pendingFinalText: string | null;
|
|
27
|
+
pendingFinalDelivery: ClaudePendingFinalDelivery | null;
|
|
28
|
+
activeInput: ClaudeInputEnvelope | null;
|
|
29
|
+
toolInProgress: boolean;
|
|
30
|
+
turnState: TurnLifecycleState;
|
|
31
|
+
currentTurnId: string | null;
|
|
32
|
+
currentTurnOpenedAt: number | null;
|
|
33
|
+
lastAcceptedIntent: DeliveryIntent | null;
|
|
34
|
+
}
|
|
22
35
|
export declare function createClaudeInputEnvelope(input: {
|
|
23
36
|
kind: ClaudeInputKind;
|
|
24
37
|
msg: SDKUserMessage;
|
|
@@ -36,6 +49,7 @@ export declare function shouldDeliverClaudeFinal(input: {
|
|
|
36
49
|
finalizedTurnKeys: ReadonlySet<string>;
|
|
37
50
|
interruptedTurnKeys: ReadonlySet<string>;
|
|
38
51
|
}): boolean;
|
|
52
|
+
export declare function resetClaudeCompletedTurnState(session: ClaudeCompletedTurnState): void;
|
|
39
53
|
export declare function mergeClaudeModelOptions(input: {
|
|
40
54
|
previous: ReadonlyArray<ModelOption>;
|
|
41
55
|
next: ReadonlyArray<ModelOption>;
|
package/dist/session-state.js
CHANGED
|
@@ -29,6 +29,17 @@ export function shouldDeliverClaudeFinal(input) {
|
|
|
29
29
|
return false;
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
32
|
+
export function resetClaudeCompletedTurnState(session) {
|
|
33
|
+
session.state.state = 'idle';
|
|
34
|
+
session.currentTurnId = null;
|
|
35
|
+
session.currentTurnOpenedAt = null;
|
|
36
|
+
session.lastAcceptedIntent = null;
|
|
37
|
+
session.pendingFinalText = null;
|
|
38
|
+
session.pendingFinalDelivery = null;
|
|
39
|
+
session.activeInput = null;
|
|
40
|
+
session.toolInProgress = false;
|
|
41
|
+
session.turnState = 'idle';
|
|
42
|
+
}
|
|
32
43
|
export function mergeClaudeModelOptions(input) {
|
|
33
44
|
const byValue = new Map();
|
|
34
45
|
for (const option of input.previous) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
34
|
-
"@canonmsg/agent-sdk": "^1.
|
|
35
|
-
"@canonmsg/core": "^0.
|
|
34
|
+
"@canonmsg/agent-sdk": "^1.2.0",
|
|
35
|
+
"@canonmsg/core": "^0.16.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|