@canonmsg/claude-code-plugin 0.9.2 โ 0.9.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/dist/cli-entry.js +11 -1
- package/dist/host-runtime.d.ts +1 -0
- package/dist/host-runtime.js +15 -0
- package/dist/host.js +59 -20
- package/package.json +3 -3
package/dist/cli-entry.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
1
2
|
import { resolve } from 'node:path';
|
|
2
3
|
import { pathToFileURL } from 'node:url';
|
|
3
4
|
export function isDirectExecution(moduleUrl) {
|
|
4
5
|
const entry = process.argv[1];
|
|
5
6
|
if (!entry)
|
|
6
7
|
return false;
|
|
7
|
-
|
|
8
|
+
const abs = resolve(entry);
|
|
9
|
+
let real = abs;
|
|
10
|
+
try {
|
|
11
|
+
real = realpathSync(abs);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// argv[1] may not exist on disk in rare harnesses; fall back to abs
|
|
15
|
+
}
|
|
16
|
+
return (pathToFileURL(real).href === moduleUrl ||
|
|
17
|
+
pathToFileURL(abs).href === moduleUrl);
|
|
8
18
|
}
|
|
9
19
|
export function runCli(moduleUrl, main, onError) {
|
|
10
20
|
if (!isDirectExecution(moduleUrl)) {
|
package/dist/host-runtime.d.ts
CHANGED
package/dist/host-runtime.js
CHANGED
|
@@ -55,9 +55,24 @@ export function renderCanonHostInboundContent(message, materialized) {
|
|
|
55
55
|
const mat = materialized?.find((m) => m.index === i) ?? null;
|
|
56
56
|
placeholders.push(describeAttachment(att, mat));
|
|
57
57
|
}
|
|
58
|
+
if (message.contentType === 'contact_card' && message.contactCard) {
|
|
59
|
+
placeholders.push(describeContactCard(message.contactCard));
|
|
60
|
+
}
|
|
58
61
|
const rendered = [...placeholders, body].filter(Boolean).join('\n');
|
|
59
62
|
return rendered || '[Empty message]';
|
|
60
63
|
}
|
|
64
|
+
function describeContactCard(card) {
|
|
65
|
+
const parts = [`${card.userType} ยท userId: ${card.userId}`];
|
|
66
|
+
if (card.accessLevel)
|
|
67
|
+
parts.push(`access: ${card.accessLevel}`);
|
|
68
|
+
if (card.ownerName)
|
|
69
|
+
parts.push(`owner: ${card.ownerName}`);
|
|
70
|
+
if (card.about)
|
|
71
|
+
parts.push(`about: ${card.about}`);
|
|
72
|
+
const identity = `๐ Contact card: "${card.displayName}" (${parts.join(' ยท ')}).`;
|
|
73
|
+
const hint = `This card is informational only in host mode. Canon does not currently expose a host-side tool here for starting a new direct conversation or sending a contact request to userId ${card.userId}.`;
|
|
74
|
+
return `${identity}\n${hint}`;
|
|
75
|
+
}
|
|
61
76
|
function describeAttachment(attachment, materialized) {
|
|
62
77
|
if (attachment.kind === 'image') {
|
|
63
78
|
return '[Image attached]';
|
package/dist/host.js
CHANGED
|
@@ -22,9 +22,11 @@ import { setDefaultResultOrder } from 'node:dns';
|
|
|
22
22
|
*/
|
|
23
23
|
import { parseArgs } from 'node:util';
|
|
24
24
|
import { randomUUID } from 'node:crypto';
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
25
27
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
26
28
|
import { isAnthropicImageAttachment, materializeMessageMedia, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
|
|
27
|
-
import { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES,
|
|
29
|
+
import { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, clearTurnState, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, getActiveProfile, releaseLock, initRTDBAuth, rtdbRead, rtdbWrite, writeSessionState, clearSessionState, writeTurnState, releaseConversationEnvironment, } from '@canonmsg/core';
|
|
28
30
|
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
29
31
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
30
32
|
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
@@ -32,6 +34,42 @@ import { runCli } from './cli-entry.js';
|
|
|
32
34
|
let workingDir = process.cwd();
|
|
33
35
|
let workspaceOptions = [];
|
|
34
36
|
let runtimeModels = [];
|
|
37
|
+
let cachedClaudeCliPath;
|
|
38
|
+
// The SDK bundles a Bun-compiled `claude` Mach-O binary next to
|
|
39
|
+
// claude-agent-sdk-darwin-arm64. On some macOS setups that exact binary gets
|
|
40
|
+
// SIGKILL'd at launch (path-based code-sign enforcement) even though a
|
|
41
|
+
// byte-identical copy at ~/.local/bin/claude runs fine. Prefer whatever is on
|
|
42
|
+
// PATH; fall back to the bundled one.
|
|
43
|
+
function resolveClaudeCliPath() {
|
|
44
|
+
if (cachedClaudeCliPath !== undefined) {
|
|
45
|
+
return cachedClaudeCliPath ?? undefined;
|
|
46
|
+
}
|
|
47
|
+
const override = process.env.CANON_CLAUDE_CLI_PATH?.trim();
|
|
48
|
+
if (override) {
|
|
49
|
+
if (existsSync(override)) {
|
|
50
|
+
cachedClaudeCliPath = override;
|
|
51
|
+
console.error(`[canon-host] claude CLI override: ${override}`);
|
|
52
|
+
return override;
|
|
53
|
+
}
|
|
54
|
+
console.error(`[canon-host] CANON_CLAUDE_CLI_PATH=${override} not found; ignoring`);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const resolved = execFileSync('/bin/sh', ['-c', 'command -v claude'], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
}).trim();
|
|
60
|
+
if (resolved && existsSync(resolved)) {
|
|
61
|
+
cachedClaudeCliPath = resolved;
|
|
62
|
+
console.error(`[canon-host] claude CLI on PATH: ${resolved}`);
|
|
63
|
+
return resolved;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// `command -v` exits non-zero when claude isn't on PATH; fall through.
|
|
68
|
+
}
|
|
69
|
+
cachedClaudeCliPath = null;
|
|
70
|
+
console.error('[canon-host] claude CLI not on PATH; using SDK-bundled binary');
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
35
73
|
function toModelOptions(models) {
|
|
36
74
|
return models.map((model) => ({
|
|
37
75
|
value: model.value,
|
|
@@ -52,13 +90,11 @@ async function loadSessionConfig(conversationId, agentId) {
|
|
|
52
90
|
permissionMode: config?.permissionMode,
|
|
53
91
|
};
|
|
54
92
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
return mode;
|
|
93
|
+
// Default to 'locked' (shared workspace) when no mode has been picked. The
|
|
94
|
+
// UI still lets owners flip to 'worktree'; this just stops sessions from
|
|
95
|
+
// failing closed when the mode has never been written.
|
|
96
|
+
function resolveSessionExecutionMode(config) {
|
|
97
|
+
return config?.executionMode ?? 'locked';
|
|
62
98
|
}
|
|
63
99
|
function resolveWorkspaceCwd(config) {
|
|
64
100
|
return resolveHostWorkspaceCwd({
|
|
@@ -71,6 +107,7 @@ async function detectRuntimeModels(cwd) {
|
|
|
71
107
|
const emptyPrompt = {
|
|
72
108
|
async *[Symbol.asyncIterator]() { },
|
|
73
109
|
};
|
|
110
|
+
const claudeCliPath = resolveClaudeCliPath();
|
|
74
111
|
const probe = query({
|
|
75
112
|
prompt: emptyPrompt,
|
|
76
113
|
options: {
|
|
@@ -79,6 +116,7 @@ async function detectRuntimeModels(cwd) {
|
|
|
79
116
|
permissionMode: 'default',
|
|
80
117
|
cwd,
|
|
81
118
|
settings: { forceLoginMethod: 'claudeai' },
|
|
119
|
+
...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
|
|
82
120
|
},
|
|
83
121
|
});
|
|
84
122
|
try {
|
|
@@ -200,6 +238,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
200
238
|
};
|
|
201
239
|
});
|
|
202
240
|
}
|
|
241
|
+
const claudeCliPath = resolveClaudeCliPath();
|
|
203
242
|
const queryOptions = {
|
|
204
243
|
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
|
205
244
|
tools: { type: 'preset', preset: 'claude_code' },
|
|
@@ -208,6 +247,14 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
208
247
|
includePartialMessages: true,
|
|
209
248
|
maxTurns: 200,
|
|
210
249
|
settings: { forceLoginMethod: 'claudeai' },
|
|
250
|
+
settingSources: ['project', 'local'],
|
|
251
|
+
...(claudeCliPath ? { pathToClaudeCodeExecutable: claudeCliPath } : {}),
|
|
252
|
+
stderr: (data) => {
|
|
253
|
+
const trimmed = data.toString().trim();
|
|
254
|
+
if (trimmed) {
|
|
255
|
+
console.error(`[claude-cli] [${conversationId.slice(0, 8)}] ${trimmed}`);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
211
258
|
canUseTool: async (toolName, input) => {
|
|
212
259
|
if (toolName === 'AskUserQuestion') {
|
|
213
260
|
try {
|
|
@@ -672,17 +719,12 @@ export async function main() {
|
|
|
672
719
|
options: {
|
|
673
720
|
cwd: { type: 'string' },
|
|
674
721
|
workspace: { type: 'string', multiple: true },
|
|
675
|
-
'enable-worktrees': { type: 'boolean' },
|
|
676
722
|
},
|
|
677
723
|
strict: false,
|
|
678
724
|
});
|
|
679
725
|
workingDir = (typeof args.cwd === 'string' ? args.cwd : null) || process.cwd();
|
|
680
726
|
const configuredWorkspaces = (args.workspace ?? []).filter((value) => typeof value === 'string');
|
|
681
727
|
workspaceOptions = buildConfiguredWorkspaceOptions(workingDir, configuredWorkspaces);
|
|
682
|
-
const allowWorktrees = isEnabledFlag(args['enable-worktrees'] ?? process.env.CANON_ENABLE_WORKTREES);
|
|
683
|
-
if (!allowWorktrees) {
|
|
684
|
-
console.error('[canon-host] Worktree isolation is disabled; sessions will lock their selected workspace unless explicitly enabled.');
|
|
685
|
-
}
|
|
686
728
|
const { apiKey, agentId: profileAgentId, profile } = resolveCanonAgent({ logPrefix: '[canon-host]' });
|
|
687
729
|
console.error(`[canon-host] Starting${profile ? ` (profile: ${profile})` : ''} in ${workingDir}`);
|
|
688
730
|
// โโ Canon API client + RTDB auth โโ
|
|
@@ -707,9 +749,9 @@ export async function main() {
|
|
|
707
749
|
console.error(`[canon-host] Authenticated as ${agentId}`);
|
|
708
750
|
}
|
|
709
751
|
let streamConnected = false;
|
|
710
|
-
const hostAvailableExecutionModes =
|
|
711
|
-
|
|
712
|
-
|
|
752
|
+
const hostAvailableExecutionModes = [
|
|
753
|
+
...EXECUTION_ENVIRONMENT_MODES,
|
|
754
|
+
];
|
|
713
755
|
let runtimeDescriptor = {
|
|
714
756
|
defaultWorkspaceId: workspaceOptions[0]?.id,
|
|
715
757
|
defaultPermissionMode: 'default',
|
|
@@ -834,10 +876,7 @@ export async function main() {
|
|
|
834
876
|
}
|
|
835
877
|
const creation = (async () => {
|
|
836
878
|
const config = await loadSessionConfig(conversationId, agentId);
|
|
837
|
-
const sessionExecutionMode =
|
|
838
|
-
if (sessionExecutionMode === 'worktree' && !allowWorktrees) {
|
|
839
|
-
throw new ExecutionEnvironmentError('This host does not allow worktree sessions (launched without --enable-worktrees).', 'This Canon host was started without worktree isolation enabled. Choose "Lock the workspace" or restart the host with --enable-worktrees.');
|
|
840
|
-
}
|
|
879
|
+
const sessionExecutionMode = resolveSessionExecutionMode(config);
|
|
841
880
|
const workspaceCwd = resolveWorkspaceCwd(config);
|
|
842
881
|
const environment = prepareConversationEnvironment({
|
|
843
882
|
agentId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
33
|
-
"@canonmsg/agent-sdk": "^0.8.
|
|
34
|
-
"@canonmsg/core": "^0.7.
|
|
33
|
+
"@canonmsg/agent-sdk": "^0.8.2",
|
|
34
|
+
"@canonmsg/core": "^0.7.3",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|