@canonmsg/claude-code-plugin 0.34.3 → 0.34.5
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/README.md +1 -1
- package/dist/host.d.ts +87 -6
- package/dist/host.js +65 -103
- 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 +11 -13
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Canon Plugin for Claude Code
|
|
2
2
|
|
|
3
|
-
Connect Claude Code to [Canon](https://
|
|
3
|
+
Connect Claude Code to [Canon](https://canonmail.com/agents) — a messaging app where AI agents are first-class citizens. Control Claude Code from your phone.
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
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,
|
|
@@ -551,6 +470,16 @@ async function readApprovalDiffPreImage(filePath) {
|
|
|
551
470
|
throw error;
|
|
552
471
|
}
|
|
553
472
|
}
|
|
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;
|
|
482
|
+
}
|
|
554
483
|
// ── Constants ───────────────────────────────────────────────────────
|
|
555
484
|
const MAX_SESSIONS = 5;
|
|
556
485
|
// IDLE_TIMEOUT_MS (30 minutes) is shared with the other coding-agent hosts.
|
|
@@ -692,6 +621,10 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
692
621
|
// from here.
|
|
693
622
|
if (!claudeInputOwnsTurnSlot(input))
|
|
694
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;
|
|
695
628
|
session.activeInput = input;
|
|
696
629
|
}
|
|
697
630
|
const inputStream = {
|
|
@@ -793,6 +726,16 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
793
726
|
...(config.effort ? { effort: config.effort } : {}),
|
|
794
727
|
settings: { forceLoginMethod: 'claudeai' },
|
|
795
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
|
+
},
|
|
796
739
|
// Canonical Canon verbs, in-process (projection 2 of canon.verbs.v1).
|
|
797
740
|
// Fresh instance per session: the SDK connects the instance to its own
|
|
798
741
|
// in-process transport, so instances are not shared across queries.
|
|
@@ -1826,6 +1769,8 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1826
1769
|
// ever saw streamed.
|
|
1827
1770
|
if (!isClaudeMainTurnMessage(msg.parent_tool_use_id))
|
|
1828
1771
|
break;
|
|
1772
|
+
if (!msg.error && msg.message.model)
|
|
1773
|
+
session.observedModel = msg.message.model;
|
|
1829
1774
|
// The assistant message is the first place the tool call's typed
|
|
1830
1775
|
// INPUT is visible — content_block_start carries only the name. It
|
|
1831
1776
|
// is what turns "Bash" into "Running: npm test" and TodoWrite into
|
|
@@ -1942,6 +1887,10 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1942
1887
|
}
|
|
1943
1888
|
case 'system': {
|
|
1944
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
|
+
}
|
|
1945
1894
|
if (subtype === 'session_state_changed') {
|
|
1946
1895
|
const state = msg.state;
|
|
1947
1896
|
// The echo shares one field with the host's own reservation and
|
|
@@ -2425,12 +2374,9 @@ export async function main() {
|
|
|
2425
2374
|
return;
|
|
2426
2375
|
await runtimeState.writeRuntimeInfo(conversationId, {
|
|
2427
2376
|
surfaceMode: 'host',
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
supportsInputInterrupt: descriptor.supportsInputInterrupt,
|
|
2432
|
-
streamingTextMode: descriptor.streamingTextMode,
|
|
2433
|
-
},
|
|
2377
|
+
// The shared publisher sanitizes configuration while retaining commands.
|
|
2378
|
+
descriptor,
|
|
2379
|
+
facts: buildClaudeSessionFacts(sessions.get(conversationId)),
|
|
2434
2380
|
});
|
|
2435
2381
|
}));
|
|
2436
2382
|
const failure = results.find((result) => result.status === 'rejected');
|
|
@@ -2993,11 +2939,20 @@ export async function main() {
|
|
|
2993
2939
|
replyContext,
|
|
2994
2940
|
message: m,
|
|
2995
2941
|
});
|
|
2996
|
-
const
|
|
2942
|
+
const commandInput = buildClaudeNativeCommandInput({
|
|
2943
|
+
text: m.text,
|
|
2944
|
+
senderType: m.senderType,
|
|
2997
2945
|
promptText,
|
|
2946
|
+
commands: runtimeMetadata.commands,
|
|
2947
|
+
});
|
|
2948
|
+
const messageContent = await buildCanonUserContent({
|
|
2949
|
+
promptText: commandInput.promptText,
|
|
2998
2950
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
2999
2951
|
});
|
|
3000
|
-
const turnModes =
|
|
2952
|
+
const turnModes = {
|
|
2953
|
+
...resolveClaudeTurnModes(participantContext),
|
|
2954
|
+
commandContext: commandInput.commandContext,
|
|
2955
|
+
};
|
|
3001
2956
|
session.enqueueInbound({
|
|
3002
2957
|
type: 'user',
|
|
3003
2958
|
message: {
|
|
@@ -3262,12 +3217,19 @@ export async function main() {
|
|
|
3262
3217
|
replyContext,
|
|
3263
3218
|
message: m,
|
|
3264
3219
|
});
|
|
3265
|
-
const
|
|
3220
|
+
const commandInput = buildClaudeNativeCommandInput({
|
|
3221
|
+
text: m.text,
|
|
3222
|
+
senderType: m.senderType,
|
|
3266
3223
|
promptText,
|
|
3224
|
+
commands: runtimeMetadata.commands,
|
|
3225
|
+
});
|
|
3226
|
+
const messageContent = await buildCanonUserContent({
|
|
3227
|
+
promptText: commandInput.promptText,
|
|
3267
3228
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
3268
3229
|
});
|
|
3269
3230
|
const turnModes = {
|
|
3270
3231
|
...resolveClaudeTurnModes(participantContext),
|
|
3232
|
+
commandContext: commandInput.commandContext,
|
|
3271
3233
|
replyAuthority: payload.replyAuthority ?? null,
|
|
3272
3234
|
};
|
|
3273
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.5",
|
|
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,11 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "0.3.228",
|
|
34
|
-
"@canonmsg/agent-sdk": "^10.
|
|
35
|
-
"@canonmsg/agent-tools": "^0.9.
|
|
36
|
-
"@canonmsg/coding-agent-host": "^0.7.
|
|
37
|
-
"@canonmsg/core": "^12.
|
|
38
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
34
|
+
"@canonmsg/agent-sdk": "^10.3.0",
|
|
35
|
+
"@canonmsg/agent-tools": "^0.9.1",
|
|
36
|
+
"@canonmsg/coding-agent-host": "^0.7.1",
|
|
37
|
+
"@canonmsg/core": "^12.4.0",
|
|
38
|
+
"@canonmsg/rich-cards": "^0.10.5",
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
@@ -49,12 +49,7 @@
|
|
|
49
49
|
"ai-agents",
|
|
50
50
|
"messaging"
|
|
51
51
|
],
|
|
52
|
-
"
|
|
53
|
-
"type": "git",
|
|
54
|
-
"url": "https://github.com/HeyBobChan/canon",
|
|
55
|
-
"directory": "packages/claude-code-plugin"
|
|
56
|
-
},
|
|
57
|
-
"homepage": "https://github.com/HeyBobChan/canon/tree/main/packages/claude-code-plugin",
|
|
52
|
+
"homepage": "https://canonmail.com/agents/integrations#claude-code",
|
|
58
53
|
"publishConfig": {
|
|
59
54
|
"access": "public"
|
|
60
55
|
},
|
|
@@ -63,5 +58,8 @@
|
|
|
63
58
|
"typescript": "~5.7.0",
|
|
64
59
|
"vitest": "^4.1.8"
|
|
65
60
|
},
|
|
66
|
-
"license": "MIT"
|
|
61
|
+
"license": "MIT",
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://canonmail.com/support"
|
|
64
|
+
}
|
|
67
65
|
}
|