@canonmsg/claude-code-plugin 0.31.3 → 0.31.4

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "Canon",
3
3
  "description": "Connect Claude Code to Canon — messaging where AI agents are first-class citizens",
4
- "version": "0.31.3",
4
+ "version": "0.31.4",
5
5
  "channels": [
6
6
  {
7
7
  "server": "canon-channel",
package/README.md CHANGED
@@ -89,6 +89,11 @@ canon-claude --cwd /path/to/project --turn-verbosity quiet
89
89
  | `verbose` | Live streaming text plus the margin activity rows on the final, everywhere |
90
90
  | `quiet` | The thinking indicator and the answer, nothing in between, everywhere |
91
91
 
92
+ In verbose mode, authored text remains continual across tool use: text before a
93
+ tool stays as its own speech bubble, the tool appears in the activity margin,
94
+ and text after the result resumes in a new bubble. These are ephemeral streaming
95
+ updates; only the final durable `turn_complete` message is notification-eligible.
96
+
92
97
  Quiet drops the live `/streaming` narration and the final's `turnTrail` activity rows. It does **not** drop the thinking indicator (which now stays up for the turn's whole working phase rather than handing over to a bubble that never appears; while the turn is parked on an approval the clients suppress an agent's dots and the header line carries the state), the turn state, the answer — including every part of a long chunked one — failure notices, generated files, or approval and question cards and their receipts.
93
98
 
94
99
  This is an agent-developer setting. Canon never changes it, and it is deliberately not part of the per-conversation session config a user can edit. `canon-necromance` replays a stored launch command verbatim, so add the flag at registration time if you want a non-default value.
package/dist/host.js CHANGED
@@ -34,7 +34,7 @@ import { runCli } from '@canonmsg/core';
34
34
  import { CANON_VERB_MCP_SERVER_NAME, createCanonVerbMcpServer } from '@canonmsg/agent-tools';
35
35
  import { synthesizeClaudeApprovalDiff } from './approval-diff.js';
36
36
  import { decideClaudeToolPermissionForMode, parseAllowedNonOwnerClaudeTools, } from './tool-policy.js';
37
- import { applyClaudeSessionControl, boundClaudeFinalMetadata, buildClaudeFinalChunkingOptions, buildClaudeFinalMessageId, buildClaudeFinalTurnMetadata, buildClaudePendingFinalDelivery, buildClaudeTurnFailureNotice, buildTruncatedFinalText, buildUndeliverableFinalNotice, canDrainClaudeQueuedInput, claudeFinalWillChunk, classifyFinalDeliveryFailure, claudeInputOwnsTurnSlot, createClaudeTurnActivityState, decideClaudeControlSignalAction, decideClaudeInboundDispatch, dispatchClaudeInput, isClaudeTurnSlotReserved, readClaudeFinalDeliveryResume, releaseClaudeTurnSlot, runClaudeExhaustedFinalDelivery, shouldApplyClaudeEchoedSessionState, shouldReleaseClaudeTurnSlot, isClaudeMainTurnMessage, openClaudeTurn, planClaudeAssistantTrail, planClaudeStreamingWrite, planClaudeToolCallStart, planClaudeToolProgress, planClaudeToolResults, claudeFinalTurnTrail, publishedClaudeTurnState, rememberDispatchedClaudeInput, resetClaudeTurnActivityState, shouldOpenClaudeTurnOnRunning, shouldStopTypingDotsOnStreamedText, isOpenClaudeTurnState, takeClaudeResultOwner, takeClaudeToolBlockIdByIndex, claudeModelInfoToOption, claudeOriginForCanonSender, composeClaudeTurnFinal, describeUndeliveredClaudeFinal, confirmClaudeInterrupt, createClaudeInputEnvelope, deriveClaudeSupplementalModelProbes, formatClaudeCliVersion, formatClaudeControlError, isClaudeCustomModelOption, isSupportedClaudeCliVersion, mergeClaudeDiscoveredModelOptions, parseClaudeCliVersion, resolveClaudeTurnResponseRouting, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, shouldRouteClaudeTurnArtifacts, MINIMUM_CLAUDE_CLI_VERSION, } from './session-state.js';
37
+ import { applyClaudeSessionControl, boundClaudeFinalMetadata, buildClaudeFinalChunkingOptions, buildClaudeFinalMessageId, buildClaudeFinalTurnMetadata, buildClaudePendingFinalDelivery, buildClaudeTurnFailureNotice, buildTruncatedFinalText, buildUndeliverableFinalNotice, canDrainClaudeQueuedInput, beginClaudeAssistantResponse, claimClaudeTextSegmentId, claudeFinalWillChunk, classifyFinalDeliveryFailure, claudeInputOwnsTurnSlot, createClaudeTurnActivityState, decideClaudeControlSignalAction, decideClaudeInboundDispatch, dispatchClaudeInput, endClaudeAssistantResponse, isClaudeTurnSlotReserved, readClaudeFinalDeliveryResume, releaseClaudeTurnSlot, runClaudeExhaustedFinalDelivery, shouldApplyClaudeEchoedSessionState, shouldReleaseClaudeTurnSlot, isClaudeMainTurnMessage, openClaudeTurn, planClaudeAssistantTrail, planClaudeStreamingWrite, planClaudeToolCallStart, planClaudeToolProgress, planClaudeToolResults, claudeFinalTurnTrail, publishedClaudeTurnState, rememberDispatchedClaudeInput, resetClaudeTurnActivityState, shouldOpenClaudeTurnOnRunning, shouldStopTypingDotsOnStreamedText, isOpenClaudeTurnState, takeClaudeResultOwner, takeClaudeToolBlockIdByIndex, claudeModelInfoToOption, claudeOriginForCanonSender, composeClaudeTurnFinal, describeUndeliveredClaudeFinal, confirmClaudeInterrupt, createClaudeInputEnvelope, deriveClaudeSupplementalModelProbes, formatClaudeCliVersion, formatClaudeControlError, isClaudeCustomModelOption, isSupportedClaudeCliVersion, mergeClaudeDiscoveredModelOptions, parseClaudeCliVersion, resolveClaudeTurnResponseRouting, resolveClaudeModelOptions, resetClaudeCompletedTurnState, shouldDeliverClaudeFinal, shouldRouteClaudeTurnArtifacts, MINIMUM_CLAUDE_CLI_VERSION, } from './session-state.js';
38
38
  import { CLAUDE_SUPPORTED_DIALOG_KINDS, buildClaudeAskUserPermissionDenied, buildClaudeAskUserPermissionResult, createClaudeUserDialogCoordinator, parseClaudeAskUserDialog, parseClaudeAskUserToolInput, resolveClaudeUserDialogRequestId, } from './user-dialog.js';
39
39
  function parseRuntimeVisibilityPreset(value) {
40
40
  return value === 'normal' || value === 'minimal' || value === 'full' ? value : undefined;
@@ -2066,7 +2066,17 @@ function createSession(conversationId, environment, agentId, client, typingSigna
2066
2066
  // being written.
2067
2067
  if (!isClaudeMainTurnMessage(msg.parent_tool_use_id))
2068
2068
  break;
2069
- if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
2069
+ if (event?.type === 'message_start') {
2070
+ beginClaudeAssistantResponse(session.turnActivity);
2071
+ }
2072
+ else if (event?.type === 'message_stop') {
2073
+ endClaudeAssistantResponse(session.turnActivity);
2074
+ }
2075
+ else if (event?.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
2076
+ // A tool is also a response boundary for speech. This fallback
2077
+ // keeps continual streaming correct on SDK streams that omit raw
2078
+ // message_start/message_stop events.
2079
+ endClaudeAssistantResponse(session.turnActivity);
2070
2080
  // Claimed per CALL, keyed by the SDK's tool_use id. The old id was
2071
2081
  // the turn id, so every call in a turn wrote over the same row.
2072
2082
  const commands = planClaudeToolCallStart(session.turnActivity, {
@@ -2104,8 +2114,10 @@ function createSession(conversationId, environment, agentId, client, typingSigna
2104
2114
  stopVisibleWorkSignal();
2105
2115
  }
2106
2116
  writeTurn();
2107
- const blockIndex = typeof event.index === 'number' ? event.index : 'latest';
2108
- onStreamDelta(event.delta.text, `text:${session.currentTurnId ?? conversationId}:${blockIndex}`);
2117
+ onStreamDelta(event.delta.text, claimClaudeTextSegmentId(session.turnActivity, {
2118
+ turnId: session.currentTurnId ?? conversationId,
2119
+ index: event.index,
2120
+ }));
2109
2121
  }
2110
2122
  break;
2111
2123
  }
@@ -356,6 +356,10 @@ export interface ClaudeTurnActivityState {
356
356
  blockIdByToolUseId: Map<string, string>;
357
357
  /** This turn's plan block, once TodoWrite has produced one. */
358
358
  planBlockId: string | null;
359
+ /** Monotonic identity for assistant responses inside this turn. */
360
+ assistantResponseCount: number;
361
+ /** The response whose text deltas are currently being streamed. */
362
+ activeAssistantResponseKey: string | null;
359
363
  }
360
364
  /**
361
365
  * Whether an SDK message describes the agent's OWN work rather than a
@@ -367,6 +371,22 @@ export interface ClaudeTurnActivityState {
367
371
  export declare function isClaudeMainTurnMessage(parentToolUseId: unknown): boolean;
368
372
  export declare function createClaudeTurnActivityState(): ClaudeTurnActivityState;
369
373
  export declare function resetClaudeTurnActivityState(state: ClaudeTurnActivityState): void;
374
+ /** Open a fresh assistant response, whose content-block indexes start at zero. */
375
+ export declare function beginClaudeAssistantResponse(state: ClaudeTurnActivityState): string;
376
+ /** Close the response so a later text delta cannot reuse its block identity. */
377
+ export declare function endClaudeAssistantResponse(state: ClaudeTurnActivityState): void;
378
+ /**
379
+ * Stable id for one text block inside one assistant response.
380
+ *
381
+ * Anthropic restarts `content_block.index` for every assistant response. The
382
+ * response key is therefore required to distinguish text before a tool from
383
+ * text emitted after its result. Lazily opening a response keeps older SDK
384
+ * streams that omit `message_start` safe as well.
385
+ */
386
+ export declare function claimClaudeTextSegmentId(state: ClaudeTurnActivityState, input: {
387
+ turnId?: string | null;
388
+ index?: unknown;
389
+ }): string;
370
390
  export declare function claudeToolBlockId(toolUseId: string): string;
371
391
  export declare function claudePlanBlockId(turnId: string | null | undefined): string;
372
392
  /**
@@ -376,12 +376,42 @@ export function createClaudeTurnActivityState() {
376
376
  blockIdByIndex: new Map(),
377
377
  blockIdByToolUseId: new Map(),
378
378
  planBlockId: null,
379
+ assistantResponseCount: 0,
380
+ activeAssistantResponseKey: null,
379
381
  };
380
382
  }
381
383
  export function resetClaudeTurnActivityState(state) {
382
384
  state.blockIdByIndex.clear();
383
385
  state.blockIdByToolUseId.clear();
384
386
  state.planBlockId = null;
387
+ state.assistantResponseCount = 0;
388
+ state.activeAssistantResponseKey = null;
389
+ }
390
+ /** Open a fresh assistant response, whose content-block indexes start at zero. */
391
+ export function beginClaudeAssistantResponse(state) {
392
+ state.assistantResponseCount += 1;
393
+ state.activeAssistantResponseKey = `response-${state.assistantResponseCount}`;
394
+ return state.activeAssistantResponseKey;
395
+ }
396
+ /** Close the response so a later text delta cannot reuse its block identity. */
397
+ export function endClaudeAssistantResponse(state) {
398
+ state.activeAssistantResponseKey = null;
399
+ }
400
+ /**
401
+ * Stable id for one text block inside one assistant response.
402
+ *
403
+ * Anthropic restarts `content_block.index` for every assistant response. The
404
+ * response key is therefore required to distinguish text before a tool from
405
+ * text emitted after its result. Lazily opening a response keeps older SDK
406
+ * streams that omit `message_start` safe as well.
407
+ */
408
+ export function claimClaudeTextSegmentId(state, input) {
409
+ const responseKey = state.activeAssistantResponseKey
410
+ ?? beginClaudeAssistantResponse(state);
411
+ const index = typeof input.index === 'number' && Number.isInteger(input.index)
412
+ ? input.index
413
+ : 'latest';
414
+ return buildTrailBlockId('text', input.turnId ?? 'turn', responseKey, index);
385
415
  }
386
416
  export function claudeToolBlockId(toolUseId) {
387
417
  return buildTrailBlockId('tool', toolUseId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/claude-code-plugin",
3
- "version": "0.31.3",
3
+ "version": "0.31.4",
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",