@canonmsg/claude-code-plugin 0.31.2 → 0.31.3
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 +2 -0
- package/dist/canon-user-content.d.ts +22 -0
- package/dist/canon-user-content.js +72 -0
- package/dist/host.js +13 -25
- package/dist/server.js +3 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ canon-claude --cwd ~/dev --workspace-root ~/dev
|
|
|
63
63
|
|
|
64
64
|
`--cwd` is the default workspace. Each `--workspace-root` value is an approved local root; the host discovers immediate child projects with common markers such as `.git`, `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` and publishes them as selectable projects during session creation. Use repeated `--workspace /path/to/project` entries to advertise specific projects outside those roots. Worktree mode creates a best-effort per-conversation git worktree under `~/.canon/conversation-worktrees`; shared-project mode runs directly in the selected directory.
|
|
65
65
|
|
|
66
|
+
Inbound Canon images are materialized with the Agent SDK's bounded 10 MiB default. The host sends supported images to the direct Anthropic transport as native base64 blocks only when each raw file is at most 7 MiB and the exact encoded content array plus prompt text fit the aggregate request budget with reserved framing headroom. Seven MiB is derived specifically from the direct transport's 10 MB encoded-image limit; it is not a portable default for Bedrock or Vertex. Materialized images omitted from native blocks remain available by local path. Images over the 10 MiB automatic-download limit instead receive a validated HTTPS link in the Claude prompt; materialized images never receive a duplicate link.
|
|
67
|
+
|
|
66
68
|
Current Canon truth for Claude host mode:
|
|
67
69
|
|
|
68
70
|
- model is live-editable
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { type AnthropicImageBudgetOptions, type MaterializedCanonAttachment } from '@canonmsg/agent-sdk';
|
|
3
|
+
import { renderCanonHostInboundContent, type CanonReplyContext } from '@canonmsg/core';
|
|
4
|
+
type ClaudeRenderableInboundMessage = Parameters<typeof renderCanonHostInboundContent>[0];
|
|
5
|
+
/**
|
|
6
|
+
* Claude-only rendering policy for Canon media. Core deliberately omits all
|
|
7
|
+
* URLs globally. This wrapper restores validated HTTPS links only for images
|
|
8
|
+
* that the bounded runtime did not materialize, while materialized images keep
|
|
9
|
+
* their local-path placeholder without a duplicate link.
|
|
10
|
+
*/
|
|
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
|
+
/**
|
|
14
|
+
* Build the Claude Code SDK's multimodal user content without allowing native
|
|
15
|
+
* image blocks to consume the whole request. The prompt text is preserved
|
|
16
|
+
* verbatim, including local paths for images omitted from native blocks.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildCanonUserContent(input: {
|
|
19
|
+
promptText: string;
|
|
20
|
+
materialized: ReadonlyArray<MaterializedCanonAttachment>;
|
|
21
|
+
}, budget?: Omit<AnthropicImageBudgetOptions, 'promptText'>): Promise<string | SDKUserMessage['message']['content']>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { toAnthropicImageBlocksWithinBudget, } from '@canonmsg/agent-sdk';
|
|
2
|
+
import { renderCanonHostInboundContent, } from '@canonmsg/core';
|
|
3
|
+
const MAX_PROMPT_ATTACHMENT_URL_LENGTH = 8_192;
|
|
4
|
+
function validatedPromptAttachmentUrl(value) {
|
|
5
|
+
if (typeof value !== 'string'
|
|
6
|
+
|| value.length === 0
|
|
7
|
+
|| value.length > MAX_PROMPT_ATTACHMENT_URL_LENGTH
|
|
8
|
+
|| /[\u0000-\u0020\u007f]/.test(value)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(value);
|
|
13
|
+
if (parsed.protocol !== 'https:'
|
|
14
|
+
|| parsed.hostname.length === 0
|
|
15
|
+
|| parsed.username.length > 0
|
|
16
|
+
|| parsed.password.length > 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return parsed.href;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Claude-only rendering policy for Canon media. Core deliberately omits all
|
|
27
|
+
* URLs globally. This wrapper restores validated HTTPS links only for images
|
|
28
|
+
* that the bounded runtime did not materialize, while materialized images keep
|
|
29
|
+
* their local-path placeholder without a duplicate link.
|
|
30
|
+
*/
|
|
31
|
+
export function renderClaudeInboundContent(message, materialized = []) {
|
|
32
|
+
const rendered = renderCanonHostInboundContent(message, materialized);
|
|
33
|
+
const materializedIndexes = new Set(materialized.map((attachment) => attachment.index));
|
|
34
|
+
const imageLinks = (message.attachments ?? []).flatMap((attachment, index) => {
|
|
35
|
+
if (attachment.kind !== 'image' || materializedIndexes.has(index))
|
|
36
|
+
return [];
|
|
37
|
+
const url = validatedPromptAttachmentUrl(attachment.url);
|
|
38
|
+
return url ? [`[Image ${index + 1} link: ${url}]`] : [];
|
|
39
|
+
});
|
|
40
|
+
return imageLinks.length > 0 ? `${rendered}\n${imageLinks.join('\n')}` : rendered;
|
|
41
|
+
}
|
|
42
|
+
export function withClaudeReplyContextMediaReferences(replyContext, materialized) {
|
|
43
|
+
if (!replyContext?.found)
|
|
44
|
+
return replyContext;
|
|
45
|
+
return {
|
|
46
|
+
...replyContext,
|
|
47
|
+
body: renderClaudeInboundContent({
|
|
48
|
+
text: replyContext.text,
|
|
49
|
+
contentType: replyContext.contentType,
|
|
50
|
+
attachments: replyContext.attachments,
|
|
51
|
+
contactCard: replyContext.contactCard,
|
|
52
|
+
senderType: replyContext.senderType ?? undefined,
|
|
53
|
+
}, materialized),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the Claude Code SDK's multimodal user content without allowing native
|
|
58
|
+
* image blocks to consume the whole request. The prompt text is preserved
|
|
59
|
+
* verbatim, including local paths for images omitted from native blocks.
|
|
60
|
+
*/
|
|
61
|
+
export async function buildCanonUserContent(input, budget) {
|
|
62
|
+
const imageBlocks = await toAnthropicImageBlocksWithinBudget(input.materialized, {
|
|
63
|
+
promptText: input.promptText,
|
|
64
|
+
...budget,
|
|
65
|
+
});
|
|
66
|
+
if (imageBlocks.length === 0)
|
|
67
|
+
return input.promptText;
|
|
68
|
+
return [
|
|
69
|
+
{ type: 'text', text: input.promptText },
|
|
70
|
+
...imageBlocks,
|
|
71
|
+
];
|
|
72
|
+
}
|
package/dist/host.js
CHANGED
|
@@ -26,9 +26,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
26
26
|
import { existsSync } from 'node:fs';
|
|
27
27
|
import { readFile } from 'node:fs/promises';
|
|
28
28
|
import { query, } from '@anthropic-ai/claude-agent-sdk';
|
|
29
|
-
import {
|
|
29
|
+
import { materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
30
30
|
import { captureTurnArtifactSnapshot, createTurnArtifactRouter, IDLE_TIMEOUT_MS, collectMissedInboundMessages, createReconnectRecoveryGate, STARTUP_RECOVERY_MAX_MESSAGES, STARTUP_RECOVERY_PAGE_SIZE, } from '@canonmsg/coding-agent-host';
|
|
31
|
-
import {
|
|
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, 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';
|
|
32
33
|
import { runCli } from '@canonmsg/core';
|
|
33
34
|
import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
|
|
34
35
|
import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
|
|
@@ -781,43 +782,30 @@ function resolveClaudeTurnModes(participantContext) {
|
|
|
781
782
|
};
|
|
782
783
|
}
|
|
783
784
|
function renderInboundContent(message, materialized) {
|
|
784
|
-
return
|
|
785
|
+
return renderClaudeInboundContent(message, materialized);
|
|
785
786
|
}
|
|
786
787
|
async function materializePromptReplyContext(input) {
|
|
787
788
|
if (!input.replyContext?.found || !input.replyContext.attachments?.length) {
|
|
788
789
|
return { replyContext: input.replyContext, materialized: [] };
|
|
789
790
|
}
|
|
790
791
|
try {
|
|
791
|
-
|
|
792
|
+
const result = await materializeReplyContextMedia(input.replyContext, {
|
|
792
793
|
agentId: input.agentId,
|
|
793
794
|
conversationId: input.conversationId,
|
|
794
795
|
});
|
|
796
|
+
return {
|
|
797
|
+
...result,
|
|
798
|
+
replyContext: withClaudeReplyContextMediaReferences(result.replyContext, result.materialized),
|
|
799
|
+
};
|
|
795
800
|
}
|
|
796
801
|
catch (error) {
|
|
797
802
|
console.error(`${input.logPrefix} Failed to materialize replied-to media:`, error instanceof Error ? error.message : error);
|
|
798
|
-
return {
|
|
803
|
+
return {
|
|
804
|
+
replyContext: withClaudeReplyContextMediaReferences(input.replyContext, []),
|
|
805
|
+
materialized: [],
|
|
806
|
+
};
|
|
799
807
|
}
|
|
800
808
|
}
|
|
801
|
-
/**
|
|
802
|
-
* Build the multimodal `content` payload handed to the Claude Code SDK.
|
|
803
|
-
*
|
|
804
|
-
* Images are delivered as native Anthropic `image` blocks (base64) so the
|
|
805
|
-
* vision model sees pixels, not a text reference to a URL. Non-image
|
|
806
|
-
* attachments (audio, files) are referenced by their local path inside the
|
|
807
|
-
* text prompt — see `renderCanonHostInboundContent` — so the agent can
|
|
808
|
-
* Read them on demand.
|
|
809
|
-
*/
|
|
810
|
-
async function buildCanonUserContent(input) {
|
|
811
|
-
const imageAttachments = input.materialized.filter(isAnthropicImageAttachment);
|
|
812
|
-
if (imageAttachments.length === 0) {
|
|
813
|
-
return input.promptText;
|
|
814
|
-
}
|
|
815
|
-
const imageBlocks = await Promise.all(imageAttachments.map((attachment) => toAnthropicImageBlock(attachment)));
|
|
816
|
-
return [
|
|
817
|
-
{ type: 'text', text: input.promptText },
|
|
818
|
-
...imageBlocks,
|
|
819
|
-
];
|
|
820
|
-
}
|
|
821
809
|
// ── Session factory ─────────────────────────────────────────────────
|
|
822
810
|
function createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
823
811
|
const { cwd } = environment;
|
package/dist/server.js
CHANGED
|
@@ -10,9 +10,10 @@ import { setDefaultResultOrder } from 'node:dns';
|
|
|
10
10
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
11
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, getActiveProfileLock, markLocalRuntimeStopped, loadProfiles, isProfileLocked, resolveCanonAgent, verifyResolvedAgentEnvironment, getActiveProfile, releaseLock, upsertLocalRuntimeEntry, initRTDBAuth,
|
|
13
|
+
import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, getActiveProfileLock, markLocalRuntimeStopped, loadProfiles, isProfileLocked, resolveCanonAgent, verifyResolvedAgentEnvironment, getActiveProfile, releaseLock, upsertLocalRuntimeEntry, initRTDBAuth, } from '@canonmsg/core';
|
|
14
14
|
import { materializeMessageMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
15
15
|
import { ApprovalHttpServer } from './approval-server.js';
|
|
16
|
+
import { renderClaudeInboundContent } from './canon-user-content.js';
|
|
16
17
|
import { runCli } from '@canonmsg/core';
|
|
17
18
|
import { parseReplyArgs, parseSendMessageArgs, parseSetTypingArgs, } from './mcp-args.js';
|
|
18
19
|
import { canonVerbToolDefinitions, executeCanonVerbTool, isCanonToolVerb, stampSendToTurnComplete, } from '@canonmsg/agent-tools';
|
|
@@ -55,7 +56,7 @@ function getPrimaryAttachment(message) {
|
|
|
55
56
|
return null;
|
|
56
57
|
}
|
|
57
58
|
function renderInboundContent(message, materialized) {
|
|
58
|
-
return
|
|
59
|
+
return renderClaudeInboundContent(message, materialized);
|
|
59
60
|
}
|
|
60
61
|
function toolArgumentError(error) {
|
|
61
62
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.3",
|
|
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",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"test": "vitest run"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
34
|
-
"@canonmsg/agent-sdk": "^8.
|
|
35
|
-
"@canonmsg/agent-tools": "^0.5.
|
|
33
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.228",
|
|
34
|
+
"@canonmsg/agent-sdk": "^8.6.0",
|
|
35
|
+
"@canonmsg/agent-tools": "^0.5.3",
|
|
36
36
|
"@canonmsg/coding-agent-host": "^0.5.0",
|
|
37
|
-
"@canonmsg/core": "^10.
|
|
38
|
-
"@canonmsg/rich-cards": "^0.10.
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
37
|
+
"@canonmsg/core": "^10.4.0",
|
|
38
|
+
"@canonmsg/rich-cards": "^0.10.2",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=18.0.0"
|