@canonmsg/claude-code-plugin 0.10.0 → 0.12.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/README.md +13 -4
- package/dist/host.js +230 -13
- package/dist/server.js +21 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ Send a message to your agent in the Canon app. Claude Code responds with full ca
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
22
|
- **Two-way messaging** — Messages from Canon flow to Claude Code and back
|
|
23
|
-
- **Session controls** —
|
|
24
|
-
- **Live
|
|
23
|
+
- **Session controls** — Canon renders setup and live controls from the runtime descriptor Claude publishes
|
|
24
|
+
- **Live preview** — See Claude's current live preview/status in the app
|
|
25
25
|
- **Interrupt** — Stop Claude mid-response from the app
|
|
26
26
|
- **Context meter** — See context window usage in the app
|
|
27
27
|
- **Max plan auth** — Uses your Claude subscription, no API key billing
|
|
@@ -35,10 +35,19 @@ canon-claude --cwd /path/to/project
|
|
|
35
35
|
Advertise multiple project choices to the Canon app:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
canon-claude --cwd ~/dev --workspace ~/dev
|
|
38
|
+
canon-claude --cwd ~/dev --workspace-root ~/dev
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
`--cwd` is the default workspace. Each `--workspace` value
|
|
41
|
+
`--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
|
|
42
|
+
|
|
43
|
+
Current Canon truth for Claude host mode:
|
|
44
|
+
|
|
45
|
+
- model is live-editable
|
|
46
|
+
- thinking level / effort is live-editable when the host reports it
|
|
47
|
+
- workspace selection is setup-only
|
|
48
|
+
- execution mode selection is setup-only
|
|
49
|
+
- permission modes are rendered exactly as the Claude runtime reports them; Canon does not flatten them to a smaller hardcoded set
|
|
50
|
+
- if worktree creation is unavailable for the selected project, Canon may fall back to shared-project execution and surface the fallback reason in session details
|
|
42
51
|
|
|
43
52
|
## Multiple agents
|
|
44
53
|
|
package/dist/host.js
CHANGED
|
@@ -26,18 +26,22 @@ 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,
|
|
29
|
+
import { CLAUDE_PERMISSION_MODE_OPTIONS, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildPlanApprovalRequest, buildQuestionRequest, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, getActiveProfile, releaseLock, initRTDBAuth, rtdbRead, rtdbWrite, writeRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, releaseConversationEnvironment, } from '@canonmsg/core';
|
|
30
30
|
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
31
31
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
32
32
|
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
33
33
|
import { runCli } from './cli-entry.js';
|
|
34
34
|
let workingDir = process.cwd();
|
|
35
35
|
let workspaceOptions = [];
|
|
36
|
+
let workspaceRoots = [];
|
|
37
|
+
let workspaceRootMetadata = [];
|
|
36
38
|
let runtimeModels = [];
|
|
37
39
|
const CLAUDE_METADATA_TTL_MS = 5 * 60 * 1000;
|
|
38
40
|
let runtimeMetadata = {
|
|
39
41
|
account: null,
|
|
40
42
|
mcpServers: [],
|
|
43
|
+
commands: [],
|
|
44
|
+
agents: [],
|
|
41
45
|
plugins: [],
|
|
42
46
|
loadedAt: 0,
|
|
43
47
|
};
|
|
@@ -56,25 +60,32 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
56
60
|
},
|
|
57
61
|
{
|
|
58
62
|
id: 'workspace',
|
|
59
|
-
label: '
|
|
63
|
+
label: 'Project',
|
|
60
64
|
options: input.workspaces.map((workspace) => ({
|
|
61
65
|
value: workspace.id,
|
|
62
66
|
label: workspace.label,
|
|
67
|
+
...(workspace.description ? { description: workspace.description } : {}),
|
|
68
|
+
...(workspace.workspaceRootId ? { workspaceRootId: workspace.workspaceRootId } : {}),
|
|
69
|
+
...(workspace.workspaceRelativePath ? { workspaceRelativePath: workspace.workspaceRelativePath } : {}),
|
|
70
|
+
...(workspace.source ? { source: workspace.source } : {}),
|
|
63
71
|
})),
|
|
64
72
|
defaultValue: input.workspaces[0]?.id ?? null,
|
|
65
73
|
availability: 'setup',
|
|
66
74
|
liveBehavior: 'none',
|
|
67
75
|
selectionPolicy: 'inherit',
|
|
76
|
+
description: input.workspaceRoots?.length
|
|
77
|
+
? 'Choose one of the projects discovered inside the approved local roots for this host.'
|
|
78
|
+
: 'Choose one of the local projects advertised by this host.',
|
|
68
79
|
},
|
|
69
80
|
{
|
|
70
81
|
id: 'executionMode',
|
|
71
82
|
label: 'Execution mode',
|
|
72
83
|
options: input.executionModes.map((mode) => ({
|
|
73
84
|
value: mode,
|
|
74
|
-
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared
|
|
85
|
+
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared project',
|
|
75
86
|
description: mode === 'worktree'
|
|
76
|
-
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected
|
|
77
|
-
: 'Runs directly in the selected
|
|
87
|
+
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected project is a git repo.'
|
|
88
|
+
: 'Runs directly in the selected project folder. Changes happen there.',
|
|
78
89
|
})),
|
|
79
90
|
defaultValue: null,
|
|
80
91
|
availability: 'setup',
|
|
@@ -106,6 +117,94 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
106
117
|
selectionPolicy: 'inherit',
|
|
107
118
|
},
|
|
108
119
|
],
|
|
120
|
+
actions: [
|
|
121
|
+
{
|
|
122
|
+
id: 'plan',
|
|
123
|
+
label: 'Plan first',
|
|
124
|
+
description: 'Switch Claude into Plan mode. Text after /plan is kept as a ready prompt while the mode applies.',
|
|
125
|
+
aliases: ['plan'],
|
|
126
|
+
category: 'plan',
|
|
127
|
+
placements: ['composer_slash', 'command_palette'],
|
|
128
|
+
availability: ['always'],
|
|
129
|
+
trailingTextBehavior: 'send_as_prompt',
|
|
130
|
+
dispatch: {
|
|
131
|
+
kind: 'control',
|
|
132
|
+
controlId: 'permissionMode',
|
|
133
|
+
value: 'plan',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'runtime-status',
|
|
138
|
+
label: 'Runtime status',
|
|
139
|
+
description: 'Open Claude runtime details, including account, project, and policy status.',
|
|
140
|
+
aliases: ['status'],
|
|
141
|
+
category: 'details',
|
|
142
|
+
placements: ['composer_slash', 'command_palette'],
|
|
143
|
+
availability: ['always'],
|
|
144
|
+
dispatch: { kind: 'open_details', target: 'status' },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'mcp-inventory',
|
|
148
|
+
label: 'MCP servers',
|
|
149
|
+
description: 'Open the Claude MCP inventory published by the host.',
|
|
150
|
+
aliases: ['mcp'],
|
|
151
|
+
category: 'details',
|
|
152
|
+
placements: ['composer_slash', 'command_palette'],
|
|
153
|
+
availability: ['always'],
|
|
154
|
+
dispatch: { kind: 'open_details', target: 'mcp' },
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'plugin-inventory',
|
|
158
|
+
label: 'Plugins',
|
|
159
|
+
description: 'Open the Claude plugin and command inventory published by the host.',
|
|
160
|
+
aliases: ['plugins'],
|
|
161
|
+
category: 'details',
|
|
162
|
+
placements: ['composer_slash', 'command_palette'],
|
|
163
|
+
availability: ['always'],
|
|
164
|
+
dispatch: { kind: 'open_details', target: 'plugins' },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'model-details',
|
|
168
|
+
label: 'Model control',
|
|
169
|
+
description: 'Open runtime details and use the live Model control.',
|
|
170
|
+
aliases: ['model'],
|
|
171
|
+
category: 'details',
|
|
172
|
+
placements: ['composer_slash', 'command_palette'],
|
|
173
|
+
availability: ['always'],
|
|
174
|
+
dispatch: { kind: 'open_details', target: 'model' },
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'permission-details',
|
|
178
|
+
label: 'Permission mode',
|
|
179
|
+
description: 'Open runtime details and use the live Permission mode control.',
|
|
180
|
+
aliases: ['permission'],
|
|
181
|
+
category: 'details',
|
|
182
|
+
placements: ['composer_slash', 'command_palette'],
|
|
183
|
+
availability: ['always'],
|
|
184
|
+
dispatch: { kind: 'open_details', target: 'permissionMode' },
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'effort-details',
|
|
188
|
+
label: 'Thinking level',
|
|
189
|
+
description: 'Open runtime details and use the live Thinking level control.',
|
|
190
|
+
aliases: ['effort'],
|
|
191
|
+
category: 'details',
|
|
192
|
+
placements: ['composer_slash', 'command_palette'],
|
|
193
|
+
availability: ['always'],
|
|
194
|
+
dispatch: { kind: 'open_details', target: 'effort' },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'stop',
|
|
198
|
+
label: 'Stop',
|
|
199
|
+
description: 'Interrupt the current Claude turn.',
|
|
200
|
+
aliases: ['stop'],
|
|
201
|
+
category: 'turn',
|
|
202
|
+
placements: ['composer_slash', 'command_palette'],
|
|
203
|
+
availability: ['busy'],
|
|
204
|
+
dispatch: { kind: 'signal', signal: 'interrupt' },
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
workspaceRoots: input.workspaceRoots,
|
|
109
208
|
supportsInterrupt: true,
|
|
110
209
|
streamingTextMode: 'delta',
|
|
111
210
|
};
|
|
@@ -172,11 +271,10 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
172
271
|
effort: normalizeClaudeEffortLevel(config?.effort),
|
|
173
272
|
};
|
|
174
273
|
}
|
|
175
|
-
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
176
|
-
// UI still lets owners flip to 'worktree'; this just stops sessions from
|
|
177
|
-
// failing closed when the mode has never been written.
|
|
178
274
|
function resolveSessionExecutionMode(config) {
|
|
179
|
-
|
|
275
|
+
if (config?.executionMode)
|
|
276
|
+
return config.executionMode;
|
|
277
|
+
throw new ExecutionEnvironmentError('Session config is missing an execution mode.', 'Choose Isolated worktree or Use shared project before starting this coding session.');
|
|
180
278
|
}
|
|
181
279
|
function resolveWorkspaceCwd(config) {
|
|
182
280
|
return resolveHostWorkspaceCwd({
|
|
@@ -245,6 +343,8 @@ async function inspectClaudeRuntimeMetadata(cwd, activeQuery) {
|
|
|
245
343
|
return {
|
|
246
344
|
account: init.account ?? null,
|
|
247
345
|
mcpServers: pluginInfo?.mcpServers ?? mcpServers,
|
|
346
|
+
commands: pluginInfo?.commands ?? init.commands ?? [],
|
|
347
|
+
agents: pluginInfo?.agents ?? init.agents ?? [],
|
|
248
348
|
plugins: pluginInfo?.plugins ?? [],
|
|
249
349
|
loadedAt: Date.now(),
|
|
250
350
|
};
|
|
@@ -369,10 +469,32 @@ function buildClaudeRuntimeInfo(input) {
|
|
|
369
469
|
description: plugin.source ?? plugin.path,
|
|
370
470
|
})),
|
|
371
471
|
},
|
|
472
|
+
{
|
|
473
|
+
id: 'commands',
|
|
474
|
+
label: 'Commands',
|
|
475
|
+
entries: metadata.commands.map((command) => ({
|
|
476
|
+
id: command.name,
|
|
477
|
+
label: command.name.startsWith('/') ? command.name : `/${command.name}`,
|
|
478
|
+
status: 'configured',
|
|
479
|
+
description: command.description ?? command.argumentHint ?? undefined,
|
|
480
|
+
})),
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
id: 'agents',
|
|
484
|
+
label: 'Agents',
|
|
485
|
+
entries: metadata.agents.map((agent) => ({
|
|
486
|
+
id: agent.name,
|
|
487
|
+
label: agent.name,
|
|
488
|
+
status: 'configured',
|
|
489
|
+
description: agent.description ?? agent.model ?? undefined,
|
|
490
|
+
})),
|
|
491
|
+
},
|
|
372
492
|
],
|
|
373
493
|
execution: {
|
|
374
494
|
resolvedWorkspaceLabel: input.workspaceLabel,
|
|
375
495
|
resolvedCwd: input.resolvedCwd,
|
|
496
|
+
workspaceRootId: input.workspaceRootId ?? null,
|
|
497
|
+
workspaceRelativePath: input.workspaceRelativePath ?? null,
|
|
376
498
|
executionMode: input.executionMode,
|
|
377
499
|
executionBranch: input.executionBranch,
|
|
378
500
|
worktreePath: input.worktreePath,
|
|
@@ -495,6 +617,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
495
617
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
496
618
|
tools: { type: 'preset', preset: 'claude_code' },
|
|
497
619
|
permissionMode: config.permissionMode,
|
|
620
|
+
allowDangerouslySkipPermissions: true,
|
|
498
621
|
cwd,
|
|
499
622
|
includePartialMessages: true,
|
|
500
623
|
maxTurns: 200,
|
|
@@ -534,7 +657,30 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
534
657
|
if (toolName === 'ExitPlanMode') {
|
|
535
658
|
try {
|
|
536
659
|
const planId = randomUUID();
|
|
537
|
-
const
|
|
660
|
+
const allowedPrompts = Array.isArray(input.allowedPrompts)
|
|
661
|
+
? input.allowedPrompts
|
|
662
|
+
.map((entry) => {
|
|
663
|
+
if (!entry || typeof entry !== 'object') {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
const prompt = entry.prompt;
|
|
667
|
+
const tool = entry.tool;
|
|
668
|
+
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
tool: typeof tool === 'string' && tool.trim() ? tool : 'Tool',
|
|
673
|
+
prompt,
|
|
674
|
+
};
|
|
675
|
+
})
|
|
676
|
+
.filter((entry) => Boolean(entry))
|
|
677
|
+
: [];
|
|
678
|
+
const planApproval = buildPlanApprovalRequest(planId, 'Plan ready for review.', allowedPrompts.length > 0 ? {
|
|
679
|
+
summary: 'Claude has prepared a plan and is asking permission to continue.',
|
|
680
|
+
allowedPrompts,
|
|
681
|
+
} : {
|
|
682
|
+
summary: 'Claude has prepared a plan and is asking permission to continue.',
|
|
683
|
+
});
|
|
538
684
|
await client.sendMessage(conversationId, planApproval.text, {
|
|
539
685
|
metadata: {
|
|
540
686
|
...planApproval.metadata,
|
|
@@ -594,6 +740,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
594
740
|
currentTurnOpenedAt: null,
|
|
595
741
|
lastAcceptedIntent: null,
|
|
596
742
|
lastActivity: Date.now(),
|
|
743
|
+
typingKeepaliveTimer: null,
|
|
597
744
|
closed: false,
|
|
598
745
|
sdkSessionId: resumeSessionId || null,
|
|
599
746
|
pendingReply: null,
|
|
@@ -650,6 +797,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
650
797
|
session.pendingFinalDeliveryText = null;
|
|
651
798
|
session.toolInProgress = false;
|
|
652
799
|
session.turnState = 'idle';
|
|
800
|
+
stopVisibleWorkSignal();
|
|
653
801
|
writeTurn();
|
|
654
802
|
drainPendingInput();
|
|
655
803
|
}
|
|
@@ -668,6 +816,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
668
816
|
session.pendingFinalDeliveryText = finalText;
|
|
669
817
|
session.state.state = 'running';
|
|
670
818
|
session.turnState = 'streaming';
|
|
819
|
+
stopVisibleWorkSignal();
|
|
671
820
|
streamingBuffer.replace(finalText);
|
|
672
821
|
writeState();
|
|
673
822
|
writeTurn();
|
|
@@ -719,6 +868,9 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
719
868
|
executionMode: session.environment.mode,
|
|
720
869
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
721
870
|
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
871
|
+
...(resolveExecutionFallbackReason(session.environment)
|
|
872
|
+
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
873
|
+
: {}),
|
|
722
874
|
hostMode: true,
|
|
723
875
|
isActive: true,
|
|
724
876
|
}).catch(() => { });
|
|
@@ -737,6 +889,25 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
737
889
|
: {}),
|
|
738
890
|
}).catch(() => { });
|
|
739
891
|
}
|
|
892
|
+
function refreshVisibleWorkSignal() {
|
|
893
|
+
if (session.closed)
|
|
894
|
+
return;
|
|
895
|
+
if (session.turnState !== 'thinking' && session.turnState !== 'tool')
|
|
896
|
+
return;
|
|
897
|
+
client.setTyping(conversationId, true, 'thinking').catch(() => { });
|
|
898
|
+
}
|
|
899
|
+
function startVisibleWorkSignal() {
|
|
900
|
+
refreshVisibleWorkSignal();
|
|
901
|
+
if (session.typingKeepaliveTimer)
|
|
902
|
+
return;
|
|
903
|
+
session.typingKeepaliveTimer = setInterval(refreshVisibleWorkSignal, 3500);
|
|
904
|
+
}
|
|
905
|
+
function stopVisibleWorkSignal() {
|
|
906
|
+
if (session.typingKeepaliveTimer) {
|
|
907
|
+
clearInterval(session.typingKeepaliveTimer);
|
|
908
|
+
session.typingKeepaliveTimer = null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
740
911
|
async function markQueuedMessageAccepted(sourceMessageId, markAccepted) {
|
|
741
912
|
if (!markAccepted || !sourceMessageId)
|
|
742
913
|
return;
|
|
@@ -768,6 +939,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
768
939
|
if (session.state.state === 'running' && intent === 'interrupt') {
|
|
769
940
|
session.pendingInputs.unshift({ msg, intent, sourceMessageId, markAccepted });
|
|
770
941
|
session.turnState = 'interrupted';
|
|
942
|
+
stopVisibleWorkSignal();
|
|
771
943
|
writeTurn();
|
|
772
944
|
void session.query.interrupt().catch(() => { });
|
|
773
945
|
return;
|
|
@@ -808,18 +980,22 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
808
980
|
if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
|
|
809
981
|
session.toolInProgress = true;
|
|
810
982
|
session.turnState = 'tool';
|
|
983
|
+
startVisibleWorkSignal();
|
|
811
984
|
writeTurn();
|
|
812
985
|
}
|
|
813
986
|
else if (event?.type === 'content_block_stop' && session.toolInProgress) {
|
|
814
987
|
session.toolInProgress = false;
|
|
815
988
|
if (session.state.state === 'running') {
|
|
816
989
|
session.turnState = 'thinking';
|
|
990
|
+
startVisibleWorkSignal();
|
|
817
991
|
writeTurn();
|
|
818
992
|
}
|
|
819
993
|
}
|
|
820
994
|
else if (event?.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
|
|
821
995
|
session.toolInProgress = false;
|
|
822
996
|
session.turnState = 'streaming';
|
|
997
|
+
stopVisibleWorkSignal();
|
|
998
|
+
client.setTyping(conversationId, false).catch(() => { });
|
|
823
999
|
writeTurn();
|
|
824
1000
|
onStreamDelta(event.delta.text);
|
|
825
1001
|
}
|
|
@@ -840,9 +1016,10 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
840
1016
|
session.pendingFinalText = null;
|
|
841
1017
|
session.toolInProgress = false;
|
|
842
1018
|
session.turnState = 'thinking';
|
|
843
|
-
|
|
1019
|
+
startVisibleWorkSignal();
|
|
844
1020
|
}
|
|
845
1021
|
else if (state === 'idle') {
|
|
1022
|
+
stopVisibleWorkSignal();
|
|
846
1023
|
client.setTyping(conversationId, false).catch(() => { });
|
|
847
1024
|
}
|
|
848
1025
|
else if (state === 'requires_action') {
|
|
@@ -853,6 +1030,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
853
1030
|
}
|
|
854
1031
|
session.toolInProgress = false;
|
|
855
1032
|
session.turnState = 'waiting_input';
|
|
1033
|
+
stopVisibleWorkSignal();
|
|
856
1034
|
client.setTyping(conversationId, false).catch(() => { });
|
|
857
1035
|
}
|
|
858
1036
|
writeState();
|
|
@@ -952,6 +1130,12 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
952
1130
|
availableModels: modelList,
|
|
953
1131
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
954
1132
|
availableExecutionModes: config.availableExecutionModes,
|
|
1133
|
+
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1134
|
+
models: modelList,
|
|
1135
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1136
|
+
workspaceRoots: workspaceRootMetadata,
|
|
1137
|
+
executionModes: config.availableExecutionModes,
|
|
1138
|
+
}),
|
|
955
1139
|
});
|
|
956
1140
|
writeSessionState(conversationId, agentId, {
|
|
957
1141
|
...session.state,
|
|
@@ -960,6 +1144,9 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
960
1144
|
executionMode: session.environment.mode,
|
|
961
1145
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
962
1146
|
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1147
|
+
...(resolveExecutionFallbackReason(session.environment)
|
|
1148
|
+
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
1149
|
+
: {}),
|
|
963
1150
|
hostMode: true,
|
|
964
1151
|
isActive: true,
|
|
965
1152
|
}).catch(() => { });
|
|
@@ -981,12 +1168,24 @@ export async function main() {
|
|
|
981
1168
|
options: {
|
|
982
1169
|
cwd: { type: 'string' },
|
|
983
1170
|
workspace: { type: 'string', multiple: true },
|
|
1171
|
+
'workspace-root': { type: 'string', multiple: true },
|
|
984
1172
|
},
|
|
985
1173
|
strict: false,
|
|
986
1174
|
});
|
|
987
1175
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
988
1176
|
const configuredWorkspaces = (args.workspace ?? []).filter((value) => typeof value === 'string');
|
|
989
|
-
|
|
1177
|
+
const configuredWorkspaceRoots = (args['workspace-root'] ?? []).filter((value) => typeof value === 'string');
|
|
1178
|
+
const workspaceDiscovery = buildConfiguredWorkspaceOptionsWithRoots({
|
|
1179
|
+
primaryCwd: workingDir,
|
|
1180
|
+
configuredWorkspaces,
|
|
1181
|
+
workspaceRoots: configuredWorkspaceRoots,
|
|
1182
|
+
});
|
|
1183
|
+
workspaceOptions = workspaceDiscovery.workspaceOptions;
|
|
1184
|
+
workspaceRoots = workspaceDiscovery.workspaceRoots;
|
|
1185
|
+
workspaceRootMetadata = buildPublicWorkspaceRoots(workspaceRoots);
|
|
1186
|
+
for (const warning of workspaceDiscovery.warnings) {
|
|
1187
|
+
console.error(`[canon-host] ${warning}`);
|
|
1188
|
+
}
|
|
990
1189
|
const { apiKey, agentId: profileAgentId, profile } = resolveCanonAgent({ logPrefix: '[canon-host]' });
|
|
991
1190
|
console.error(`[canon-host] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
992
1191
|
// ── Canon API client + RTDB auth ──
|
|
@@ -1023,6 +1222,7 @@ export async function main() {
|
|
|
1023
1222
|
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1024
1223
|
models: runtimeModels,
|
|
1025
1224
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1225
|
+
workspaceRoots: workspaceRootMetadata,
|
|
1026
1226
|
executionModes: hostAvailableExecutionModes,
|
|
1027
1227
|
}),
|
|
1028
1228
|
};
|
|
@@ -1080,11 +1280,14 @@ export async function main() {
|
|
|
1080
1280
|
descriptor: runtimeDescriptor.runtimeDescriptor ?? buildClaudeRuntimeDescriptor({
|
|
1081
1281
|
models: runtimeModels,
|
|
1082
1282
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1283
|
+
workspaceRoots: workspaceRootMetadata,
|
|
1083
1284
|
executionModes: hostAvailableExecutionModes,
|
|
1084
1285
|
}),
|
|
1085
1286
|
metadata,
|
|
1086
1287
|
workspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
1087
1288
|
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
1289
|
+
workspaceRootId: workspace?.workspaceRootId ?? null,
|
|
1290
|
+
workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
|
|
1088
1291
|
executionMode: session?.environment.mode ?? null,
|
|
1089
1292
|
executionBranch: session?.environment.branch ?? null,
|
|
1090
1293
|
worktreePath: session?.environment.worktreePath ?? null,
|
|
@@ -1109,6 +1312,7 @@ export async function main() {
|
|
|
1109
1312
|
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1110
1313
|
models: runtimeModels,
|
|
1111
1314
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1315
|
+
workspaceRoots: workspaceRootMetadata,
|
|
1112
1316
|
executionModes: hostAvailableExecutionModes,
|
|
1113
1317
|
}),
|
|
1114
1318
|
};
|
|
@@ -1123,6 +1327,7 @@ export async function main() {
|
|
|
1123
1327
|
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1124
1328
|
models: runtimeModels,
|
|
1125
1329
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1330
|
+
workspaceRoots: workspaceRootMetadata,
|
|
1126
1331
|
executionModes: hostAvailableExecutionModes,
|
|
1127
1332
|
}),
|
|
1128
1333
|
};
|
|
@@ -1182,6 +1387,10 @@ export async function main() {
|
|
|
1182
1387
|
if (!session)
|
|
1183
1388
|
return;
|
|
1184
1389
|
session.closed = true;
|
|
1390
|
+
if (session.typingKeepaliveTimer) {
|
|
1391
|
+
clearInterval(session.typingKeepaliveTimer);
|
|
1392
|
+
session.typingKeepaliveTimer = null;
|
|
1393
|
+
}
|
|
1185
1394
|
releaseConversationEnvironment(session.environment);
|
|
1186
1395
|
// Save SDK session ID for future resume
|
|
1187
1396
|
if (session.sdkSessionId) {
|
|
@@ -1370,7 +1579,12 @@ export async function main() {
|
|
|
1370
1579
|
const message = error instanceof Error ? error.message : String(error);
|
|
1371
1580
|
const userMessage = error instanceof ExecutionEnvironmentError ? error.userMessage : message;
|
|
1372
1581
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to create session: ${message}`);
|
|
1373
|
-
await client.sendMessage(convoId, `I couldn't start a coding session for this workspace: ${userMessage}
|
|
1582
|
+
await client.sendMessage(convoId, `I couldn't start a coding session for this workspace: ${userMessage}`, {
|
|
1583
|
+
metadata: {
|
|
1584
|
+
turnSemantics: 'turn_complete',
|
|
1585
|
+
turnComplete: true,
|
|
1586
|
+
},
|
|
1587
|
+
}).catch(() => { });
|
|
1374
1588
|
return;
|
|
1375
1589
|
}
|
|
1376
1590
|
const turnMetadata = normalizeTurnMetadata(m.metadata);
|
|
@@ -1467,6 +1681,9 @@ export async function main() {
|
|
|
1467
1681
|
executionMode: session.environment.mode,
|
|
1468
1682
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1469
1683
|
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1684
|
+
...(resolveExecutionFallbackReason(session.environment)
|
|
1685
|
+
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
1686
|
+
: {}),
|
|
1470
1687
|
hostMode: true,
|
|
1471
1688
|
isActive: true,
|
|
1472
1689
|
}).catch(() => { });
|
package/dist/server.js
CHANGED
|
@@ -26,6 +26,10 @@ let agentContext = null;
|
|
|
26
26
|
let approvalManager = null;
|
|
27
27
|
let approvalServer = null;
|
|
28
28
|
const conversationCache = new Map();
|
|
29
|
+
const TURN_COMPLETE_METADATA = {
|
|
30
|
+
turnSemantics: 'turn_complete',
|
|
31
|
+
turnComplete: true,
|
|
32
|
+
};
|
|
29
33
|
/** The conversation that most recently triggered work (for approval routing) */
|
|
30
34
|
let triggerConversationId = null;
|
|
31
35
|
function getPrimaryAttachment(message) {
|
|
@@ -221,7 +225,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
221
225
|
case 'reply': {
|
|
222
226
|
const conversationId = args.conversation_id;
|
|
223
227
|
const text = args.text;
|
|
224
|
-
const result = await client.sendMessage(conversationId, text
|
|
228
|
+
const result = await client.sendMessage(conversationId, text, {
|
|
229
|
+
metadata: TURN_COMPLETE_METADATA,
|
|
230
|
+
});
|
|
225
231
|
return {
|
|
226
232
|
content: [
|
|
227
233
|
{
|
|
@@ -261,6 +267,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
261
267
|
else {
|
|
262
268
|
opts.contentType = contentType;
|
|
263
269
|
}
|
|
270
|
+
opts.metadata = {
|
|
271
|
+
...(opts.metadata ?? {}),
|
|
272
|
+
...TURN_COMPLETE_METADATA,
|
|
273
|
+
};
|
|
264
274
|
const result = await client.sendMessage(conversationId, text, opts);
|
|
265
275
|
return {
|
|
266
276
|
content: [
|
|
@@ -288,6 +298,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
288
298
|
const messageOptions = typeof args.message_options === 'object' && args.message_options != null
|
|
289
299
|
? args.message_options
|
|
290
300
|
: undefined;
|
|
301
|
+
const linkedMessageOptions = messageOptions;
|
|
291
302
|
const result = await client.sendLinkedMessage({
|
|
292
303
|
sourceConversationId,
|
|
293
304
|
targetConversationId,
|
|
@@ -308,11 +319,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
308
319
|
targetContext: targetContext,
|
|
309
320
|
}
|
|
310
321
|
: {}),
|
|
311
|
-
...(
|
|
322
|
+
...(linkedMessageOptions
|
|
312
323
|
? {
|
|
313
|
-
messageOptions:
|
|
324
|
+
messageOptions: {
|
|
325
|
+
...linkedMessageOptions,
|
|
326
|
+
metadata: {
|
|
327
|
+
...(linkedMessageOptions.metadata ?? {}),
|
|
328
|
+
...TURN_COMPLETE_METADATA,
|
|
329
|
+
},
|
|
330
|
+
},
|
|
314
331
|
}
|
|
315
|
-
: {}),
|
|
332
|
+
: { messageOptions: { metadata: TURN_COMPLETE_METADATA } }),
|
|
316
333
|
});
|
|
317
334
|
return {
|
|
318
335
|
content: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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": "^0.9.
|
|
35
|
-
"@canonmsg/core": "^0.
|
|
34
|
+
"@canonmsg/agent-sdk": "^0.9.2",
|
|
35
|
+
"@canonmsg/core": "^0.10.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|