@canonmsg/claude-code-plugin 0.9.0 → 0.9.2
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.d.ts +2 -0
- package/dist/cli-entry.js +16 -0
- package/dist/host-runtime.d.ts +0 -3
- package/dist/host-runtime.js +4 -15
- package/dist/host.d.ts +1 -1
- package/dist/host.js +30 -29
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/register.d.ts +1 -1
- package/dist/register.js +80 -73
- package/dist/server.d.ts +1 -1
- package/dist/server.js +20 -28
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +16 -9
- package/dist/streaming-buffer.d.ts +16 -0
- package/dist/streaming-buffer.js +52 -0
- package/package.json +11 -4
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
export function isDirectExecution(moduleUrl) {
|
|
4
|
+
const entry = process.argv[1];
|
|
5
|
+
if (!entry)
|
|
6
|
+
return false;
|
|
7
|
+
return pathToFileURL(resolve(entry)).href === moduleUrl;
|
|
8
|
+
}
|
|
9
|
+
export function runCli(moduleUrl, main, onError) {
|
|
10
|
+
if (!isDirectExecution(moduleUrl)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
Promise.resolve()
|
|
14
|
+
.then(() => main())
|
|
15
|
+
.catch(onError);
|
|
16
|
+
}
|
package/dist/host-runtime.d.ts
CHANGED
|
@@ -28,9 +28,6 @@ export interface HostInboundParticipantContext {
|
|
|
28
28
|
type HostInboundMessage = {
|
|
29
29
|
text?: string | null;
|
|
30
30
|
contentType?: CanonMessage['contentType'] | null;
|
|
31
|
-
audioUrl?: string | null;
|
|
32
|
-
audioDurationMs?: number | null;
|
|
33
|
-
imageUrl?: string | null;
|
|
34
31
|
attachments?: CanonMessage['attachments'];
|
|
35
32
|
senderType?: CanonMessage['senderType'];
|
|
36
33
|
mentions?: string[] | null;
|
package/dist/host-runtime.js
CHANGED
|
@@ -50,21 +50,10 @@ export function renderCanonHostInboundContent(message, materialized) {
|
|
|
50
50
|
const body = message.text || '';
|
|
51
51
|
const placeholders = [];
|
|
52
52
|
const attachments = message.attachments ?? [];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
placeholders.push(describeAttachment(att, mat));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else if (message.contentType === 'audio' && message.audioUrl) {
|
|
61
|
-
const duration = message.audioDurationMs
|
|
62
|
-
? ` (${Math.round(message.audioDurationMs / 1000)}s)`
|
|
63
|
-
: '';
|
|
64
|
-
placeholders.push(`[Voice message${duration}]`);
|
|
65
|
-
}
|
|
66
|
-
else if (message.contentType === 'image' && message.imageUrl) {
|
|
67
|
-
placeholders.push('[Image attached]');
|
|
53
|
+
for (let i = 0; i < attachments.length; i += 1) {
|
|
54
|
+
const att = attachments[i];
|
|
55
|
+
const mat = materialized?.find((m) => m.index === i) ?? null;
|
|
56
|
+
placeholders.push(describeAttachment(att, mat));
|
|
68
57
|
}
|
|
69
58
|
const rendered = [...placeholders, body].filter(Boolean).join('\n');
|
|
70
59
|
return rendered || '[Empty message]';
|
package/dist/host.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/host.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
setDefaultResultOrder('ipv4first');
|
|
4
3
|
/**
|
|
5
4
|
* Canon Agent SDK host wrapper — multi-session.
|
|
6
5
|
*
|
|
@@ -28,6 +27,8 @@ import { isAnthropicImageAttachment, materializeMessageMedia, toAnthropicImageBl
|
|
|
28
27
|
import { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildPublicWorkspaceOptions, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, 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';
|
|
29
28
|
import { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
30
29
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
30
|
+
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
31
|
+
import { runCli } from './cli-entry.js';
|
|
31
32
|
let workingDir = process.cwd();
|
|
32
33
|
let workspaceOptions = [];
|
|
33
34
|
let runtimeModels = [];
|
|
@@ -299,31 +300,33 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
299
300
|
sdkSessionId: resumeSessionId || null,
|
|
300
301
|
pendingReply: null,
|
|
301
302
|
};
|
|
303
|
+
const streamingBuffer = createStreamingBuffer({
|
|
304
|
+
throttleMs: STREAMING_THROTTLE_MS,
|
|
305
|
+
getText: () => session.streamingText,
|
|
306
|
+
setText: (text) => {
|
|
307
|
+
session.streamingText = text;
|
|
308
|
+
},
|
|
309
|
+
getTimer: () => session.streamingTimer,
|
|
310
|
+
setTimer: (timer) => {
|
|
311
|
+
session.streamingTimer = timer;
|
|
312
|
+
},
|
|
313
|
+
onWrite: (text) => {
|
|
314
|
+
rtdbWrite(`/streaming/${conversationId}/${agentId}`, {
|
|
315
|
+
text,
|
|
316
|
+
status: 'streaming',
|
|
317
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
318
|
+
}).catch(() => { });
|
|
319
|
+
},
|
|
320
|
+
});
|
|
302
321
|
// ── Per-session streaming helpers ──
|
|
303
322
|
function flushStreaming() {
|
|
304
|
-
|
|
305
|
-
return;
|
|
306
|
-
rtdbWrite(`/streaming/${conversationId}/${agentId}`, {
|
|
307
|
-
text: session.streamingText,
|
|
308
|
-
status: 'streaming',
|
|
309
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
310
|
-
}).catch(() => { });
|
|
323
|
+
streamingBuffer.flush();
|
|
311
324
|
}
|
|
312
325
|
function onStreamDelta(text) {
|
|
313
|
-
|
|
314
|
-
if (!session.streamingTimer) {
|
|
315
|
-
session.streamingTimer = setTimeout(() => {
|
|
316
|
-
session.streamingTimer = null;
|
|
317
|
-
flushStreaming();
|
|
318
|
-
}, STREAMING_THROTTLE_MS);
|
|
319
|
-
}
|
|
326
|
+
streamingBuffer.append(text);
|
|
320
327
|
}
|
|
321
328
|
function clearStreaming() {
|
|
322
|
-
|
|
323
|
-
clearTimeout(session.streamingTimer);
|
|
324
|
-
session.streamingTimer = null;
|
|
325
|
-
}
|
|
326
|
-
session.streamingText = '';
|
|
329
|
+
streamingBuffer.clear();
|
|
327
330
|
rtdbWrite(`/streaming/${conversationId}/${agentId}`, null).catch(() => { });
|
|
328
331
|
}
|
|
329
332
|
function clearIdleResetTimer() {
|
|
@@ -367,8 +370,7 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
367
370
|
session.pendingFinalDeliveryText = finalText;
|
|
368
371
|
session.state.state = 'running';
|
|
369
372
|
session.turnState = 'streaming';
|
|
370
|
-
|
|
371
|
-
flushStreaming();
|
|
373
|
+
streamingBuffer.replace(finalText);
|
|
372
374
|
writeState();
|
|
373
375
|
writeTurn();
|
|
374
376
|
client.setTyping(conversationId, true, 'typing').catch(() => { });
|
|
@@ -664,7 +666,8 @@ function createSession(conversationId, environment, agentId, client, onSessionEn
|
|
|
664
666
|
return session;
|
|
665
667
|
}
|
|
666
668
|
// ── Main ────────────────────────────────────────────────────────────
|
|
667
|
-
async function main() {
|
|
669
|
+
export async function main() {
|
|
670
|
+
setDefaultResultOrder('ipv4first');
|
|
668
671
|
const { values: args } = parseArgs({
|
|
669
672
|
options: {
|
|
670
673
|
cwd: { type: 'string' },
|
|
@@ -783,6 +786,8 @@ async function main() {
|
|
|
783
786
|
}
|
|
784
787
|
if (session.streamingTimer)
|
|
785
788
|
clearTimeout(session.streamingTimer);
|
|
789
|
+
session.streamingText = '';
|
|
790
|
+
rtdbWrite(`/streaming/${conversationId}/${agentId}`, null).catch(() => { });
|
|
786
791
|
if (session.idleResetTimer) {
|
|
787
792
|
clearTimeout(session.idleResetTimer);
|
|
788
793
|
session.idleResetTimer = null;
|
|
@@ -791,7 +796,6 @@ async function main() {
|
|
|
791
796
|
clearTimeout(session.finalDeliveryTimer);
|
|
792
797
|
session.finalDeliveryTimer = null;
|
|
793
798
|
}
|
|
794
|
-
rtdbWrite(`/streaming/${conversationId}/${agentId}`, null).catch(() => { });
|
|
795
799
|
clearSessionState(conversationId, agentId).catch(() => { });
|
|
796
800
|
clearTurnState(conversationId, agentId).catch(() => { });
|
|
797
801
|
client.setTyping(conversationId, false).catch(() => { });
|
|
@@ -911,10 +915,7 @@ async function main() {
|
|
|
911
915
|
try {
|
|
912
916
|
materialized = await materializeMessageMedia({
|
|
913
917
|
id: m.id,
|
|
914
|
-
attachments: m.attachments,
|
|
915
|
-
imageUrl: m.imageUrl ?? null,
|
|
916
|
-
audioUrl: m.audioUrl ?? null,
|
|
917
|
-
audioDurationMs: m.audioDurationMs ?? null,
|
|
918
|
+
attachments: m.attachments ?? [],
|
|
918
919
|
}, { agentId, conversationId: convoId });
|
|
919
920
|
}
|
|
920
921
|
catch (error) {
|
|
@@ -1156,7 +1157,7 @@ async function main() {
|
|
|
1156
1157
|
await new Promise(() => { });
|
|
1157
1158
|
}
|
|
1158
1159
|
// ── Entry ───────────────────────────────────────────────────────────
|
|
1159
|
-
|
|
1160
|
+
runCli(import.meta.url, main, (err) => {
|
|
1160
1161
|
console.error('[canon-host] Fatal error:', err);
|
|
1161
1162
|
const ap = getActiveProfile();
|
|
1162
1163
|
if (ap)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/register.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/register.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
setDefaultResultOrder('ipv4first');
|
|
4
3
|
/**
|
|
5
4
|
* CLI script for Canon agent registration.
|
|
6
5
|
* Invoked by the /canon-register skill.
|
|
@@ -15,80 +14,88 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
15
14
|
import { join } from 'node:path';
|
|
16
15
|
import { homedir } from 'node:os';
|
|
17
16
|
import { parseArgs } from 'node:util';
|
|
17
|
+
import { runCli } from './cli-entry.js';
|
|
18
18
|
const CANON_DIR = join(homedir(), '.canon');
|
|
19
19
|
const AGENTS_PATH = join(CANON_DIR, 'agents.json');
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
profiles =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
20
|
+
export async function main() {
|
|
21
|
+
setDefaultResultOrder('ipv4first');
|
|
22
|
+
const { values } = parseArgs({
|
|
23
|
+
options: {
|
|
24
|
+
name: { type: 'string' },
|
|
25
|
+
description: { type: 'string' },
|
|
26
|
+
phone: { type: 'string' },
|
|
27
|
+
profile: { type: 'string' },
|
|
28
|
+
'base-url': { type: 'string' },
|
|
29
|
+
},
|
|
30
|
+
strict: true,
|
|
31
|
+
});
|
|
32
|
+
if (!values.name || !values.description || !values.phone) {
|
|
33
|
+
console.error('Usage: node register.js --name "Agent Name" --description "Description" --phone "+15551234567" [--profile "my-agent"]');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
// Default profile name: sanitized agent name
|
|
37
|
+
const profileName = values.profile || values.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
38
|
+
let existingAgentId;
|
|
39
|
+
try {
|
|
40
|
+
const profiles = JSON.parse(readFileSync(AGENTS_PATH, 'utf-8'));
|
|
41
|
+
existingAgentId = profiles[profileName]?.agentId;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// File doesn't exist yet
|
|
45
|
+
}
|
|
46
|
+
console.log(`Registering agent "${values.name}" (profile: ${profileName})...`);
|
|
47
|
+
const result = await registerAndWaitForApproval({
|
|
48
|
+
name: values.name,
|
|
49
|
+
description: values.description,
|
|
50
|
+
ownerPhone: values.phone,
|
|
51
|
+
developerInfo: 'Claude Code plugin',
|
|
52
|
+
clientType: 'claude-code',
|
|
53
|
+
baseUrl: values['base-url'],
|
|
54
|
+
requestedAgentId: existingAgentId,
|
|
55
|
+
}, {
|
|
56
|
+
onSubmitted: (requestId) => {
|
|
57
|
+
console.log(`Registration submitted (request ID: ${requestId}).`);
|
|
58
|
+
console.log('Waiting for approval in Canon app...');
|
|
59
|
+
},
|
|
60
|
+
onPollUpdate: () => {
|
|
61
|
+
process.stdout.write('.');
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
console.log(''); // newline after dots
|
|
65
|
+
switch (result.status) {
|
|
66
|
+
case 'approved': {
|
|
67
|
+
// Save to ~/.canon/agents.json
|
|
68
|
+
mkdirSync(CANON_DIR, { recursive: true });
|
|
69
|
+
let profiles = {};
|
|
70
|
+
try {
|
|
71
|
+
profiles = JSON.parse(readFileSync(AGENTS_PATH, 'utf-8'));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// File doesn't exist yet
|
|
75
|
+
}
|
|
76
|
+
profiles[profileName] = {
|
|
77
|
+
apiKey: result.apiKey,
|
|
78
|
+
agentId: result.agentId,
|
|
79
|
+
agentName: result.agentName,
|
|
80
|
+
registeredAt: new Date().toISOString(),
|
|
81
|
+
};
|
|
82
|
+
writeFileSync(AGENTS_PATH, JSON.stringify(profiles, null, 2));
|
|
83
|
+
console.log(`Approved! Agent: ${result.agentName} (${result.agentId})`);
|
|
84
|
+
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
85
|
+
console.log('Restart Claude Code to connect.');
|
|
86
|
+
break;
|
|
73
87
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.log(`Saved as profile "${profileName}" in ~/.canon/agents.json`);
|
|
83
|
-
console.log('Restart Claude Code to connect.');
|
|
84
|
-
break;
|
|
88
|
+
case 'rejected':
|
|
89
|
+
console.log('Registration was rejected.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
break;
|
|
92
|
+
case 'timeout':
|
|
93
|
+
console.log('Registration timed out (5 minutes). Try again later.');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
break;
|
|
85
96
|
}
|
|
86
|
-
case 'rejected':
|
|
87
|
-
console.log('Registration was rejected.');
|
|
88
|
-
process.exit(1);
|
|
89
|
-
break;
|
|
90
|
-
case 'timeout':
|
|
91
|
-
console.log('Registration timed out (5 minutes). Try again later.');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
break;
|
|
94
97
|
}
|
|
98
|
+
runCli(import.meta.url, main, (error) => {
|
|
99
|
+
console.error('[canon-register] Fatal error:', error);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare function main(): Promise<void>;
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setDefaultResultOrder } from 'node:dns';
|
|
3
|
-
setDefaultResultOrder('ipv4first');
|
|
4
3
|
/**
|
|
5
4
|
* Canon channel MCP server for Claude Code.
|
|
6
5
|
*
|
|
@@ -13,6 +12,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
13
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
13
|
import { CanonClient, CanonStream, ApprovalManager, loadProfiles, isProfileLocked, resolveCanonAgent, getActiveProfile, releaseLock, initRTDBAuth, writeSessionState, clearSessionState, } from '@canonmsg/core';
|
|
15
14
|
import { ApprovalHttpServer } from './approval-server.js';
|
|
15
|
+
import { runCli } from './cli-entry.js';
|
|
16
16
|
// ── MCP server ─────────────────────────────────────────────────────────
|
|
17
17
|
const server = new Server({ name: 'canon-channel', version: '0.1.0' }, {
|
|
18
18
|
capabilities: {
|
|
@@ -32,36 +32,31 @@ function getPrimaryAttachment(message) {
|
|
|
32
32
|
const attachment = message.attachments?.[0];
|
|
33
33
|
if (attachment?.url)
|
|
34
34
|
return attachment;
|
|
35
|
-
if (message.audioUrl) {
|
|
36
|
-
return {
|
|
37
|
-
kind: 'audio',
|
|
38
|
-
url: message.audioUrl,
|
|
39
|
-
durationMs: message.audioDurationMs,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
if (message.imageUrl) {
|
|
43
|
-
return {
|
|
44
|
-
kind: 'image',
|
|
45
|
-
url: message.imageUrl,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
35
|
return null;
|
|
49
36
|
}
|
|
50
37
|
function renderInboundContent(message) {
|
|
51
|
-
|
|
38
|
+
const content = message.text || '';
|
|
52
39
|
const attachment = getPrimaryAttachment(message);
|
|
40
|
+
// MCP channel mode does not yet materialize media into local files. Surface
|
|
41
|
+
// a placeholder that tells the model media *was* attached without inlining a
|
|
42
|
+
// Firebase Storage URL: the model cannot fetch it, and reasoning about a
|
|
43
|
+
// URL-as-string leads to hallucinated image descriptions. The `attachment_*`
|
|
44
|
+
// fields on the structured `meta` sidechannel still carry the URL for UI /
|
|
45
|
+
// telemetry consumers.
|
|
46
|
+
let placeholder = null;
|
|
53
47
|
if (attachment?.kind === 'audio') {
|
|
54
48
|
const duration = attachment.durationMs ? ` (${Math.round(attachment.durationMs / 1000)}s)` : '';
|
|
55
|
-
|
|
56
|
-
? `[Voice message${duration}: ${attachment.url}]\n${content}`
|
|
57
|
-
: `[Voice message${duration}: ${attachment.url}]`;
|
|
49
|
+
placeholder = `[Voice message${duration} — ask sender to resend if you need the audio]`;
|
|
58
50
|
}
|
|
59
|
-
if (attachment?.kind === 'image') {
|
|
60
|
-
|
|
51
|
+
else if (attachment?.kind === 'image') {
|
|
52
|
+
placeholder = '[Image attached — not yet materialized into this session; ask sender to describe if needed]';
|
|
61
53
|
}
|
|
62
|
-
if (attachment?.kind === 'file') {
|
|
54
|
+
else if (attachment?.kind === 'file') {
|
|
63
55
|
const label = attachment.fileName || 'File';
|
|
64
|
-
|
|
56
|
+
placeholder = `[File: ${label} — not yet materialized into this session]`;
|
|
57
|
+
}
|
|
58
|
+
if (placeholder) {
|
|
59
|
+
return content ? `${placeholder}\n${content}` : placeholder;
|
|
65
60
|
}
|
|
66
61
|
return content;
|
|
67
62
|
}
|
|
@@ -486,10 +481,6 @@ async function handleInboundMessage(payload) {
|
|
|
486
481
|
}
|
|
487
482
|
}
|
|
488
483
|
const primaryAttachment = getPrimaryAttachment(m);
|
|
489
|
-
if (m.imageUrl)
|
|
490
|
-
meta.image_url = m.imageUrl;
|
|
491
|
-
if (m.audioUrl)
|
|
492
|
-
meta.audio_url = m.audioUrl;
|
|
493
484
|
if (primaryAttachment?.url) {
|
|
494
485
|
meta.attachment_kind = primaryAttachment.kind;
|
|
495
486
|
meta.attachment_url = primaryAttachment.url;
|
|
@@ -602,7 +593,8 @@ async function startChannel() {
|
|
|
602
593
|
}
|
|
603
594
|
}
|
|
604
595
|
// ── Main ───────────────────────────────────────────────────────────────
|
|
605
|
-
async function main() {
|
|
596
|
+
export async function main() {
|
|
597
|
+
setDefaultResultOrder('ipv4first');
|
|
606
598
|
const transport = new StdioServerTransport();
|
|
607
599
|
await server.connect(transport);
|
|
608
600
|
// Start the Canon channel after MCP connection is established
|
|
@@ -626,7 +618,7 @@ async function main() {
|
|
|
626
618
|
process.on('SIGINT', shutdown);
|
|
627
619
|
process.on('SIGTERM', shutdown);
|
|
628
620
|
}
|
|
629
|
-
|
|
621
|
+
runCli(import.meta.url, main, (err) => {
|
|
630
622
|
console.error('[canon] Fatal error:', err);
|
|
631
623
|
process.exit(1);
|
|
632
624
|
});
|
package/dist/setup.d.ts
CHANGED
package/dist/setup.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
10
10
|
import { join, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { runCli } from './cli-entry.js';
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
14
15
|
const skillsDir = join(home, '.claude', 'skills');
|
|
@@ -55,12 +56,18 @@ Then start Claude Code with:
|
|
|
55
56
|
`);
|
|
56
57
|
}
|
|
57
58
|
// ── Main ───────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
console.log('
|
|
60
|
-
console.log('
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.log('
|
|
64
|
-
console.log(' Run /canon-
|
|
65
|
-
console.log('
|
|
66
|
-
|
|
59
|
+
export function main() {
|
|
60
|
+
console.log('Canon Claude Code Plugin Setup');
|
|
61
|
+
console.log('==============================\n');
|
|
62
|
+
console.log('Installing skills...');
|
|
63
|
+
installSkills();
|
|
64
|
+
console.log('\nRegistering your agent...');
|
|
65
|
+
console.log(' Run /canon-register in Claude Code to register a new agent');
|
|
66
|
+
console.log(' Run /canon-configure if you already have an API key\n');
|
|
67
|
+
console.log('MCP server configuration:');
|
|
68
|
+
showMcpInstructions();
|
|
69
|
+
}
|
|
70
|
+
runCli(import.meta.url, main, (error) => {
|
|
71
|
+
console.error('[canon-setup] Fatal error:', error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface StreamingBufferOptions {
|
|
2
|
+
throttleMs: number;
|
|
3
|
+
getText: () => string;
|
|
4
|
+
setText: (text: string) => void;
|
|
5
|
+
getTimer: () => ReturnType<typeof setTimeout> | null;
|
|
6
|
+
setTimer: (timer: ReturnType<typeof setTimeout> | null) => void;
|
|
7
|
+
onWrite: (text: string) => void;
|
|
8
|
+
schedule?: typeof setTimeout;
|
|
9
|
+
cancel?: typeof clearTimeout;
|
|
10
|
+
}
|
|
11
|
+
export declare function createStreamingBuffer(options: StreamingBufferOptions): {
|
|
12
|
+
append(delta: string): void;
|
|
13
|
+
flush: () => void;
|
|
14
|
+
clear(): void;
|
|
15
|
+
replace(text: string): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function createStreamingBuffer(options) {
|
|
2
|
+
const schedule = options.schedule ?? setTimeout;
|
|
3
|
+
const cancel = options.cancel ?? clearTimeout;
|
|
4
|
+
const flush = () => {
|
|
5
|
+
const timer = options.getTimer();
|
|
6
|
+
if (timer) {
|
|
7
|
+
cancel(timer);
|
|
8
|
+
options.setTimer(null);
|
|
9
|
+
}
|
|
10
|
+
const text = options.getText();
|
|
11
|
+
if (text) {
|
|
12
|
+
options.onWrite(text);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
append(delta) {
|
|
17
|
+
if (!delta)
|
|
18
|
+
return;
|
|
19
|
+
options.setText(`${options.getText()}${delta}`);
|
|
20
|
+
if (options.getTimer())
|
|
21
|
+
return;
|
|
22
|
+
const timer = schedule(() => {
|
|
23
|
+
options.setTimer(null);
|
|
24
|
+
const text = options.getText();
|
|
25
|
+
if (text) {
|
|
26
|
+
options.onWrite(text);
|
|
27
|
+
}
|
|
28
|
+
}, options.throttleMs);
|
|
29
|
+
options.setTimer(timer);
|
|
30
|
+
},
|
|
31
|
+
flush,
|
|
32
|
+
clear() {
|
|
33
|
+
const timer = options.getTimer();
|
|
34
|
+
if (timer) {
|
|
35
|
+
cancel(timer);
|
|
36
|
+
options.setTimer(null);
|
|
37
|
+
}
|
|
38
|
+
options.setText('');
|
|
39
|
+
},
|
|
40
|
+
replace(text) {
|
|
41
|
+
const timer = options.getTimer();
|
|
42
|
+
if (timer) {
|
|
43
|
+
cancel(timer);
|
|
44
|
+
options.setTimer(null);
|
|
45
|
+
}
|
|
46
|
+
options.setText(text);
|
|
47
|
+
if (text) {
|
|
48
|
+
options.onWrite(text);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
15
|
"canon-channel-server": "dist/server.js",
|
|
9
16
|
"canon-claude": "dist/host.js",
|
|
@@ -23,8 +30,8 @@
|
|
|
23
30
|
},
|
|
24
31
|
"dependencies": {
|
|
25
32
|
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
26
|
-
"@canonmsg/agent-sdk": "^0.8.
|
|
27
|
-
"@canonmsg/core": "^0.7.
|
|
33
|
+
"@canonmsg/agent-sdk": "^0.8.1",
|
|
34
|
+
"@canonmsg/core": "^0.7.1",
|
|
28
35
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
29
36
|
},
|
|
30
37
|
"engines": {
|