@canonmsg/claude-code-plugin 0.9.5 โ 0.10.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 +8 -0
- package/dist/host-runtime.d.ts +29 -2
- package/dist/host-runtime.js +93 -2
- package/dist/host.js +406 -22
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -32,6 +32,14 @@ Send a message to your agent in the Canon app. Claude Code responds with full ca
|
|
|
32
32
|
canon-claude --cwd /path/to/project
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
Advertise multiple project choices to the Canon app:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
canon-claude --cwd ~/dev --workspace ~/dev/canon --workspace ~/dev/yumyumv2
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`--cwd` is the default workspace. Each `--workspace` value appears as a selectable workspace during session creation. Worktree mode creates a per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-workspace mode runs directly in the selected directory.
|
|
42
|
+
|
|
35
43
|
## Multiple agents
|
|
36
44
|
|
|
37
45
|
```bash
|
package/dist/host-runtime.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ interface HostWorkspaceResolverOption {
|
|
|
37
37
|
id: string;
|
|
38
38
|
cwd: string;
|
|
39
39
|
}
|
|
40
|
+
export declare const HOST_ADMISSION_ACTION_CAPABILITIES: Readonly<{
|
|
41
|
+
canStartDirectConversation: false;
|
|
42
|
+
canSendContactRequest: false;
|
|
43
|
+
canApprovePendingContactRequests: false;
|
|
44
|
+
canRejectPendingContactRequests: false;
|
|
45
|
+
}>;
|
|
40
46
|
export declare function buildCanonHostPrompt(input: {
|
|
41
47
|
hostLabel: string;
|
|
42
48
|
content: string;
|
|
@@ -82,12 +88,33 @@ export declare function buildHydratedInboundContext(input: {
|
|
|
82
88
|
hydratedFromPage: boolean;
|
|
83
89
|
};
|
|
84
90
|
export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
|
|
85
|
-
export declare function
|
|
91
|
+
export declare function publishHostSessionSnapshots(input: {
|
|
92
|
+
conversationIds: string[];
|
|
93
|
+
agentId: string;
|
|
94
|
+
clientType: AgentClientType;
|
|
95
|
+
runtime: AgentRuntime;
|
|
96
|
+
workspaceOptions: HostWorkspaceResolverOption[];
|
|
97
|
+
defaultCwd: string;
|
|
98
|
+
liveSessionConfigByConversation?: ReadonlyMap<string, {
|
|
99
|
+
model?: string;
|
|
100
|
+
permissionMode?: string;
|
|
101
|
+
effort?: string;
|
|
102
|
+
runtimeControlValues?: Record<string, string>;
|
|
103
|
+
workspaceId?: string;
|
|
104
|
+
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
105
|
+
executionBranch?: string | null;
|
|
106
|
+
}>;
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
109
|
+
runtimeControlValues?: Record<string, string>;
|
|
110
|
+
}) | null;
|
|
86
111
|
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
87
112
|
conversationId: string;
|
|
88
113
|
agentId: string;
|
|
89
114
|
extraStringFields?: readonly TExtra[];
|
|
90
|
-
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>>
|
|
115
|
+
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
116
|
+
runtimeControlValues?: Record<string, string>;
|
|
117
|
+
}) | null>;
|
|
91
118
|
export declare function resolveHostWorkspaceCwd(input: {
|
|
92
119
|
workspaceOptions: HostWorkspaceResolverOption[];
|
|
93
120
|
config: {
|
package/dist/host-runtime.js
CHANGED
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
* behavior here, update that copy too and adjust the shared golden
|
|
12
12
|
* fixture test (`packages/claude-code-plugin/src/host-runtime.test.ts`).
|
|
13
13
|
*/
|
|
14
|
-
import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
|
|
14
|
+
import { buildAgentSessionSnapshot, buildConversationWorktreeSpec, buildBehaviorPolicyLines, buildParticipationHistorySnapshot, buildWorkSessionsPromptLines, mergeWorkSessionContexts, normalizeOptionalString, patchAgentSessionSnapshot, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, rtdbRead, rtdbWrite, } from '@canonmsg/core';
|
|
15
|
+
export const HOST_ADMISSION_ACTION_CAPABILITIES = Object.freeze({
|
|
16
|
+
canStartDirectConversation: false,
|
|
17
|
+
canSendContactRequest: false,
|
|
18
|
+
canApprovePendingContactRequests: false,
|
|
19
|
+
canRejectPendingContactRequests: false,
|
|
20
|
+
});
|
|
15
21
|
export function buildCanonHostPrompt(input) {
|
|
16
22
|
const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
|
|
17
23
|
return [
|
|
@@ -70,7 +76,21 @@ function describeContactCard(card) {
|
|
|
70
76
|
if (card.about)
|
|
71
77
|
parts.push(`about: ${card.about}`);
|
|
72
78
|
const identity = `๐ Contact card: "${card.displayName}" (${parts.join(' ยท ')}).`;
|
|
73
|
-
const
|
|
79
|
+
const missingCapabilities = [
|
|
80
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canStartDirectConversation
|
|
81
|
+
? 'start a direct conversation'
|
|
82
|
+
: null,
|
|
83
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canSendContactRequest
|
|
84
|
+
? 'send a contact request'
|
|
85
|
+
: null,
|
|
86
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canApprovePendingContactRequests
|
|
87
|
+
? 'approve pending requests'
|
|
88
|
+
: null,
|
|
89
|
+
!HOST_ADMISSION_ACTION_CAPABILITIES.canRejectPendingContactRequests
|
|
90
|
+
? 'reject pending requests'
|
|
91
|
+
: null,
|
|
92
|
+
].filter(Boolean).join(', ');
|
|
93
|
+
const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
|
|
74
94
|
return `${identity}\n${hint}`;
|
|
75
95
|
}
|
|
76
96
|
function describeAttachment(attachment, materialized) {
|
|
@@ -117,6 +137,70 @@ export async function publishHostAgentRuntime(agentId, clientType, runtime) {
|
|
|
117
137
|
updatedAt: { '.sv': 'timestamp' },
|
|
118
138
|
});
|
|
119
139
|
}
|
|
140
|
+
export async function publishHostSessionSnapshots(input) {
|
|
141
|
+
if (input.conversationIds.length === 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await Promise.all(input.conversationIds.map(async (conversationId) => {
|
|
145
|
+
const persistedConfig = await loadHostSessionConfig({
|
|
146
|
+
conversationId,
|
|
147
|
+
agentId: input.agentId,
|
|
148
|
+
extraStringFields: ['permissionMode', 'effort'],
|
|
149
|
+
});
|
|
150
|
+
const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
|
|
151
|
+
const mergedConfig = {
|
|
152
|
+
...(persistedConfig ?? {}),
|
|
153
|
+
...(liveConfig ?? {}),
|
|
154
|
+
};
|
|
155
|
+
const snapshot = buildAgentSessionSnapshot({
|
|
156
|
+
conversationId,
|
|
157
|
+
agentId: input.agentId,
|
|
158
|
+
runtime: {
|
|
159
|
+
...input.runtime,
|
|
160
|
+
clientType: input.clientType,
|
|
161
|
+
hostMode: true,
|
|
162
|
+
},
|
|
163
|
+
sessionConfig: {
|
|
164
|
+
...(mergedConfig.model ? { model: mergedConfig.model } : {}),
|
|
165
|
+
...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
|
|
166
|
+
...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
|
|
167
|
+
...(mergedConfig.runtimeControlValues
|
|
168
|
+
? { runtimeControlValues: mergedConfig.runtimeControlValues }
|
|
169
|
+
: {}),
|
|
170
|
+
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
171
|
+
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
172
|
+
},
|
|
173
|
+
lastHeartbeatAt: undefined,
|
|
174
|
+
});
|
|
175
|
+
let executionBranch = liveConfig?.executionBranch ?? null;
|
|
176
|
+
if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
|
|
177
|
+
const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
|
|
178
|
+
if (workspace) {
|
|
179
|
+
executionBranch = buildConversationWorktreeSpec({
|
|
180
|
+
agentId: input.agentId,
|
|
181
|
+
conversationId,
|
|
182
|
+
workspaceCwd: workspace.cwd,
|
|
183
|
+
}).branch;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return patchAgentSessionSnapshot(conversationId, input.agentId, {
|
|
187
|
+
clientType: input.clientType,
|
|
188
|
+
hostMode: true,
|
|
189
|
+
model: snapshot.model ?? null,
|
|
190
|
+
permissionMode: snapshot.permissionMode ?? null,
|
|
191
|
+
effort: snapshot.effort ?? null,
|
|
192
|
+
runtimeControlValues: snapshot.runtimeControlValues ?? null,
|
|
193
|
+
workspaceId: snapshot.workspaceId ?? null,
|
|
194
|
+
executionMode: snapshot.executionMode ?? null,
|
|
195
|
+
executionBranch,
|
|
196
|
+
modelOptions: snapshot.modelOptions,
|
|
197
|
+
permissionModeOptions: snapshot.permissionModeOptions,
|
|
198
|
+
workspaceOptions: snapshot.workspaceOptions,
|
|
199
|
+
availableExecutionModes: snapshot.availableExecutionModes,
|
|
200
|
+
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
201
|
+
});
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
120
204
|
export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
121
205
|
const baseConfig = readSessionWorkspaceConfig(raw);
|
|
122
206
|
if (!raw || typeof raw !== 'object') {
|
|
@@ -127,9 +211,16 @@ export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
|
127
211
|
const value = normalizeOptionalString(data[field]);
|
|
128
212
|
return value ? [[field, value]] : [];
|
|
129
213
|
}));
|
|
214
|
+
const runtimeControlValues = Object.fromEntries(Object.entries(data.runtimeControlValues && typeof data.runtimeControlValues === 'object'
|
|
215
|
+
? data.runtimeControlValues
|
|
216
|
+
: {}).flatMap(([key, value]) => {
|
|
217
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
218
|
+
return normalizedValue ? [[key, normalizedValue]] : [];
|
|
219
|
+
}));
|
|
130
220
|
return {
|
|
131
221
|
...(baseConfig ?? {}),
|
|
132
222
|
...extraConfig,
|
|
223
|
+
...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
|
|
133
224
|
};
|
|
134
225
|
}
|
|
135
226
|
export async function loadHostSessionConfig(input) {
|
package/dist/host.js
CHANGED
|
@@ -24,16 +24,97 @@ import { parseArgs } from 'node:util';
|
|
|
24
24
|
import { randomUUID } from 'node:crypto';
|
|
25
25
|
import { execFileSync } from 'node:child_process';
|
|
26
26
|
import { existsSync } from 'node:fs';
|
|
27
|
-
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
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, buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, 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, writeSessionState, clearSessionState, writeTurnState, releaseConversationEnvironment, } from '@canonmsg/core';
|
|
30
|
-
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
29
|
+
import { CLAUDE_PERMISSION_MODE_OPTIONS, buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildPlanApprovalRequest, buildQuestionRequest, 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
|
+
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
36
|
let runtimeModels = [];
|
|
37
|
+
const CLAUDE_METADATA_TTL_MS = 5 * 60 * 1000;
|
|
38
|
+
let runtimeMetadata = {
|
|
39
|
+
account: null,
|
|
40
|
+
mcpServers: [],
|
|
41
|
+
plugins: [],
|
|
42
|
+
loadedAt: 0,
|
|
43
|
+
};
|
|
44
|
+
let runtimeMetadataRefresh = null;
|
|
45
|
+
function buildClaudeRuntimeDescriptor(input) {
|
|
46
|
+
return {
|
|
47
|
+
coreControls: [
|
|
48
|
+
{
|
|
49
|
+
id: 'model',
|
|
50
|
+
label: 'Model',
|
|
51
|
+
options: input.models,
|
|
52
|
+
defaultValue: input.models[0]?.value ?? null,
|
|
53
|
+
availability: 'setup_and_live',
|
|
54
|
+
liveBehavior: 'immediate',
|
|
55
|
+
selectionPolicy: 'inherit',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'workspace',
|
|
59
|
+
label: 'Workspace',
|
|
60
|
+
options: input.workspaces.map((workspace) => ({
|
|
61
|
+
value: workspace.id,
|
|
62
|
+
label: workspace.label,
|
|
63
|
+
})),
|
|
64
|
+
defaultValue: input.workspaces[0]?.id ?? null,
|
|
65
|
+
availability: 'setup',
|
|
66
|
+
liveBehavior: 'none',
|
|
67
|
+
selectionPolicy: 'inherit',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'executionMode',
|
|
71
|
+
label: 'Execution mode',
|
|
72
|
+
options: input.executionModes.map((mode) => ({
|
|
73
|
+
value: mode,
|
|
74
|
+
label: mode === 'worktree' ? 'Isolated worktree' : 'Use shared workspace',
|
|
75
|
+
description: mode === 'worktree'
|
|
76
|
+
? 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected workspace is a git repo.'
|
|
77
|
+
: 'Runs directly in the selected workspace. Canon records usage, but does not create a separate checkout.',
|
|
78
|
+
})),
|
|
79
|
+
defaultValue: null,
|
|
80
|
+
availability: 'setup',
|
|
81
|
+
liveBehavior: 'none',
|
|
82
|
+
selectionPolicy: 'required_explicit',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
runtimeControls: [
|
|
86
|
+
{
|
|
87
|
+
id: 'permissionMode',
|
|
88
|
+
label: 'Permission mode',
|
|
89
|
+
options: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
90
|
+
defaultValue: 'default',
|
|
91
|
+
availability: 'setup_and_live',
|
|
92
|
+
liveBehavior: 'immediate',
|
|
93
|
+
selectionPolicy: 'inherit',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'effort',
|
|
97
|
+
label: 'Thinking level',
|
|
98
|
+
options: [
|
|
99
|
+
{ value: 'low', label: 'Low' },
|
|
100
|
+
{ value: 'medium', label: 'Medium' },
|
|
101
|
+
{ value: 'high', label: 'High' },
|
|
102
|
+
],
|
|
103
|
+
defaultValue: 'medium',
|
|
104
|
+
availability: 'setup_and_live',
|
|
105
|
+
liveBehavior: 'immediate',
|
|
106
|
+
selectionPolicy: 'inherit',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
supportsInterrupt: true,
|
|
110
|
+
streamingTextMode: 'delta',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function normalizeClaudeEffortLevel(value) {
|
|
114
|
+
return value === 'low' || value === 'medium' || value === 'high'
|
|
115
|
+
? value
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
37
118
|
let cachedClaudeCliPath;
|
|
38
119
|
// The SDK bundles a Bun-compiled `claude` Mach-O binary next to
|
|
39
120
|
// claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
|
|
@@ -83,11 +164,12 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
83
164
|
const config = await loadHostSessionConfig({
|
|
84
165
|
conversationId,
|
|
85
166
|
agentId,
|
|
86
|
-
extraStringFields: ['permissionMode'],
|
|
167
|
+
extraStringFields: ['permissionMode', 'effort'],
|
|
87
168
|
});
|
|
88
169
|
return {
|
|
89
170
|
...config,
|
|
90
171
|
permissionMode: config?.permissionMode,
|
|
172
|
+
effort: normalizeClaudeEffortLevel(config?.effort),
|
|
91
173
|
};
|
|
92
174
|
}
|
|
93
175
|
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
@@ -103,6 +185,14 @@ function resolveWorkspaceCwd(config) {
|
|
|
103
185
|
defaultCwd: workingDir,
|
|
104
186
|
});
|
|
105
187
|
}
|
|
188
|
+
function resolveExecutionFallbackReason(environment) {
|
|
189
|
+
if (!environment?.reason || environment.mode !== 'locked') {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return environment.reason === 'Sharing the base workspace (locked mode)'
|
|
193
|
+
? null
|
|
194
|
+
: environment.reason;
|
|
195
|
+
}
|
|
106
196
|
async function detectRuntimeModels(cwd) {
|
|
107
197
|
const emptyPrompt = {
|
|
108
198
|
async *[Symbol.asyncIterator]() { },
|
|
@@ -130,6 +220,166 @@ async function detectRuntimeModels(cwd) {
|
|
|
130
220
|
catch { }
|
|
131
221
|
}
|
|
132
222
|
}
|
|
223
|
+
async function inspectClaudeRuntimeMetadata(cwd, activeQuery) {
|
|
224
|
+
const emptyPrompt = {
|
|
225
|
+
async *[Symbol.asyncIterator]() { },
|
|
226
|
+
};
|
|
227
|
+
const claudeCliPath = resolveClaudeCliPath();
|
|
228
|
+
const probe = activeQuery ?? query({
|
|
229
|
+
prompt: emptyPrompt,
|
|
230
|
+
options: {
|
|
231
|
+
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
232
|
+
tools: { type: 'preset', preset: 'claude_code' },
|
|
233
|
+
permissionMode: 'default',
|
|
234
|
+
cwd,
|
|
235
|
+
settings: { forceLoginMethod: 'claudeai' },
|
|
236
|
+
...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
try {
|
|
240
|
+
const [init, mcpServers, pluginInfo] = await Promise.all([
|
|
241
|
+
probe.initializationResult(),
|
|
242
|
+
probe.mcpServerStatus().catch(() => []),
|
|
243
|
+
probe.reloadPlugins().catch(() => null),
|
|
244
|
+
]);
|
|
245
|
+
return {
|
|
246
|
+
account: init.account ?? null,
|
|
247
|
+
mcpServers: pluginInfo?.mcpServers ?? mcpServers,
|
|
248
|
+
plugins: pluginInfo?.plugins ?? [],
|
|
249
|
+
loadedAt: Date.now(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (!activeQuery) {
|
|
254
|
+
try {
|
|
255
|
+
probe.close();
|
|
256
|
+
}
|
|
257
|
+
catch { }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function refreshClaudeRuntimeMetadata(input) {
|
|
262
|
+
if (!input.force
|
|
263
|
+
&& runtimeMetadata.loadedAt > 0
|
|
264
|
+
&& Date.now() - runtimeMetadata.loadedAt < CLAUDE_METADATA_TTL_MS) {
|
|
265
|
+
return runtimeMetadata;
|
|
266
|
+
}
|
|
267
|
+
if (runtimeMetadataRefresh) {
|
|
268
|
+
return runtimeMetadataRefresh;
|
|
269
|
+
}
|
|
270
|
+
runtimeMetadataRefresh = inspectClaudeRuntimeMetadata(input.cwd, input.activeQuery ?? null)
|
|
271
|
+
.then((metadata) => {
|
|
272
|
+
runtimeMetadata = metadata;
|
|
273
|
+
return metadata;
|
|
274
|
+
})
|
|
275
|
+
.finally(() => {
|
|
276
|
+
runtimeMetadataRefresh = null;
|
|
277
|
+
});
|
|
278
|
+
return runtimeMetadataRefresh;
|
|
279
|
+
}
|
|
280
|
+
function mapClaudeMcpStatus(status) {
|
|
281
|
+
switch (status) {
|
|
282
|
+
case 'connected':
|
|
283
|
+
return 'ready';
|
|
284
|
+
case 'needs-auth':
|
|
285
|
+
return 'auth_needed';
|
|
286
|
+
case 'disabled':
|
|
287
|
+
return 'configured';
|
|
288
|
+
case 'failed':
|
|
289
|
+
return 'error';
|
|
290
|
+
default:
|
|
291
|
+
return 'unknown';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function buildClaudeRuntimeInfo(input) {
|
|
295
|
+
const { descriptor, metadata } = input;
|
|
296
|
+
const statusItems = [
|
|
297
|
+
metadata.account?.email
|
|
298
|
+
? {
|
|
299
|
+
id: 'account',
|
|
300
|
+
label: 'Account',
|
|
301
|
+
value: metadata.account.email,
|
|
302
|
+
tone: 'success',
|
|
303
|
+
}
|
|
304
|
+
: null,
|
|
305
|
+
metadata.account?.subscriptionType
|
|
306
|
+
? {
|
|
307
|
+
id: 'subscription',
|
|
308
|
+
label: 'Plan',
|
|
309
|
+
value: metadata.account.subscriptionType,
|
|
310
|
+
}
|
|
311
|
+
: null,
|
|
312
|
+
metadata.account?.apiProvider
|
|
313
|
+
? {
|
|
314
|
+
id: 'provider',
|
|
315
|
+
label: 'Provider',
|
|
316
|
+
value: metadata.account.apiProvider,
|
|
317
|
+
}
|
|
318
|
+
: null,
|
|
319
|
+
metadata.account?.tokenSource || metadata.account?.apiKeySource
|
|
320
|
+
? {
|
|
321
|
+
id: 'auth',
|
|
322
|
+
label: 'Auth',
|
|
323
|
+
value: metadata.account.tokenSource ?? metadata.account?.apiKeySource ?? 'connected',
|
|
324
|
+
}
|
|
325
|
+
: null,
|
|
326
|
+
runtimeModels[0]
|
|
327
|
+
? {
|
|
328
|
+
id: 'fallbackModel',
|
|
329
|
+
label: 'Fallback model',
|
|
330
|
+
value: runtimeModels[0].label,
|
|
331
|
+
}
|
|
332
|
+
: null,
|
|
333
|
+
{
|
|
334
|
+
id: 'toolPolicy',
|
|
335
|
+
label: 'Tool policy',
|
|
336
|
+
value: 'Claude Code preset',
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: 'browser',
|
|
340
|
+
label: 'Browser tools',
|
|
341
|
+
value: 'Runtime policy',
|
|
342
|
+
},
|
|
343
|
+
].filter((item) => Boolean(item));
|
|
344
|
+
return {
|
|
345
|
+
descriptor,
|
|
346
|
+
surfaceMode: 'host',
|
|
347
|
+
statusItems,
|
|
348
|
+
inventories: [
|
|
349
|
+
{
|
|
350
|
+
id: 'mcp',
|
|
351
|
+
label: 'MCP',
|
|
352
|
+
entries: metadata.mcpServers.map((server) => ({
|
|
353
|
+
id: server.name,
|
|
354
|
+
label: server.name,
|
|
355
|
+
status: mapClaudeMcpStatus(server.status),
|
|
356
|
+
description: server.error
|
|
357
|
+
?? server.serverInfo?.version
|
|
358
|
+
?? server.scope
|
|
359
|
+
?? undefined,
|
|
360
|
+
})),
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 'plugins',
|
|
364
|
+
label: 'Plugins',
|
|
365
|
+
entries: metadata.plugins.map((plugin) => ({
|
|
366
|
+
id: plugin.name,
|
|
367
|
+
label: plugin.name,
|
|
368
|
+
status: 'configured',
|
|
369
|
+
description: plugin.source ?? plugin.path,
|
|
370
|
+
})),
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
execution: {
|
|
374
|
+
resolvedWorkspaceLabel: input.workspaceLabel,
|
|
375
|
+
resolvedCwd: input.resolvedCwd,
|
|
376
|
+
executionMode: input.executionMode,
|
|
377
|
+
executionBranch: input.executionBranch,
|
|
378
|
+
worktreePath: input.worktreePath,
|
|
379
|
+
fallbackReason: input.fallbackReason,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
133
383
|
// โโ Constants โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
134
384
|
const MAX_SESSIONS = 5;
|
|
135
385
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
@@ -222,7 +472,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
222
472
|
// eslint-disable-next-line prefer-const -- session must be declared before queryOptions but assigned after query()
|
|
223
473
|
let session;
|
|
224
474
|
const REPLY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
225
|
-
function waitForReply() {
|
|
475
|
+
function waitForReply(type, requestId) {
|
|
226
476
|
// Keep session alive while waiting for user input
|
|
227
477
|
session.lastActivity = Date.now();
|
|
228
478
|
return new Promise((resolve, reject) => {
|
|
@@ -231,6 +481,8 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
231
481
|
reject(new Error('Timed out waiting for user reply'));
|
|
232
482
|
}, REPLY_TIMEOUT_MS);
|
|
233
483
|
session.pendingReply = {
|
|
484
|
+
type,
|
|
485
|
+
requestId,
|
|
234
486
|
resolve: (metadata) => {
|
|
235
487
|
clearTimeout(timer);
|
|
236
488
|
resolve(metadata);
|
|
@@ -260,17 +512,16 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
260
512
|
try {
|
|
261
513
|
const questionId = randomUUID();
|
|
262
514
|
const questions = input.questions ?? [];
|
|
263
|
-
|
|
515
|
+
const questionCard = buildQuestionRequest(questionId, questions);
|
|
516
|
+
await client.sendMessage(conversationId, questionCard.text, {
|
|
264
517
|
metadata: {
|
|
265
|
-
|
|
266
|
-
questionId,
|
|
267
|
-
questions,
|
|
518
|
+
...questionCard.metadata,
|
|
268
519
|
turnSemantics: 'control',
|
|
269
520
|
replyBehavior: 'suppress_auto_reply',
|
|
270
521
|
},
|
|
271
522
|
});
|
|
272
523
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Question sent (${questionId.slice(0, 8)})`);
|
|
273
|
-
const reply = await waitForReply();
|
|
524
|
+
const reply = await waitForReply('question_reply', questionId);
|
|
274
525
|
return {
|
|
275
526
|
behavior: 'allow',
|
|
276
527
|
updatedInput: { ...input, answers: reply.answers },
|
|
@@ -283,16 +534,16 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
283
534
|
if (toolName === 'ExitPlanMode') {
|
|
284
535
|
try {
|
|
285
536
|
const planId = randomUUID();
|
|
286
|
-
|
|
537
|
+
const planApproval = buildPlanApprovalRequest(planId);
|
|
538
|
+
await client.sendMessage(conversationId, planApproval.text, {
|
|
287
539
|
metadata: {
|
|
288
|
-
|
|
289
|
-
planId,
|
|
540
|
+
...planApproval.metadata,
|
|
290
541
|
turnSemantics: 'control',
|
|
291
542
|
replyBehavior: 'suppress_auto_reply',
|
|
292
543
|
},
|
|
293
544
|
});
|
|
294
545
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Plan approval sent (${planId.slice(0, 8)})`);
|
|
295
|
-
const reply = await waitForReply();
|
|
546
|
+
const reply = await waitForReply('plan_approval_reply', planId);
|
|
296
547
|
if (reply.decision === 'approve') {
|
|
297
548
|
return { behavior: 'allow' };
|
|
298
549
|
}
|
|
@@ -467,6 +718,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
467
718
|
cwd,
|
|
468
719
|
executionMode: session.environment.mode,
|
|
469
720
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
721
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
470
722
|
hostMode: true,
|
|
471
723
|
isActive: true,
|
|
472
724
|
}).catch(() => { });
|
|
@@ -667,6 +919,15 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
667
919
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial model:`, err);
|
|
668
920
|
}
|
|
669
921
|
}
|
|
922
|
+
if (config.effort) {
|
|
923
|
+
try {
|
|
924
|
+
await q.applyFlagSettings({ effortLevel: config.effort });
|
|
925
|
+
session.state.effort = config.effort;
|
|
926
|
+
}
|
|
927
|
+
catch (err) {
|
|
928
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to set initial effort:`, err);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
670
931
|
if (!resumeSessionId) {
|
|
671
932
|
// New session โ send seed message to activate streaming input mode
|
|
672
933
|
sendInput({
|
|
@@ -698,6 +959,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
698
959
|
cwd,
|
|
699
960
|
executionMode: session.environment.mode,
|
|
700
961
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
962
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
701
963
|
hostMode: true,
|
|
702
964
|
isActive: true,
|
|
703
965
|
}).catch(() => { });
|
|
@@ -758,13 +1020,80 @@ export async function main() {
|
|
|
758
1020
|
availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
759
1021
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
760
1022
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
1023
|
+
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1024
|
+
models: runtimeModels,
|
|
1025
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1026
|
+
executionModes: hostAvailableExecutionModes,
|
|
1027
|
+
}),
|
|
761
1028
|
};
|
|
1029
|
+
const conversationCache = new Map();
|
|
1030
|
+
const knownConversationIds = new Set();
|
|
1031
|
+
let lastKnownConversationRefreshAt = 0;
|
|
762
1032
|
const publishRuntimeHeartbeat = async () => {
|
|
763
1033
|
if (!streamConnected)
|
|
764
1034
|
return;
|
|
1035
|
+
await refreshKnownConversationIds().catch((error) => {
|
|
1036
|
+
console.error('[canon-host] Failed to refresh known conversations:', error);
|
|
1037
|
+
});
|
|
1038
|
+
const activeSessionQuery = sessions.values().next().value?.query ?? null;
|
|
1039
|
+
const metadata = await refreshClaudeRuntimeMetadata({
|
|
1040
|
+
cwd: workingDir,
|
|
1041
|
+
activeQuery: activeSessionQuery,
|
|
1042
|
+
}).catch((error) => {
|
|
1043
|
+
console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
|
|
1044
|
+
return runtimeMetadata;
|
|
1045
|
+
});
|
|
765
1046
|
await publishAgentRuntime(agentId, runtimeDescriptor).catch((error) => {
|
|
766
1047
|
console.error('[canon-host] Failed to publish agent runtime:', error);
|
|
767
1048
|
});
|
|
1049
|
+
await publishHostSessionSnapshots({
|
|
1050
|
+
conversationIds: Array.from(knownConversationIds),
|
|
1051
|
+
agentId,
|
|
1052
|
+
clientType: 'claude-code',
|
|
1053
|
+
runtime: runtimeDescriptor,
|
|
1054
|
+
workspaceOptions,
|
|
1055
|
+
defaultCwd: workingDir,
|
|
1056
|
+
liveSessionConfigByConversation: new Map(Array.from(sessions.values()).map((session) => {
|
|
1057
|
+
const workspaceId = resolveWorkspaceIdForBaseCwd(session.environment.baseCwd);
|
|
1058
|
+
return [
|
|
1059
|
+
session.conversationId,
|
|
1060
|
+
{
|
|
1061
|
+
...(session.state.model ? { model: session.state.model } : {}),
|
|
1062
|
+
...(session.state.permissionMode ? { permissionMode: session.state.permissionMode } : {}),
|
|
1063
|
+
...(session.state.effort ? { effort: session.state.effort } : {}),
|
|
1064
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1065
|
+
executionMode: session.environment.mode,
|
|
1066
|
+
executionBranch: session.environment.branch ?? null,
|
|
1067
|
+
},
|
|
1068
|
+
];
|
|
1069
|
+
})),
|
|
1070
|
+
}).catch((error) => {
|
|
1071
|
+
console.error('[canon-host] Failed to publish session snapshots:', error);
|
|
1072
|
+
});
|
|
1073
|
+
await Promise.all(Array.from(knownConversationIds).map(async (conversationId) => {
|
|
1074
|
+
const session = sessions.get(conversationId);
|
|
1075
|
+
const workspaceId = session
|
|
1076
|
+
? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
|
|
1077
|
+
: runtimeDescriptor.defaultWorkspaceId;
|
|
1078
|
+
const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
|
|
1079
|
+
const payload = buildClaudeRuntimeInfo({
|
|
1080
|
+
descriptor: runtimeDescriptor.runtimeDescriptor ?? buildClaudeRuntimeDescriptor({
|
|
1081
|
+
models: runtimeModels,
|
|
1082
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1083
|
+
executionModes: hostAvailableExecutionModes,
|
|
1084
|
+
}),
|
|
1085
|
+
metadata,
|
|
1086
|
+
workspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
1087
|
+
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
1088
|
+
executionMode: session?.environment.mode ?? null,
|
|
1089
|
+
executionBranch: session?.environment.branch ?? null,
|
|
1090
|
+
worktreePath: session?.environment.worktreePath ?? null,
|
|
1091
|
+
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
1092
|
+
});
|
|
1093
|
+
await writeRuntimeInfo(conversationId, agentId, payload);
|
|
1094
|
+
})).catch((error) => {
|
|
1095
|
+
console.error('[canon-host] Failed to publish runtime info:', error);
|
|
1096
|
+
});
|
|
768
1097
|
};
|
|
769
1098
|
// โโ Load conversations (informational โ sessions created on demand) โโ
|
|
770
1099
|
try {
|
|
@@ -777,6 +1106,11 @@ export async function main() {
|
|
|
777
1106
|
availableModels: runtimeModels,
|
|
778
1107
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
779
1108
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
1109
|
+
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1110
|
+
models: runtimeModels,
|
|
1111
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1112
|
+
executionModes: hostAvailableExecutionModes,
|
|
1113
|
+
}),
|
|
780
1114
|
};
|
|
781
1115
|
}
|
|
782
1116
|
catch {
|
|
@@ -786,10 +1120,20 @@ export async function main() {
|
|
|
786
1120
|
availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
787
1121
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
788
1122
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
1123
|
+
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
1124
|
+
models: runtimeModels,
|
|
1125
|
+
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
1126
|
+
executionModes: hostAvailableExecutionModes,
|
|
1127
|
+
}),
|
|
789
1128
|
};
|
|
790
1129
|
}
|
|
791
1130
|
try {
|
|
792
1131
|
const convos = await client.getConversations();
|
|
1132
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
1133
|
+
for (const conversation of convos) {
|
|
1134
|
+
knownConversationIds.add(conversation.id);
|
|
1135
|
+
conversationCache.set(conversation.id, conversation);
|
|
1136
|
+
}
|
|
793
1137
|
console.error(`[canon-host] ${convos.length} conversations`);
|
|
794
1138
|
}
|
|
795
1139
|
catch {
|
|
@@ -800,11 +1144,25 @@ export async function main() {
|
|
|
800
1144
|
const pendingSessionCreations = new Map();
|
|
801
1145
|
/** Saved SDK session IDs for resume after idle eviction, keyed by conversation + workspace */
|
|
802
1146
|
const savedSessionIds = new Map();
|
|
803
|
-
const conversationCache = new Map();
|
|
804
1147
|
const { getConversationMeta } = createConversationMetadataLoader({
|
|
805
1148
|
client,
|
|
806
1149
|
conversationCache,
|
|
807
1150
|
});
|
|
1151
|
+
function resolveWorkspaceIdForBaseCwd(baseCwd) {
|
|
1152
|
+
return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
|
|
1153
|
+
}
|
|
1154
|
+
async function refreshKnownConversationIds(force = false) {
|
|
1155
|
+
if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const conversations = await client.getConversations();
|
|
1159
|
+
knownConversationIds.clear();
|
|
1160
|
+
for (const conversation of conversations) {
|
|
1161
|
+
knownConversationIds.add(conversation.id);
|
|
1162
|
+
conversationCache.set(conversation.id, conversation);
|
|
1163
|
+
}
|
|
1164
|
+
lastKnownConversationRefreshAt = Date.now();
|
|
1165
|
+
}
|
|
808
1166
|
async function loadHydratedInboundContext(input) {
|
|
809
1167
|
const [conversation, page] = await Promise.all([
|
|
810
1168
|
getConversationMeta(input.conversationId),
|
|
@@ -865,6 +1223,7 @@ export async function main() {
|
|
|
865
1223
|
}
|
|
866
1224
|
}
|
|
867
1225
|
async function getOrCreateSession(conversationId) {
|
|
1226
|
+
knownConversationIds.add(conversationId);
|
|
868
1227
|
const existing = sessions.get(conversationId);
|
|
869
1228
|
if (existing && !existing.closed) {
|
|
870
1229
|
existing.lastActivity = Date.now();
|
|
@@ -908,6 +1267,7 @@ export async function main() {
|
|
|
908
1267
|
}, {
|
|
909
1268
|
model: config?.model ?? runtimeModels[0]?.value,
|
|
910
1269
|
permissionMode: config?.permissionMode ?? 'default',
|
|
1270
|
+
effort: config?.effort,
|
|
911
1271
|
availableModels: runtimeModels,
|
|
912
1272
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
913
1273
|
}, resumeId);
|
|
@@ -937,15 +1297,33 @@ export async function main() {
|
|
|
937
1297
|
if (m.senderId === agentId)
|
|
938
1298
|
return;
|
|
939
1299
|
const convoId = payload.conversationId;
|
|
1300
|
+
knownConversationIds.add(convoId);
|
|
940
1301
|
// Check for metadata replies (question answers, plan approvals)
|
|
941
1302
|
if (m.metadata?.type === 'question_reply' || m.metadata?.type === 'plan_approval_reply') {
|
|
942
1303
|
const existingSession = sessions.get(convoId);
|
|
1304
|
+
const pendingReply = existingSession?.pendingReply;
|
|
1305
|
+
const isOwnerReply = m.isOwner ?? (ownerId != null && m.senderId === ownerId);
|
|
1306
|
+
const replyRequestId = m.metadata.type === 'question_reply'
|
|
1307
|
+
? m.metadata.questionId
|
|
1308
|
+
: m.metadata.planId;
|
|
1309
|
+
if (!pendingReply) {
|
|
1310
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Ignoring stale ${m.metadata.type} with no pending request`);
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (!isOwnerReply) {
|
|
1314
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Ignoring ${m.metadata.type} from non-owner ${m.senderId}`);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (pendingReply.type !== m.metadata.type || pendingReply.requestId !== replyRequestId) {
|
|
1318
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Ignoring mismatched ${m.metadata.type} (${String(replyRequestId ?? 'missing-id')})`);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
943
1321
|
if (existingSession?.pendingReply) {
|
|
944
1322
|
const { resolve } = existingSession.pendingReply;
|
|
945
1323
|
existingSession.pendingReply = null;
|
|
946
1324
|
existingSession.lastActivity = Date.now();
|
|
947
1325
|
resolve(m.metadata);
|
|
948
|
-
console.error(`[canon-host] [${convoId.slice(0, 8)}] Reply received (${m.metadata.type})`);
|
|
1326
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Reply received (${m.metadata.type}:${pendingReply.requestId.slice(0, 8)})`);
|
|
949
1327
|
return;
|
|
950
1328
|
}
|
|
951
1329
|
}
|
|
@@ -1071,12 +1449,16 @@ export async function main() {
|
|
|
1071
1449
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
|
|
1072
1450
|
}
|
|
1073
1451
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
effortLevel:
|
|
1078
|
-
|
|
1079
|
-
|
|
1452
|
+
const nextEffort = normalizeClaudeEffortLevel(ctrl.effort);
|
|
1453
|
+
if (nextEffort && nextEffort !== session.state.effort) {
|
|
1454
|
+
try {
|
|
1455
|
+
await session.query.applyFlagSettings({ effortLevel: nextEffort });
|
|
1456
|
+
session.state.effort = nextEffort;
|
|
1457
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort โ ${nextEffort}`);
|
|
1458
|
+
}
|
|
1459
|
+
catch (err) {
|
|
1460
|
+
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
|
|
1461
|
+
}
|
|
1080
1462
|
}
|
|
1081
1463
|
writeSessionState(convoId, agentId, {
|
|
1082
1464
|
...session.state,
|
|
@@ -1084,6 +1466,7 @@ export async function main() {
|
|
|
1084
1466
|
cwd: session.cwd,
|
|
1085
1467
|
executionMode: session.environment.mode,
|
|
1086
1468
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1469
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1087
1470
|
hostMode: true,
|
|
1088
1471
|
isActive: true,
|
|
1089
1472
|
}).catch(() => { });
|
|
@@ -1145,6 +1528,7 @@ export async function main() {
|
|
|
1145
1528
|
cwd: session.cwd,
|
|
1146
1529
|
executionMode: session.environment.mode,
|
|
1147
1530
|
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1531
|
+
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1148
1532
|
hostMode: true,
|
|
1149
1533
|
isActive: true,
|
|
1150
1534
|
}).catch(() => { });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.
|
|
35
|
-
"@canonmsg/core": "^0.
|
|
34
|
+
"@canonmsg/agent-sdk": "^0.9.0",
|
|
35
|
+
"@canonmsg/core": "^0.8.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|