@canonmsg/core 0.22.0 → 0.24.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/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CanonMessage, type CanonConversation, type CanonContact, type CanonContactRequest, type CanonMessagesPage, type CanonResolveAdmissionResult, type AgentContext, type AddMemberResult, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
1
+ import { type CanonMessage, type CanonConversation, type CanonContact, type CanonContactRequest, type CanonMessagesPage, type CanonResolveAdmissionResult, type AgentContext, type AddMemberResult, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetRuntimeTurnOptions, type SetStreamingOptions } from './types.js';
2
2
  import type { RuntimeInputKind } from './runtime-cards.js';
3
3
  import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk, SessionRule } from './approval-types.js';
4
4
  import type { RuntimeInputChoice, RuntimeInputNativeMetadata } from './runtime-cards.js';
@@ -55,6 +55,7 @@ export declare class CanonClient {
55
55
  removeMember(conversationId: string, userId: string): Promise<void>;
56
56
  setStreaming(options: SetStreamingOptions): Promise<void>;
57
57
  clearStreaming(conversationId: string): Promise<void>;
58
+ setRuntimeTurn(options: SetRuntimeTurnOptions): Promise<void>;
58
59
  setTyping(conversationId: string, typing: boolean, status?: 'thinking' | 'typing'): Promise<void>;
59
60
  createRuntimeInputRequest(options: {
60
61
  conversationId: string;
package/dist/client.js CHANGED
@@ -310,6 +310,15 @@ export class CanonClient {
310
310
  if (!res.ok)
311
311
  throw new CanonApiError(res.status, await res.text());
312
312
  }
313
+ async setRuntimeTurn(options) {
314
+ const res = await fetch(`${this.baseUrl}/runtime/turn`, {
315
+ method: 'POST',
316
+ headers: this.authHeaders(),
317
+ body: JSON.stringify(options),
318
+ });
319
+ if (!res.ok)
320
+ throw new CanonApiError(res.status, await res.text());
321
+ }
313
322
  async setTyping(conversationId, typing, status) {
314
323
  const res = await fetch(`${this.baseUrl}/typing`, {
315
324
  method: 'POST',
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
2
- export type { AddMemberResult, AgentCapabilities, AgentClientType, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonGroupContext, CanonGroupContextMode, CanonKnownRecentParticipant, CanonMembershipChange, CanonResolveAdmissionResult, ConversationUpdatedPayload, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, CanonRuntimeProvenance, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimePresentationField, CanonRuntimePresentationHint, CanonRuntimePresentationPolicy, CanonRuntimePresentationPreset, CanonRuntimeVisibility, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
2
+ export type { AddMemberResult, AgentCapabilities, AgentClientType, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonGroupContext, CanonGroupContextMode, CanonKnownRecentParticipant, CanonMembershipChange, CanonResolveAdmissionResult, ConversationUpdatedPayload, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, CanonRuntimeProvenance, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, SetRuntimeTurnOptions, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimePresentationField, CanonRuntimePresentationHint, CanonRuntimePresentationPolicy, CanonRuntimePresentationPreset, CanonRuntimeVisibility, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
3
3
  export { DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, RUNTIME_PRESENTATION_FIELDS, buildRuntimePresentationPolicy, getRuntimePresentationHint, isRuntimePresentationField, isRuntimePresentationFieldHidden, redactAgentRuntimeForConversation, redactAgentSessionSnapshotForConversation, redactExecutionMetadataForConversation, redactRuntimeActivityItemForConversation, redactRuntimeDescriptorForConversation, redactRuntimeInfoForConversation, redactSessionStateForConversation, } from './runtime-presentation.js';
4
4
  export { buildRuntimeProvenance, resolveRuntimeProvenance, } from './provenance.js';
5
5
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
@@ -24,6 +24,8 @@ export { buildRuntimeInputOutcome, buildRuntimeInputReply, buildRuntimeInputRequ
24
24
  export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputOutcomeMetadata, RuntimeInputReplyMetadata, RuntimeInputReplyStatus, RuntimeInputRequestMetadata, RuntimeInputResolutionStatus, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
25
25
  export { createStreamingHelper } from './streaming.js';
26
26
  export type { RTDBHandle, RTDBRef, ServerTimestamp, StreamingHelperOptions, StreamingNode } from './streaming.js';
27
+ export { createTurnOutputController } from './turn-output-controller.js';
28
+ export type { TurnOutputController, TurnOutputControllerOptions, TurnOutputMode, TurnOutputSnapshot, } from './turn-output-controller.js';
27
29
  export { clearPendingRegistration, getOrCreatePendingRegistration, loadPendingRegistrations, loadProfiles, savePendingRegistrations, saveProfiles, updatePendingRegistration, upsertAgentProfile, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
28
30
  export type { AgentProfile, PendingRegistration, ProfileLockHandle } from './agent-profiles.js';
29
31
  export { resolveCanonAgent, resolveCanonProfile, getActiveProfile, getActiveProfileLock } from './agent-resolver.js';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export { DEFAULT_APPROVAL_CONFIG, parseApprovalRequestMetadata, parseApprovalRep
22
22
  export { buildRuntimeInputOutcome, buildRuntimeInputReply, buildRuntimeInputRequest, buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, parseRuntimeInputOutcomeMetadata, parseRuntimeInputReplyMetadata, parseRuntimeInputRequestMetadata, } from './runtime-cards.js';
23
23
  // Streaming (RTDB helpers)
24
24
  export { createStreamingHelper } from './streaming.js';
25
+ export { createTurnOutputController } from './turn-output-controller.js';
25
26
  // Agent profiles (loading, locking, resolution)
26
27
  export { clearPendingRegistration, getOrCreatePendingRegistration, loadPendingRegistrations, loadProfiles, savePendingRegistrations, saveProfiles, updatePendingRegistration, upsertAgentProfile, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
27
28
  // Agent resolver
@@ -10,6 +10,7 @@ export interface RuntimeStreamingPayload {
10
10
  text: string;
11
11
  status: 'thinking' | 'streaming' | 'tool';
12
12
  messageId?: string;
13
+ turnId?: string | null;
13
14
  updatedAt?: number | {
14
15
  '.sv': 'timestamp';
15
16
  };
@@ -23,6 +23,7 @@ export interface StreamingNode {
23
23
  startedAt: unknown;
24
24
  updatedAt: unknown;
25
25
  messageId: string;
26
+ turnId?: string | null;
26
27
  }
27
28
  /**
28
29
  * Creates helpers that read/write the RTDB streaming node for a single
@@ -41,7 +42,7 @@ export interface StreamingNode {
41
42
  export declare function createStreamingHelper(opts: StreamingHelperOptions): {
42
43
  forConversation: (conversationId: string, agentId: string) => {
43
44
  /** Write the initial streaming node (status: "thinking", empty text). */
44
- start(messageId: string): Promise<void>;
45
+ start(messageId: string, turnId?: string | null): Promise<void>;
45
46
  /** Update the accumulated text and optionally change status. */
46
47
  update(text: string, status?: StreamingStatus): Promise<void>;
47
48
  /** Set status without changing text (e.g. switching to "tool"). */
package/dist/streaming.js CHANGED
@@ -20,13 +20,14 @@ export function createStreamingHelper(opts) {
20
20
  const nodeRef = db.ref(nodePath);
21
21
  return {
22
22
  /** Write the initial streaming node (status: "thinking", empty text). */
23
- async start(messageId) {
23
+ async start(messageId, turnId) {
24
24
  const node = {
25
25
  text: '',
26
26
  status: 'thinking',
27
27
  startedAt: serverTimestamp,
28
28
  updatedAt: serverTimestamp,
29
29
  messageId,
30
+ ...(turnId ? { turnId } : {}),
30
31
  };
31
32
  await nodeRef.set(node);
32
33
  },
@@ -0,0 +1,35 @@
1
+ import type { StreamingStatus } from './types.js';
2
+ export type TurnOutputMode = 'delta' | 'block' | 'snapshot' | 'status';
3
+ export interface TurnOutputSnapshot {
4
+ turnId: string;
5
+ messageId: string;
6
+ text: string;
7
+ status: StreamingStatus;
8
+ }
9
+ export interface TurnOutputControllerOptions {
10
+ turnId: string;
11
+ mode?: TurnOutputMode;
12
+ throttleMs?: number;
13
+ blockSeparator?: string;
14
+ writeSnapshot: (snapshot: TurnOutputSnapshot) => void | Promise<void>;
15
+ clearSnapshot: () => void | Promise<void>;
16
+ schedule?: typeof setTimeout;
17
+ cancel?: typeof clearTimeout;
18
+ }
19
+ export interface TurnOutputController {
20
+ readonly turnId: string;
21
+ readonly mode: TurnOutputMode;
22
+ getText(): string;
23
+ getStatus(): StreamingStatus;
24
+ startThinking(text?: string): Promise<void>;
25
+ startStreaming(text?: string): Promise<void>;
26
+ appendDelta(delta: string): void;
27
+ appendBlock(block: string): void;
28
+ replaceSnapshot(text: string, status?: StreamingStatus): Promise<void>;
29
+ setStatus(status: StreamingStatus, text?: string): Promise<void>;
30
+ flush(status?: StreamingStatus): Promise<void>;
31
+ waitingInput(): Promise<void>;
32
+ interrupt(): Promise<void>;
33
+ clear(): Promise<void>;
34
+ }
35
+ export declare function createTurnOutputController(options: TurnOutputControllerOptions): TurnOutputController;
@@ -0,0 +1,125 @@
1
+ const DEFAULT_THROTTLE_MS = 120;
2
+ const DEFAULT_BLOCK_SEPARATOR = '\n\n';
3
+ function normalizeStatus(status) {
4
+ return status === 'thinking' || status === 'tool' || status === 'streaming'
5
+ ? status
6
+ : 'streaming';
7
+ }
8
+ function appendBlockText(currentText, block, separator) {
9
+ if (!block)
10
+ return currentText;
11
+ const prefix = currentText ? separator : '';
12
+ const combined = `${currentText}${prefix}${block}`;
13
+ return separator === '\n\n' ? combined.replace(/\n{3,}/g, '\n\n') : combined;
14
+ }
15
+ export function createTurnOutputController(options) {
16
+ const schedule = options.schedule ?? setTimeout;
17
+ const cancel = options.cancel ?? clearTimeout;
18
+ const throttleMs = Math.max(0, options.throttleMs ?? DEFAULT_THROTTLE_MS);
19
+ const mode = options.mode ?? 'snapshot';
20
+ const blockSeparator = options.blockSeparator ?? DEFAULT_BLOCK_SEPARATOR;
21
+ let text = '';
22
+ let status = 'thinking';
23
+ let timer = null;
24
+ let pendingPromise = Promise.resolve();
25
+ const write = (nextStatus = status) => {
26
+ if (mode === 'status')
27
+ return;
28
+ const snapshot = {
29
+ turnId: options.turnId,
30
+ messageId: options.turnId,
31
+ text,
32
+ status: normalizeStatus(nextStatus),
33
+ };
34
+ pendingPromise = pendingPromise
35
+ .catch(() => { })
36
+ .then(() => Promise.resolve(options.writeSnapshot(snapshot)).catch(() => { }));
37
+ };
38
+ const cancelTimer = () => {
39
+ if (!timer)
40
+ return;
41
+ cancel(timer);
42
+ timer = null;
43
+ };
44
+ const scheduleWrite = () => {
45
+ if (mode === 'status')
46
+ return;
47
+ if (throttleMs <= 0) {
48
+ write();
49
+ return;
50
+ }
51
+ if (timer)
52
+ return;
53
+ timer = schedule(() => {
54
+ timer = null;
55
+ write();
56
+ }, throttleMs);
57
+ };
58
+ const flush = async (nextStatus = status) => {
59
+ cancelTimer();
60
+ write(nextStatus);
61
+ await pendingPromise;
62
+ };
63
+ const clear = async () => {
64
+ cancelTimer();
65
+ text = '';
66
+ status = 'thinking';
67
+ pendingPromise = pendingPromise
68
+ .catch(() => { })
69
+ .then(() => Promise.resolve(options.clearSnapshot()).catch(() => { }));
70
+ await pendingPromise;
71
+ };
72
+ return {
73
+ turnId: options.turnId,
74
+ mode,
75
+ getText() {
76
+ return text;
77
+ },
78
+ getStatus() {
79
+ return status;
80
+ },
81
+ async startThinking(nextText = '') {
82
+ text = nextText;
83
+ status = 'thinking';
84
+ await flush('thinking');
85
+ },
86
+ async startStreaming(nextText = text) {
87
+ text = nextText;
88
+ status = 'streaming';
89
+ await flush('streaming');
90
+ },
91
+ appendDelta(delta) {
92
+ if (!delta)
93
+ return;
94
+ if (status !== 'streaming')
95
+ text = '';
96
+ text += delta;
97
+ status = 'streaming';
98
+ scheduleWrite();
99
+ },
100
+ appendBlock(block) {
101
+ if (!block)
102
+ return;
103
+ if (status !== 'streaming')
104
+ text = '';
105
+ text = appendBlockText(text, block, blockSeparator);
106
+ status = 'streaming';
107
+ scheduleWrite();
108
+ },
109
+ async replaceSnapshot(nextText, nextStatus = 'streaming') {
110
+ text = nextText;
111
+ status = normalizeStatus(nextStatus);
112
+ await flush(status);
113
+ },
114
+ async setStatus(nextStatus, nextText) {
115
+ if (nextText !== undefined)
116
+ text = nextText;
117
+ status = normalizeStatus(nextStatus);
118
+ await flush(status);
119
+ },
120
+ flush,
121
+ waitingInput: clear,
122
+ interrupt: clear,
123
+ clear,
124
+ };
125
+ }
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
2
2
  import type { ResolvedAgentBehaviorPolicy } from './policy.js';
3
- import type { TurnLifecycleState } from './turn-protocol.js';
3
+ import type { DeliveryIntent, RuntimeCapabilities, TurnLifecycleState } from './turn-protocol.js';
4
4
  import type { CanonSelfContext } from './self-context.js';
5
5
  export type { ExecutionEnvironmentMode };
6
6
  export type MediaAttachmentKind = 'image' | 'audio' | 'file';
@@ -613,6 +613,23 @@ export interface SetStreamingOptions {
613
613
  text: string;
614
614
  status: StreamingStatus;
615
615
  messageId: string;
616
+ turnId?: string | null;
617
+ }
618
+ export interface SetRuntimeTurnOptions {
619
+ conversationId: string;
620
+ state: TurnLifecycleState;
621
+ turnId?: string | null;
622
+ queueDepth?: number;
623
+ currentSpeakerId?: string | null;
624
+ lastAcceptedIntent?: DeliveryIntent | null;
625
+ activeMessageIds?: string[];
626
+ capabilities?: Partial<RuntimeCapabilities>;
627
+ openedAt?: number | null;
628
+ /**
629
+ * Set when this publish represents real turn progress. Omit for heartbeat or
630
+ * status-only refreshes so Canon can preserve stale-turn recovery semantics.
631
+ */
632
+ turnUpdatedAt?: number | null;
616
633
  }
617
634
  /** Written by Canon app to /control/{convoId}/{agentId}/session in RTDB */
618
635
  export interface SessionControl {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Canon core — shared types, REST client, SSE stream, and registration for Canon messaging",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,7 +47,8 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.0.0",
50
- "typescript": "~5.7.0"
50
+ "typescript": "~5.7.0",
51
+ "vitest": "^3.0.0"
51
52
  },
52
53
  "license": "MIT"
53
54
  }