@canonmsg/claude-code-plugin 0.25.2 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/host.js +27 -43
- package/dist/session-state.d.ts +5 -0
- package/dist/session-state.js +11 -0
- package/dist/startup-recovery.js +4 -1
- package/dist/tool-policy.d.ts +3 -1
- package/dist/tool-policy.js +11 -3
- package/dist/user-dialog.d.ts +2 -0
- package/dist/user-dialog.js +34 -0
- package/package.json +3 -3
package/dist/host.js
CHANGED
|
@@ -33,8 +33,8 @@ import { decideAutoReply, } from './inbound-policy.js';
|
|
|
33
33
|
import { runCli } from './cli-entry.js';
|
|
34
34
|
import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
|
|
35
35
|
import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
|
|
36
|
-
import { applyClaudeSessionControl, buildClaudeFinalMessageId, claudeModelInfoToOption, confirmClaudeInterrupt, createClaudeInputEnvelope, formatClaudeControlError, isClaudeCustomModelOption, mergeClaudeDiscoveredModelOptions, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
37
|
-
import { CLAUDE_SUPPORTED_DIALOG_KINDS, buildClaudeAskUserPermissionDenied, buildClaudeAskUserPermissionResult, parseClaudeAskUserDialog, parseClaudeAskUserToolInput, } from './user-dialog.js';
|
|
36
|
+
import { applyClaudeSessionControl, buildClaudeFinalMessageId, claudeModelInfoToOption, confirmClaudeInterrupt, createClaudeInputEnvelope, formatClaudeControlError, isClaudeCustomModelOption, mergeClaudeDiscoveredModelOptions, resolveClaudeTurnResponseRouting, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, } from './session-state.js';
|
|
37
|
+
import { CLAUDE_SUPPORTED_DIALOG_KINDS, buildClaudeAskUserPermissionDenied, buildClaudeAskUserPermissionResult, createClaudeUserDialogCoordinator, parseClaudeAskUserDialog, parseClaudeAskUserToolInput, resolveClaudeUserDialogRequestId, } from './user-dialog.js';
|
|
38
38
|
import { collectMissedInboundMessages, STARTUP_RECOVERY_MAX_MESSAGES, STARTUP_RECOVERY_PAGE_SIZE, } from './startup-recovery.js';
|
|
39
39
|
function parseRuntimeVisibilityPreset(value) {
|
|
40
40
|
return value === 'normal' || value === 'minimal' || value === 'full' ? value : undefined;
|
|
@@ -738,7 +738,6 @@ async function buildCanonUserContent(input) {
|
|
|
738
738
|
// ── Session factory ─────────────────────────────────────────────────
|
|
739
739
|
function createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
|
|
740
740
|
const { cwd } = environment;
|
|
741
|
-
const cardResponseUserId = config.ownerId ?? undefined;
|
|
742
741
|
// Per-session input stream with message queue
|
|
743
742
|
let resolveInput = null;
|
|
744
743
|
let inputDone = false;
|
|
@@ -796,23 +795,17 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
796
795
|
// eslint-disable-next-line prefer-const -- session must be declared before queryOptions but assigned after query()
|
|
797
796
|
let session;
|
|
798
797
|
const REPLY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
799
|
-
const
|
|
800
|
-
const completedUserDialogs = new Map();
|
|
798
|
+
const coordinateUserDialog = createClaudeUserDialogCoordinator();
|
|
801
799
|
async function requestClaudeUserDialog(dialog, nativeRequestId, signal) {
|
|
802
800
|
const questionId = /^[A-Za-z0-9_.:-]{1,160}$/.test(nativeRequestId)
|
|
803
801
|
? nativeRequestId
|
|
804
802
|
: randomUUID();
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
return completed;
|
|
808
|
-
const existing = activeUserDialogs.get(questionId);
|
|
809
|
-
if (existing)
|
|
810
|
-
return existing;
|
|
811
|
-
const responseUserId = session.activeInput?.requestingUserId ?? undefined;
|
|
803
|
+
const responseRouting = resolveClaudeTurnResponseRouting(session.activeInput, config.ownerId, dialog.questions.some((question) => question.isSecret));
|
|
804
|
+
const responseUserId = responseRouting.responseUserId;
|
|
812
805
|
if (!responseUserId) {
|
|
813
806
|
return buildClaudeAskUserPermissionDenied('No eligible human participant can answer this question.');
|
|
814
807
|
}
|
|
815
|
-
|
|
808
|
+
return coordinateUserDialog(questionId, async () => {
|
|
816
809
|
try {
|
|
817
810
|
const reply = await config.runtimeInputManager.request('input', conversationId, {
|
|
818
811
|
kind: 'clarify',
|
|
@@ -846,24 +839,9 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
846
839
|
catch {
|
|
847
840
|
return buildClaudeAskUserPermissionDenied(signal.aborted ? 'The question was interrupted.' : 'No reply received (timed out)');
|
|
848
841
|
}
|
|
849
|
-
})
|
|
850
|
-
activeUserDialogs.set(questionId, run);
|
|
851
|
-
try {
|
|
852
|
-
const result = await run;
|
|
853
|
-
completedUserDialogs.set(questionId, result);
|
|
854
|
-
while (completedUserDialogs.size > 64) {
|
|
855
|
-
const oldest = completedUserDialogs.keys().next().value;
|
|
856
|
-
if (oldest === undefined)
|
|
857
|
-
break;
|
|
858
|
-
completedUserDialogs.delete(oldest);
|
|
859
|
-
}
|
|
860
|
-
return result;
|
|
861
|
-
}
|
|
862
|
-
finally {
|
|
863
|
-
activeUserDialogs.delete(questionId);
|
|
864
|
-
}
|
|
842
|
+
});
|
|
865
843
|
}
|
|
866
|
-
function waitForReply(type, requestId) {
|
|
844
|
+
function waitForReply(type, requestId, responseUserId) {
|
|
867
845
|
// Keep session alive while waiting for user input
|
|
868
846
|
session.lastActivity = Date.now();
|
|
869
847
|
return new Promise((resolve, reject) => {
|
|
@@ -874,6 +852,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
874
852
|
session.pendingReply = {
|
|
875
853
|
type,
|
|
876
854
|
requestId,
|
|
855
|
+
responseUserId,
|
|
877
856
|
resolve: (metadata) => {
|
|
878
857
|
clearTimeout(timer);
|
|
879
858
|
resolve(metadata);
|
|
@@ -916,7 +895,7 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
916
895
|
const dialog = parseClaudeAskUserDialog(request);
|
|
917
896
|
if (!dialog)
|
|
918
897
|
return { behavior: 'cancelled' };
|
|
919
|
-
const nativeRequestId = dialog
|
|
898
|
+
const nativeRequestId = resolveClaudeUserDialogRequestId(request, dialog) ?? randomUUID();
|
|
920
899
|
const result = await requestClaudeUserDialog(dialog, nativeRequestId, signal);
|
|
921
900
|
return signal.aborted ? { behavior: 'cancelled' } : { behavior: 'completed', result };
|
|
922
901
|
},
|
|
@@ -927,7 +906,14 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
927
906
|
return buildClaudeAskUserPermissionDenied('Claude did not provide a valid question.');
|
|
928
907
|
return requestClaudeUserDialog(dialog, options.toolUseID, options.signal);
|
|
929
908
|
}
|
|
909
|
+
const turnResponseRouting = resolveClaudeTurnResponseRouting(session.activeInput, config.ownerId);
|
|
930
910
|
if (toolName === 'ExitPlanMode') {
|
|
911
|
+
if (!turnResponseRouting.responseUserId) {
|
|
912
|
+
return {
|
|
913
|
+
behavior: 'deny',
|
|
914
|
+
message: 'No eligible human participant can review this plan.',
|
|
915
|
+
};
|
|
916
|
+
}
|
|
931
917
|
try {
|
|
932
918
|
const planId = randomUUID();
|
|
933
919
|
const allowedPrompts = Array.isArray(input.allowedPrompts)
|
|
@@ -963,22 +949,21 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
963
949
|
title: 'Plan ready for review.',
|
|
964
950
|
summary: 'Claude has prepared a plan and is asking permission to continue.',
|
|
965
951
|
...(planBody ? { body: planBody } : {}),
|
|
966
|
-
|
|
952
|
+
responseUserId: turnResponseRouting.responseUserId,
|
|
967
953
|
...(allowedPrompts.length > 0 ? { allowedPrompts } : {}),
|
|
968
954
|
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
969
955
|
expiresAt: Date.now() + REPLY_TIMEOUT_MS,
|
|
970
956
|
});
|
|
971
957
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Plan approval sent (${planId.slice(0, 8)})`);
|
|
972
|
-
const reply = await waitForReply('plan_approval_reply', planId);
|
|
958
|
+
const reply = await waitForReply('plan_approval_reply', planId, turnResponseRouting.responseUserId);
|
|
973
959
|
if (reply.decision === 'approve') {
|
|
974
960
|
// Honor opt-in selective grants: when the card sent an explicit
|
|
975
961
|
// granted subset, pre-approve those tools as session rules so they
|
|
976
962
|
// do not re-prompt. Owner-turn only; absent grants preserve the
|
|
977
963
|
// legacy behavior (each tool still prompts individually).
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
if (isOwnerTurn && config.approvalManager && Array.isArray(reply.grantedPrompts)) {
|
|
964
|
+
if (turnResponseRouting.allowSessionRule
|
|
965
|
+
&& config.approvalManager
|
|
966
|
+
&& Array.isArray(reply.grantedPrompts)) {
|
|
982
967
|
// Only pre-approve tools the plan actually offered, and bound the
|
|
983
968
|
// count/length to match parseSessionRule's caps — grantedPrompts
|
|
984
969
|
// is untrusted wire metadata, not the validated approval path.
|
|
@@ -1038,9 +1023,9 @@ function createSession(conversationId, environment, agentId, client, typingSigna
|
|
|
1038
1023
|
conversationId,
|
|
1039
1024
|
runtimeId: config.runtimeId,
|
|
1040
1025
|
turnId: session.currentTurnId,
|
|
1041
|
-
isOwnerTurn:
|
|
1042
|
-
|
|
1043
|
-
|
|
1026
|
+
isOwnerTurn: turnResponseRouting.isOwnerTurn,
|
|
1027
|
+
responseUserId: turnResponseRouting.responseUserId,
|
|
1028
|
+
allowSessionRule: turnResponseRouting.allowSessionRule,
|
|
1044
1029
|
approvalManager: config.approvalManager,
|
|
1045
1030
|
canRequestApproval: config.canRequestApproval,
|
|
1046
1031
|
allowedNonOwnerTools: allowedNonOwnerClaudeTools,
|
|
@@ -2578,14 +2563,13 @@ export async function main() {
|
|
|
2578
2563
|
return false;
|
|
2579
2564
|
const existingSession = sessions.get(conversationId);
|
|
2580
2565
|
const pendingReply = existingSession?.pendingReply;
|
|
2581
|
-
const isOwnerReply = message.isOwner ?? (ownerId != null && message.senderId === ownerId);
|
|
2582
2566
|
const replyRequestId = message.metadata.planId;
|
|
2583
2567
|
if (!pendingReply) {
|
|
2584
2568
|
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Ignoring stale ${message.metadata.type} with no pending request`);
|
|
2585
2569
|
return true;
|
|
2586
2570
|
}
|
|
2587
|
-
if (
|
|
2588
|
-
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Ignoring ${message.metadata.type} from
|
|
2571
|
+
if (message.senderId !== pendingReply.responseUserId) {
|
|
2572
|
+
console.error(`[canon-host] [${conversationId.slice(0, 8)}] Ignoring ${message.metadata.type} from unexpected responder ${message.senderId}`);
|
|
2589
2573
|
return true;
|
|
2590
2574
|
}
|
|
2591
2575
|
if (pendingReply.type !== message.metadata.type || pendingReply.requestId !== replyRequestId) {
|
package/dist/session-state.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export declare function createClaudeInputEnvelope(input: {
|
|
|
51
51
|
isOwnerTurn?: boolean;
|
|
52
52
|
requestingUserId?: string | null;
|
|
53
53
|
}): ClaudeInputEnvelope;
|
|
54
|
+
export declare function resolveClaudeTurnResponseRouting(turn: Pick<ClaudeInputEnvelope, 'kind' | 'isOwnerTurn' | 'requestingUserId'> | null | undefined, ownerId: string | null | undefined, ownerOnly?: boolean): {
|
|
55
|
+
isOwnerTurn: boolean;
|
|
56
|
+
responseUserId?: string;
|
|
57
|
+
allowSessionRule: boolean;
|
|
58
|
+
};
|
|
54
59
|
export declare function buildClaudeFinalMessageId(input: {
|
|
55
60
|
agentId: string;
|
|
56
61
|
conversationId: string;
|
package/dist/session-state.js
CHANGED
|
@@ -21,6 +21,17 @@ export function createClaudeInputEnvelope(input) {
|
|
|
21
21
|
: `${input.kind}:${fallbackId}`,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
export function resolveClaudeTurnResponseRouting(turn, ownerId, ownerOnly = false) {
|
|
25
|
+
const isOwnerTurn = turn?.kind === 'canon' ? turn.isOwnerTurn : true;
|
|
26
|
+
const responseUserId = ownerOnly
|
|
27
|
+
? ownerId || undefined
|
|
28
|
+
: (turn?.requestingUserId ?? ownerId) || undefined;
|
|
29
|
+
return {
|
|
30
|
+
isOwnerTurn,
|
|
31
|
+
...(responseUserId ? { responseUserId } : {}),
|
|
32
|
+
allowSessionRule: Boolean(responseUserId && ownerId && responseUserId === ownerId),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
24
35
|
export function buildClaudeFinalMessageId(input) {
|
|
25
36
|
const digest = createHash('sha256')
|
|
26
37
|
.update(`${input.agentId}\n${input.conversationId}\n${input.turnKey}`)
|
package/dist/startup-recovery.js
CHANGED
|
@@ -50,7 +50,10 @@ export async function collectMissedInboundMessages(input) {
|
|
|
50
50
|
const resolvedCursor = cursorSearchComplete()
|
|
51
51
|
? findNewestCursor(collected)
|
|
52
52
|
: null;
|
|
53
|
-
|
|
53
|
+
// Preserve the API's deterministic newest-first order. Timestamps are
|
|
54
|
+
// display data and may be equal or absent, so reversing wire order is the
|
|
55
|
+
// only lossless oldest-first replay sequence.
|
|
56
|
+
const ascending = [...collected].reverse();
|
|
54
57
|
const completedSourceMessageIds = new Set(collected.flatMap((message) => {
|
|
55
58
|
if (message.senderId !== input.agentId)
|
|
56
59
|
return [];
|
package/dist/tool-policy.d.ts
CHANGED
|
@@ -24,12 +24,14 @@ export declare function decideClaudeToolPermissionForMode(input: {
|
|
|
24
24
|
runtimeId?: string;
|
|
25
25
|
turnId?: string | null;
|
|
26
26
|
isOwnerTurn: boolean;
|
|
27
|
+
responseUserId?: string | null;
|
|
28
|
+
allowSessionRule?: boolean;
|
|
27
29
|
approvalManager?: ApprovalManager | null;
|
|
28
30
|
canRequestApproval?: () => Promise<boolean>;
|
|
29
31
|
allowedNonOwnerTools?: Iterable<string>;
|
|
30
32
|
/**
|
|
31
33
|
* Lazy file-change preview for the approval card (Edit/Write-class tools).
|
|
32
|
-
* Invoked only once the call is headed for
|
|
34
|
+
* Invoked only once the call is headed for a responder approval; best-effort —
|
|
33
35
|
* a throw or undefined simply omits the diff, never blocks the approval.
|
|
34
36
|
*/
|
|
35
37
|
buildApprovalDiff?: () => Promise<CanonUnifiedDiff | undefined>;
|
package/dist/tool-policy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const NON_OWNER_DENY_MESSAGE = 'Only the agent owner can authorize runtime tool use in this chat.';
|
|
2
2
|
const NATIVE_APPROVAL_UNAVAILABLE_MESSAGE = 'Runtime action approval is not available for this conversation.';
|
|
3
|
-
const NATIVE_APPROVAL_DENIED_MESSAGE = 'The
|
|
3
|
+
const NATIVE_APPROVAL_DENIED_MESSAGE = 'The user denied this action.';
|
|
4
4
|
export const CLAUDE_NATIVE_APPROVAL_PERMISSION_MODE = 'default';
|
|
5
5
|
const SENSITIVE_CLAUDE_TOOLS = new Set([
|
|
6
6
|
'agent',
|
|
@@ -174,6 +174,12 @@ export async function decideClaudeToolPermissionForMode(input) {
|
|
|
174
174
|
message: NATIVE_APPROVAL_UNAVAILABLE_MESSAGE,
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
|
+
if (!input.isOwnerTurn && !input.responseUserId) {
|
|
178
|
+
return {
|
|
179
|
+
behavior: 'deny',
|
|
180
|
+
message: NATIVE_APPROVAL_UNAVAILABLE_MESSAGE,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
177
183
|
let diff;
|
|
178
184
|
if (input.buildApprovalDiff) {
|
|
179
185
|
try {
|
|
@@ -186,6 +192,7 @@ export async function decideClaudeToolPermissionForMode(input) {
|
|
|
186
192
|
let result;
|
|
187
193
|
try {
|
|
188
194
|
const risk = classifyClaudeApprovalRisk(input.toolName, input.toolInput);
|
|
195
|
+
const allowSessionRule = input.allowSessionRule ?? input.isOwnerTurn;
|
|
189
196
|
result = await input.approvalManager.requestApproval(input.conversationId, input.toolName, input.toolInput, {
|
|
190
197
|
category: classifyClaudeApprovalCategory(input.toolName),
|
|
191
198
|
risk,
|
|
@@ -198,8 +205,9 @@ export async function decideClaudeToolPermissionForMode(input) {
|
|
|
198
205
|
},
|
|
199
206
|
details: buildClaudeApprovalDetails(input.toolName, input.toolInput),
|
|
200
207
|
...(diff ? { diff } : {}),
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
...(input.responseUserId ? { responseUserId: input.responseUserId } : {}),
|
|
209
|
+
ignoreSessionRules: !allowSessionRule,
|
|
210
|
+
allowSessionRule,
|
|
203
211
|
});
|
|
204
212
|
}
|
|
205
213
|
catch {
|
package/dist/user-dialog.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface ClaudeAskUserDialog {
|
|
|
8
8
|
updatedInput: Record<string, unknown>;
|
|
9
9
|
requestId?: string;
|
|
10
10
|
}
|
|
11
|
+
export declare function resolveClaudeUserDialogRequestId(request: Pick<UserDialogRequest, 'toolUseID'>, dialog: Pick<ClaudeAskUserDialog, 'requestId'>): string | undefined;
|
|
12
|
+
export declare function createClaudeUserDialogCoordinator<T>(maxCompleted?: number): (requestId: string, run: () => Promise<T>) => Promise<T>;
|
|
11
13
|
export declare function parseClaudeAskUserDialog(request: UserDialogRequest): ClaudeAskUserDialog | null;
|
|
12
14
|
export declare function parseClaudeAskUserToolInput(input: Record<string, unknown>, requestId: string): ClaudeAskUserDialog | null;
|
|
13
15
|
export declare function buildClaudeAskUserPermissionResult(dialog: ClaudeAskUserDialog, response: {
|
package/dist/user-dialog.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { resolveQuestionAllowOther, } from '@canonmsg/core';
|
|
2
2
|
export const CLAUDE_ASK_USER_DIALOG_KIND = 'permission_ask_user_question';
|
|
3
3
|
export const CLAUDE_SUPPORTED_DIALOG_KINDS = [CLAUDE_ASK_USER_DIALOG_KIND];
|
|
4
|
+
export function resolveClaudeUserDialogRequestId(request, dialog) {
|
|
5
|
+
const toolUseId = typeof request.toolUseID === 'string' ? request.toolUseID.trim() : '';
|
|
6
|
+
if (toolUseId)
|
|
7
|
+
return toolUseId.slice(0, 160);
|
|
8
|
+
const payloadRequestId = dialog.requestId?.trim();
|
|
9
|
+
return payloadRequestId ? payloadRequestId.slice(0, 160) : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function createClaudeUserDialogCoordinator(maxCompleted = 64) {
|
|
12
|
+
const active = new Map();
|
|
13
|
+
const completed = new Map();
|
|
14
|
+
return async (requestId, run) => {
|
|
15
|
+
if (completed.has(requestId))
|
|
16
|
+
return completed.get(requestId);
|
|
17
|
+
const activeResult = active.get(requestId);
|
|
18
|
+
if (activeResult)
|
|
19
|
+
return activeResult;
|
|
20
|
+
const pending = Promise.resolve().then(run);
|
|
21
|
+
active.set(requestId, pending);
|
|
22
|
+
try {
|
|
23
|
+
const result = await pending;
|
|
24
|
+
completed.set(requestId, result);
|
|
25
|
+
while (completed.size > maxCompleted) {
|
|
26
|
+
const oldest = completed.keys().next().value;
|
|
27
|
+
if (oldest === undefined)
|
|
28
|
+
break;
|
|
29
|
+
completed.delete(oldest);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
active.delete(requestId);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
4
38
|
function isRecord(value) {
|
|
5
39
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/claude-code-plugin",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Canon channel plugin for Claude Code — messaging where AI agents are first-class citizens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@anthropic-ai/claude-agent-sdk": "0.3.204",
|
|
34
|
-
"@canonmsg/agent-sdk": "^5.1.
|
|
34
|
+
"@canonmsg/agent-sdk": "^5.1.1",
|
|
35
35
|
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
36
|
-
"@canonmsg/core": "^4.2.
|
|
36
|
+
"@canonmsg/core": "^4.2.2",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|