@canonmsg/claude-code-plugin 0.32.0 → 0.33.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/.claude-plugin/plugin.json +1 -1
- package/dist/canon-user-content.d.ts +3 -3
- package/dist/canon-user-content.js +4 -4
- package/dist/host.d.ts +4 -2
- package/dist/host.js +58 -123
- package/dist/register.js +0 -1
- package/dist/server.js +11 -7
- package/dist/session-state.d.ts +0 -4
- package/dist/session-state.js +0 -3
- package/dist/tool-policy.js +2 -6
- package/package.json +6 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { type AnthropicImageBudgetOptions, type MaterializedCanonAttachment } from '@canonmsg/agent-sdk';
|
|
3
|
-
import { renderCanonHostInboundContent, type CanonReplyContext
|
|
3
|
+
import { renderCanonHostInboundContent, type CanonReplyContext } from '@canonmsg/core';
|
|
4
4
|
type ClaudeRenderableInboundMessage = Parameters<typeof renderCanonHostInboundContent>[0];
|
|
5
5
|
/**
|
|
6
6
|
* Claude-only rendering policy for Canon media. Core deliberately omits all
|
|
@@ -8,8 +8,8 @@ type ClaudeRenderableInboundMessage = Parameters<typeof renderCanonHostInboundCo
|
|
|
8
8
|
* that the bounded runtime did not materialize, while materialized images keep
|
|
9
9
|
* their local-path placeholder without a duplicate link.
|
|
10
10
|
*/
|
|
11
|
-
export declare function renderClaudeInboundContent(message: ClaudeRenderableInboundMessage, materialized?: ReadonlyArray<MaterializedCanonAttachment
|
|
12
|
-
export declare function withClaudeReplyContextMediaReferences(replyContext: CanonReplyContext | null, materialized: ReadonlyArray<MaterializedCanonAttachment
|
|
11
|
+
export declare function renderClaudeInboundContent(message: ClaudeRenderableInboundMessage, materialized?: ReadonlyArray<MaterializedCanonAttachment>): string;
|
|
12
|
+
export declare function withClaudeReplyContextMediaReferences(replyContext: CanonReplyContext | null, materialized: ReadonlyArray<MaterializedCanonAttachment>): CanonReplyContext | null;
|
|
13
13
|
/**
|
|
14
14
|
* Build the Claude Code SDK's multimodal user content without allowing native
|
|
15
15
|
* image blocks to consume the whole request. The prompt text is preserved
|
|
@@ -28,8 +28,8 @@ function validatedPromptAttachmentUrl(value) {
|
|
|
28
28
|
* that the bounded runtime did not materialize, while materialized images keep
|
|
29
29
|
* their local-path placeholder without a duplicate link.
|
|
30
30
|
*/
|
|
31
|
-
export function renderClaudeInboundContent(message, materialized = []
|
|
32
|
-
const rendered = renderCanonHostInboundContent(message, materialized
|
|
31
|
+
export function renderClaudeInboundContent(message, materialized = []) {
|
|
32
|
+
const rendered = renderCanonHostInboundContent(message, materialized);
|
|
33
33
|
const materializedIndexes = new Set(materialized.map((attachment) => attachment.index));
|
|
34
34
|
const imageLinks = (message.attachments ?? []).flatMap((attachment, index) => {
|
|
35
35
|
if (attachment.kind !== 'image' || materializedIndexes.has(index))
|
|
@@ -39,7 +39,7 @@ export function renderClaudeInboundContent(message, materialized = [], renderOpt
|
|
|
39
39
|
});
|
|
40
40
|
return imageLinks.length > 0 ? `${rendered}\n${imageLinks.join('\n')}` : rendered;
|
|
41
41
|
}
|
|
42
|
-
export function withClaudeReplyContextMediaReferences(replyContext, materialized
|
|
42
|
+
export function withClaudeReplyContextMediaReferences(replyContext, materialized) {
|
|
43
43
|
if (!replyContext?.found)
|
|
44
44
|
return replyContext;
|
|
45
45
|
return {
|
|
@@ -50,7 +50,7 @@ export function withClaudeReplyContextMediaReferences(replyContext, materialized
|
|
|
50
50
|
attachments: replyContext.attachments,
|
|
51
51
|
contactCard: replyContext.contactCard,
|
|
52
52
|
senderType: replyContext.senderType ?? undefined,
|
|
53
|
-
}, materialized
|
|
53
|
+
}, materialized),
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
/**
|
package/dist/host.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type PermissionResult, type SDKControlReloadPluginsResponse } from '@anthropic-ai/claude-agent-sdk';
|
|
3
3
|
import { type RecoveryCheckpointTracker } from '@canonmsg/coding-agent-host';
|
|
4
|
-
import { type CanonRuntimeCommandDescriptor, type HostInboundParticipantContext as InboundParticipantContext, type CanonReplyContext, type MessageCreatedPayload, type ResolvedAgentBehaviorPolicy, type TurnVerbosityConfig } from '@canonmsg/core';
|
|
4
|
+
import { type CanonRuntimeCommandDescriptor, type ExecutionEnvironmentMode, type HostInboundParticipantContext as InboundParticipantContext, type CanonReplyContext, type MessageCreatedPayload, type ResolvedAgentBehaviorPolicy, type TurnVerbosityConfig } from '@canonmsg/core';
|
|
5
5
|
/**
|
|
6
6
|
* `--turn-verbosity` beats `CANON_TURN_VERBOSITY`; `null` means "unset, use the
|
|
7
7
|
* per-conversation-type default".
|
|
@@ -25,9 +25,11 @@ export declare function buildClaudePlanAllowResult(input: Record<string, unknown
|
|
|
25
25
|
* through. Canon-injected aliases keep precedence over native names.
|
|
26
26
|
*/
|
|
27
27
|
export declare function buildNativeSlashCommandDescriptors(native: SDKControlReloadPluginsResponse['commands'], reservedAliases: ReadonlySet<string>): CanonRuntimeCommandDescriptor[];
|
|
28
|
+
export declare function resolveSessionExecutionMode(config: {
|
|
29
|
+
executionMode?: ExecutionEnvironmentMode;
|
|
30
|
+
} | null | undefined, defaultExecutionMode?: ExecutionEnvironmentMode): ExecutionEnvironmentMode;
|
|
28
31
|
export declare function createClaudeRecoveryCheckpointTracker(persist: (messageId: string) => boolean): RecoveryCheckpointTracker;
|
|
29
32
|
export declare const NO_REPLY_TOOL_NAME: string;
|
|
30
|
-
export declare const REACH_OUT_TOOL_NAME = "mcp__canon__send_to";
|
|
31
33
|
export declare function buildCanonPrompt(input: {
|
|
32
34
|
content: string;
|
|
33
35
|
conversationId: string;
|
package/dist/host.js
CHANGED
|
@@ -29,9 +29,9 @@ 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 { 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, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId,
|
|
32
|
+
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, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, parseTurnVerbosityConfig, prepareConversationEnvironment, resolveCanonAgent, verifyResolvedAgentEnvironment, decideAutoReply, initRTDBAuth, isChunkedSendMessageError, sendMessageWithRetry, sendMessageWithRetryChunked, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, resolveTurnVerbosity, shouldTriggerAgentTurn, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
33
33
|
import { runCli } from '@canonmsg/core';
|
|
34
|
-
import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer
|
|
34
|
+
import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
|
|
35
35
|
import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
|
|
36
36
|
import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
|
|
37
37
|
import { applyClaudeSessionControl, boundClaudeFinalMetadata, buildClaudeFinalChunkingOptions, buildClaudeFinalMessageId, buildClaudeFinalTurnMetadata, buildClaudePendingFinalDelivery, buildClaudeTurnFailureNotice, buildTruncatedFinalText, buildUndeliverableFinalNotice, canDrainClaudeQueuedInput, beginClaudeAssistantResponse, claimClaudeTextSegmentId, claudeFinalWillChunk, classifyFinalDeliveryFailure, claudeInputOwnsTurnSlot, createClaudeTurnActivityState, decideClaudeControlSignalAction, decideClaudeInboundDispatch, dispatchClaudeInput, endClaudeAssistantResponse, isClaudeTurnSlotReserved, readClaudeFinalDeliveryResume, releaseClaudeTurnSlot, runClaudeExhaustedFinalDelivery, shouldApplyClaudeEchoedSessionState, shouldReleaseClaudeTurnSlot, isClaudeMainTurnMessage, openClaudeTurn, planClaudeAssistantTrail, planClaudeStreamingWrite, planClaudeToolCallStart, planClaudeToolProgress, planClaudeToolResults, claudeFinalTurnTrail, publishedClaudeTurnState, rememberDispatchedClaudeInput, resetClaudeTurnActivityState, shouldOpenClaudeTurnOnRunning, shouldStopTypingDotsOnStreamedText, isOpenClaudeTurnState, takeClaudeResultOwner, takeClaudeToolBlockIdByIndex, claudeModelInfoToOption, claudeOriginForCanonSender, composeClaudeTurnFinal, describeUndeliveredClaudeFinal, confirmClaudeInterrupt, createClaudeInputEnvelope, deriveClaudeSupplementalModelProbes, formatClaudeCliVersion, formatClaudeControlError, isClaudeCustomModelOption, isSupportedClaudeCliVersion, mergeClaudeDiscoveredModelOptions, parseClaudeCliVersion, resolveClaudeTurnResponseRouting, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, shouldRouteClaudeTurnArtifacts, MINIMUM_CLAUDE_CLI_VERSION, } from './session-state.js';
|
|
@@ -53,6 +53,8 @@ COMMON FLAGS
|
|
|
53
53
|
--cwd <path> Project directory to run Claude Code from
|
|
54
54
|
--workspace <path> Additional project to expose in Canon
|
|
55
55
|
--workspace-root <path> Discover projects under an approved root
|
|
56
|
+
--default-execution-mode <worktree|locked>
|
|
57
|
+
Default for new Canon conversations
|
|
56
58
|
--runtime-visibility <normal|minimal|full>
|
|
57
59
|
Detail visibility preset
|
|
58
60
|
--show-runtime-detail <field>
|
|
@@ -271,6 +273,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
271
273
|
workspaces: input.workspaces,
|
|
272
274
|
workspaceRoots: input.workspaceRoots,
|
|
273
275
|
executionModes: input.executionModes,
|
|
276
|
+
defaultExecutionMode: input.defaultExecutionMode,
|
|
274
277
|
permissionModes: CLAUDE_PERMISSION_MODE_OPTIONS,
|
|
275
278
|
defaultPermissionMode: 'default',
|
|
276
279
|
effortOptions: [...EFFORT_OPTIONS],
|
|
@@ -303,11 +306,7 @@ function buildClaudeRuntimeDescriptor(input) {
|
|
|
303
306
|
activation: { kind: 'control', controlId: 'permissionMode', value: 'plan' },
|
|
304
307
|
},
|
|
305
308
|
],
|
|
306
|
-
admissionActions:
|
|
307
|
-
...HOST_ADMISSION_ACTIONS_DISABLED,
|
|
308
|
-
requestContact: true,
|
|
309
|
-
reachOut: true,
|
|
310
|
-
},
|
|
309
|
+
admissionActions: HOST_ADMISSION_ACTIONS_DISABLED,
|
|
311
310
|
presentation: input.presentation,
|
|
312
311
|
commands,
|
|
313
312
|
});
|
|
@@ -425,7 +424,6 @@ async function loadSessionConfig(conversationId, agentId, rtdb) {
|
|
|
425
424
|
agentId,
|
|
426
425
|
rtdb,
|
|
427
426
|
extraStringFields: ['permissionMode', 'effort'],
|
|
428
|
-
retryMissingMs: 3_000,
|
|
429
427
|
});
|
|
430
428
|
return {
|
|
431
429
|
...config,
|
|
@@ -436,10 +434,15 @@ async function loadSessionConfig(conversationId, agentId, rtdb) {
|
|
|
436
434
|
ultracode: parseClaudeUltracodeMode(config?.runtimeControlValues?.ultracode),
|
|
437
435
|
};
|
|
438
436
|
}
|
|
439
|
-
function resolveSessionExecutionMode(config) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
437
|
+
export function resolveSessionExecutionMode(config, defaultExecutionMode = 'worktree') {
|
|
438
|
+
return config?.executionMode ?? defaultExecutionMode;
|
|
439
|
+
}
|
|
440
|
+
function resolveConfiguredDefaultExecutionMode(value) {
|
|
441
|
+
if (value == null || value === '')
|
|
442
|
+
return 'worktree';
|
|
443
|
+
if (value === 'worktree' || value === 'locked')
|
|
444
|
+
return value;
|
|
445
|
+
throw new Error('--default-execution-mode must be worktree or locked');
|
|
443
446
|
}
|
|
444
447
|
function resolveWorkspaceCwd(config) {
|
|
445
448
|
return resolveHostWorkspaceCwd({
|
|
@@ -757,16 +760,6 @@ const CLAUDE_RUNTIME_CAPABILITIES = {
|
|
|
757
760
|
*/
|
|
758
761
|
const NO_REPLY_VERB = 'no_reply';
|
|
759
762
|
export const NO_REPLY_TOOL_NAME = `mcp__${CANON_VERB_MCP_SERVER_NAME}__${NO_REPLY_VERB}`;
|
|
760
|
-
export const REACH_OUT_TOOL_NAME = `mcp__${CANON_VERB_MCP_SERVER_NAME}__send_to`;
|
|
761
|
-
function ownerBoundRenderOptions(isOwner) {
|
|
762
|
-
const admissionActions = isOwner
|
|
763
|
-
? { ...HOST_ADMISSION_ACTIONS_DISABLED, requestContact: true, reachOut: true }
|
|
764
|
-
: HOST_ADMISSION_ACTIONS_DISABLED;
|
|
765
|
-
return {
|
|
766
|
-
admissionActions,
|
|
767
|
-
...(admissionActions.reachOut ? { reachOutToolName: REACH_OUT_TOOL_NAME } : {}),
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
763
|
export function buildCanonPrompt(input) {
|
|
771
764
|
return renderCodingHostInboundPrompt(buildCanonInboundFrameV1(buildCanonTurnContextV2({
|
|
772
765
|
content: input.content,
|
|
@@ -799,8 +792,8 @@ function resolveClaudeTurnModes(participantContext) {
|
|
|
799
792
|
}),
|
|
800
793
|
};
|
|
801
794
|
}
|
|
802
|
-
function renderInboundContent(message, materialized
|
|
803
|
-
return renderClaudeInboundContent(message, materialized
|
|
795
|
+
function renderInboundContent(message, materialized) {
|
|
796
|
+
return renderClaudeInboundContent(message, materialized);
|
|
804
797
|
}
|
|
805
798
|
async function materializePromptReplyContext(input) {
|
|
806
799
|
if (!input.replyContext?.found || !input.replyContext.attachments?.length) {
|
|
@@ -813,27 +806,17 @@ async function materializePromptReplyContext(input) {
|
|
|
813
806
|
});
|
|
814
807
|
return {
|
|
815
808
|
...result,
|
|
816
|
-
replyContext: withClaudeReplyContextMediaReferences(result.replyContext, result.materialized
|
|
809
|
+
replyContext: withClaudeReplyContextMediaReferences(result.replyContext, result.materialized),
|
|
817
810
|
};
|
|
818
811
|
}
|
|
819
812
|
catch (error) {
|
|
820
813
|
console.error(`${input.logPrefix} Failed to materialize replied-to media:`, error instanceof Error ? error.message : error);
|
|
821
814
|
return {
|
|
822
|
-
replyContext: withClaudeReplyContextMediaReferences(input.replyContext, []
|
|
815
|
+
replyContext: withClaudeReplyContextMediaReferences(input.replyContext, []),
|
|
823
816
|
materialized: [],
|
|
824
817
|
};
|
|
825
818
|
}
|
|
826
819
|
}
|
|
827
|
-
function ownerBoundReplyContactTarget(replyContext) {
|
|
828
|
-
const card = replyContext?.found ? replyContext.contactCard : undefined;
|
|
829
|
-
if (!card?.userId)
|
|
830
|
-
return undefined;
|
|
831
|
-
return {
|
|
832
|
-
targetUserId: card.userId,
|
|
833
|
-
...(card.canonContactId ? { canonContactId: card.canonContactId } : {}),
|
|
834
|
-
sourceCardMessageId: replyContext.messageId,
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
820
|
// ── Session factory ─────────────────────────────────────────────────
|
|
838
821
|
function createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
839
822
|
const { cwd } = environment;
|
|
@@ -852,7 +835,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
852
835
|
messageQueue.push(input);
|
|
853
836
|
}
|
|
854
837
|
};
|
|
855
|
-
let enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}
|
|
838
|
+
let enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}) => {
|
|
856
839
|
sendInput(createClaudeInputEnvelope({
|
|
857
840
|
kind: 'canon',
|
|
858
841
|
msg,
|
|
@@ -861,7 +844,6 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
861
844
|
markAccepted,
|
|
862
845
|
isOwnerTurn,
|
|
863
846
|
requestingUserId,
|
|
864
|
-
replyContactTarget,
|
|
865
847
|
...turnModes,
|
|
866
848
|
}));
|
|
867
849
|
};
|
|
@@ -1008,24 +990,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1008
990
|
const turnKey = session.activeInput?.turnKey;
|
|
1009
991
|
if (turnKey)
|
|
1010
992
|
session.silencedTurnKeys.add(turnKey);
|
|
1011
|
-
}, {
|
|
1012
|
-
ownerBoundCommunication: {
|
|
1013
|
-
getContext: () => {
|
|
1014
|
-
const input = session.activeInput;
|
|
1015
|
-
if (!input?.isOwnerTurn || !input.sourceMessageId)
|
|
1016
|
-
return null;
|
|
1017
|
-
return {
|
|
1018
|
-
isOwnerTurn: true,
|
|
1019
|
-
conversationId,
|
|
1020
|
-
sourceMessageId: input.sourceMessageId,
|
|
1021
|
-
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1022
|
-
...(input.replyContactTarget
|
|
1023
|
-
? { replyContactTarget: input.replyContactTarget }
|
|
1024
|
-
: {}),
|
|
1025
|
-
};
|
|
1026
|
-
},
|
|
1027
|
-
},
|
|
1028
|
-
}),
|
|
993
|
+
}, { communication: config.communicationEnabled !== false }),
|
|
1029
994
|
},
|
|
1030
995
|
},
|
|
1031
996
|
...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
|
|
@@ -1214,7 +1179,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1214
1179
|
environment,
|
|
1215
1180
|
query: q,
|
|
1216
1181
|
sendInput,
|
|
1217
|
-
enqueueInbound: (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}
|
|
1182
|
+
enqueueInbound: (msg, intent = 'queue', sourceMessageId, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}) => enqueueInboundMessage(msg, intent, sourceMessageId, markAccepted, isOwnerTurn, requestingUserId, turnModes),
|
|
1218
1183
|
state: {
|
|
1219
1184
|
model: undefined,
|
|
1220
1185
|
permissionMode: config.permissionMode,
|
|
@@ -1968,7 +1933,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1968
1933
|
function hasInterruptibleSdkInput() {
|
|
1969
1934
|
return session.activeInput !== null && session.dispatchingInput === null;
|
|
1970
1935
|
}
|
|
1971
|
-
enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId = null, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}
|
|
1936
|
+
enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId = null, markAccepted = false, isOwnerTurn = false, requestingUserId = null, turnModes = {}) => {
|
|
1972
1937
|
session.lastActivity = Date.now();
|
|
1973
1938
|
const input = createClaudeInputEnvelope({
|
|
1974
1939
|
kind: 'canon',
|
|
@@ -1978,7 +1943,6 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1978
1943
|
markAccepted,
|
|
1979
1944
|
isOwnerTurn,
|
|
1980
1945
|
requestingUserId,
|
|
1981
|
-
replyContactTarget,
|
|
1982
1946
|
...turnModes,
|
|
1983
1947
|
});
|
|
1984
1948
|
const decision = decideClaudeInboundDispatch({
|
|
@@ -2430,11 +2394,13 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
2430
2394
|
availableModels: modelList,
|
|
2431
2395
|
availableWorkspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2432
2396
|
availableExecutionModes: config.availableExecutionModes,
|
|
2397
|
+
defaultExecutionMode: config.defaultExecutionMode,
|
|
2433
2398
|
runtimeDescriptor: buildClaudeRuntimeDescriptor({
|
|
2434
2399
|
models: modelList,
|
|
2435
2400
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2436
2401
|
workspaceRoots: workspaceRootMetadata,
|
|
2437
2402
|
executionModes: config.availableExecutionModes,
|
|
2403
|
+
defaultExecutionMode: config.defaultExecutionMode,
|
|
2438
2404
|
presentation: config.runtimePresentation,
|
|
2439
2405
|
}),
|
|
2440
2406
|
});
|
|
@@ -2464,6 +2430,7 @@ export async function main() {
|
|
|
2464
2430
|
cwd: { type: 'string' },
|
|
2465
2431
|
workspace: { type: 'string', multiple: true },
|
|
2466
2432
|
'workspace-root': { type: 'string', multiple: true },
|
|
2433
|
+
'default-execution-mode': { type: 'string' },
|
|
2467
2434
|
'runtime-visibility': { type: 'string' },
|
|
2468
2435
|
'show-runtime-detail': { type: 'string', multiple: true },
|
|
2469
2436
|
'hide-runtime-detail': { type: 'string', multiple: true },
|
|
@@ -2476,6 +2443,7 @@ export async function main() {
|
|
|
2476
2443
|
env: process.env.CANON_TURN_VERBOSITY,
|
|
2477
2444
|
onWarning: (message) => console.error(`[canon-host] ${message}`),
|
|
2478
2445
|
});
|
|
2446
|
+
const defaultExecutionMode = resolveConfiguredDefaultExecutionMode(args['default-execution-mode']);
|
|
2479
2447
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
2480
2448
|
const configuredWorkspaces = (args.workspace ?? []).filter((value) => typeof value === 'string');
|
|
2481
2449
|
const configuredWorkspaceRoots = (args['workspace-root'] ?? []).filter((value) => typeof value === 'string');
|
|
@@ -2516,11 +2484,13 @@ export async function main() {
|
|
|
2516
2484
|
let agentId;
|
|
2517
2485
|
let ownerId = null;
|
|
2518
2486
|
let ownerName = null;
|
|
2487
|
+
let communicationEnabled = true;
|
|
2519
2488
|
try {
|
|
2520
2489
|
const ctx = await client.getAgentMe();
|
|
2521
2490
|
agentId = ctx.agentId;
|
|
2522
2491
|
ownerId = ctx.ownerId;
|
|
2523
2492
|
ownerName = ctx.ownerName;
|
|
2493
|
+
communicationEnabled = ctx.outboundPolicy !== 'closed';
|
|
2524
2494
|
console.error(`[canon-host] Connected as ${ctx.displayName || agentId}`);
|
|
2525
2495
|
}
|
|
2526
2496
|
catch {
|
|
@@ -2567,15 +2537,6 @@ export async function main() {
|
|
|
2567
2537
|
hostMode: true,
|
|
2568
2538
|
rtdb,
|
|
2569
2539
|
});
|
|
2570
|
-
const recoveryCheckpointTrackers = new Map();
|
|
2571
|
-
function recoveryCheckpointsFor(conversationId) {
|
|
2572
|
-
let tracker = recoveryCheckpointTrackers.get(conversationId);
|
|
2573
|
-
if (!tracker) {
|
|
2574
|
-
tracker = createClaudeRecoveryCheckpointTracker((messageId) => persistConversationRecoveryCursor(conversationId, messageId));
|
|
2575
|
-
recoveryCheckpointTrackers.set(conversationId, tracker);
|
|
2576
|
-
}
|
|
2577
|
-
return tracker;
|
|
2578
|
-
}
|
|
2579
2540
|
let streamConnected = false;
|
|
2580
2541
|
const hostAvailableExecutionModes = [
|
|
2581
2542
|
...EXECUTION_ENVIRONMENT_MODES,
|
|
@@ -2587,6 +2548,7 @@ export async function main() {
|
|
|
2587
2548
|
hide: stringArgs(args['hide-runtime-detail']),
|
|
2588
2549
|
});
|
|
2589
2550
|
let runtimeDescriptor = {
|
|
2551
|
+
defaultExecutionMode,
|
|
2590
2552
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
2591
2553
|
defaultPermissionMode: 'default',
|
|
2592
2554
|
availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
@@ -2597,6 +2559,7 @@ export async function main() {
|
|
|
2597
2559
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2598
2560
|
workspaceRoots: workspaceRootMetadata,
|
|
2599
2561
|
executionModes: hostAvailableExecutionModes,
|
|
2562
|
+
defaultExecutionMode,
|
|
2600
2563
|
presentation: runtimePresentation,
|
|
2601
2564
|
}),
|
|
2602
2565
|
};
|
|
@@ -2675,6 +2638,7 @@ export async function main() {
|
|
|
2675
2638
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2676
2639
|
workspaceRoots: workspaceRootMetadata,
|
|
2677
2640
|
executionModes: hostAvailableExecutionModes,
|
|
2641
|
+
defaultExecutionMode,
|
|
2678
2642
|
presentation: runtimePresentation,
|
|
2679
2643
|
}),
|
|
2680
2644
|
};
|
|
@@ -2709,6 +2673,7 @@ export async function main() {
|
|
|
2709
2673
|
try {
|
|
2710
2674
|
runtimeModels = await detectRuntimeModels(workingDir);
|
|
2711
2675
|
runtimeDescriptor = {
|
|
2676
|
+
defaultExecutionMode,
|
|
2712
2677
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
2713
2678
|
defaultModel: runtimeModels[0]?.value,
|
|
2714
2679
|
defaultPermissionMode: 'default',
|
|
@@ -2721,12 +2686,14 @@ export async function main() {
|
|
|
2721
2686
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2722
2687
|
workspaceRoots: workspaceRootMetadata,
|
|
2723
2688
|
executionModes: hostAvailableExecutionModes,
|
|
2689
|
+
defaultExecutionMode,
|
|
2724
2690
|
presentation: runtimePresentation,
|
|
2725
2691
|
}),
|
|
2726
2692
|
};
|
|
2727
2693
|
}
|
|
2728
2694
|
catch {
|
|
2729
2695
|
runtimeDescriptor = {
|
|
2696
|
+
defaultExecutionMode,
|
|
2730
2697
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
2731
2698
|
defaultPermissionMode: 'default',
|
|
2732
2699
|
availablePermissionModes: [...CLAUDE_PERMISSION_MODE_OPTIONS],
|
|
@@ -2737,6 +2704,7 @@ export async function main() {
|
|
|
2737
2704
|
workspaces: buildPublicWorkspaceOptions(workspaceOptions),
|
|
2738
2705
|
workspaceRoots: workspaceRootMetadata,
|
|
2739
2706
|
executionModes: hostAvailableExecutionModes,
|
|
2707
|
+
defaultExecutionMode,
|
|
2740
2708
|
presentation: runtimePresentation,
|
|
2741
2709
|
}),
|
|
2742
2710
|
};
|
|
@@ -2759,6 +2727,15 @@ export async function main() {
|
|
|
2759
2727
|
}
|
|
2760
2728
|
// ── Session manager ──
|
|
2761
2729
|
const sessions = new Map();
|
|
2730
|
+
const recoveryCheckpointTrackers = new Map();
|
|
2731
|
+
function recoveryCheckpointsFor(conversationId) {
|
|
2732
|
+
let tracker = recoveryCheckpointTrackers.get(conversationId);
|
|
2733
|
+
if (!tracker) {
|
|
2734
|
+
tracker = createClaudeRecoveryCheckpointTracker((messageId) => persistConversationRecoveryCursor(conversationId, messageId));
|
|
2735
|
+
recoveryCheckpointTrackers.set(conversationId, tracker);
|
|
2736
|
+
}
|
|
2737
|
+
return tracker;
|
|
2738
|
+
}
|
|
2762
2739
|
const approvalManager = ownerId ? new ApprovalManager(client, agentId, ownerId) : null;
|
|
2763
2740
|
// Interactive runtime input (AskUserQuestion) works with or without an owner,
|
|
2764
2741
|
// so its poll engine is always present. Create + poll fold onto the built-in
|
|
@@ -2887,7 +2864,6 @@ export async function main() {
|
|
|
2887
2864
|
ownerName,
|
|
2888
2865
|
membershipChange: pendingMembershipChanges.get(input.conversationId) ?? null,
|
|
2889
2866
|
groupContextMode: getGroupContextMode(input.conversationId, conversation),
|
|
2890
|
-
renderOptions: input.renderOptions,
|
|
2891
2867
|
});
|
|
2892
2868
|
}
|
|
2893
2869
|
function closeSession(conversationId, options = {}) {
|
|
@@ -3025,7 +3001,7 @@ export async function main() {
|
|
|
3025
3001
|
}
|
|
3026
3002
|
const creation = (async () => {
|
|
3027
3003
|
const config = await loadSessionConfig(conversationId, agentId, rtdb);
|
|
3028
|
-
const sessionExecutionMode = resolveSessionExecutionMode(config);
|
|
3004
|
+
const sessionExecutionMode = resolveSessionExecutionMode(config, defaultExecutionMode);
|
|
3029
3005
|
const workspaceCwd = resolveWorkspaceCwd(config);
|
|
3030
3006
|
const environment = prepareConversationEnvironment({
|
|
3031
3007
|
agentId,
|
|
@@ -3086,12 +3062,14 @@ export async function main() {
|
|
|
3086
3062
|
ultracode: config?.ultracode,
|
|
3087
3063
|
availableModels: runtimeModels,
|
|
3088
3064
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
3065
|
+
defaultExecutionMode,
|
|
3089
3066
|
runtimePresentation,
|
|
3090
3067
|
approvalManager,
|
|
3091
3068
|
runtimeInputManager,
|
|
3092
3069
|
canRequestApproval: () => canRequestCanonApproval(conversationId),
|
|
3093
3070
|
runtimeId,
|
|
3094
3071
|
ownerId,
|
|
3072
|
+
communicationEnabled,
|
|
3095
3073
|
onInputCompleted: (sourceMessageId) => {
|
|
3096
3074
|
recoveryCheckpointsFor(conversationId).settle(sourceMessageId);
|
|
3097
3075
|
settleInboundMessageId(sourceMessageId, true);
|
|
@@ -3151,15 +3129,13 @@ export async function main() {
|
|
|
3151
3129
|
}
|
|
3152
3130
|
const sender = m.senderName || m.senderId;
|
|
3153
3131
|
const isOwner = m.isOwner ?? (ownerId != null && m.senderId === ownerId);
|
|
3154
|
-
const
|
|
3155
|
-
const content = renderInboundContent(m, materialized, renderOptions);
|
|
3132
|
+
const content = renderInboundContent(m, materialized);
|
|
3156
3133
|
const hydrated = await loadHydratedInboundContext({
|
|
3157
3134
|
conversationId: input.conversationId,
|
|
3158
3135
|
message: m,
|
|
3159
3136
|
senderName: sender,
|
|
3160
3137
|
isOwner,
|
|
3161
3138
|
hydratedPage: input.hydratedPage,
|
|
3162
|
-
renderOptions,
|
|
3163
3139
|
});
|
|
3164
3140
|
const behavior = input.hydratedPage?.behavior ?? hydrated.behavior;
|
|
3165
3141
|
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
@@ -3169,7 +3145,6 @@ export async function main() {
|
|
|
3169
3145
|
agentId,
|
|
3170
3146
|
conversationId: input.conversationId,
|
|
3171
3147
|
logPrefix: `[canon-host] [${input.conversationId.slice(0, 8)}]`,
|
|
3172
|
-
renderOptions,
|
|
3173
3148
|
});
|
|
3174
3149
|
const replyContext = replyMedia.replyContext;
|
|
3175
3150
|
const participantContext = hydrated.participantContext;
|
|
@@ -3202,7 +3177,7 @@ export async function main() {
|
|
|
3202
3177
|
return 'handled';
|
|
3203
3178
|
}
|
|
3204
3179
|
session.activeSelfContextId = activeSelfContextId;
|
|
3205
|
-
const
|
|
3180
|
+
const promptText = buildCanonPrompt({
|
|
3206
3181
|
content,
|
|
3207
3182
|
conversationId: input.conversationId,
|
|
3208
3183
|
participantContext,
|
|
@@ -3213,12 +3188,6 @@ export async function main() {
|
|
|
3213
3188
|
replyContext,
|
|
3214
3189
|
message: m,
|
|
3215
3190
|
});
|
|
3216
|
-
const lifecycleContext = isOwner
|
|
3217
|
-
? formatPendingContactLifecycleContext(takeLocalRuntimeContactLifecycleEvents(runtimeId, input.conversationId))
|
|
3218
|
-
: null;
|
|
3219
|
-
const promptText = lifecycleContext
|
|
3220
|
-
? `${basePromptText}\n\n${lifecycleContext}`
|
|
3221
|
-
: basePromptText;
|
|
3222
3191
|
const messageContent = await buildCanonUserContent({
|
|
3223
3192
|
promptText,
|
|
3224
3193
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
@@ -3236,7 +3205,7 @@ export async function main() {
|
|
|
3236
3205
|
senderId: m.senderId,
|
|
3237
3206
|
senderName: m.senderName,
|
|
3238
3207
|
}),
|
|
3239
|
-
}, deliveryIntent, m.id ?? null, shouldMarkAccepted, isOwner, m.senderType === 'human' ? m.senderId : null, turnModes
|
|
3208
|
+
}, deliveryIntent, m.id ?? null, shouldMarkAccepted, isOwner, m.senderType === 'human' ? m.senderId : null, turnModes);
|
|
3240
3209
|
return 'queued';
|
|
3241
3210
|
}
|
|
3242
3211
|
const acceptedInboundMessageIds = new Set();
|
|
@@ -3316,27 +3285,6 @@ export async function main() {
|
|
|
3316
3285
|
const conversationsDiscoveredWhileOffline = startupRecoveryComplete
|
|
3317
3286
|
? new Set([...knownConversationIds].filter((id) => !knownBeforeRefresh.has(id)))
|
|
3318
3287
|
: new Set();
|
|
3319
|
-
try {
|
|
3320
|
-
let cursor = readLocalRuntimeEntry(runtimeId)?.contactLifecycleCursor ?? null;
|
|
3321
|
-
for (let pageNumber = 0; pageNumber < 10; pageNumber += 1) {
|
|
3322
|
-
const page = await client.listContactRequestLifecyclePage({
|
|
3323
|
-
cursor,
|
|
3324
|
-
limit: 100,
|
|
3325
|
-
});
|
|
3326
|
-
await reconcileContactLifecycleEvents({
|
|
3327
|
-
requests: page.requests,
|
|
3328
|
-
requesterId: agentId,
|
|
3329
|
-
record: (request) => recordLocalRuntimeContactLifecycleEvent(runtimeId, request),
|
|
3330
|
-
});
|
|
3331
|
-
cursor = page.nextCursor;
|
|
3332
|
-
saveLocalRuntimeContactLifecycleCursor(runtimeId, cursor);
|
|
3333
|
-
if (!page.hasMore)
|
|
3334
|
-
break;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
catch (error) {
|
|
3338
|
-
console.error('[canon-host] Contact lifecycle recovery failed:', error instanceof Error ? error.message : error);
|
|
3339
|
-
}
|
|
3340
3288
|
for (const conversationId of knownConversationIds) {
|
|
3341
3289
|
const recoveryCheckpoints = recoveryCheckpointsFor(conversationId);
|
|
3342
3290
|
const recoveryBatch = recoveryCheckpoints.reserveBatch();
|
|
@@ -3436,8 +3384,7 @@ export async function main() {
|
|
|
3436
3384
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to materialize media:`, error instanceof Error ? error.message : error);
|
|
3437
3385
|
}
|
|
3438
3386
|
}
|
|
3439
|
-
const
|
|
3440
|
-
const content = renderInboundContent(m, materialized, renderOptions);
|
|
3387
|
+
const content = renderInboundContent(m, materialized);
|
|
3441
3388
|
const hydrated = await loadHydratedInboundContext({
|
|
3442
3389
|
conversationId: convoId,
|
|
3443
3390
|
message: m,
|
|
@@ -3446,7 +3393,6 @@ export async function main() {
|
|
|
3446
3393
|
activeSelfContextId: payload.activeSelfContextId,
|
|
3447
3394
|
selfContexts: payload.selfContexts,
|
|
3448
3395
|
provenance: payload.provenance,
|
|
3449
|
-
renderOptions,
|
|
3450
3396
|
});
|
|
3451
3397
|
const behavior = payload.behavior ?? hydrated.behavior;
|
|
3452
3398
|
const activeSelfContextId = hydrated.activeSelfContextId;
|
|
@@ -3456,7 +3402,6 @@ export async function main() {
|
|
|
3456
3402
|
agentId,
|
|
3457
3403
|
conversationId: convoId,
|
|
3458
3404
|
logPrefix: `[canon-host] [${convoId.slice(0, 8)}]`,
|
|
3459
|
-
renderOptions,
|
|
3460
3405
|
});
|
|
3461
3406
|
const replyContext = replyMedia.replyContext;
|
|
3462
3407
|
const participantContext = hydrated.participantContext;
|
|
@@ -3495,7 +3440,7 @@ export async function main() {
|
|
|
3495
3440
|
return false;
|
|
3496
3441
|
}
|
|
3497
3442
|
session.activeSelfContextId = activeSelfContextId;
|
|
3498
|
-
const
|
|
3443
|
+
const promptText = buildCanonPrompt({
|
|
3499
3444
|
content,
|
|
3500
3445
|
conversationId: convoId,
|
|
3501
3446
|
participantContext,
|
|
@@ -3506,12 +3451,6 @@ export async function main() {
|
|
|
3506
3451
|
replyContext,
|
|
3507
3452
|
message: m,
|
|
3508
3453
|
});
|
|
3509
|
-
const lifecycleContext = isOwner
|
|
3510
|
-
? formatPendingContactLifecycleContext(takeLocalRuntimeContactLifecycleEvents(runtimeId, convoId))
|
|
3511
|
-
: null;
|
|
3512
|
-
const promptText = lifecycleContext
|
|
3513
|
-
? `${basePromptText}\n\n${lifecycleContext}`
|
|
3514
|
-
: basePromptText;
|
|
3515
3454
|
const messageContent = await buildCanonUserContent({
|
|
3516
3455
|
promptText,
|
|
3517
3456
|
materialized: [...replyMedia.materialized, ...materialized],
|
|
@@ -3529,7 +3468,7 @@ export async function main() {
|
|
|
3529
3468
|
senderId: m.senderId,
|
|
3530
3469
|
senderName: m.senderName,
|
|
3531
3470
|
}),
|
|
3532
|
-
}, deliveryIntent, m.id ?? null, shouldMarkAccepted, isOwner, m.senderType === 'human' ? m.senderId : null, turnModes
|
|
3471
|
+
}, deliveryIntent, m.id ?? null, shouldMarkAccepted, isOwner, m.senderType === 'human' ? m.senderId : null, turnModes);
|
|
3533
3472
|
return true;
|
|
3534
3473
|
})().then((queued) => {
|
|
3535
3474
|
if (!queued) {
|
|
@@ -3547,24 +3486,20 @@ export async function main() {
|
|
|
3547
3486
|
onConversationUpdated: (payload) => {
|
|
3548
3487
|
handleConversationUpdated(payload);
|
|
3549
3488
|
},
|
|
3550
|
-
onContactRequestUpdated: (payload) => {
|
|
3551
|
-
if (payload.requesterId !== agentId || !payload.sourceConversationId)
|
|
3552
|
-
return;
|
|
3553
|
-
recordLocalRuntimeContactLifecycleEvent(runtimeId, payload);
|
|
3554
|
-
},
|
|
3555
3489
|
onReplayExpired: () => {
|
|
3556
3490
|
void reconnectRecovery.onReplayExpired().catch((error) => {
|
|
3557
|
-
console.error('[canon-host] Replay-
|
|
3491
|
+
console.error('[canon-host] Replay-expiry recovery failed:', error instanceof Error ? error.message : error);
|
|
3558
3492
|
});
|
|
3559
3493
|
},
|
|
3560
3494
|
onConnected: () => {
|
|
3561
3495
|
streamConnected = true;
|
|
3562
3496
|
void publishRuntimeHeartbeat();
|
|
3563
3497
|
const recovery = reconnectRecovery.onConnected();
|
|
3564
|
-
if (recovery)
|
|
3498
|
+
if (recovery) {
|
|
3565
3499
|
void recovery.catch((error) => {
|
|
3566
|
-
console.error('[canon-host] Reconnect recovery failed:', error);
|
|
3500
|
+
console.error('[canon-host] Reconnect recovery failed:', error instanceof Error ? error.message : error);
|
|
3567
3501
|
});
|
|
3502
|
+
}
|
|
3568
3503
|
console.error('[canon-host] SSE connected');
|
|
3569
3504
|
},
|
|
3570
3505
|
onDisconnected: () => {
|
package/dist/register.js
CHANGED
|
@@ -37,7 +37,6 @@ After approval, start it with CANON_AGENT=<profile> canon-claude --cwd /path/to/
|
|
|
37
37
|
const OPTIONS = {
|
|
38
38
|
moduleUrl: import.meta.url,
|
|
39
39
|
clientType: 'claude-code',
|
|
40
|
-
sessionSetupPolicy: 'runtime_descriptor_required',
|
|
41
40
|
cliName: 'canon-register',
|
|
42
41
|
hostBinName: 'canon-claude',
|
|
43
42
|
developerInfo: 'Claude Code plugin',
|
package/dist/server.js
CHANGED
|
@@ -16,8 +16,7 @@ import { ApprovalHttpServer } from './approval-server.js';
|
|
|
16
16
|
import { renderClaudeInboundContent } from './canon-user-content.js';
|
|
17
17
|
import { runCli } from '@canonmsg/core';
|
|
18
18
|
import { parseReplyArgs, parseSendMessageArgs, parseSetTypingArgs, } from './mcp-args.js';
|
|
19
|
-
import {
|
|
20
|
-
const OWNER_BOUND_COMMUNICATION_VERBS = new Set(OWNER_BOUND_CANON_COMMUNICATION_VERBS);
|
|
19
|
+
import { canonVerbToolDefinitions, createCanonCommunicationBinding, executeCanonVerbTool, isCanonToolVerb, } from '@canonmsg/agent-tools';
|
|
21
20
|
const HELP = `canon-channel-server — Claude Code MCP channel server for Canon
|
|
22
21
|
|
|
23
22
|
USAGE
|
|
@@ -46,6 +45,7 @@ const conversationCache = new Map();
|
|
|
46
45
|
const TURN_COMPLETE_METADATA = {
|
|
47
46
|
turnSemantics: 'turn_complete',
|
|
48
47
|
};
|
|
48
|
+
const communicationBinding = createCanonCommunicationBinding();
|
|
49
49
|
/** Last owner-visible conversation to route approval cards into. */
|
|
50
50
|
let approvalConversationId = null;
|
|
51
51
|
/** Whether the message that most recently triggered work came from the owner. */
|
|
@@ -175,7 +175,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
175
175
|
// Canonical verbs — projections of canon.verbs.v1 (@canonmsg/agent-tools).
|
|
176
176
|
// Channel mode owns no turn, so `no_reply` is an ack-only no-op here: the
|
|
177
177
|
// standalone session has no final delivery for it to suppress.
|
|
178
|
-
...canonVerbToolDefinitions()
|
|
178
|
+
...canonVerbToolDefinitions(),
|
|
179
|
+
...(agentContext?.outboundPolicy === 'closed' ? [] : communicationBinding.tools),
|
|
179
180
|
],
|
|
180
181
|
}));
|
|
181
182
|
// ── Tool handlers ──────────────────────────────────────────────────────
|
|
@@ -315,13 +316,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
315
316
|
}
|
|
316
317
|
default: {
|
|
317
318
|
const name = request.params.name;
|
|
318
|
-
if (
|
|
319
|
-
|
|
319
|
+
if (communicationBinding.isToolName(name)) {
|
|
320
|
+
const result = await communicationBinding.execute(client, name, args);
|
|
321
|
+
return {
|
|
322
|
+
content: result.content.map((item) => ({ type: 'text', text: item.text })),
|
|
323
|
+
...(result.isError ? { isError: true } : {}),
|
|
324
|
+
};
|
|
320
325
|
}
|
|
321
326
|
if (isCanonToolVerb(name)) {
|
|
322
327
|
const rawArgs = args && typeof args === 'object' ? { ...args } : {};
|
|
323
|
-
const
|
|
324
|
-
const verbResult = await executeCanonVerbTool(client, name, verbArgs);
|
|
328
|
+
const verbResult = await executeCanonVerbTool(client, name, rawArgs);
|
|
325
329
|
return {
|
|
326
330
|
content: verbResult.content.map((item) => ({ type: 'text', text: item.text })),
|
|
327
331
|
...(verbResult.isError ? { isError: true } : {}),
|
package/dist/session-state.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { PermissionMode, SDKMessageOrigin, SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import type { DeliveryIntent, ModelOption, RuntimeStreamingPayload, TurnLifecycleState, TurnOutputBlock, TurnOutputSnapshot, TurnVerbosity } from '@canonmsg/core';
|
|
3
|
-
import type { OwnerBoundCanonContactTarget } from '@canonmsg/agent-tools';
|
|
4
3
|
import type { TurnArtifactRoutingDecision, TurnArtifactSnapshot } from '@canonmsg/coding-agent-host';
|
|
5
4
|
export type ClaudeInputKind = 'seed' | 'canon';
|
|
6
5
|
export type ClaudeArtifactRoutingMode = 'workspace-generated' | 'disabled';
|
|
@@ -24,8 +23,6 @@ export interface ClaudeInputEnvelope {
|
|
|
24
23
|
turnKey: string;
|
|
25
24
|
isOwnerTurn: boolean;
|
|
26
25
|
requestingUserId: string | null;
|
|
27
|
-
/** Trusted contact-card target resolved from the owner message's reply. */
|
|
28
|
-
replyContactTarget?: OwnerBoundCanonContactTarget;
|
|
29
26
|
}
|
|
30
27
|
/**
|
|
31
28
|
* The per-turn modes an inbound message resolves from its participant context,
|
|
@@ -105,7 +102,6 @@ export declare function createClaudeInputEnvelope(input: {
|
|
|
105
102
|
artifactBaseline?: TurnArtifactSnapshot | null;
|
|
106
103
|
isOwnerTurn?: boolean;
|
|
107
104
|
requestingUserId?: string | null;
|
|
108
|
-
replyContactTarget?: OwnerBoundCanonContactTarget;
|
|
109
105
|
}): ClaudeInputEnvelope;
|
|
110
106
|
export declare function resolveClaudeTurnResponseRouting(turn: Pick<ClaudeInputEnvelope, 'kind' | 'isOwnerTurn' | 'requestingUserId'> | null | undefined, ownerId: string | null | undefined, ownerOnly?: boolean): {
|
|
111
107
|
isOwnerTurn: boolean;
|
package/dist/session-state.js
CHANGED
|
@@ -28,9 +28,6 @@ export function createClaudeInputEnvelope(input) {
|
|
|
28
28
|
requestingUserId: input.kind === 'canon' && input.requestingUserId
|
|
29
29
|
? input.requestingUserId
|
|
30
30
|
: null,
|
|
31
|
-
...(input.kind === 'canon' && input.replyContactTarget
|
|
32
|
-
? { replyContactTarget: input.replyContactTarget }
|
|
33
|
-
: {}),
|
|
34
31
|
turnKey: input.kind === 'canon' && sourceMessageId
|
|
35
32
|
? `canon:${sourceMessageId}`
|
|
36
33
|
: `${input.kind}:${fallbackId}`,
|
package/dist/tool-policy.js
CHANGED
|
@@ -37,13 +37,9 @@ function normalizeToolName(toolName) {
|
|
|
37
37
|
function isCanonOutboundTool(normalizedToolName) {
|
|
38
38
|
return normalizedToolName.endsWith('__reply')
|
|
39
39
|
|| normalizedToolName.endsWith('__send_message')
|
|
40
|
-
|| normalizedToolName.endsWith('__send_contextual_message')
|
|
41
|
-
|| normalizedToolName.endsWith('__send_to')
|
|
42
40
|
|| normalizedToolName.endsWith('__share_contact')
|
|
43
41
|
|| normalizedToolName === 'reply'
|
|
44
42
|
|| normalizedToolName === 'send_message'
|
|
45
|
-
|| normalizedToolName === 'send_contextual_message'
|
|
46
|
-
|| normalizedToolName === 'send_to'
|
|
47
43
|
|| normalizedToolName === 'share_contact';
|
|
48
44
|
}
|
|
49
45
|
/**
|
|
@@ -79,7 +75,7 @@ const CANON_INTERACTION_LIFECYCLE_VERBS = new Set([
|
|
|
79
75
|
* Verbs a NON-owner conversation member may trigger: same-conversation HITL
|
|
80
76
|
* and display surfaces, whose responder routing and owner-only escalation
|
|
81
77
|
* (secret/sudo, session rules) are enforced server-side. Cross-conversation
|
|
82
|
-
* verbs (
|
|
78
|
+
* cross-conversation verbs (share_contact) and the reads (contacts, conversation
|
|
83
79
|
* lists — they expose the agent's other relationships to this conversation)
|
|
84
80
|
* stay owner-only until the provenance trigger-token primitive lands.
|
|
85
81
|
*/
|
|
@@ -257,7 +253,7 @@ export async function decideClaudeToolPermissionForMode(input) {
|
|
|
257
253
|
}
|
|
258
254
|
// HITL + read verbs are allowed without an approval card: the HITL verbs
|
|
259
255
|
// themselves render the human-facing card (recursion otherwise), and the
|
|
260
|
-
// reads are side-effect free. Outbound verbs (
|
|
256
|
+
// reads are side-effect free. Outbound verbs (share_contact)
|
|
261
257
|
// fall through to the mode policy — in native-approval mode they gate
|
|
262
258
|
// like any other side effect; in default mode owner turns allow.
|
|
263
259
|
if (CANON_HITL_OR_READ_VERBS.has(canonVerb) && !input.ruleForcedAsk) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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,11 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "0.3.228",
|
|
34
|
-
"@canonmsg/agent-sdk": "^
|
|
35
|
-
"@canonmsg/agent-tools": "^0.
|
|
36
|
-
"@canonmsg/coding-agent-host": "^0.
|
|
37
|
-
"@canonmsg/core": "^
|
|
38
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
34
|
+
"@canonmsg/agent-sdk": "^9.0.0",
|
|
35
|
+
"@canonmsg/agent-tools": "^0.7.0",
|
|
36
|
+
"@canonmsg/coding-agent-host": "^0.7.0",
|
|
37
|
+
"@canonmsg/core": "^11.0.0",
|
|
38
|
+
"@canonmsg/rich-cards": "^0.10.3",
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|