@canonmsg/claude-code-plugin 0.24.0 → 0.25.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.
Files changed (2) hide show
  1. package/dist/host.js +31 -46
  2. package/package.json +3 -3
package/dist/host.js CHANGED
@@ -28,7 +28,7 @@ import { readFile } from 'node:fs/promises';
28
28
  import { query, } from '@anthropic-ai/claude-agent-sdk';
29
29
  import { isAnthropicImageAttachment, materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, toAnthropicImageBlock, } from '@canonmsg/agent-sdk';
30
30
  import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/coding-agent-host';
31
- import { CLAUDE_EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonInboundFrameV1, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPlanApprovalRequest, resolveQuestionAllowOther, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, normalizeRuntimeCommandDescriptors, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, sendMessageWithRetry, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
31
+ import { CLAUDE_EFFORT_OPTIONS, CLAUDE_PERMISSION_MODE_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildCanonInboundFrameV1, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildConversationEnvironmentKey, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, resolveQuestionAllowOther, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, normalizeRuntimeCommandDescriptors, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, CanonClient, CanonStream, ControlChannelPoller, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, ApprovalManager, RuntimeRequestManager, ExecutionEnvironmentError, FINAL_MESSAGE_HANDOFF_MS, buildLocalRuntimeId, getActiveProfileLock, heartbeatLocalRuntimeEntry, normalizeTurnMetadata, prepareConversationEnvironment, resolveCanonAgent, initRTDBAuth, sendMessageWithRetry, loadHostSessionConfig, loadRuntimeSessionState, markLocalRuntimeStopped, releaseConversationEnvironment, saveRuntimeSessionState, clearRuntimeSessionState, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
32
32
  import { decideAutoReply, } from './inbound-policy.js';
33
33
  import { runCli } from './cli-entry.js';
34
34
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
@@ -769,7 +769,6 @@ async function buildCanonUserContent(input) {
769
769
  function createSession(conversationId, environment, agentId, client, typingSignals, runtimeState, onSessionEnd, onRuntimeDescriptorUpdate, config, resumeSessionId) {
770
770
  const { cwd } = environment;
771
771
  const cardResponseUserId = config.ownerId ?? undefined;
772
- const cardResponseUser = cardResponseUserId ? { responseUserId: cardResponseUserId } : undefined;
773
772
  // Per-session input stream with message queue
774
773
  let resolveInput = null;
775
774
  let inputDone = false;
@@ -844,30 +843,6 @@ function createSession(conversationId, environment, agentId, client, typingSigna
844
843
  };
845
844
  });
846
845
  }
847
- async function waitForRuntimeInputResponse(input) {
848
- session.lastActivity = Date.now();
849
- while (Date.now() < input.expiresAt) {
850
- const response = await client.consumeRuntimeInputResponse({
851
- conversationId,
852
- inputId: input.inputId,
853
- }).catch(() => null);
854
- if (response?.status === 'submitted') {
855
- return { status: 'submitted', answers: response.answers, value: response.value };
856
- }
857
- if (response?.status === 'cancelled' || response?.status === 'timeout') {
858
- return { status: response.status };
859
- }
860
- await sleep(1_000);
861
- }
862
- const response = await client.consumeRuntimeInputResponse({
863
- conversationId,
864
- inputId: input.inputId,
865
- }).catch(() => null);
866
- if (response?.status === 'submitted') {
867
- return { status: 'submitted', answers: response.answers, value: response.value };
868
- }
869
- return { status: 'timeout' };
870
- }
871
846
  function buildArtifactMediaMetadata() {
872
847
  return {
873
848
  ...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
@@ -903,18 +878,19 @@ function createSession(conversationId, environment, agentId, client, typingSigna
903
878
  const questionId = randomUUID();
904
879
  const questions = mapClaudeQuestions(input.questions);
905
880
  const expiresAt = Date.now() + REPLY_TIMEOUT_MS;
906
- await client.createRuntimeInputRequest({
907
- conversationId,
908
- inputId: questionId,
881
+ // Create + poll fold onto the shared spine. `responderPolicy: 'infer'`
882
+ // keeps the claude host's responder rule byte-identical (explicit
883
+ // `cardResponseUserId` wins, else omit — never the manager's ownerId
884
+ // fallback), and `native` carries the claude-specific descriptor.
885
+ const reply = await config.runtimeInputManager.request('input', conversationId, {
909
886
  kind: 'clarify',
910
- expiresAt,
911
887
  title: 'Claude needs input',
912
888
  prompt: questions?.length
913
889
  ? 'Claude needs your input to continue.'
914
890
  : 'Claude needs input to continue.',
915
891
  ...(questions ? { questions } : {}),
916
892
  sensitive: Boolean(questions?.some((question) => question.isSecret)),
917
- ...(cardResponseUserId ? { responseUserId: cardResponseUserId } : {}),
893
+ responseUserId: cardResponseUserId,
918
894
  native: {
919
895
  runtime: 'claude-code',
920
896
  method: 'AskUserQuestion',
@@ -922,9 +898,15 @@ function createSession(conversationId, environment, agentId, client, typingSigna
922
898
  turnId: session.currentTurnId ?? undefined,
923
899
  },
924
900
  turnId: session.currentTurnId ?? undefined,
901
+ }, {
902
+ requestId: questionId,
903
+ expiresAt,
904
+ responderPolicy: 'infer',
905
+ onCreated: () => {
906
+ console.error(`[canon-host] [${conversationId.slice(0, 8)}] Runtime input question sent (${questionId.slice(0, 8)})`);
907
+ session.lastActivity = Date.now();
908
+ },
925
909
  });
926
- console.error(`[canon-host] [${conversationId.slice(0, 8)}] Runtime input question sent (${questionId.slice(0, 8)})`);
927
- const reply = await waitForRuntimeInputResponse({ inputId: questionId, expiresAt });
928
910
  if (reply.status !== 'submitted') {
929
911
  return { behavior: 'deny', message: 'No reply received (timed out)' };
930
912
  }
@@ -970,22 +952,19 @@ function createSession(conversationId, environment, agentId, client, typingSigna
970
952
  const planBody = typeof input.plan === 'string' && input.plan.trim()
971
953
  ? input.plan.trim()
972
954
  : undefined;
973
- const planApproval = buildPlanApprovalRequest(planId, 'Plan ready for review.', {
955
+ // Route plan-CREATE through the unified spine (`/runtime-plan/request`):
956
+ // seeds the server-owned pending node/attention/state and authors the
957
+ // `plan_approval` card. Resolution is UNCHANGED — the gate still blocks
958
+ // on the server-authored `plan_approval_reply` below.
959
+ await client.createRuntimePlanRequest({
960
+ conversationId,
961
+ planId,
962
+ title: 'Plan ready for review.',
974
963
  summary: 'Claude has prepared a plan and is asking permission to continue.',
975
964
  ...(planBody ? { body: planBody } : {}),
976
- ...(cardResponseUser ?? {}),
965
+ ...(cardResponseUserId ? { responseUserId: cardResponseUserId } : {}),
977
966
  ...(allowedPrompts.length > 0 ? { allowedPrompts } : {}),
978
- });
979
- await sendMessageWithRetry(client, conversationId, planApproval.text, {
980
- messageId: `claude-plan-${planId}`,
981
- ...(session.activeSelfContextId
982
- ? { selfContextId: session.activeSelfContextId }
983
- : {}),
984
- metadata: {
985
- ...planApproval.metadata,
986
- turnSemantics: 'control',
987
- replyBehavior: 'suppress_auto_reply',
988
- },
967
+ ...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
989
968
  });
990
969
  console.error(`[canon-host] [${conversationId.slice(0, 8)}] Plan approval sent (${planId.slice(0, 8)})`);
991
970
  const reply = await waitForReply('plan_approval_reply', planId);
@@ -2062,6 +2041,10 @@ export async function main() {
2062
2041
  // ── Session manager ──
2063
2042
  const sessions = new Map();
2064
2043
  const approvalManager = ownerId ? new ApprovalManager(client, agentId, ownerId) : null;
2044
+ // Interactive runtime input (AskUserQuestion) works with or without an owner,
2045
+ // so its poll engine is always present. Create + poll fold onto the built-in
2046
+ // input descriptor via `request()` (see the AskUserQuestion canUseTool branch).
2047
+ const runtimeInputManager = new RuntimeRequestManager(client, { agentId, ownerId: ownerId ?? '' });
2065
2048
  const pendingSessionCreations = new Map();
2066
2049
  // Shared /control channel poller (claude host profile): session + signal
2067
2050
  // keys, sequential conversations, active/idle cadence + jitter sampled at
@@ -2368,6 +2351,7 @@ export async function main() {
2368
2351
  availableExecutionModes: hostAvailableExecutionModes,
2369
2352
  runtimePresentation,
2370
2353
  approvalManager,
2354
+ runtimeInputManager,
2371
2355
  canRequestApproval: () => canRequestCanonApproval(conversationId),
2372
2356
  runtimeId,
2373
2357
  ownerId,
@@ -2778,6 +2762,7 @@ export async function main() {
2778
2762
  clearInterval(idleCheck);
2779
2763
  stream.stop();
2780
2764
  approvalManager?.dispose();
2765
+ runtimeInputManager.dispose();
2781
2766
  await runtimeState.clearAgentRuntime().catch(() => { });
2782
2767
  for (const convoId of [...sessions.keys()]) {
2783
2768
  closeSession(convoId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.24.0",
3
+ "version": "0.25.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",
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "^0.3.200",
34
- "@canonmsg/agent-sdk": "^5.0.0",
34
+ "@canonmsg/agent-sdk": "^5.1.0",
35
35
  "@canonmsg/coding-agent-host": "^0.2.2",
36
- "@canonmsg/core": "^4.1.0",
36
+ "@canonmsg/core": "^4.2.0",
37
37
  "@modelcontextprotocol/sdk": "^1.29.0"
38
38
  },
39
39
  "engines": {