@canonmsg/claude-code-plugin 0.34.2 → 0.34.4
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/.claude-plugin/plugin.json +1 -1
- package/dist/host.d.ts +87 -6
- package/dist/host.js +66 -266
- package/dist/native-command-input.d.ts +18 -0
- package/dist/native-command-input.js +26 -0
- package/dist/session-state.d.ts +3 -0
- package/dist/session-state.js +1 -0
- package/package.json +3 -3
package/dist/host.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type PermissionResult, type SDKControlReloadPluginsResponse } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { type PermissionResult, type Query, type SDKControlReloadPluginsResponse, type SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
3
3
|
import { type RecoveryCheckpointTracker } from '@canonmsg/coding-agent-host';
|
|
4
|
-
import { type CanonRuntimeCommandDescriptor, type ExecutionEnvironmentMode, type HostInboundParticipantContext as InboundParticipantContext, type CanonReplyContext, type MessageCreatedPayload, type ResolvedAgentBehaviorPolicy, type TurnVerbosityConfig } from '@canonmsg/core';
|
|
4
|
+
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeFact, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type DeliveryIntent, type ModelOption, type HostInboundParticipantContext as InboundParticipantContext, type CanonReplyContext, type MessageCreatedPayload, type ResolvedAgentBehaviorPolicy, type TurnLifecycleState, type TurnVerbosity, type TurnVerbosityConfig, type PreparedExecutionEnvironment, type WorkspaceOption, type CanonWorkspaceRootMetadata } from '@canonmsg/core';
|
|
5
|
+
import { type ClaudeInputEnvelope, type ClaudePendingFinalDelivery, type ClaudeTurnActivityState, type ClaudeTurnModes, type ClaudeRuntimeControlError } from './session-state.js';
|
|
5
6
|
/**
|
|
6
7
|
* `--turn-verbosity` beats `CANON_TURN_VERBOSITY`; `null` means "unset, use the
|
|
7
8
|
* per-conversation-type default".
|
|
@@ -19,13 +20,92 @@ export declare function resolveConfiguredClaudeTurnVerbosity(input: {
|
|
|
19
20
|
}): TurnVerbosityConfig | null;
|
|
20
21
|
export declare function buildClaudePlanAllowResult(input: Record<string, unknown>): PermissionResult;
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* inventory is exposed as composer slash entries that pass the raw slash text
|
|
25
|
-
* through. Canon-injected aliases keep precedence over native names.
|
|
23
|
+
* Publish only commands returned by the SDK, using its canonical name and
|
|
24
|
+
* aliases. Reserve aliases only for implemented Canon session actions.
|
|
26
25
|
*/
|
|
27
26
|
export declare function buildNativeSlashCommandDescriptors(native: SDKControlReloadPluginsResponse['commands'], reservedAliases: ReadonlySet<string>): CanonRuntimeCommandDescriptor[];
|
|
27
|
+
export declare function buildClaudeRuntimeDescriptor(input: {
|
|
28
|
+
nativeCommands?: SDKControlReloadPluginsResponse['commands'];
|
|
29
|
+
models: ModelOption[];
|
|
30
|
+
workspaces: WorkspaceOption[];
|
|
31
|
+
workspaceRoots?: CanonWorkspaceRootMetadata[];
|
|
32
|
+
executionModes: ExecutionEnvironmentMode[];
|
|
33
|
+
defaultExecutionMode?: ExecutionEnvironmentMode;
|
|
34
|
+
presentation?: CanonRuntimePresentationPolicy;
|
|
35
|
+
}): CanonRuntimeDescriptor;
|
|
28
36
|
export declare function resolveSessionExecutionMode(defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
37
|
+
interface SessionState {
|
|
38
|
+
model?: string;
|
|
39
|
+
permissionMode?: string;
|
|
40
|
+
effort?: string;
|
|
41
|
+
ultracode?: string;
|
|
42
|
+
state?: 'idle' | 'running' | 'requires_action';
|
|
43
|
+
contextUsage?: {
|
|
44
|
+
percentage: number;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
maxTokens: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
interface Session {
|
|
50
|
+
observedModel?: string;
|
|
51
|
+
observedEffort?: string;
|
|
52
|
+
conversationId: string;
|
|
53
|
+
cwd: string;
|
|
54
|
+
environment: PreparedExecutionEnvironment;
|
|
55
|
+
query: Query;
|
|
56
|
+
sendInput: (input: ClaudeInputEnvelope) => void;
|
|
57
|
+
enqueueInbound: (msg: SDKUserMessage, intent?: DeliveryIntent, sourceMessageId?: string | null, markAccepted?: boolean, isOwnerTurn?: boolean, requestingUserId?: string | null, turnModes?: ClaudeTurnModes) => void;
|
|
58
|
+
state: SessionState;
|
|
59
|
+
availableModels: ModelOption[];
|
|
60
|
+
/**
|
|
61
|
+
* The verbosity the RUNNING turn was opened with. Re-affirmed from each
|
|
62
|
+
* turn's envelope at `openTurn` and never re-read mid-turn: Claude replays a
|
|
63
|
+
* final's metadata byte-for-byte across delivery retries, so a trail that
|
|
64
|
+
* appeared or vanished between attempts would turn a harmless replay into a
|
|
65
|
+
* 409.
|
|
66
|
+
*/
|
|
67
|
+
turnVerbosity: TurnVerbosity;
|
|
68
|
+
streamingText: string;
|
|
69
|
+
streamingTimer: ReturnType<typeof setTimeout> | null;
|
|
70
|
+
idleResetTimer: ReturnType<typeof setTimeout> | null;
|
|
71
|
+
finalDeliveryTimer: ReturnType<typeof setTimeout> | null;
|
|
72
|
+
pendingInputs: ClaudeInputEnvelope[];
|
|
73
|
+
activeInput: ClaudeInputEnvelope | null;
|
|
74
|
+
/**
|
|
75
|
+
* The input being dispatched: the slot is reserved but the SDK does not have
|
|
76
|
+
* it yet. Only an interrupt cares — there is nothing in flight to stop.
|
|
77
|
+
*/
|
|
78
|
+
dispatchingInput: ClaudeInputEnvelope | null;
|
|
79
|
+
finalizedTurnKeys: Set<string>;
|
|
80
|
+
interruptedTurnKeys: Set<string>;
|
|
81
|
+
/** Turns whose model called `no_reply`: end without posting anything. */
|
|
82
|
+
silencedTurnKeys: Set<string>;
|
|
83
|
+
pendingFinalText: string | null;
|
|
84
|
+
pendingFinalDelivery: ClaudePendingFinalDelivery | null;
|
|
85
|
+
runtimeControlErrors: Record<string, ClaudeRuntimeControlError>;
|
|
86
|
+
toolInProgress: boolean;
|
|
87
|
+
turnState: TurnLifecycleState;
|
|
88
|
+
currentTurnId: string | null;
|
|
89
|
+
currentTurnOpenedAt: number | null;
|
|
90
|
+
currentTurnUpdatedAt: number | null;
|
|
91
|
+
/** Per-turn margin-trail bookkeeping (one block per tool call). */
|
|
92
|
+
turnActivity: ClaudeTurnActivityState;
|
|
93
|
+
activeSelfContextId: string | null;
|
|
94
|
+
lastAcceptedIntent: DeliveryIntent | null;
|
|
95
|
+
lastActivity: number;
|
|
96
|
+
typingKeepaliveTimer: ReturnType<typeof setInterval> | null;
|
|
97
|
+
controlInterruptPending: boolean;
|
|
98
|
+
closed: boolean;
|
|
99
|
+
/** SDK session ID — captured from result messages for resume support */
|
|
100
|
+
sdkSessionId: string | null;
|
|
101
|
+
writeState: () => void;
|
|
102
|
+
writeTurn: () => void;
|
|
103
|
+
clearStreaming: () => Promise<void>;
|
|
104
|
+
markInputCompleted: (input: ClaudeInputEnvelope | null | undefined) => void;
|
|
105
|
+
setRuntimeControlError: (controlId: string, value: string, error: unknown) => void;
|
|
106
|
+
clearRuntimeControlError: (controlId: string) => void;
|
|
107
|
+
}
|
|
108
|
+
export declare function buildClaudeSessionFacts(session?: Pick<Session, 'closed' | 'observedModel' | 'observedEffort'>): CanonRuntimeFact[];
|
|
29
109
|
export declare function createClaudeRecoveryCheckpointTracker(persist: (messageId: string) => boolean): RecoveryCheckpointTracker;
|
|
30
110
|
export declare const NO_REPLY_TOOL_NAME: string;
|
|
31
111
|
export declare function buildCanonPrompt(input: {
|
|
@@ -40,3 +120,4 @@ export declare function buildCanonPrompt(input: {
|
|
|
40
120
|
message?: MessageCreatedPayload['message'];
|
|
41
121
|
}): string;
|
|
42
122
|
export declare function main(): Promise<void>;
|
|
123
|
+
export {};
|
package/dist/host.js
CHANGED
|
@@ -29,6 +29,7 @@ import { query, } from '@anthropic-ai/claude-agent-sdk';
|
|
|
29
29
|
import { materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
30
30
|
import { captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, collectMissedInboundMessages, createRecoveryCheckpointTracker, createReconnectRecoveryCoordinator, STARTUP_RECOVERY_PAGE_SIZE, } from '@canonmsg/coding-agent-host';
|
|
31
31
|
import { buildCanonUserContent, renderClaudeInboundContent, withClaudeReplyContextMediaReferences, } from './canon-user-content.js';
|
|
32
|
+
import { buildClaudeNativeCommandInput, createClaudeCommandContextHook, getClaudeNativeCommandOutput, } from './native-command-input.js';
|
|
32
33
|
import { EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonInboundFrameV1, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, normalizeRuntimeCommandDescriptors, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createRuntimeHeartbeat, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, parseTurnVerbosityConfig, prepareConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, decideAutoReply, initRTDBAuth, isChunkedSendMessageError, sendMessageWithRetry, sendMessageWithRetryChunked, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, resolveLocalRuntimeSessionState, saveRuntimeSessionState, clearRuntimeSessionState, publishHostSessionSnapshots, renderCodingHostInboundPrompt, resolveConfiguredWorkspaceCwd, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
33
34
|
import { runCli } from '@canonmsg/core';
|
|
34
35
|
import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
|
|
@@ -134,16 +135,14 @@ export function buildClaudePlanAllowResult(input) {
|
|
|
134
135
|
return { behavior: 'allow', updatedInput: input };
|
|
135
136
|
}
|
|
136
137
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* inventory is exposed as composer slash entries that pass the raw slash text
|
|
140
|
-
* through. Canon-injected aliases keep precedence over native names.
|
|
138
|
+
* Publish only commands returned by the SDK, using its canonical name and
|
|
139
|
+
* aliases. Reserve aliases only for implemented Canon session actions.
|
|
141
140
|
*/
|
|
142
141
|
export function buildNativeSlashCommandDescriptors(native, reservedAliases) {
|
|
143
142
|
const descriptors = [];
|
|
144
|
-
for (const command of native
|
|
143
|
+
for (const command of native) {
|
|
145
144
|
const name = String(command.name ?? '').replace(/^\//, '').trim();
|
|
146
|
-
if (
|
|
145
|
+
if (!/^[A-Za-z0-9:_-]+$/.test(name))
|
|
147
146
|
continue;
|
|
148
147
|
descriptors.push({
|
|
149
148
|
id: `claude-native-${name}`,
|
|
@@ -151,12 +150,13 @@ export function buildNativeSlashCommandDescriptors(native, reservedAliases) {
|
|
|
151
150
|
description: command.description
|
|
152
151
|
?? ('argumentHint' in command ? command.argumentHint : undefined)
|
|
153
152
|
?? 'Claude Code command (runs in the runtime).',
|
|
154
|
-
aliases: [name],
|
|
153
|
+
aliases: [name, ...(command.aliases ?? []).filter((alias) => /^\/?[A-Za-z0-9:_-]+$/.test(alias))],
|
|
155
154
|
category: 'runtime',
|
|
156
155
|
placements: ['composer_slash', 'command_palette'],
|
|
157
156
|
availability: ['always'],
|
|
158
157
|
trailingTextBehavior: 'send_as_prompt',
|
|
159
|
-
|
|
158
|
+
args: [{ id: 'prompt', label: 'Arguments', kind: 'string', captureRemaining: true }],
|
|
159
|
+
dispatch: { kind: 'text_passthrough', template: `/${name} {argument}` },
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
return normalizeRuntimeCommandDescriptors(descriptors, {
|
|
@@ -164,100 +164,19 @@ export function buildNativeSlashCommandDescriptors(native, reservedAliases) {
|
|
|
164
164
|
maxCommands: MAX_NATIVE_SLASH_COMMANDS,
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
|
-
function buildClaudeRuntimeDescriptor(input) {
|
|
167
|
+
export function buildClaudeRuntimeDescriptor(input) {
|
|
168
168
|
const commands = [
|
|
169
169
|
{
|
|
170
170
|
id: 'runtime-status',
|
|
171
|
-
label: '
|
|
172
|
-
description: 'Open
|
|
171
|
+
label: 'Session info',
|
|
172
|
+
description: 'Open Canon session information.',
|
|
173
173
|
primitive: 'runtime.status',
|
|
174
|
-
aliases: ['
|
|
174
|
+
aliases: ['session-info'],
|
|
175
175
|
category: 'details',
|
|
176
176
|
placements: ['composer_slash', 'command_palette'],
|
|
177
177
|
availability: ['always'],
|
|
178
178
|
dispatch: { kind: 'open_details', target: 'status' },
|
|
179
179
|
},
|
|
180
|
-
{
|
|
181
|
-
id: 'mcp-inventory',
|
|
182
|
-
label: 'MCP servers',
|
|
183
|
-
description: 'Open the Claude MCP inventory published by the host.',
|
|
184
|
-
aliases: ['mcp'],
|
|
185
|
-
category: 'details',
|
|
186
|
-
placements: ['composer_slash', 'command_palette'],
|
|
187
|
-
availability: ['always'],
|
|
188
|
-
dispatch: { kind: 'open_details', target: 'mcp' },
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
id: 'plugin-inventory',
|
|
192
|
-
label: 'Plugins',
|
|
193
|
-
description: 'Open the Claude plugin and command inventory published by the host.',
|
|
194
|
-
aliases: ['plugins'],
|
|
195
|
-
category: 'details',
|
|
196
|
-
placements: ['composer_slash', 'command_palette'],
|
|
197
|
-
availability: ['always'],
|
|
198
|
-
dispatch: { kind: 'open_details', target: 'plugins' },
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
id: 'model-details',
|
|
202
|
-
label: 'Model control',
|
|
203
|
-
description: 'Open runtime details and use the live Model control.',
|
|
204
|
-
aliases: ['model'],
|
|
205
|
-
category: 'details',
|
|
206
|
-
placements: ['composer_slash', 'command_palette'],
|
|
207
|
-
availability: ['always'],
|
|
208
|
-
dispatch: { kind: 'open_details', target: 'model' },
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
id: 'permission-details',
|
|
212
|
-
label: 'Permission mode',
|
|
213
|
-
description: 'Open runtime details and use the live Permission mode control.',
|
|
214
|
-
aliases: ['permission'],
|
|
215
|
-
category: 'details',
|
|
216
|
-
placements: ['composer_slash', 'command_palette'],
|
|
217
|
-
availability: ['always'],
|
|
218
|
-
dispatch: { kind: 'open_details', target: 'permissionMode' },
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
id: 'effort-details',
|
|
222
|
-
label: 'Thinking level',
|
|
223
|
-
description: 'Set Claude effort using /effort or /think.',
|
|
224
|
-
primitive: 'runtime.reasoning.set',
|
|
225
|
-
aliases: ['effort', 'think'],
|
|
226
|
-
category: 'runtime',
|
|
227
|
-
placements: ['composer_slash', 'command_palette'],
|
|
228
|
-
availability: ['always'],
|
|
229
|
-
args: [{
|
|
230
|
-
id: 'level',
|
|
231
|
-
label: 'Level',
|
|
232
|
-
kind: 'enum',
|
|
233
|
-
required: true,
|
|
234
|
-
choices: [...EFFORT_OPTIONS],
|
|
235
|
-
}],
|
|
236
|
-
dispatch: {
|
|
237
|
-
kind: 'control',
|
|
238
|
-
controlId: 'effort',
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
id: 'ultracode-details',
|
|
243
|
-
label: 'Ultracode',
|
|
244
|
-
description: 'Toggle Claude ultracode using /ultracode.',
|
|
245
|
-
aliases: ['ultracode'],
|
|
246
|
-
category: 'runtime',
|
|
247
|
-
placements: ['composer_slash', 'command_palette'],
|
|
248
|
-
availability: ['always'],
|
|
249
|
-
args: [{
|
|
250
|
-
id: 'level',
|
|
251
|
-
label: 'Level',
|
|
252
|
-
kind: 'enum',
|
|
253
|
-
required: true,
|
|
254
|
-
choices: [...CLAUDE_ULTRACODE_OPTIONS],
|
|
255
|
-
}],
|
|
256
|
-
dispatch: {
|
|
257
|
-
kind: 'control',
|
|
258
|
-
controlId: 'ultracode',
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
180
|
{
|
|
262
181
|
...RUNTIME_NEW_SESSION_ACTION,
|
|
263
182
|
primitive: 'session.new',
|
|
@@ -265,7 +184,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
265
184
|
RUNTIME_STOP_ACTION,
|
|
266
185
|
RUNTIME_STOP_AND_DROP_ACTION,
|
|
267
186
|
];
|
|
268
|
-
commands.push(...buildNativeSlashCommandDescriptors(runtimeMetadata.commands, new Set(commands.flatMap((command) => [command.id, ...(command.aliases ?? [])]))));
|
|
187
|
+
commands.push(...buildNativeSlashCommandDescriptors(input.nativeCommands ?? runtimeMetadata.commands, new Set(commands.flatMap((command) => [command.id, ...(command.aliases ?? [])]))));
|
|
269
188
|
return buildFirstPartyCodingRuntimeDescriptor({
|
|
270
189
|
clientType: 'claude-code',
|
|
271
190
|
models: input.models,
|
|
@@ -431,14 +350,6 @@ function resolveWorkspaceCwd() {
|
|
|
431
350
|
defaultCwd: workingDir,
|
|
432
351
|
});
|
|
433
352
|
}
|
|
434
|
-
function resolveExecutionFallbackReason(environment) {
|
|
435
|
-
if (!environment?.reason || environment.mode !== 'locked') {
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
return environment.reason === 'Sharing the base workspace (locked mode)'
|
|
439
|
-
? null
|
|
440
|
-
: environment.reason;
|
|
441
|
-
}
|
|
442
353
|
async function detectRuntimeModels(cwd) {
|
|
443
354
|
const discovered = await detectRuntimeModelsForSelection(cwd, 'default');
|
|
444
355
|
const supplemental = await detectSupplementalRuntimeModels(cwd, deriveClaudeSupplementalModelProbes(discovered));
|
|
@@ -559,146 +470,15 @@ async function readApprovalDiffPreImage(filePath) {
|
|
|
559
470
|
throw error;
|
|
560
471
|
}
|
|
561
472
|
}
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
return 'error';
|
|
572
|
-
default:
|
|
573
|
-
return 'unknown';
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
function buildClaudeRuntimeInfo(input) {
|
|
577
|
-
const { descriptor, metadata } = input;
|
|
578
|
-
const statusItems = [
|
|
579
|
-
metadata.account?.email
|
|
580
|
-
? {
|
|
581
|
-
id: 'account',
|
|
582
|
-
label: 'Account',
|
|
583
|
-
value: 'Connected',
|
|
584
|
-
tone: 'success',
|
|
585
|
-
tier: 'diagnostic',
|
|
586
|
-
}
|
|
587
|
-
: null,
|
|
588
|
-
metadata.account?.subscriptionType
|
|
589
|
-
? {
|
|
590
|
-
id: 'subscription',
|
|
591
|
-
label: 'Plan',
|
|
592
|
-
value: metadata.account.subscriptionType,
|
|
593
|
-
}
|
|
594
|
-
: null,
|
|
595
|
-
metadata.account?.apiProvider
|
|
596
|
-
? {
|
|
597
|
-
id: 'provider',
|
|
598
|
-
label: 'Provider',
|
|
599
|
-
value: metadata.account.apiProvider,
|
|
600
|
-
}
|
|
601
|
-
: null,
|
|
602
|
-
metadata.account?.tokenSource || metadata.account?.apiKeySource
|
|
603
|
-
? {
|
|
604
|
-
id: 'auth',
|
|
605
|
-
label: 'Auth',
|
|
606
|
-
value: metadata.account.tokenSource ?? metadata.account?.apiKeySource ?? 'connected',
|
|
607
|
-
tier: 'diagnostic',
|
|
608
|
-
}
|
|
609
|
-
: null,
|
|
610
|
-
runtimeModels[0]
|
|
611
|
-
? {
|
|
612
|
-
id: 'fallbackModel',
|
|
613
|
-
label: 'Fallback model',
|
|
614
|
-
value: runtimeModels[0].label,
|
|
615
|
-
tier: 'diagnostic',
|
|
616
|
-
source: 'host-default',
|
|
617
|
-
}
|
|
618
|
-
: null,
|
|
619
|
-
{
|
|
620
|
-
id: 'toolPolicy',
|
|
621
|
-
label: 'Tool policy',
|
|
622
|
-
value: 'Claude Code preset',
|
|
623
|
-
tier: 'detail',
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
id: 'browser',
|
|
627
|
-
label: 'Browser tools',
|
|
628
|
-
value: 'Runtime policy',
|
|
629
|
-
tier: 'detail',
|
|
630
|
-
},
|
|
631
|
-
{
|
|
632
|
-
id: 'mediaOut',
|
|
633
|
-
label: 'Media out',
|
|
634
|
-
value: 'Generated media artifacts',
|
|
635
|
-
tier: 'detail',
|
|
636
|
-
},
|
|
637
|
-
].filter((item) => Boolean(item));
|
|
638
|
-
return {
|
|
639
|
-
descriptor,
|
|
640
|
-
surfaceMode: 'host',
|
|
641
|
-
statusItems,
|
|
642
|
-
inventories: [
|
|
643
|
-
{
|
|
644
|
-
id: 'mcp',
|
|
645
|
-
label: 'MCP',
|
|
646
|
-
entries: metadata.mcpServers.map((server) => ({
|
|
647
|
-
id: server.name,
|
|
648
|
-
label: server.name,
|
|
649
|
-
status: mapClaudeMcpStatus(server.status),
|
|
650
|
-
description: server.error
|
|
651
|
-
?? server.serverInfo?.version
|
|
652
|
-
?? server.scope
|
|
653
|
-
?? undefined,
|
|
654
|
-
})),
|
|
655
|
-
tier: 'diagnostic',
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
id: 'plugins',
|
|
659
|
-
label: 'Plugins',
|
|
660
|
-
entries: metadata.plugins.map((plugin) => ({
|
|
661
|
-
id: plugin.name,
|
|
662
|
-
label: plugin.name,
|
|
663
|
-
status: 'configured',
|
|
664
|
-
description: plugin.source ?? plugin.path,
|
|
665
|
-
})),
|
|
666
|
-
tier: 'diagnostic',
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
id: 'commands',
|
|
670
|
-
label: 'Commands',
|
|
671
|
-
entries: metadata.commands.map((command) => ({
|
|
672
|
-
id: command.name,
|
|
673
|
-
label: command.name.startsWith('/') ? command.name : `/${command.name}`,
|
|
674
|
-
status: 'configured',
|
|
675
|
-
description: command.description ?? command.argumentHint ?? undefined,
|
|
676
|
-
})),
|
|
677
|
-
tier: 'diagnostic',
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
id: 'agents',
|
|
681
|
-
label: 'Agents',
|
|
682
|
-
entries: metadata.agents.map((agent) => ({
|
|
683
|
-
id: agent.name,
|
|
684
|
-
label: agent.name,
|
|
685
|
-
status: 'configured',
|
|
686
|
-
description: agent.description ?? agent.model ?? undefined,
|
|
687
|
-
})),
|
|
688
|
-
tier: 'diagnostic',
|
|
689
|
-
},
|
|
690
|
-
],
|
|
691
|
-
execution: {
|
|
692
|
-
resolvedWorkspaceLabel: input.workspaceLabel,
|
|
693
|
-
resolvedCwd: input.resolvedCwd,
|
|
694
|
-
workspaceRootId: input.workspaceRootId ?? null,
|
|
695
|
-
workspaceRelativePath: input.workspaceRelativePath ?? null,
|
|
696
|
-
executionMode: input.executionMode,
|
|
697
|
-
executionBranch: input.executionBranch,
|
|
698
|
-
worktreePath: input.worktreePath,
|
|
699
|
-
fallbackReason: input.fallbackReason,
|
|
700
|
-
},
|
|
701
|
-
};
|
|
473
|
+
export function buildClaudeSessionFacts(session) {
|
|
474
|
+
if (!session || session.closed)
|
|
475
|
+
return [];
|
|
476
|
+
const facts = [{ id: 'runtime', label: 'Runtime', value: 'Claude Agent SDK', group: 'runtime' }];
|
|
477
|
+
if (session.observedModel)
|
|
478
|
+
facts.push({ id: 'model', label: 'Model', value: session.observedModel, group: 'model' });
|
|
479
|
+
if (session.observedEffort)
|
|
480
|
+
facts.push({ id: 'reasoning', label: 'Reasoning', value: session.observedEffort, group: 'model' });
|
|
481
|
+
return facts;
|
|
702
482
|
}
|
|
703
483
|
// ── Constants ───────────────────────────────────────────────────────
|
|
704
484
|
const MAX_SESSIONS = 5;
|
|
@@ -841,6 +621,10 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
841
621
|
// from here.
|
|
842
622
|
if (!claudeInputOwnsTurnSlot(input))
|
|
843
623
|
return;
|
|
624
|
+
// Native commands can change settings without reporting the new value.
|
|
625
|
+
// Wait for this turn's SDK observations instead of repeating old defaults.
|
|
626
|
+
session.observedModel = undefined;
|
|
627
|
+
session.observedEffort = undefined;
|
|
844
628
|
session.activeInput = input;
|
|
845
629
|
}
|
|
846
630
|
const inputStream = {
|
|
@@ -942,6 +726,16 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
942
726
|
...(config.effort ? { effort: config.effort } : {}),
|
|
943
727
|
settings: { forceLoginMethod: 'claudeai' },
|
|
944
728
|
settingSources: ['project', 'local'],
|
|
729
|
+
hooks: {
|
|
730
|
+
UserPromptExpansion: [{
|
|
731
|
+
hooks: [createClaudeCommandContextHook(() => session.activeInput?.commandContext)],
|
|
732
|
+
}],
|
|
733
|
+
Stop: [{ hooks: [async (input) => {
|
|
734
|
+
if (!input.agent_id)
|
|
735
|
+
session.observedEffort = input.effort?.level;
|
|
736
|
+
return {};
|
|
737
|
+
}] }],
|
|
738
|
+
},
|
|
945
739
|
// Canonical Canon verbs, in-process (projection 2 of canon.verbs.v1).
|
|
946
740
|
// Fresh instance per session: the SDK connects the instance to its own
|
|
947
741
|
// in-process transport, so instances are not shared across queries.
|
|
@@ -1975,6 +1769,8 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1975
1769
|
// ever saw streamed.
|
|
1976
1770
|
if (!isClaudeMainTurnMessage(msg.parent_tool_use_id))
|
|
1977
1771
|
break;
|
|
1772
|
+
if (!msg.error && msg.message.model)
|
|
1773
|
+
session.observedModel = msg.message.model;
|
|
1978
1774
|
// The assistant message is the first place the tool call's typed
|
|
1979
1775
|
// INPUT is visible — content_block_start carries only the name. It
|
|
1980
1776
|
// is what turns "Bash" into "Running: npm test" and TodoWrite into
|
|
@@ -2091,6 +1887,10 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
2091
1887
|
}
|
|
2092
1888
|
case 'system': {
|
|
2093
1889
|
const subtype = msg.subtype;
|
|
1890
|
+
const commandOutput = getClaudeNativeCommandOutput(msg);
|
|
1891
|
+
if (commandOutput && claudeInputOwnsTurnSlot(session.activeInput) && !isActiveTurnInterrupted()) {
|
|
1892
|
+
session.pendingFinalText = [session.pendingFinalText, commandOutput].filter(Boolean).join('\n');
|
|
1893
|
+
}
|
|
2094
1894
|
if (subtype === 'session_state_changed') {
|
|
2095
1895
|
const state = msg.state;
|
|
2096
1896
|
// The echo shares one field with the host's own reservation and
|
|
@@ -2541,12 +2341,11 @@ export async function main() {
|
|
|
2541
2341
|
if (signal.aborted)
|
|
2542
2342
|
return;
|
|
2543
2343
|
const activeSessionQuery = sessions.values().next().value?.query ?? null;
|
|
2544
|
-
|
|
2344
|
+
await refreshClaudeRuntimeMetadata({
|
|
2545
2345
|
cwd: workingDir,
|
|
2546
2346
|
activeQuery: activeSessionQuery,
|
|
2547
2347
|
}).catch((error) => {
|
|
2548
2348
|
console.error('[canon-host] Failed to refresh Claude runtime metadata:', error);
|
|
2549
|
-
return runtimeMetadata;
|
|
2550
2349
|
});
|
|
2551
2350
|
if (signal.aborted)
|
|
2552
2351
|
return;
|
|
@@ -2570,27 +2369,15 @@ export async function main() {
|
|
|
2570
2369
|
if (signal.aborted)
|
|
2571
2370
|
return;
|
|
2572
2371
|
const results = await Promise.allSettled(Array.from(knownConversationIds).map(async (conversationId) => {
|
|
2573
|
-
const session = sessions.get(conversationId);
|
|
2574
|
-
const workspaceId = session
|
|
2575
|
-
? resolveWorkspaceIdForBaseCwd(session.environment.baseCwd)
|
|
2576
|
-
: runtimeDescriptor.defaultWorkspaceId;
|
|
2577
|
-
const workspace = workspaceOptions.find((option) => option.id === workspaceId) ?? null;
|
|
2578
2372
|
const descriptor = runtimeDescriptor.runtimeDescriptor;
|
|
2579
2373
|
if (!descriptor)
|
|
2580
2374
|
return;
|
|
2581
|
-
|
|
2375
|
+
await runtimeState.writeRuntimeInfo(conversationId, {
|
|
2376
|
+
surfaceMode: 'host',
|
|
2377
|
+
// The shared publisher sanitizes configuration while retaining commands.
|
|
2582
2378
|
descriptor,
|
|
2583
|
-
|
|
2584
|
-
workspaceLabel: workspace?.label ?? workspaceId ?? null,
|
|
2585
|
-
resolvedCwd: session?.cwd ?? workspace?.cwd ?? workingDir,
|
|
2586
|
-
workspaceRootId: workspace?.workspaceRootId ?? null,
|
|
2587
|
-
workspaceRelativePath: workspace?.workspaceRelativePath ?? null,
|
|
2588
|
-
executionMode: session?.environment.mode ?? null,
|
|
2589
|
-
executionBranch: session?.environment.branch ?? null,
|
|
2590
|
-
worktreePath: session?.environment.worktreePath ?? null,
|
|
2591
|
-
fallbackReason: resolveExecutionFallbackReason(session?.environment),
|
|
2379
|
+
facts: buildClaudeSessionFacts(sessions.get(conversationId)),
|
|
2592
2380
|
});
|
|
2593
|
-
await runtimeState.writeRuntimeInfo(conversationId, payload);
|
|
2594
2381
|
}));
|
|
2595
2382
|
const failure = results.find((result) => result.status === 'rejected');
|
|
2596
2383
|
if (failure?.status === 'rejected')
|
|
@@ -2715,9 +2502,6 @@ export async function main() {
|
|
|
2715
2502
|
client,
|
|
2716
2503
|
conversationCache,
|
|
2717
2504
|
});
|
|
2718
|
-
function resolveWorkspaceIdForBaseCwd(baseCwd) {
|
|
2719
|
-
return workspaceOptions.find((option) => option.cwd === baseCwd)?.id;
|
|
2720
|
-
}
|
|
2721
2505
|
async function refreshKnownConversationIds(force = false) {
|
|
2722
2506
|
if (!force && Date.now() - lastKnownConversationRefreshAt < HEARTBEAT_MS) {
|
|
2723
2507
|
return;
|
|
@@ -3155,11 +2939,20 @@ export async function main() {
|
|
|
3155
2939
|
replyContext,
|
|
3156
2940
|
message: m,
|
|
3157
2941
|
});
|
|
3158
|
-
const
|
|
2942
|
+
const commandInput = buildClaudeNativeCommandInput({
|
|
2943
|
+
text: m.text,
|
|
2944
|
+
senderType: m.senderType,
|
|
3159
2945
|
promptText,
|
|
2946
|
+
commands: runtimeMetadata.commands,
|
|
2947
|
+
});
|
|
2948
|
+
const messageContent = await buildCanonUserContent({
|
|
2949
|
+
promptText: commandInput.promptText,
|
|
3160
2950
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
3161
2951
|
});
|
|
3162
|
-
const turnModes =
|
|
2952
|
+
const turnModes = {
|
|
2953
|
+
...resolveClaudeTurnModes(participantContext),
|
|
2954
|
+
commandContext: commandInput.commandContext,
|
|
2955
|
+
};
|
|
3163
2956
|
session.enqueueInbound({
|
|
3164
2957
|
type: 'user',
|
|
3165
2958
|
message: {
|
|
@@ -3424,12 +3217,19 @@ export async function main() {
|
|
|
3424
3217
|
replyContext,
|
|
3425
3218
|
message: m,
|
|
3426
3219
|
});
|
|
3427
|
-
const
|
|
3220
|
+
const commandInput = buildClaudeNativeCommandInput({
|
|
3221
|
+
text: m.text,
|
|
3222
|
+
senderType: m.senderType,
|
|
3428
3223
|
promptText,
|
|
3224
|
+
commands: runtimeMetadata.commands,
|
|
3225
|
+
});
|
|
3226
|
+
const messageContent = await buildCanonUserContent({
|
|
3227
|
+
promptText: commandInput.promptText,
|
|
3429
3228
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
3430
3229
|
});
|
|
3431
3230
|
const turnModes = {
|
|
3432
3231
|
...resolveClaudeTurnModes(participantContext),
|
|
3232
|
+
commandContext: commandInput.commandContext,
|
|
3433
3233
|
replyAuthority: payload.replyAuthority ?? null,
|
|
3434
3234
|
};
|
|
3435
3235
|
session.enqueueInbound({
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HookCallback, SDKControlReloadPluginsResponse } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
/** Keep SDK command syntax intact; pass Canon context to the expansion hook. */
|
|
3
|
+
export declare function buildClaudeNativeCommandInput(input: {
|
|
4
|
+
text?: string | null;
|
|
5
|
+
senderType?: string;
|
|
6
|
+
promptText: string;
|
|
7
|
+
commands: SDKControlReloadPluginsResponse['commands'];
|
|
8
|
+
}): {
|
|
9
|
+
promptText: string;
|
|
10
|
+
commandContext?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function createClaudeCommandContextHook(getContext: () => string | undefined): HookCallback;
|
|
13
|
+
/** Local SDK commands bypass assistant messages but still have a visible result. */
|
|
14
|
+
export declare function getClaudeNativeCommandOutput(message: {
|
|
15
|
+
type: string;
|
|
16
|
+
subtype?: string;
|
|
17
|
+
content?: unknown;
|
|
18
|
+
}): string | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Keep SDK command syntax intact; pass Canon context to the expansion hook. */
|
|
2
|
+
export function buildClaudeNativeCommandInput(input) {
|
|
3
|
+
const text = input.text?.trim();
|
|
4
|
+
const name = text?.match(/^\/([A-Za-z0-9:_-]+)(?:\s|$)/)?.[1];
|
|
5
|
+
if (input.senderType !== 'human' || !name || !input.commands.some((command) => ([command.name, ...(command.aliases ?? [])].some((alias) => (alias.replace(/^\//, '').toLowerCase() === name.toLowerCase())))))
|
|
6
|
+
return { promptText: input.promptText };
|
|
7
|
+
return {
|
|
8
|
+
promptText: text,
|
|
9
|
+
...(text !== input.promptText ? { commandContext: input.promptText } : {}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function createClaudeCommandContextHook(getContext) {
|
|
13
|
+
return async (input) => {
|
|
14
|
+
const additionalContext = getContext();
|
|
15
|
+
return input.hook_event_name === 'UserPromptExpansion' && additionalContext
|
|
16
|
+
? { hookSpecificOutput: { hookEventName: 'UserPromptExpansion', additionalContext } }
|
|
17
|
+
: {};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Local SDK commands bypass assistant messages but still have a visible result. */
|
|
21
|
+
export function getClaudeNativeCommandOutput(message) {
|
|
22
|
+
return message.type === 'system' && message.subtype === 'local_command_output'
|
|
23
|
+
&& typeof message.content === 'string' && message.content.trim()
|
|
24
|
+
? message.content.trim()
|
|
25
|
+
: null;
|
|
26
|
+
}
|
package/dist/session-state.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { TurnArtifactRoutingDecision, TurnArtifactSnapshot } from '@canonms
|
|
|
5
5
|
export type ClaudeInputKind = 'seed' | 'canon';
|
|
6
6
|
export type ClaudeArtifactRoutingMode = 'workspace-generated' | 'disabled';
|
|
7
7
|
export interface ClaudeInputEnvelope {
|
|
8
|
+
commandContext?: string;
|
|
8
9
|
kind: ClaudeInputKind;
|
|
9
10
|
/** Correlates this envelope with its turn result's `user_message_uuid`. */
|
|
10
11
|
messageUuid: string;
|
|
@@ -32,6 +33,7 @@ export interface ClaudeInputEnvelope {
|
|
|
32
33
|
* grouped so they travel together from the enqueue site onto the envelope.
|
|
33
34
|
*/
|
|
34
35
|
export interface ClaudeTurnModes {
|
|
36
|
+
commandContext?: string;
|
|
35
37
|
artifactRoutingMode?: ClaudeArtifactRoutingMode;
|
|
36
38
|
turnVerbosity?: TurnVerbosity;
|
|
37
39
|
replyAuthority?: AgentReplyAuthorityV1 | null;
|
|
@@ -96,6 +98,7 @@ export interface ClaudeCompletedTurnState {
|
|
|
96
98
|
turnActivity?: ClaudeTurnActivityState;
|
|
97
99
|
}
|
|
98
100
|
export declare function createClaudeInputEnvelope(input: {
|
|
101
|
+
commandContext?: string;
|
|
99
102
|
kind: ClaudeInputKind;
|
|
100
103
|
msg: SDKUserMessage;
|
|
101
104
|
intent?: DeliveryIntent;
|
package/dist/session-state.js
CHANGED
|
@@ -10,6 +10,7 @@ export function createClaudeInputEnvelope(input) {
|
|
|
10
10
|
// from a mutable slot and arrival order.
|
|
11
11
|
const messageUuid = randomUUID();
|
|
12
12
|
return {
|
|
13
|
+
...(input.commandContext ? { commandContext: input.commandContext } : {}),
|
|
13
14
|
kind: input.kind,
|
|
14
15
|
messageUuid,
|
|
15
16
|
msg: { ...input.msg, uuid: messageUuid },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.4",
|
|
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,10 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "0.3.228",
|
|
34
|
-
"@canonmsg/agent-sdk": "^10.2.
|
|
34
|
+
"@canonmsg/agent-sdk": "^10.2.1",
|
|
35
35
|
"@canonmsg/agent-tools": "^0.9.0",
|
|
36
36
|
"@canonmsg/coding-agent-host": "^0.7.0",
|
|
37
|
-
"@canonmsg/core": "^12.
|
|
37
|
+
"@canonmsg/core": "^12.3.1",
|
|
38
38
|
"@canonmsg/rich-cards": "^0.10.4",
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
40
40
|
},
|