@canonmsg/claude-code-plugin 0.19.6 → 0.21.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/README.md +1 -1
- package/dist/approval-diff.d.ts +1 -1
- package/dist/approval-diff.js +1 -1
- package/dist/approval-server.d.ts +1 -1
- package/dist/bridge-bin.d.ts +14 -0
- package/dist/bridge-bin.js +27 -0
- package/dist/cli-entry.d.ts +2 -2
- package/dist/cli-entry.js +1 -1
- package/dist/host.d.ts +1 -1
- package/dist/host.js +628 -925
- package/dist/mcp-args.d.ts +1 -1
- package/dist/register.js +73 -25
- package/dist/server.js +23 -11
- package/dist/session-defaults.d.ts +34 -0
- package/dist/session-defaults.js +123 -0
- package/dist/session-state.d.ts +2 -2
- package/dist/subagent-activity.d.ts +11 -0
- package/dist/subagent-activity.js +51 -0
- package/dist/tool-policy.d.ts +2 -1
- package/package.json +10 -8
- package/dist/inbound-policy.d.ts +0 -22
- package/dist/inbound-policy.js +0 -46
- package/dist/startup-recovery.d.ts +0 -46
- package/dist/startup-recovery.js +0 -59
- package/dist/streaming-buffer.d.ts +0 -16
- package/dist/streaming-buffer.js +0 -52
package/dist/mcp-args.d.ts
CHANGED
package/dist/register.js
CHANGED
|
@@ -9,10 +9,47 @@ import { setDefaultResultOrder } from 'node:dns';
|
|
|
9
9
|
*
|
|
10
10
|
* On approval, saves the agent to ~/.canon/agents.json under the profile name.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { ackRegistrationApprovalWith, resolveCanonBaseUrl, runRegistrationFlow, } from '@canonmsg/core/contract';
|
|
13
|
+
import { clearPendingRegistration, getOrCreatePendingRegistration, updatePendingRegistration, upsertAgentProfile, AGENTS_PATH, } from '@canonmsg/core/local';
|
|
14
|
+
import { resolvePackagedBridgeBin } from './bridge-bin.js';
|
|
15
|
+
import { connectBridge } from '@canonmsg/framework';
|
|
13
16
|
import { readFileSync } from 'node:fs';
|
|
14
17
|
import { parseArgs } from 'node:util';
|
|
15
18
|
import { runCli } from './cli-entry.js';
|
|
19
|
+
/**
|
|
20
|
+
* Registration rides the bridge (Phase 6, same shape as the Hermes port): a
|
|
21
|
+
* short-lived PRE-IDENTITY registration daemon (offline stub +
|
|
22
|
+
* CANON_BRIDGE_REGISTRATION_BASE_URL, managed-child so it dies with this
|
|
23
|
+
* process) serves the three unauthenticated §B10 routes over JSON-RPC.
|
|
24
|
+
*/
|
|
25
|
+
async function openRegistrationBridge(baseUrl) {
|
|
26
|
+
return connectBridge({
|
|
27
|
+
profile: 'registration',
|
|
28
|
+
mode: 'managed-child',
|
|
29
|
+
...(resolvePackagedBridgeBin() ? { binPath: resolvePackagedBridgeBin() } : {}),
|
|
30
|
+
env: {
|
|
31
|
+
...process.env,
|
|
32
|
+
CANON_BRIDGE_AGENT_ID: 'canon-registration',
|
|
33
|
+
CANON_BRIDGE_REGISTRATION_BASE_URL: baseUrl,
|
|
34
|
+
},
|
|
35
|
+
hello: { clientType: 'claude-code', wantFamilies: [] },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function bridgeRegistrationTransport(bridge) {
|
|
39
|
+
return {
|
|
40
|
+
register: (_baseUrl, body) => bridge.client.call('register', body),
|
|
41
|
+
checkStatus: (_baseUrl, requestId, pollToken) => bridge.client.call('getRegistrationStatus', {
|
|
42
|
+
requestId,
|
|
43
|
+
...(pollToken ? { pollToken } : {}),
|
|
44
|
+
}),
|
|
45
|
+
ackRegistrationStatus: async (_baseUrl, requestId, pollToken) => {
|
|
46
|
+
await bridge.client.call('ackRegistrationStatus', {
|
|
47
|
+
requestId,
|
|
48
|
+
...(pollToken ? { pollToken } : {}),
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
16
53
|
const HELP = `canon-register — register or reconnect a Claude Code agent in Canon
|
|
17
54
|
|
|
18
55
|
USAGE
|
|
@@ -62,29 +99,39 @@ export async function main() {
|
|
|
62
99
|
}
|
|
63
100
|
console.log(`Registering agent "${values.name}" (profile: ${profileName})...`);
|
|
64
101
|
const pending = getOrCreatePendingRegistration(profileName, 'claude-code');
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
const baseUrl = resolveCanonBaseUrl(values['base-url']);
|
|
103
|
+
const bridge = await openRegistrationBridge(baseUrl);
|
|
104
|
+
const transport = bridgeRegistrationTransport(bridge);
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = await runRegistrationFlow({
|
|
108
|
+
name: values.name,
|
|
109
|
+
description: values.description,
|
|
110
|
+
ownerPhone: values.phone,
|
|
111
|
+
developerInfo: 'Claude Code plugin',
|
|
112
|
+
clientType: 'claude-code',
|
|
113
|
+
baseUrl: values['base-url'],
|
|
114
|
+
requestedAgentId: existingAgentId,
|
|
115
|
+
localRegistrationId: pending.localRegistrationId,
|
|
116
|
+
}, transport, {
|
|
117
|
+
onSubmitted: (requestId, pollToken) => {
|
|
118
|
+
updatePendingRegistration(profileName, {
|
|
119
|
+
requestId,
|
|
120
|
+
pollToken,
|
|
121
|
+
clientType: 'claude-code',
|
|
122
|
+
});
|
|
123
|
+
console.log(`Registration submitted (request ID: ${requestId}).`);
|
|
124
|
+
console.log('Waiting for approval in Canon app...');
|
|
125
|
+
},
|
|
126
|
+
onPollUpdate: () => {
|
|
127
|
+
process.stdout.write('.');
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
bridge.client.close();
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
88
135
|
console.log(''); // newline after dots
|
|
89
136
|
switch (result.status) {
|
|
90
137
|
case 'approved': {
|
|
@@ -101,7 +148,7 @@ export async function main() {
|
|
|
101
148
|
...(typeof values['base-url'] === 'string' ? { baseUrl: values['base-url'] } : {}),
|
|
102
149
|
});
|
|
103
150
|
if (result.requestId) {
|
|
104
|
-
await
|
|
151
|
+
await ackRegistrationApprovalWith(transport, values['base-url'], result.requestId, result.pollToken);
|
|
105
152
|
}
|
|
106
153
|
clearPendingRegistration(profileName);
|
|
107
154
|
console.log(`Approved! Agent: ${result.agentName} (${result.agentId})`);
|
|
@@ -121,6 +168,7 @@ export async function main() {
|
|
|
121
168
|
process.exit(1);
|
|
122
169
|
break;
|
|
123
170
|
}
|
|
171
|
+
bridge.client.close();
|
|
124
172
|
}
|
|
125
173
|
runCli(import.meta.url, main, (error) => {
|
|
126
174
|
console.error('[canon-register] Fatal error:', error);
|
package/dist/server.js
CHANGED
|
@@ -10,7 +10,7 @@ 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,
|
|
13
|
+
import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, markLocalRuntimeStopped, loadProfiles, isProfileLocked, resolveCanonAgent, releaseLock, upsertLocalRuntimeEntry, initRTDBAuth, renderCanonHostInboundContent, } from '@canonmsg/core';
|
|
14
14
|
import { materializeMessageMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
15
15
|
import { ApprovalHttpServer } from './approval-server.js';
|
|
16
16
|
import { runCli } from './cli-entry.js';
|
|
@@ -34,6 +34,16 @@ const server = new Server({ name: 'canon-channel', version: '0.1.0' }, {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
let client = null;
|
|
37
|
+
// Scoped RTDB handle for this channel server's single agent. Held alongside
|
|
38
|
+
// the other per-process singletons (client, stream, ...) so both startChannel
|
|
39
|
+
// and the shutdown handler in main() write session state as this agent — not
|
|
40
|
+
// through any module-global default.
|
|
41
|
+
let rtdb = null;
|
|
42
|
+
// The one agent profile this server resolved+locked, and its lock handle.
|
|
43
|
+
// Held here (not via a core module-global) so the list_agents tool handler and
|
|
44
|
+
// the shutdown/error paths — all outside startChannel's scope — can read them.
|
|
45
|
+
let activeProfile = null;
|
|
46
|
+
let activeLockHandle = null;
|
|
37
47
|
let stream = null;
|
|
38
48
|
let agentContext = null;
|
|
39
49
|
let approvalManager = null;
|
|
@@ -386,7 +396,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
386
396
|
profile: name,
|
|
387
397
|
agentName: p.agentName,
|
|
388
398
|
agentId: p.agentId,
|
|
389
|
-
status:
|
|
399
|
+
status: activeProfile === name ? 'active (this session)' : lock.locked ? `in use (PID ${lock.pid})` : 'available',
|
|
390
400
|
};
|
|
391
401
|
});
|
|
392
402
|
if (entries.length === 0) {
|
|
@@ -547,6 +557,8 @@ async function startChannel() {
|
|
|
547
557
|
profile = resolved.profile;
|
|
548
558
|
baseUrl = resolved.baseUrl;
|
|
549
559
|
profileAgentName = resolved.agentName;
|
|
560
|
+
activeProfile = resolved.profile;
|
|
561
|
+
activeLockHandle = resolved.lockHandle ?? null;
|
|
550
562
|
}
|
|
551
563
|
catch (err) {
|
|
552
564
|
console.error(err instanceof Error ? err.message : err);
|
|
@@ -577,7 +589,7 @@ async function startChannel() {
|
|
|
577
589
|
}
|
|
578
590
|
}
|
|
579
591
|
// Initialize RTDB auth for session state reporting
|
|
580
|
-
initRTDBAuth(client);
|
|
592
|
+
rtdb = initRTDBAuth(client);
|
|
581
593
|
const runtimeId = buildLocalRuntimeId({
|
|
582
594
|
runtime: 'claude-code',
|
|
583
595
|
profile,
|
|
@@ -612,7 +624,7 @@ async function startChannel() {
|
|
|
612
624
|
// Write initial session state
|
|
613
625
|
const initialState = { cwd: process.cwd(), isActive: true };
|
|
614
626
|
for (const convoId of conversationCache.keys()) {
|
|
615
|
-
writeSessionState(convoId, agentId, initialState).catch((err) => console.error(`[canon] Failed to write initial state for ${convoId}:`, err));
|
|
627
|
+
rtdb.writeSessionState(convoId, agentId, initialState).catch((err) => console.error(`[canon] Failed to write initial state for ${convoId}:`, err));
|
|
616
628
|
}
|
|
617
629
|
// Start SSE stream
|
|
618
630
|
stream = new CanonStream({
|
|
@@ -665,9 +677,10 @@ export async function main() {
|
|
|
665
677
|
// Graceful shutdown — release agent lock and clear session state
|
|
666
678
|
const shutdown = () => {
|
|
667
679
|
// Mark all conversations as inactive
|
|
668
|
-
if (agentContext) {
|
|
680
|
+
if (agentContext && rtdb) {
|
|
681
|
+
const scopedRtdb = rtdb;
|
|
669
682
|
for (const convoId of conversationCache.keys()) {
|
|
670
|
-
clearSessionState(convoId, agentContext.agentId).catch(() => { });
|
|
683
|
+
scopedRtdb.clearSessionState(convoId, agentContext.agentId).catch(() => { });
|
|
671
684
|
}
|
|
672
685
|
}
|
|
673
686
|
approvalManager?.dispose();
|
|
@@ -675,15 +688,14 @@ export async function main() {
|
|
|
675
688
|
stream?.stop();
|
|
676
689
|
const runtimeId = buildLocalRuntimeId({
|
|
677
690
|
runtime: 'claude-code',
|
|
678
|
-
profile:
|
|
691
|
+
profile: activeProfile,
|
|
679
692
|
cwd: process.cwd(),
|
|
680
693
|
launchCommand: ['canon-channel-server'],
|
|
681
694
|
});
|
|
682
695
|
markLocalRuntimeStopped(runtimeId);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
getActiveProfileLock()?.release();
|
|
696
|
+
if (activeProfile)
|
|
697
|
+
releaseLock(activeProfile);
|
|
698
|
+
activeLockHandle?.release();
|
|
687
699
|
process.exit(0);
|
|
688
700
|
};
|
|
689
701
|
process.on('SIGINT', shutdown);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentDefinition, EffortLevel, ThinkingConfig } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
export type ClaudeThinkingMode = 'adaptive' | 'off';
|
|
3
|
+
export type ClaudeUltracodeMode = 'on' | 'off';
|
|
4
|
+
export type ClaudeEffortLevel = Extract<EffortLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'>;
|
|
5
|
+
export interface ClaudeSessionDefaults {
|
|
6
|
+
model?: string;
|
|
7
|
+
effort?: ClaudeEffortLevel;
|
|
8
|
+
thinking?: ClaudeThinkingMode;
|
|
9
|
+
ultracode?: ClaudeUltracodeMode;
|
|
10
|
+
fallbackModel?: string;
|
|
11
|
+
agentProgressSummaries?: boolean;
|
|
12
|
+
agents?: Record<string, AgentDefinition>;
|
|
13
|
+
}
|
|
14
|
+
export interface ClaudeSessionControlValues {
|
|
15
|
+
thinking?: ClaudeThinkingMode;
|
|
16
|
+
ultracode?: ClaudeUltracodeMode;
|
|
17
|
+
}
|
|
18
|
+
export interface ClaudeEffectiveSessionSettings extends ClaudeSessionDefaults {
|
|
19
|
+
runtimeControlValues: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseClaudeThinkingMode(value: unknown): ClaudeThinkingMode | undefined;
|
|
22
|
+
export declare function parseClaudeUltracodeMode(value: unknown): ClaudeUltracodeMode | undefined;
|
|
23
|
+
export declare function parseClaudeSessionDefaults(raw: unknown): ClaudeSessionDefaults;
|
|
24
|
+
export declare function parseClaudeSessionControlValues(values: Record<string, string> | null | undefined): ClaudeSessionControlValues;
|
|
25
|
+
export declare function claudeThinkingToConfig(mode: ClaudeThinkingMode | undefined): ThinkingConfig | undefined;
|
|
26
|
+
export declare function claudeThinkingToFlagValue(mode: ClaudeThinkingMode): boolean;
|
|
27
|
+
export declare function resolveEffectiveSessionSettings(input: {
|
|
28
|
+
profileDefaults?: ClaudeSessionDefaults;
|
|
29
|
+
sessionConfig?: {
|
|
30
|
+
model?: string;
|
|
31
|
+
effort?: ClaudeEffortLevel;
|
|
32
|
+
runtimeControlValues?: Record<string, string>;
|
|
33
|
+
} | null;
|
|
34
|
+
}): ClaudeEffectiveSessionSettings;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const CLAUDE_EFFORT_VALUES = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
2
|
+
function optionalString(value) {
|
|
3
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
4
|
+
}
|
|
5
|
+
function optionalBoolean(value) {
|
|
6
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
export function parseClaudeThinkingMode(value) {
|
|
9
|
+
if (value === false || value === 'off' || value === 'disabled')
|
|
10
|
+
return 'off';
|
|
11
|
+
if (value === true || value === 'on' || value === 'adaptive' || value === 'enabled')
|
|
12
|
+
return 'adaptive';
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export function parseClaudeUltracodeMode(value) {
|
|
16
|
+
if (value === true || value === 'on' || value === 'enabled')
|
|
17
|
+
return 'on';
|
|
18
|
+
if (value === false || value === 'off' || value === 'disabled')
|
|
19
|
+
return 'off';
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function parseEffort(value) {
|
|
23
|
+
const normalized = optionalString(value);
|
|
24
|
+
if (!normalized)
|
|
25
|
+
return undefined;
|
|
26
|
+
return CLAUDE_EFFORT_VALUES.has(normalized)
|
|
27
|
+
? normalized
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
function parseStringArray(value) {
|
|
31
|
+
if (!Array.isArray(value))
|
|
32
|
+
return undefined;
|
|
33
|
+
const out = value
|
|
34
|
+
.map((entry) => optionalString(entry))
|
|
35
|
+
.filter((entry) => Boolean(entry));
|
|
36
|
+
return out.length > 0 ? out : undefined;
|
|
37
|
+
}
|
|
38
|
+
function parseAgentDefinition(value) {
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
40
|
+
return null;
|
|
41
|
+
const data = value;
|
|
42
|
+
const description = optionalString(data.description);
|
|
43
|
+
const prompt = optionalString(data.prompt);
|
|
44
|
+
if (!description || !prompt)
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
description,
|
|
48
|
+
prompt,
|
|
49
|
+
...(parseStringArray(data.tools) ? { tools: parseStringArray(data.tools) } : {}),
|
|
50
|
+
...(parseStringArray(data.disallowedTools) ? { disallowedTools: parseStringArray(data.disallowedTools) } : {}),
|
|
51
|
+
...(optionalString(data.model) ? { model: optionalString(data.model) } : {}),
|
|
52
|
+
...(parseStringArray(data.skills) ? { skills: parseStringArray(data.skills) } : {}),
|
|
53
|
+
...(typeof data.maxTurns === 'number' && Number.isFinite(data.maxTurns) && data.maxTurns > 0
|
|
54
|
+
? { maxTurns: Math.floor(data.maxTurns) }
|
|
55
|
+
: {}),
|
|
56
|
+
...(typeof data.background === 'boolean' ? { background: data.background } : {}),
|
|
57
|
+
...(parseEffort(data.effort) ? { effort: parseEffort(data.effort) } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function parseAgents(value) {
|
|
61
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
62
|
+
return undefined;
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [rawName, rawAgent] of Object.entries(value)) {
|
|
65
|
+
const name = rawName.trim();
|
|
66
|
+
if (!name)
|
|
67
|
+
continue;
|
|
68
|
+
const agent = parseAgentDefinition(rawAgent);
|
|
69
|
+
if (agent)
|
|
70
|
+
out[name] = agent;
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
73
|
+
}
|
|
74
|
+
export function parseClaudeSessionDefaults(raw) {
|
|
75
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
76
|
+
return {};
|
|
77
|
+
const data = raw;
|
|
78
|
+
return {
|
|
79
|
+
...(optionalString(data.model) ? { model: optionalString(data.model) } : {}),
|
|
80
|
+
...(parseEffort(data.effort) ? { effort: parseEffort(data.effort) } : {}),
|
|
81
|
+
...(parseClaudeThinkingMode(data.thinking) ? { thinking: parseClaudeThinkingMode(data.thinking) } : {}),
|
|
82
|
+
...(parseClaudeUltracodeMode(data.ultracode) ? { ultracode: parseClaudeUltracodeMode(data.ultracode) } : {}),
|
|
83
|
+
...(optionalString(data.fallbackModel) ? { fallbackModel: optionalString(data.fallbackModel) } : {}),
|
|
84
|
+
...(optionalBoolean(data.agentProgressSummaries) !== undefined
|
|
85
|
+
? { agentProgressSummaries: optionalBoolean(data.agentProgressSummaries) }
|
|
86
|
+
: {}),
|
|
87
|
+
...(parseAgents(data.agents) ? { agents: parseAgents(data.agents) } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function parseClaudeSessionControlValues(values) {
|
|
91
|
+
return {
|
|
92
|
+
...(parseClaudeThinkingMode(values?.thinking) ? { thinking: parseClaudeThinkingMode(values?.thinking) } : {}),
|
|
93
|
+
...(parseClaudeUltracodeMode(values?.ultracode) ? { ultracode: parseClaudeUltracodeMode(values?.ultracode) } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function claudeThinkingToConfig(mode) {
|
|
97
|
+
if (mode === 'adaptive')
|
|
98
|
+
return { type: 'adaptive' };
|
|
99
|
+
if (mode === 'off')
|
|
100
|
+
return { type: 'disabled' };
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
export function claudeThinkingToFlagValue(mode) {
|
|
104
|
+
return mode !== 'off';
|
|
105
|
+
}
|
|
106
|
+
export function resolveEffectiveSessionSettings(input) {
|
|
107
|
+
const controlValues = parseClaudeSessionControlValues(input.sessionConfig?.runtimeControlValues);
|
|
108
|
+
const runtimeControlValues = {
|
|
109
|
+
...(input.profileDefaults?.thinking ? { thinking: input.profileDefaults.thinking } : {}),
|
|
110
|
+
...(input.profileDefaults?.ultracode ? { ultracode: input.profileDefaults.ultracode } : {}),
|
|
111
|
+
...(input.sessionConfig?.runtimeControlValues ?? {}),
|
|
112
|
+
...(controlValues.thinking ? { thinking: controlValues.thinking } : {}),
|
|
113
|
+
...(controlValues.ultracode ? { ultracode: controlValues.ultracode } : {}),
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
...(input.profileDefaults ?? {}),
|
|
117
|
+
...(input.sessionConfig?.model ? { model: input.sessionConfig.model } : {}),
|
|
118
|
+
...(input.sessionConfig?.effort ? { effort: input.sessionConfig.effort } : {}),
|
|
119
|
+
...(controlValues.thinking ? { thinking: controlValues.thinking } : {}),
|
|
120
|
+
...(controlValues.ultracode ? { ultracode: controlValues.ultracode } : {}),
|
|
121
|
+
runtimeControlValues,
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/session-state.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import type { DeliveryIntent, ModelOption, TurnLifecycleState } from '@canonmsg/core';
|
|
3
|
-
import type { TurnArtifactSnapshot } from '@canonmsg/
|
|
2
|
+
import type { DeliveryIntent, ModelOption, TurnLifecycleState } from '@canonmsg/core/contract';
|
|
3
|
+
import type { TurnArtifactSnapshot } from '@canonmsg/agent-host';
|
|
4
4
|
export type ClaudeInputKind = 'seed' | 'canon';
|
|
5
5
|
export type ClaudeArtifactRoutingMode = 'workspace-generated' | 'disabled';
|
|
6
6
|
export interface ClaudeInputEnvelope {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SDKTaskNotificationMessage, SDKTaskProgressMessage, SDKTaskStartedMessage, SDKTaskUpdatedMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
type ClaudeTaskMessage = SDKTaskNotificationMessage | SDKTaskProgressMessage | SDKTaskStartedMessage | SDKTaskUpdatedMessage;
|
|
3
|
+
export interface ClaudeSubagentActivityBlock {
|
|
4
|
+
id: string;
|
|
5
|
+
kind: 'status';
|
|
6
|
+
status: 'running' | 'completed' | 'failed';
|
|
7
|
+
title: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function mapClaudeSubagentActivity(message: ClaudeTaskMessage): ClaudeSubagentActivityBlock | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function titleForMessage(message) {
|
|
2
|
+
const subtype = message.subtype;
|
|
3
|
+
if (subtype === 'task_started') {
|
|
4
|
+
return message.subagent_type
|
|
5
|
+
? `Subagent started: ${message.subagent_type}`
|
|
6
|
+
: 'Subagent started';
|
|
7
|
+
}
|
|
8
|
+
if (subtype === 'task_progress') {
|
|
9
|
+
return message.subagent_type
|
|
10
|
+
? `Subagent progress: ${message.subagent_type}`
|
|
11
|
+
: 'Subagent progress';
|
|
12
|
+
}
|
|
13
|
+
if (subtype === 'task_updated')
|
|
14
|
+
return 'Subagent updated';
|
|
15
|
+
return `Subagent ${message.status}`;
|
|
16
|
+
}
|
|
17
|
+
function textForMessage(message) {
|
|
18
|
+
if (message.subtype === 'task_started')
|
|
19
|
+
return message.description;
|
|
20
|
+
if (message.subtype === 'task_progress')
|
|
21
|
+
return message.summary ?? message.description;
|
|
22
|
+
if (message.subtype === 'task_updated')
|
|
23
|
+
return message.patch.description ?? message.patch.error;
|
|
24
|
+
return message.summary;
|
|
25
|
+
}
|
|
26
|
+
function statusForMessage(message) {
|
|
27
|
+
if (message.subtype === 'task_notification') {
|
|
28
|
+
if (message.status === 'completed')
|
|
29
|
+
return 'completed';
|
|
30
|
+
return 'failed';
|
|
31
|
+
}
|
|
32
|
+
if (message.subtype === 'task_updated') {
|
|
33
|
+
if (message.patch.status === 'completed')
|
|
34
|
+
return 'completed';
|
|
35
|
+
if (message.patch.status === 'failed' || message.patch.status === 'killed')
|
|
36
|
+
return 'failed';
|
|
37
|
+
}
|
|
38
|
+
return 'running';
|
|
39
|
+
}
|
|
40
|
+
export function mapClaudeSubagentActivity(message) {
|
|
41
|
+
if ('skip_transcript' in message && message.skip_transcript)
|
|
42
|
+
return null;
|
|
43
|
+
const text = textForMessage(message);
|
|
44
|
+
return {
|
|
45
|
+
id: `task:${message.task_id}`,
|
|
46
|
+
kind: 'status',
|
|
47
|
+
status: statusForMessage(message),
|
|
48
|
+
title: titleForMessage(message),
|
|
49
|
+
...(text ? { text } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
package/dist/tool-policy.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core/contract';
|
|
2
|
+
import type { ApprovalManager } from '@canonmsg/core/host';
|
|
2
3
|
export type ClaudeToolPolicyDecision = {
|
|
3
4
|
behavior: 'allow';
|
|
4
5
|
} | {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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",
|
|
@@ -23,17 +23,19 @@
|
|
|
23
23
|
".claude-plugin"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs
|
|
26
|
+
"prepare:workspace-deps": "node ../../scripts/run-workspace-prep.mjs ../../packages/core ../../packages/agent-sdk ../../packages/framework ../../packages/bridge ../../packages/agent-host",
|
|
27
27
|
"build": "npm run prepare:workspace-deps && node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
|
|
28
28
|
"dev": "npm run prepare:workspace-deps && tsc --watch",
|
|
29
29
|
"prepack": "npm run build",
|
|
30
30
|
"test": "vitest run"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
34
|
-
"@canonmsg/agent-
|
|
35
|
-
"@canonmsg/
|
|
36
|
-
"@canonmsg/
|
|
33
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.200",
|
|
34
|
+
"@canonmsg/agent-host": "^0.4.0",
|
|
35
|
+
"@canonmsg/agent-sdk": "^3.4.4",
|
|
36
|
+
"@canonmsg/bridge": "^0.2.0",
|
|
37
|
+
"@canonmsg/core": "^3.2.0",
|
|
38
|
+
"@canonmsg/framework": "^0.2.0",
|
|
37
39
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
38
40
|
},
|
|
39
41
|
"engines": {
|
|
@@ -50,9 +52,9 @@
|
|
|
50
52
|
"repository": {
|
|
51
53
|
"type": "git",
|
|
52
54
|
"url": "https://github.com/HeyBobChan/canon",
|
|
53
|
-
"directory": "
|
|
55
|
+
"directory": "adapters/claude-code-plugin"
|
|
54
56
|
},
|
|
55
|
-
"homepage": "https://github.com/HeyBobChan/canon/tree/main/
|
|
57
|
+
"homepage": "https://github.com/HeyBobChan/canon/tree/main/adapters/claude-code-plugin",
|
|
56
58
|
"publishConfig": {
|
|
57
59
|
"access": "public"
|
|
58
60
|
},
|
package/dist/inbound-policy.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type CanonGroupContext, type CanonGroupContextMode, type CanonConversation, type ResolvedAgentBehaviorPolicy } from '@canonmsg/core';
|
|
2
|
-
export interface InboundParticipantContext {
|
|
3
|
-
conversationType: CanonConversation['type'] | 'unknown';
|
|
4
|
-
memberCount: number | null;
|
|
5
|
-
senderType: 'human' | 'ai_agent';
|
|
6
|
-
senderName: string;
|
|
7
|
-
isOwner: boolean;
|
|
8
|
-
mentionedAgent: boolean;
|
|
9
|
-
groupContext?: CanonGroupContext;
|
|
10
|
-
groupContextMode?: CanonGroupContextMode;
|
|
11
|
-
recentSenderTypes: Array<'human' | 'ai_agent'>;
|
|
12
|
-
recentHumanCount: number;
|
|
13
|
-
recentAgentCount: number;
|
|
14
|
-
consecutiveAgentTurns: number;
|
|
15
|
-
currentAgentStreakStartedByHuman: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface AutoReplyDecision {
|
|
18
|
-
allow: boolean;
|
|
19
|
-
reason: string;
|
|
20
|
-
}
|
|
21
|
-
export declare function buildInboundContextLines(context: InboundParticipantContext): string[];
|
|
22
|
-
export declare function decideAutoReply(context: InboundParticipantContext, behavior?: ResolvedAgentBehaviorPolicy | null): AutoReplyDecision;
|
package/dist/inbound-policy.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { buildCompactGroupContextLines, evaluateParticipationPolicy, resolveAgentBehaviorPolicy, } from '@canonmsg/core';
|
|
2
|
-
function formatRecentSenders(senderTypes) {
|
|
3
|
-
if (senderTypes.length === 0)
|
|
4
|
-
return 'none';
|
|
5
|
-
return senderTypes.map((senderType) => (senderType === 'ai_agent' ? 'agent' : 'human')).join(' -> ');
|
|
6
|
-
}
|
|
7
|
-
export function buildInboundContextLines(context) {
|
|
8
|
-
const conversationTypeLabel = context.conversationType === 'unknown'
|
|
9
|
-
? 'unknown'
|
|
10
|
-
: `${context.conversationType}${context.memberCount ? ` (${context.memberCount} members)` : ''}`;
|
|
11
|
-
const senderRole = context.isOwner
|
|
12
|
-
? 'The latest sender is the verified human owner of this Canon agent.'
|
|
13
|
-
: context.senderType === 'ai_agent'
|
|
14
|
-
? 'The latest sender is another AI agent in Canon.'
|
|
15
|
-
: 'The latest sender is a human Canon participant.';
|
|
16
|
-
return [
|
|
17
|
-
senderRole,
|
|
18
|
-
`Latest sender name: ${context.senderName}`,
|
|
19
|
-
`Latest sender type: ${context.senderType}`,
|
|
20
|
-
`Conversation type: ${conversationTypeLabel}`,
|
|
21
|
-
...(context.groupContext && context.groupContextMode
|
|
22
|
-
? buildCompactGroupContextLines(context.groupContext, context.groupContextMode)
|
|
23
|
-
: []),
|
|
24
|
-
`Directly addressed to this agent: ${context.mentionedAgent ? 'yes' : 'no'}`,
|
|
25
|
-
`Recent sender pattern: ${formatRecentSenders(context.recentSenderTypes)}`,
|
|
26
|
-
`Recent human messages: ${context.recentHumanCount}`,
|
|
27
|
-
`Recent agent messages: ${context.recentAgentCount}`,
|
|
28
|
-
`Consecutive recent agent turns: ${context.consecutiveAgentTurns}`,
|
|
29
|
-
`Current agent streak started after a human message: ${context.currentAgentStreakStartedByHuman ? 'yes' : 'no'}`,
|
|
30
|
-
];
|
|
31
|
-
}
|
|
32
|
-
export function decideAutoReply(context, behavior) {
|
|
33
|
-
const decision = evaluateParticipationPolicy(behavior ?? resolveAgentBehaviorPolicy(), {
|
|
34
|
-
conversationType: context.conversationType,
|
|
35
|
-
senderType: context.senderType,
|
|
36
|
-
isOwner: context.isOwner,
|
|
37
|
-
mentionedAgent: context.mentionedAgent,
|
|
38
|
-
recentHumanCount: context.recentHumanCount,
|
|
39
|
-
consecutiveAgentTurns: context.consecutiveAgentTurns,
|
|
40
|
-
currentAgentStreakStartedByHuman: context.currentAgentStreakStartedByHuman,
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
allow: decision.allow,
|
|
44
|
-
reason: decision.reason,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Startup recovery for inbound messages missed while the host was offline.
|
|
3
|
-
*
|
|
4
|
-
* The host persists a `lastInboundMessageId` cursor per conversation. On
|
|
5
|
-
* startup we paginate `getMessagesPage` (newest-first pages, older pages via
|
|
6
|
-
* its `before` message-id parameter) until the cursor is found or a hard
|
|
7
|
-
* per-conversation bound is hit, then replay everything after the cursor.
|
|
8
|
-
*
|
|
9
|
-
* This module is intentionally identical in packages/claude-code-plugin and
|
|
10
|
-
* packages/codex-plugin — keep both copies in sync (future consolidation
|
|
11
|
-
* candidate).
|
|
12
|
-
*/
|
|
13
|
-
export declare const STARTUP_RECOVERY_PAGE_SIZE = 25;
|
|
14
|
-
export declare const STARTUP_RECOVERY_MAX_MESSAGES = 500;
|
|
15
|
-
export interface StartupRecoveryMessage {
|
|
16
|
-
id: string;
|
|
17
|
-
senderId: string;
|
|
18
|
-
createdAt?: string;
|
|
19
|
-
}
|
|
20
|
-
export interface StartupRecoveryPage {
|
|
21
|
-
messages: StartupRecoveryMessage[];
|
|
22
|
-
}
|
|
23
|
-
export type StartupRecoveryMode =
|
|
24
|
-
/** Cursor found — `messages` is everything strictly after it. */
|
|
25
|
-
'after-cursor'
|
|
26
|
-
/** Cursor present but not found within the bound — `messages` is the bounded recent window. */
|
|
27
|
-
| 'truncated-window'
|
|
28
|
-
/**
|
|
29
|
-
* No usable cursor (fresh runtime file, or the cursor message no longer
|
|
30
|
-
* exists in history) — only the newest inbound message is recovered, since
|
|
31
|
-
* a full-history replay could fire mass duplicate turns.
|
|
32
|
-
*/
|
|
33
|
-
| 'latest-only';
|
|
34
|
-
export interface StartupRecoveryResult<TPage extends StartupRecoveryPage> {
|
|
35
|
-
mode: StartupRecoveryMode;
|
|
36
|
-
/** Missed inbound messages (own messages excluded), oldest first. */
|
|
37
|
-
messages: TPage['messages'];
|
|
38
|
-
/** First page fetched — reusable as hydration context for recovered turns. */
|
|
39
|
-
newestPage: TPage;
|
|
40
|
-
}
|
|
41
|
-
export declare function collectMissedInboundMessages<TPage extends StartupRecoveryPage>(input: {
|
|
42
|
-
fetchPage: (before?: string) => Promise<TPage>;
|
|
43
|
-
cursor: string | null | undefined;
|
|
44
|
-
agentId: string;
|
|
45
|
-
maxMessages?: number;
|
|
46
|
-
}): Promise<StartupRecoveryResult<TPage>>;
|