@canonmsg/claude-code-plugin 0.12.4 → 0.12.5
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 -2
- package/dist/cli-entry.js +1 -26
- package/dist/host.js +236 -92
- package/dist/register.js +24 -0
- package/dist/server.js +14 -0
- package/dist/session-state.d.ts +44 -0
- package/dist/session-state.js +67 -0
- package/dist/setup.js +14 -0
- package/package.json +2 -2
package/dist/cli-entry.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
|
|
2
|
+
export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core';
|
package/dist/cli-entry.js
CHANGED
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
-
export function isDirectExecution(moduleUrl) {
|
|
5
|
-
const entry = process.argv[1];
|
|
6
|
-
if (!entry)
|
|
7
|
-
return false;
|
|
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);
|
|
18
|
-
}
|
|
19
|
-
export function runCli(moduleUrl, main, onError) {
|
|
20
|
-
if (!isDirectExecution(moduleUrl)) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
Promise.resolve()
|
|
24
|
-
.then(() => main())
|
|
25
|
-
.catch(onError);
|
|
26
|
-
}
|
|
1
|
+
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
|
package/dist/host.js
CHANGED
|
@@ -30,6 +30,28 @@ import { CLAUDE_PERMISSION_MODE_OPTIONS, buildCanonHostPrompt, buildConfiguredWo
|
|
|
30
30
|
import { buildInboundContextLines, decideAutoReply, } from './inbound-policy.js';
|
|
31
31
|
import { createStreamingBuffer } from './streaming-buffer.js';
|
|
32
32
|
import { runCli } from './cli-entry.js';
|
|
33
|
+
import { buildClaudeFinalMessageId, createClaudeInputEnvelope, formatClaudeControlError, mergeClaudeModelOptions, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
34
|
+
const HELP = `canon-claude — run a local Claude Code agent host for Canon
|
|
35
|
+
|
|
36
|
+
USAGE
|
|
37
|
+
canon-claude [flags]
|
|
38
|
+
|
|
39
|
+
COMMON FLAGS
|
|
40
|
+
--cwd <path> Project directory to run Claude Code from
|
|
41
|
+
--workspace <path> Additional project to expose in Canon
|
|
42
|
+
--workspace-root <path> Discover projects under an approved root
|
|
43
|
+
--help, -h Show this help
|
|
44
|
+
--version, -V Show package version
|
|
45
|
+
|
|
46
|
+
AUTH
|
|
47
|
+
CANON_AGENT=<profile> canon-claude --cwd /path/to/project
|
|
48
|
+
CANON_API_KEY=agk_live_... canon-claude --cwd /path/to/project
|
|
49
|
+
|
|
50
|
+
EXAMPLES
|
|
51
|
+
canon-claude --cwd ~/dev/canon
|
|
52
|
+
canon-claude --cwd ~/dev/canon --workspace-root ~/dev
|
|
53
|
+
|
|
54
|
+
Keep this terminal open while you want Canon to reach the agent.`;
|
|
33
55
|
let workingDir = process.cwd();
|
|
34
56
|
let workspaceOptions = [];
|
|
35
57
|
let workspaceRoots = [];
|
|
@@ -465,12 +487,40 @@ const CLAUDE_RUNTIME_CAPABILITIES = {
|
|
|
465
487
|
supportsNonFinalPermanentMessages: false,
|
|
466
488
|
};
|
|
467
489
|
function buildCanonPrompt(input) {
|
|
490
|
+
const { session, ...promptInput } = input;
|
|
468
491
|
return buildCanonHostPrompt({
|
|
469
492
|
hostLabel: 'Claude Code',
|
|
470
493
|
buildInboundContextLines,
|
|
471
|
-
|
|
494
|
+
sessionContextLines: session ? buildClaudeSessionContextLines(session) : undefined,
|
|
495
|
+
...promptInput,
|
|
472
496
|
});
|
|
473
497
|
}
|
|
498
|
+
function optionLabel(value, options) {
|
|
499
|
+
if (!value)
|
|
500
|
+
return null;
|
|
501
|
+
const label = options.find((option) => option.value === value)?.label ?? value;
|
|
502
|
+
return label === value ? value : `${label} (${value})`;
|
|
503
|
+
}
|
|
504
|
+
function buildClaudeSessionContextLines(session) {
|
|
505
|
+
const workspace = workspaceOptions.find((option) => option.cwd === session.environment.baseCwd);
|
|
506
|
+
const permissionMode = optionLabel(session.state.permissionMode, CLAUDE_PERMISSION_MODE_OPTIONS);
|
|
507
|
+
const effort = optionLabel(session.state.effort, [
|
|
508
|
+
{ value: 'low', label: 'Low' },
|
|
509
|
+
{ value: 'medium', label: 'Medium' },
|
|
510
|
+
{ value: 'high', label: 'High' },
|
|
511
|
+
]);
|
|
512
|
+
const model = optionLabel(session.state.model, session.availableModels);
|
|
513
|
+
return [
|
|
514
|
+
model ? `Selected model: ${model}` : null,
|
|
515
|
+
permissionMode ? `Permission mode: ${permissionMode}` : null,
|
|
516
|
+
effort ? `Thinking level: ${effort}` : null,
|
|
517
|
+
workspace ? `Project: ${workspace.label} (${workspace.id})` : null,
|
|
518
|
+
`Execution mode: ${session.environment.mode}`,
|
|
519
|
+
`Working directory: ${session.cwd}`,
|
|
520
|
+
session.environment.branch ? `Git branch: ${session.environment.branch}` : null,
|
|
521
|
+
session.environment.worktreePath ? `Worktree: ${session.environment.worktreePath}` : null,
|
|
522
|
+
].filter((line) => Boolean(line));
|
|
523
|
+
}
|
|
474
524
|
function renderInboundContent(message, materialized) {
|
|
475
525
|
return renderCanonHostInboundContent(message, materialized);
|
|
476
526
|
}
|
|
@@ -501,19 +551,25 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
501
551
|
let resolveInput = null;
|
|
502
552
|
let inputDone = false;
|
|
503
553
|
const messageQueue = [];
|
|
504
|
-
const sendInput = (
|
|
554
|
+
const sendInput = (input) => {
|
|
505
555
|
if (resolveInput) {
|
|
506
556
|
const r = resolveInput;
|
|
507
557
|
resolveInput = null;
|
|
508
|
-
r(
|
|
558
|
+
r(input);
|
|
509
559
|
}
|
|
510
560
|
else {
|
|
511
561
|
// Queue if iterator isn't waiting yet (e.g., during init)
|
|
512
|
-
messageQueue.push(
|
|
562
|
+
messageQueue.push(input);
|
|
513
563
|
}
|
|
514
564
|
};
|
|
515
|
-
let enqueueInboundMessage = (msg,
|
|
516
|
-
sendInput(
|
|
565
|
+
let enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId, markAccepted = false) => {
|
|
566
|
+
sendInput(createClaudeInputEnvelope({
|
|
567
|
+
kind: 'canon',
|
|
568
|
+
msg,
|
|
569
|
+
intent,
|
|
570
|
+
sourceMessageId,
|
|
571
|
+
markAccepted,
|
|
572
|
+
}));
|
|
517
573
|
};
|
|
518
574
|
const inputStream = {
|
|
519
575
|
[Symbol.asyncIterator]() {
|
|
@@ -523,10 +579,15 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
523
579
|
return Promise.resolve({ done: true, value: undefined });
|
|
524
580
|
// Drain queued messages first (sent before iterator was ready)
|
|
525
581
|
if (messageQueue.length > 0) {
|
|
526
|
-
|
|
582
|
+
const next = messageQueue.shift();
|
|
583
|
+
session.activeInput = next;
|
|
584
|
+
return Promise.resolve({ done: false, value: next.msg });
|
|
527
585
|
}
|
|
528
586
|
return new Promise((resolve) => {
|
|
529
|
-
resolveInput = (
|
|
587
|
+
resolveInput = (input) => {
|
|
588
|
+
session.activeInput = input;
|
|
589
|
+
resolve({ done: false, value: input.msg });
|
|
590
|
+
};
|
|
530
591
|
});
|
|
531
592
|
},
|
|
532
593
|
return() {
|
|
@@ -669,6 +730,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
669
730
|
state: {
|
|
670
731
|
model: config.model,
|
|
671
732
|
permissionMode: config.permissionMode,
|
|
733
|
+
effort: config.effort,
|
|
672
734
|
state: 'idle',
|
|
673
735
|
},
|
|
674
736
|
availableModels: config.availableModels,
|
|
@@ -678,8 +740,12 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
678
740
|
finalDeliveryTimer: null,
|
|
679
741
|
seedResponseHandled: !!resumeSessionId, // Resumed sessions don't need seed handling
|
|
680
742
|
pendingInputs: [],
|
|
743
|
+
activeInput: null,
|
|
744
|
+
finalizedTurnKeys: new Set(),
|
|
745
|
+
interruptedTurnKeys: new Set(),
|
|
681
746
|
pendingFinalText: null,
|
|
682
|
-
|
|
747
|
+
pendingFinalDelivery: null,
|
|
748
|
+
runtimeControlErrors: {},
|
|
683
749
|
toolInProgress: false,
|
|
684
750
|
turnState: 'idle',
|
|
685
751
|
currentTurnId: null,
|
|
@@ -690,6 +756,10 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
690
756
|
closed: false,
|
|
691
757
|
sdkSessionId: resumeSessionId || null,
|
|
692
758
|
pendingReply: null,
|
|
759
|
+
writeState: () => writeState(),
|
|
760
|
+
writeTurn: () => writeTurn(),
|
|
761
|
+
setRuntimeControlError: (controlId, value, error) => setRuntimeControlError(controlId, value, error),
|
|
762
|
+
clearRuntimeControlError: (controlId) => clearRuntimeControlError(controlId),
|
|
693
763
|
};
|
|
694
764
|
const streamingBuffer = createStreamingBuffer({
|
|
695
765
|
throttleMs: STREAMING_THROTTLE_MS,
|
|
@@ -739,7 +809,8 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
739
809
|
session.currentTurnOpenedAt = null;
|
|
740
810
|
session.lastAcceptedIntent = null;
|
|
741
811
|
session.pendingFinalText = null;
|
|
742
|
-
session.
|
|
812
|
+
session.pendingFinalDelivery = null;
|
|
813
|
+
session.activeInput = null;
|
|
743
814
|
session.toolInProgress = false;
|
|
744
815
|
session.turnState = 'idle';
|
|
745
816
|
stopVisibleWorkSignal();
|
|
@@ -755,10 +826,18 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
755
826
|
resetTurnToIdle();
|
|
756
827
|
}, FINAL_MESSAGE_HANDOFF_MS);
|
|
757
828
|
}
|
|
758
|
-
function markPendingFinalDelivery(finalText) {
|
|
829
|
+
function markPendingFinalDelivery(finalText, turn) {
|
|
759
830
|
clearIdleResetTimer();
|
|
760
831
|
session.pendingFinalText = finalText;
|
|
761
|
-
session.
|
|
832
|
+
session.pendingFinalDelivery = {
|
|
833
|
+
turnKey: turn.turnKey,
|
|
834
|
+
text: finalText,
|
|
835
|
+
messageId: buildClaudeFinalMessageId({
|
|
836
|
+
agentId,
|
|
837
|
+
conversationId,
|
|
838
|
+
turnKey: turn.turnKey,
|
|
839
|
+
}),
|
|
840
|
+
};
|
|
762
841
|
session.state.state = 'running';
|
|
763
842
|
session.turnState = 'streaming';
|
|
764
843
|
stopVisibleWorkSignal();
|
|
@@ -767,49 +846,75 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
767
846
|
writeTurn();
|
|
768
847
|
client.setTyping(conversationId, true, 'typing').catch(() => { });
|
|
769
848
|
}
|
|
770
|
-
async function deliverFinalReply(finalText) {
|
|
849
|
+
async function deliverFinalReply(finalText, turn = session.activeInput) {
|
|
850
|
+
if (!shouldDeliverClaudeFinal({
|
|
851
|
+
turn,
|
|
852
|
+
finalizedTurnKeys: session.finalizedTurnKeys,
|
|
853
|
+
interruptedTurnKeys: session.interruptedTurnKeys,
|
|
854
|
+
})) {
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
const deliverTurn = turn;
|
|
858
|
+
if (!deliverTurn)
|
|
859
|
+
return true;
|
|
860
|
+
const finalMessageId = buildClaudeFinalMessageId({
|
|
861
|
+
agentId,
|
|
862
|
+
conversationId,
|
|
863
|
+
turnKey: deliverTurn.turnKey,
|
|
864
|
+
});
|
|
771
865
|
try {
|
|
772
866
|
await client.sendMessage(conversationId, finalText, {
|
|
867
|
+
messageId: finalMessageId,
|
|
773
868
|
metadata: {
|
|
774
869
|
turnId: session.currentTurnId,
|
|
870
|
+
turnKey: deliverTurn.turnKey,
|
|
871
|
+
sourceMessageId: deliverTurn.sourceMessageId ?? undefined,
|
|
775
872
|
turnSemantics: 'turn_complete',
|
|
776
873
|
turnComplete: true,
|
|
777
874
|
deliveryIntent: session.lastAcceptedIntent ?? undefined,
|
|
778
875
|
},
|
|
779
876
|
});
|
|
877
|
+
session.finalizedTurnKeys.add(deliverTurn.turnKey);
|
|
780
878
|
session.state.state = 'idle';
|
|
781
|
-
session.
|
|
879
|
+
session.pendingFinalDelivery = null;
|
|
782
880
|
clearFinalDeliveryTimer();
|
|
783
881
|
writeState();
|
|
784
882
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Sent final reply (${finalText.length} chars)`);
|
|
785
883
|
return true;
|
|
786
884
|
}
|
|
787
885
|
catch (err) {
|
|
788
|
-
markPendingFinalDelivery(finalText);
|
|
886
|
+
markPendingFinalDelivery(finalText, deliverTurn);
|
|
789
887
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Failed to send final reply:`, err);
|
|
790
888
|
return false;
|
|
791
889
|
}
|
|
792
890
|
}
|
|
793
|
-
function scheduleFinalDeliveryRetry(
|
|
794
|
-
if (session.closed || session.
|
|
891
|
+
function scheduleFinalDeliveryRetry(pending = session.pendingFinalDelivery) {
|
|
892
|
+
if (!pending || session.closed || session.pendingFinalDelivery?.turnKey !== pending.turnKey)
|
|
795
893
|
return;
|
|
796
894
|
clearFinalDeliveryTimer();
|
|
797
895
|
session.finalDeliveryTimer = setTimeout(() => {
|
|
798
896
|
session.finalDeliveryTimer = null;
|
|
799
|
-
|
|
897
|
+
const retryTurn = session.activeInput?.turnKey === pending.turnKey
|
|
898
|
+
? session.activeInput
|
|
899
|
+
: null;
|
|
900
|
+
void deliverFinalReply(pending.text, retryTurn).then((sent) => {
|
|
800
901
|
if (sent) {
|
|
801
902
|
scheduleFinalHandoffReset();
|
|
802
903
|
return;
|
|
803
904
|
}
|
|
804
|
-
scheduleFinalDeliveryRetry(
|
|
905
|
+
scheduleFinalDeliveryRetry();
|
|
805
906
|
});
|
|
806
907
|
}, FINAL_DELIVERY_RETRY_MS);
|
|
807
908
|
}
|
|
808
909
|
function writeState() {
|
|
910
|
+
const runtimeControlErrors = Object.keys(session.runtimeControlErrors).length > 0
|
|
911
|
+
? session.runtimeControlErrors
|
|
912
|
+
: null;
|
|
809
913
|
runtimeState.writeSessionState(conversationId, {
|
|
810
914
|
model: session.state.model,
|
|
811
915
|
permissionMode: session.state.permissionMode,
|
|
812
916
|
effort: session.state.effort,
|
|
917
|
+
runtimeControlErrors,
|
|
813
918
|
contextUsage: session.state.contextUsage,
|
|
814
919
|
...(session.availableModels.length ? { availableModels: session.availableModels } : {}),
|
|
815
920
|
cwd,
|
|
@@ -823,6 +928,25 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
823
928
|
isActive: true,
|
|
824
929
|
}).catch(() => { });
|
|
825
930
|
}
|
|
931
|
+
function setRuntimeControlError(controlId, value, error) {
|
|
932
|
+
session.runtimeControlErrors = {
|
|
933
|
+
...session.runtimeControlErrors,
|
|
934
|
+
[controlId]: {
|
|
935
|
+
value,
|
|
936
|
+
message: formatClaudeControlError(error),
|
|
937
|
+
updatedAt: Date.now(),
|
|
938
|
+
},
|
|
939
|
+
};
|
|
940
|
+
writeState();
|
|
941
|
+
}
|
|
942
|
+
function clearRuntimeControlError(controlId) {
|
|
943
|
+
if (!(controlId in session.runtimeControlErrors))
|
|
944
|
+
return;
|
|
945
|
+
const next = { ...session.runtimeControlErrors };
|
|
946
|
+
delete next[controlId];
|
|
947
|
+
session.runtimeControlErrors = next;
|
|
948
|
+
writeState();
|
|
949
|
+
}
|
|
826
950
|
function writeTurn() {
|
|
827
951
|
runtimeState.writeTurnState(conversationId, {
|
|
828
952
|
turnId: session.currentTurnId,
|
|
@@ -870,22 +994,32 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
870
994
|
session.lastAcceptedIntent = next.intent;
|
|
871
995
|
writeTurn();
|
|
872
996
|
void markQueuedMessageAccepted(next.sourceMessageId, next.markAccepted)
|
|
873
|
-
.finally(() => sendInput(next
|
|
997
|
+
.finally(() => sendInput(next));
|
|
874
998
|
}
|
|
875
999
|
enqueueInboundMessage = (msg, intent = 'queue', sourceMessageId = null, markAccepted = false) => {
|
|
876
1000
|
session.lastActivity = Date.now();
|
|
877
|
-
|
|
878
|
-
|
|
1001
|
+
const input = createClaudeInputEnvelope({
|
|
1002
|
+
kind: 'canon',
|
|
1003
|
+
msg,
|
|
1004
|
+
intent,
|
|
1005
|
+
sourceMessageId,
|
|
1006
|
+
markAccepted,
|
|
1007
|
+
});
|
|
1008
|
+
if (session.pendingFinalDelivery) {
|
|
1009
|
+
session.pendingInputs.push(input);
|
|
879
1010
|
writeTurn();
|
|
880
1011
|
return;
|
|
881
1012
|
}
|
|
882
1013
|
if (session.state.state === 'running' && intent !== 'interrupt') {
|
|
883
|
-
session.pendingInputs.push(
|
|
1014
|
+
session.pendingInputs.push(input);
|
|
884
1015
|
writeTurn();
|
|
885
1016
|
return;
|
|
886
1017
|
}
|
|
887
1018
|
if (session.state.state === 'running' && intent === 'interrupt') {
|
|
888
|
-
session.
|
|
1019
|
+
if (session.activeInput) {
|
|
1020
|
+
session.interruptedTurnKeys.add(session.activeInput.turnKey);
|
|
1021
|
+
}
|
|
1022
|
+
session.pendingInputs.unshift(input);
|
|
889
1023
|
session.turnState = 'interrupted';
|
|
890
1024
|
stopVisibleWorkSignal();
|
|
891
1025
|
writeTurn();
|
|
@@ -895,7 +1029,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
895
1029
|
session.lastAcceptedIntent = intent;
|
|
896
1030
|
writeTurn();
|
|
897
1031
|
void markQueuedMessageAccepted(sourceMessageId, markAccepted)
|
|
898
|
-
.finally(() => sendInput(
|
|
1032
|
+
.finally(() => sendInput(input));
|
|
899
1033
|
};
|
|
900
1034
|
writeTurn();
|
|
901
1035
|
// ── Process SDK output messages ──
|
|
@@ -991,12 +1125,22 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
991
1125
|
const resultSessionId = msg.session_id;
|
|
992
1126
|
if (resultSessionId)
|
|
993
1127
|
session.sdkSessionId = resultSessionId;
|
|
1128
|
+
const completedInput = session.activeInput;
|
|
994
1129
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Turn complete (${msg.subtype})`);
|
|
995
1130
|
const resultText = typeof msg.result === 'string'
|
|
996
1131
|
? msg.result.trim()
|
|
997
1132
|
: '';
|
|
998
1133
|
const finalText = resultText || session.pendingFinalText?.trim() || null;
|
|
999
|
-
const
|
|
1134
|
+
const shouldDeliverFinal = finalText
|
|
1135
|
+
? shouldDeliverClaudeFinal({
|
|
1136
|
+
turn: completedInput,
|
|
1137
|
+
finalizedTurnKeys: session.finalizedTurnKeys,
|
|
1138
|
+
interruptedTurnKeys: session.interruptedTurnKeys,
|
|
1139
|
+
})
|
|
1140
|
+
: false;
|
|
1141
|
+
const finalDelivered = finalText && shouldDeliverFinal
|
|
1142
|
+
? await deliverFinalReply(finalText, completedInput)
|
|
1143
|
+
: true;
|
|
1000
1144
|
try {
|
|
1001
1145
|
const usage = await q.getContextUsage();
|
|
1002
1146
|
session.state.contextUsage = {
|
|
@@ -1011,10 +1155,19 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1011
1155
|
// Non-fatal
|
|
1012
1156
|
}
|
|
1013
1157
|
if (finalDelivered) {
|
|
1014
|
-
|
|
1158
|
+
if (completedInput?.turnKey === session.activeInput?.turnKey) {
|
|
1159
|
+
session.activeInput = null;
|
|
1160
|
+
}
|
|
1161
|
+
session.pendingFinalText = null;
|
|
1162
|
+
if (shouldDeliverFinal && finalText) {
|
|
1163
|
+
scheduleFinalHandoffReset();
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
resetTurnToIdle();
|
|
1167
|
+
}
|
|
1015
1168
|
}
|
|
1016
1169
|
else if (finalText) {
|
|
1017
|
-
scheduleFinalDeliveryRetry(
|
|
1170
|
+
scheduleFinalDeliveryRetry();
|
|
1018
1171
|
}
|
|
1019
1172
|
break;
|
|
1020
1173
|
}
|
|
@@ -1056,19 +1209,26 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1056
1209
|
}
|
|
1057
1210
|
if (!resumeSessionId) {
|
|
1058
1211
|
// New session — send seed message to activate streaming input mode
|
|
1059
|
-
sendInput({
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1212
|
+
sendInput(createClaudeInputEnvelope({
|
|
1213
|
+
kind: 'seed',
|
|
1214
|
+
msg: {
|
|
1215
|
+
type: 'user',
|
|
1216
|
+
message: {
|
|
1217
|
+
role: 'user',
|
|
1218
|
+
content: 'You are connected to Canon messaging. Follow-up messages in this session come from Canon participants, and your text responses will be sent back to the conversation. You have full Claude Code capabilities (file read/write, bash, etc).',
|
|
1219
|
+
},
|
|
1220
|
+
parent_tool_use_id: null,
|
|
1064
1221
|
},
|
|
1065
|
-
|
|
1066
|
-
});
|
|
1222
|
+
}));
|
|
1067
1223
|
}
|
|
1068
1224
|
// Refresh the canonical runtime descriptor after Claude reports supported models.
|
|
1069
1225
|
try {
|
|
1070
1226
|
const models = await q.supportedModels();
|
|
1071
|
-
const modelList =
|
|
1227
|
+
const modelList = mergeClaudeModelOptions({
|
|
1228
|
+
previous: runtimeModels,
|
|
1229
|
+
next: toModelOptions(models),
|
|
1230
|
+
selectedModel: session.state.model,
|
|
1231
|
+
});
|
|
1072
1232
|
session.availableModels = modelList;
|
|
1073
1233
|
runtimeModels = modelList;
|
|
1074
1234
|
onRuntimeDescriptorUpdate({
|
|
@@ -1085,21 +1245,7 @@ function createSession(conversationId, environment, agentId, client, runtimeStat
|
|
|
1085
1245
|
executionModes: config.availableExecutionModes,
|
|
1086
1246
|
}),
|
|
1087
1247
|
});
|
|
1088
|
-
|
|
1089
|
-
model: session.state.model,
|
|
1090
|
-
permissionMode: session.state.permissionMode,
|
|
1091
|
-
effort: session.state.effort,
|
|
1092
|
-
contextUsage: session.state.contextUsage,
|
|
1093
|
-
cwd,
|
|
1094
|
-
executionMode: session.environment.mode,
|
|
1095
|
-
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1096
|
-
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1097
|
-
...(resolveExecutionFallbackReason(session.environment)
|
|
1098
|
-
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
1099
|
-
: {}),
|
|
1100
|
-
hostMode: true,
|
|
1101
|
-
isActive: true,
|
|
1102
|
-
}).catch(() => { });
|
|
1248
|
+
writeState();
|
|
1103
1249
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Models: ${models.map((m) => m.displayName).join(', ')}`);
|
|
1104
1250
|
}
|
|
1105
1251
|
catch {
|
|
@@ -1513,7 +1659,10 @@ export async function main() {
|
|
|
1513
1659
|
availableExecutionModes: hostAvailableExecutionModes,
|
|
1514
1660
|
}, resumeId);
|
|
1515
1661
|
sessions.set(conversationId, session);
|
|
1516
|
-
await
|
|
1662
|
+
await Promise.all([
|
|
1663
|
+
baselineControlSignal(conversationId),
|
|
1664
|
+
baselineSessionControl(conversationId),
|
|
1665
|
+
]);
|
|
1517
1666
|
return session;
|
|
1518
1667
|
}
|
|
1519
1668
|
catch (error) {
|
|
@@ -1540,6 +1689,17 @@ export async function main() {
|
|
|
1540
1689
|
lastSeenSignal.set(conversationId, timestamp);
|
|
1541
1690
|
}
|
|
1542
1691
|
}
|
|
1692
|
+
async function baselineSessionControl(conversationId) {
|
|
1693
|
+
if (lastSeenControl.has(conversationId))
|
|
1694
|
+
return;
|
|
1695
|
+
const raw = await rtdbRead(`/control/${conversationId}/${agentId}/session`).catch(() => null);
|
|
1696
|
+
if (!raw || typeof raw !== 'object')
|
|
1697
|
+
return;
|
|
1698
|
+
const timestamp = Number(raw.updatedAt ?? 0);
|
|
1699
|
+
if (timestamp > 0) {
|
|
1700
|
+
lastSeenControl.set(conversationId, timestamp);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1543
1703
|
async function enqueueRecoveredInboundMessage(input) {
|
|
1544
1704
|
const m = input.message;
|
|
1545
1705
|
if (m.senderId === agentId)
|
|
@@ -1599,6 +1759,7 @@ export async function main() {
|
|
|
1599
1759
|
behavior,
|
|
1600
1760
|
workSession: m.workSession,
|
|
1601
1761
|
workSessions,
|
|
1762
|
+
session,
|
|
1602
1763
|
});
|
|
1603
1764
|
const messageContent = await buildCanonUserContent({ promptText, materialized });
|
|
1604
1765
|
session.enqueueInbound({
|
|
@@ -1747,6 +1908,7 @@ export async function main() {
|
|
|
1747
1908
|
behavior,
|
|
1748
1909
|
workSession: m.workSession,
|
|
1749
1910
|
workSessions,
|
|
1911
|
+
session,
|
|
1750
1912
|
});
|
|
1751
1913
|
const messageContent = await buildCanonUserContent({ promptText, materialized });
|
|
1752
1914
|
session.enqueueInbound({
|
|
@@ -1801,9 +1963,11 @@ export async function main() {
|
|
|
1801
1963
|
try {
|
|
1802
1964
|
await session.query.setModel(ctrl.model);
|
|
1803
1965
|
session.state.model = ctrl.model;
|
|
1966
|
+
session.clearRuntimeControlError('model');
|
|
1804
1967
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Model → ${ctrl.model}`);
|
|
1805
1968
|
}
|
|
1806
1969
|
catch (err) {
|
|
1970
|
+
session.setRuntimeControlError('model', ctrl.model, err);
|
|
1807
1971
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set model:`, err);
|
|
1808
1972
|
}
|
|
1809
1973
|
}
|
|
@@ -1811,9 +1975,11 @@ export async function main() {
|
|
|
1811
1975
|
try {
|
|
1812
1976
|
await session.query.setPermissionMode(ctrl.permissionMode);
|
|
1813
1977
|
session.state.permissionMode = ctrl.permissionMode;
|
|
1978
|
+
session.clearRuntimeControlError('permissionMode');
|
|
1814
1979
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Mode → ${ctrl.permissionMode}`);
|
|
1815
1980
|
}
|
|
1816
1981
|
catch (err) {
|
|
1982
|
+
session.setRuntimeControlError('permissionMode', ctrl.permissionMode, err);
|
|
1817
1983
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set mode:`, err);
|
|
1818
1984
|
}
|
|
1819
1985
|
}
|
|
@@ -1822,27 +1988,19 @@ export async function main() {
|
|
|
1822
1988
|
try {
|
|
1823
1989
|
await session.query.applyFlagSettings({ effortLevel: nextEffort });
|
|
1824
1990
|
session.state.effort = nextEffort;
|
|
1991
|
+
session.clearRuntimeControlError('effort');
|
|
1825
1992
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Effort → ${nextEffort}`);
|
|
1826
1993
|
}
|
|
1827
1994
|
catch (err) {
|
|
1995
|
+
session.setRuntimeControlError('effort', nextEffort, err);
|
|
1828
1996
|
console.error(`[canon-host] [${convoId.slice(0, 8)}] Failed to set effort:`, err);
|
|
1829
1997
|
}
|
|
1830
1998
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
cwd: session.cwd,
|
|
1837
|
-
executionMode: session.environment.mode,
|
|
1838
|
-
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1839
|
-
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1840
|
-
...(resolveExecutionFallbackReason(session.environment)
|
|
1841
|
-
? { executionFallbackReason: resolveExecutionFallbackReason(session.environment) ?? undefined }
|
|
1842
|
-
: {}),
|
|
1843
|
-
hostMode: true,
|
|
1844
|
-
isActive: true,
|
|
1845
|
-
}).catch(() => { });
|
|
1999
|
+
else if (ctrl.effort && !nextEffort) {
|
|
2000
|
+
session.setRuntimeControlError('effort', String(ctrl.effort), new Error('Unsupported Claude thinking level.'));
|
|
2001
|
+
}
|
|
2002
|
+
session.writeState();
|
|
2003
|
+
await rtdbWrite(`/control/${convoId}/${agentId}/session`, null).catch(() => { });
|
|
1846
2004
|
}
|
|
1847
2005
|
}
|
|
1848
2006
|
}
|
|
@@ -1866,6 +2024,10 @@ export async function main() {
|
|
|
1866
2024
|
const droppedInputs = session.pendingInputs.splice(0);
|
|
1867
2025
|
await markSessionPendingInputsRejected(convoId, droppedInputs);
|
|
1868
2026
|
}
|
|
2027
|
+
if (session.activeInput) {
|
|
2028
|
+
session.interruptedTurnKeys.add(session.activeInput.turnKey);
|
|
2029
|
+
}
|
|
2030
|
+
session.pendingFinalText = null;
|
|
1869
2031
|
session.turnState = 'interrupted';
|
|
1870
2032
|
runtimeState.writeTurnState(convoId, {
|
|
1871
2033
|
turnId: session.currentTurnId,
|
|
@@ -1907,30 +2069,8 @@ export async function main() {
|
|
|
1907
2069
|
// ── Heartbeat (per-session) ──
|
|
1908
2070
|
const heartbeat = setInterval(() => {
|
|
1909
2071
|
for (const session of sessions.values()) {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
permissionMode: session.state.permissionMode,
|
|
1913
|
-
effort: session.state.effort,
|
|
1914
|
-
contextUsage: session.state.contextUsage,
|
|
1915
|
-
cwd: session.cwd,
|
|
1916
|
-
executionMode: session.environment.mode,
|
|
1917
|
-
...(session.environment.branch ? { executionBranch: session.environment.branch } : {}),
|
|
1918
|
-
...(session.environment.worktreePath ? { worktreePath: session.environment.worktreePath } : {}),
|
|
1919
|
-
hostMode: true,
|
|
1920
|
-
isActive: true,
|
|
1921
|
-
}).catch(() => { });
|
|
1922
|
-
runtimeState.writeTurnState(session.conversationId, {
|
|
1923
|
-
turnId: session.currentTurnId,
|
|
1924
|
-
state: session.turnState,
|
|
1925
|
-
queueDepth: session.pendingInputs.length,
|
|
1926
|
-
currentSpeakerId: agentId,
|
|
1927
|
-
lastAcceptedIntent: session.lastAcceptedIntent,
|
|
1928
|
-
capabilities: CLAUDE_RUNTIME_CAPABILITIES,
|
|
1929
|
-
...(session.currentTurnOpenedAt ? { openedAt: session.currentTurnOpenedAt } : {}),
|
|
1930
|
-
...(session.turnState === 'idle' || session.turnState === 'completed' || session.turnState === 'interrupted'
|
|
1931
|
-
? { completedAt: { '.sv': 'timestamp' } }
|
|
1932
|
-
: {}),
|
|
1933
|
-
}).catch(() => { });
|
|
2072
|
+
session.writeState();
|
|
2073
|
+
session.writeTurn();
|
|
1934
2074
|
}
|
|
1935
2075
|
void publishRuntimeHeartbeat();
|
|
1936
2076
|
}, HEARTBEAT_MS);
|
|
@@ -1971,7 +2111,11 @@ export async function main() {
|
|
|
1971
2111
|
}
|
|
1972
2112
|
// ── Entry ───────────────────────────────────────────────────────────
|
|
1973
2113
|
runCli(import.meta.url, main, (err) => {
|
|
1974
|
-
|
|
2114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2115
|
+
console.error(`[canon-host] ${message}`);
|
|
1975
2116
|
getActiveProfileLock()?.release();
|
|
1976
2117
|
process.exit(1);
|
|
2118
|
+
}, {
|
|
2119
|
+
name: 'canon-claude',
|
|
2120
|
+
help: HELP,
|
|
1977
2121
|
});
|
package/dist/register.js
CHANGED
|
@@ -13,6 +13,27 @@ import { ackRegistrationApproval, clearPendingRegistration, getOrCreatePendingRe
|
|
|
13
13
|
import { readFileSync } from 'node:fs';
|
|
14
14
|
import { parseArgs } from 'node:util';
|
|
15
15
|
import { runCli } from './cli-entry.js';
|
|
16
|
+
const HELP = `canon-register — register or reconnect a Claude Code agent in Canon
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
canon-register --name <name> --description <text> --phone <e164> [flags]
|
|
20
|
+
|
|
21
|
+
REQUIRED
|
|
22
|
+
--name <name> Agent display name shown in Canon
|
|
23
|
+
--description <text> Short profile description
|
|
24
|
+
--phone <e164> Owner phone number, for example +15551234567
|
|
25
|
+
|
|
26
|
+
FLAGS
|
|
27
|
+
--profile <name> Local profile name in ~/.canon/agents.json
|
|
28
|
+
--base-url <url> Canon API base URL override
|
|
29
|
+
--help, -h Show this help
|
|
30
|
+
--version, -V Show package version
|
|
31
|
+
|
|
32
|
+
EXAMPLES
|
|
33
|
+
canon-register --name "My Claude" --description "Claude Code agent" --phone "+15551234567"
|
|
34
|
+
canon-register --name "Frontend" --description "React work" --phone "+15551234567" --profile frontend
|
|
35
|
+
|
|
36
|
+
After approval, start it with CANON_AGENT=<profile> canon-claude --cwd /path/to/project.`;
|
|
16
37
|
export async function main() {
|
|
17
38
|
setDefaultResultOrder('ipv4first');
|
|
18
39
|
const { values } = parseArgs({
|
|
@@ -102,4 +123,7 @@ export async function main() {
|
|
|
102
123
|
runCli(import.meta.url, main, (error) => {
|
|
103
124
|
console.error('[canon-register] Fatal error:', error);
|
|
104
125
|
process.exit(1);
|
|
126
|
+
}, {
|
|
127
|
+
name: 'canon-register',
|
|
128
|
+
help: HELP,
|
|
105
129
|
});
|
package/dist/server.js
CHANGED
|
@@ -13,6 +13,17 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
13
13
|
import { CanonClient, CanonStream, ApprovalManager, buildLocalRuntimeId, getActiveProfileLock, markLocalRuntimeStopped, loadProfiles, isProfileLocked, resolveCanonAgent, getActiveProfile, releaseLock, upsertLocalRuntimeEntry, initRTDBAuth, writeSessionState, clearSessionState, } from '@canonmsg/core';
|
|
14
14
|
import { ApprovalHttpServer } from './approval-server.js';
|
|
15
15
|
import { runCli } from './cli-entry.js';
|
|
16
|
+
const HELP = `canon-channel-server — Claude Code MCP channel server for Canon
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
canon-channel-server
|
|
20
|
+
|
|
21
|
+
FLAGS
|
|
22
|
+
--help, -h Show this help
|
|
23
|
+
--version, -V Show package version
|
|
24
|
+
|
|
25
|
+
This command is normally launched by Claude Code as an MCP stdio server. For
|
|
26
|
+
phone-controlled local sessions, use canon-claude instead.`;
|
|
16
27
|
// ── MCP server ─────────────────────────────────────────────────────────
|
|
17
28
|
const server = new Server({ name: 'canon-channel', version: '0.1.0' }, {
|
|
18
29
|
capabilities: {
|
|
@@ -672,4 +683,7 @@ export async function main() {
|
|
|
672
683
|
runCli(import.meta.url, main, (err) => {
|
|
673
684
|
console.error('[canon] Fatal error:', err);
|
|
674
685
|
process.exit(1);
|
|
686
|
+
}, {
|
|
687
|
+
name: 'canon-channel-server',
|
|
688
|
+
help: HELP,
|
|
675
689
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SDKUserMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import type { DeliveryIntent, ModelOption } from '@canonmsg/core';
|
|
3
|
+
export type ClaudeInputKind = 'seed' | 'canon';
|
|
4
|
+
export interface ClaudeInputEnvelope {
|
|
5
|
+
kind: ClaudeInputKind;
|
|
6
|
+
msg: SDKUserMessage;
|
|
7
|
+
intent: DeliveryIntent;
|
|
8
|
+
sourceMessageId: string | null;
|
|
9
|
+
markAccepted: boolean;
|
|
10
|
+
turnKey: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ClaudeRuntimeControlError {
|
|
13
|
+
value: string;
|
|
14
|
+
message: string;
|
|
15
|
+
updatedAt: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ClaudePendingFinalDelivery {
|
|
18
|
+
turnKey: string;
|
|
19
|
+
text: string;
|
|
20
|
+
messageId: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function createClaudeInputEnvelope(input: {
|
|
23
|
+
kind: ClaudeInputKind;
|
|
24
|
+
msg: SDKUserMessage;
|
|
25
|
+
intent?: DeliveryIntent;
|
|
26
|
+
sourceMessageId?: string | null;
|
|
27
|
+
markAccepted?: boolean;
|
|
28
|
+
}): ClaudeInputEnvelope;
|
|
29
|
+
export declare function buildClaudeFinalMessageId(input: {
|
|
30
|
+
agentId: string;
|
|
31
|
+
conversationId: string;
|
|
32
|
+
turnKey: string;
|
|
33
|
+
}): string;
|
|
34
|
+
export declare function shouldDeliverClaudeFinal(input: {
|
|
35
|
+
turn: ClaudeInputEnvelope | null;
|
|
36
|
+
finalizedTurnKeys: ReadonlySet<string>;
|
|
37
|
+
interruptedTurnKeys: ReadonlySet<string>;
|
|
38
|
+
}): boolean;
|
|
39
|
+
export declare function mergeClaudeModelOptions(input: {
|
|
40
|
+
previous: ReadonlyArray<ModelOption>;
|
|
41
|
+
next: ReadonlyArray<ModelOption>;
|
|
42
|
+
selectedModel?: string | null;
|
|
43
|
+
}): ModelOption[];
|
|
44
|
+
export declare function formatClaudeControlError(error: unknown): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
export function createClaudeInputEnvelope(input) {
|
|
3
|
+
const sourceMessageId = input.sourceMessageId ?? null;
|
|
4
|
+
const fallbackId = randomUUID();
|
|
5
|
+
return {
|
|
6
|
+
kind: input.kind,
|
|
7
|
+
msg: input.msg,
|
|
8
|
+
intent: input.intent ?? 'queue',
|
|
9
|
+
sourceMessageId,
|
|
10
|
+
markAccepted: Boolean(input.markAccepted),
|
|
11
|
+
turnKey: input.kind === 'canon' && sourceMessageId
|
|
12
|
+
? `canon:${sourceMessageId}`
|
|
13
|
+
: `${input.kind}:${fallbackId}`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function buildClaudeFinalMessageId(input) {
|
|
17
|
+
const digest = createHash('sha256')
|
|
18
|
+
.update(`${input.agentId}\n${input.conversationId}\n${input.turnKey}`)
|
|
19
|
+
.digest('hex')
|
|
20
|
+
.slice(0, 32);
|
|
21
|
+
return `claude-final-${digest}`;
|
|
22
|
+
}
|
|
23
|
+
export function shouldDeliverClaudeFinal(input) {
|
|
24
|
+
if (!input.turn || input.turn.kind !== 'canon')
|
|
25
|
+
return false;
|
|
26
|
+
if (input.finalizedTurnKeys.has(input.turn.turnKey))
|
|
27
|
+
return false;
|
|
28
|
+
if (input.interruptedTurnKeys.has(input.turn.turnKey))
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
export function mergeClaudeModelOptions(input) {
|
|
33
|
+
const byValue = new Map();
|
|
34
|
+
for (const option of input.previous) {
|
|
35
|
+
byValue.set(option.value, { ...option });
|
|
36
|
+
}
|
|
37
|
+
for (const option of input.next) {
|
|
38
|
+
byValue.set(option.value, { ...option });
|
|
39
|
+
}
|
|
40
|
+
if (input.selectedModel && !byValue.has(input.selectedModel)) {
|
|
41
|
+
byValue.set(input.selectedModel, {
|
|
42
|
+
value: input.selectedModel,
|
|
43
|
+
label: input.selectedModel,
|
|
44
|
+
description: 'Selected for this Canon session but not returned by the latest Claude model probe.',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const nextValues = new Set(input.next.map((option) => option.value));
|
|
48
|
+
return [...byValue.values()].map((option) => {
|
|
49
|
+
if (nextValues.has(option.value))
|
|
50
|
+
return option;
|
|
51
|
+
return {
|
|
52
|
+
...option,
|
|
53
|
+
description: option.description
|
|
54
|
+
? `${option.description} Previously advertised; not returned by the latest Claude model probe.`
|
|
55
|
+
: 'Previously advertised; not returned by the latest Claude model probe.',
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function formatClaudeControlError(error) {
|
|
60
|
+
if (error instanceof Error && error.message.trim()) {
|
|
61
|
+
return error.message.trim();
|
|
62
|
+
}
|
|
63
|
+
if (typeof error === 'string' && error.trim()) {
|
|
64
|
+
return error.trim();
|
|
65
|
+
}
|
|
66
|
+
return 'Claude Code did not apply that control.';
|
|
67
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -10,6 +10,17 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
|
10
10
|
import { join, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { runCli } from './cli-entry.js';
|
|
13
|
+
const HELP = `canon-setup — install Claude Code Canon skills and print MCP setup
|
|
14
|
+
|
|
15
|
+
USAGE
|
|
16
|
+
canon-setup
|
|
17
|
+
|
|
18
|
+
FLAGS
|
|
19
|
+
--help, -h Show this help
|
|
20
|
+
--version, -V Show package version
|
|
21
|
+
|
|
22
|
+
This command writes Canon Claude Code skill files under ~/.claude/skills and
|
|
23
|
+
prints the MCP server configuration for Claude Code channel mode.`;
|
|
13
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
25
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
15
26
|
const skillsDir = join(home, '.claude', 'skills');
|
|
@@ -70,4 +81,7 @@ export function main() {
|
|
|
70
81
|
runCli(import.meta.url, main, (error) => {
|
|
71
82
|
console.error('[canon-setup] Fatal error:', error);
|
|
72
83
|
process.exit(1);
|
|
84
|
+
}, {
|
|
85
|
+
name: 'canon-setup',
|
|
86
|
+
help: HELP,
|
|
73
87
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
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",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "^0.2.101",
|
|
34
34
|
"@canonmsg/agent-sdk": "^1.1.0",
|
|
35
|
-
"@canonmsg/core": "^0.15.
|
|
35
|
+
"@canonmsg/core": "^0.15.1",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|