@canonmsg/core 0.15.5 → 0.17.1

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.
@@ -132,6 +132,7 @@ export function buildAgentSessionSnapshot(input) {
132
132
  executionFallbackReason: input.sessionState?.executionFallbackReason,
133
133
  turnState: input.turnState?.state,
134
134
  supportsQueue: input.turnState?.capabilities?.supportsQueue,
135
+ supportsInputInterrupt: input.turnState?.capabilities?.supportsInterrupt,
135
136
  queueDepth: input.turnState?.queueDepth ?? 0,
136
137
  waitingForInput: input.turnState?.state === 'waiting_input',
137
138
  contextUsage: input.sessionState?.contextUsage,
package/dist/browser.d.ts CHANGED
@@ -7,7 +7,7 @@ export type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
7
7
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
8
8
  export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
9
9
  export { buildAgentSessionSnapshot } from './agent-session.js';
10
- export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
10
+ export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
11
11
  export type { AgentBehaviorSettings, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationStyle, ResolvedAgentBehaviorPolicy, } from './policy.js';
12
12
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
13
13
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
package/dist/browser.js CHANGED
@@ -4,7 +4,7 @@ export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_AP
4
4
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
5
5
  export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
6
6
  export { buildAgentSessionSnapshot } from './agent-session.js';
7
- export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
7
+ export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
8
8
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
9
9
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
10
10
  export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
@@ -0,0 +1,25 @@
1
+ import type { CanonConversation, CanonGroupContext, CanonGroupContextMode, CanonKnownRecentParticipant, CanonMembershipChange, CanonMessage } from './types.js';
2
+ type MessageLike = {
3
+ senderId?: string | null;
4
+ senderName?: string | null;
5
+ senderType?: CanonMessage['senderType'];
6
+ isOwner?: boolean;
7
+ createdAt?: string | null;
8
+ };
9
+ export declare function diffCanonMemberIds(previousMemberIds: unknown[], nextMemberIds: unknown[]): CanonMembershipChange | null;
10
+ export declare function buildCanonKnownRecentParticipants(input: {
11
+ messages: MessageLike[];
12
+ agentId: string;
13
+ ownerId?: string | null;
14
+ maxParticipants?: number;
15
+ }): CanonKnownRecentParticipant[];
16
+ export declare function buildCanonGroupContext(input: {
17
+ conversation: CanonConversation | null | undefined;
18
+ messages: MessageLike[];
19
+ agentId: string;
20
+ ownerId?: string | null;
21
+ ownerName?: string | null;
22
+ membershipChange?: CanonMembershipChange | null;
23
+ }): CanonGroupContext | undefined;
24
+ export declare function buildCompactGroupContextLines(groupContext: CanonGroupContext, mode: CanonGroupContextMode): string[];
25
+ export {};
@@ -0,0 +1,100 @@
1
+ function uniqueStringIds(ids) {
2
+ return Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.length > 0)));
3
+ }
4
+ export function diffCanonMemberIds(previousMemberIds, nextMemberIds) {
5
+ const previous = new Set(uniqueStringIds(previousMemberIds));
6
+ const next = uniqueStringIds(nextMemberIds);
7
+ const nextSet = new Set(next);
8
+ const addedMemberIds = next.filter((id) => !previous.has(id));
9
+ const removedMemberIds = [...previous].filter((id) => !nextSet.has(id));
10
+ if (addedMemberIds.length === 0 && removedMemberIds.length === 0) {
11
+ return null;
12
+ }
13
+ return {
14
+ addedMemberIds,
15
+ removedMemberIds,
16
+ memberCount: next.length,
17
+ };
18
+ }
19
+ export function buildCanonKnownRecentParticipants(input) {
20
+ const maxParticipants = input.maxParticipants ?? 8;
21
+ const indexed = input.messages
22
+ .map((message, index) => {
23
+ const timestamp = typeof message.createdAt === 'string'
24
+ ? Date.parse(message.createdAt)
25
+ : Number.NaN;
26
+ return {
27
+ message,
28
+ index,
29
+ sortKey: Number.isFinite(timestamp) ? timestamp : index,
30
+ };
31
+ })
32
+ .sort((left, right) => right.sortKey - left.sortKey || right.index - left.index);
33
+ const participants = new Map();
34
+ for (const { message } of indexed) {
35
+ if (!message.senderId || participants.has(message.senderId))
36
+ continue;
37
+ const isSelf = message.senderId === input.agentId;
38
+ const isOwner = message.isOwner ?? (typeof input.ownerId === 'string' && input.ownerId.length > 0
39
+ ? message.senderId === input.ownerId
40
+ : false);
41
+ participants.set(message.senderId, {
42
+ id: message.senderId,
43
+ name: message.senderName ?? (isSelf ? 'this agent' : message.senderId),
44
+ userType: message.senderType ?? 'unknown',
45
+ isOwner,
46
+ isSelf,
47
+ });
48
+ if (participants.size >= maxParticipants)
49
+ break;
50
+ }
51
+ return [...participants.values()];
52
+ }
53
+ export function buildCanonGroupContext(input) {
54
+ if (input.conversation?.type !== 'group')
55
+ return undefined;
56
+ const memberIds = uniqueStringIds(input.conversation.memberIds ?? []);
57
+ const ownerId = input.ownerId ?? '';
58
+ return {
59
+ memberCount: memberIds.length,
60
+ memberIds,
61
+ ownerId,
62
+ ownerName: input.ownerName ?? '',
63
+ ownerPresent: ownerId.length > 0 && memberIds.includes(ownerId),
64
+ knownRecentParticipants: buildCanonKnownRecentParticipants({
65
+ messages: input.messages,
66
+ agentId: input.agentId,
67
+ ownerId,
68
+ }),
69
+ ...(input.membershipChange ? { membershipChange: input.membershipChange } : {}),
70
+ };
71
+ }
72
+ function formatParticipant(participant) {
73
+ const labels = [
74
+ participant.userType === 'ai_agent'
75
+ ? 'agent'
76
+ : participant.userType === 'human'
77
+ ? 'human'
78
+ : 'unknown type',
79
+ ];
80
+ if (participant.isOwner)
81
+ labels.push('owner');
82
+ if (participant.isSelf)
83
+ labels.push('you');
84
+ return `${participant.name} (${labels.join(', ')})`;
85
+ }
86
+ export function buildCompactGroupContextLines(groupContext, mode) {
87
+ if (mode === 'membership_change' && groupContext.membershipChange) {
88
+ const { addedMemberIds, removedMemberIds, memberCount } = groupContext.membershipChange;
89
+ return [
90
+ `Group membership changed: +${addedMemberIds.length}, -${removedMemberIds.length}. Member count now ${memberCount}. Agent owner present: ${groupContext.ownerPresent ? 'yes' : 'no'}.`,
91
+ ];
92
+ }
93
+ const lines = [
94
+ `Group context: ${groupContext.memberCount} members. Agent owner present: ${groupContext.ownerPresent ? 'yes' : 'no'}.`,
95
+ ];
96
+ if (groupContext.knownRecentParticipants.length > 0) {
97
+ lines.push(`Known recent participants: ${groupContext.knownRecentParticipants.map(formatParticipant).join(', ')}.`);
98
+ }
99
+ return lines;
100
+ }
@@ -1,4 +1,4 @@
1
- import { type AgentClientType, type AgentRuntime, type CanonConversation, type CanonMessage, type CanonMessagesPage, type MessageCreatedPayload } from './types.js';
1
+ import { type AgentClientType, type AgentRuntime, type CanonConversation, type CanonGroupContext, type CanonGroupContextMode, type CanonMembershipChange, type CanonMessage, type CanonMessagesPage, type MessageCreatedPayload } from './types.js';
2
2
  import { type CanonClient } from './client.js';
3
3
  import { type SessionWorkspaceConfig } from './execution-environment.js';
4
4
  import { type ResolvedAgentBehaviorPolicy } from './policy.js';
@@ -13,6 +13,8 @@ export interface HostInboundParticipantContext {
13
13
  senderName: string;
14
14
  isOwner: boolean;
15
15
  mentionedAgent: boolean;
16
+ groupContext?: CanonGroupContext;
17
+ groupContextMode?: CanonGroupContextMode;
16
18
  recentSenderTypes: Array<'human' | 'ai_agent'>;
17
19
  recentHumanCount: number;
18
20
  recentAgentCount: number;
@@ -20,6 +22,7 @@ export interface HostInboundParticipantContext {
20
22
  currentAgentStreakStartedByHuman: boolean;
21
23
  }
22
24
  type HostInboundMessage = {
25
+ id?: string | null;
23
26
  text?: string | null;
24
27
  contentType?: CanonMessage['contentType'] | null;
25
28
  attachments?: CanonMessage['attachments'];
@@ -61,12 +64,19 @@ export declare function buildHydratedInboundContext(input: {
61
64
  agentId: string;
62
65
  conversation: CanonConversation | null;
63
66
  page?: CanonMessagesPage | null;
67
+ activeSelfContextId?: string | null;
68
+ selfContexts?: MessageCreatedPayload['selfContexts'];
64
69
  message: HostInboundMessage;
65
70
  senderName: string;
66
71
  isOwner: boolean;
72
+ ownerId?: string | null;
73
+ ownerName?: string | null;
74
+ membershipChange?: CanonMembershipChange | null;
75
+ groupContextMode?: CanonGroupContextMode;
67
76
  }): {
68
77
  participantContext: HostInboundParticipantContext;
69
78
  behavior?: ResolvedAgentBehaviorPolicy | null;
79
+ activeSelfContextId: string | null;
70
80
  selfContexts: NonNullable<MessageCreatedPayload['selfContexts']>;
71
81
  hydratedFromPage: boolean;
72
82
  };
@@ -1,7 +1,8 @@
1
+ import { buildCanonGroupContext } from './group-context.js';
1
2
  import { buildAgentSessionSnapshot } from './agent-session.js';
2
3
  import { buildConversationWorktreeSpec, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, } from './execution-environment.js';
3
4
  import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, } from './policy.js';
4
- import { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
5
+ import { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
5
6
  import { rtdbRead } from './rtdb-rest.js';
6
7
  import { createRuntimeStatePublisher } from './runtime-state-publisher.js';
7
8
  const HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES = Object.freeze({
@@ -102,6 +103,26 @@ function describeAttachment(attachment, materialized) {
102
103
  }
103
104
  export function buildHydratedInboundContext(input) {
104
105
  const history = buildParticipationHistorySnapshot(input.page?.messages ?? [], input.agentId);
106
+ const activeSelfContextId = resolveMessageActiveSelfContextId({
107
+ messageId: input.message.id,
108
+ activeSelfContextId: input.activeSelfContextId,
109
+ activeSelfContextIdByMessageId: input.page?.activeSelfContextIdByMessageId,
110
+ });
111
+ const activeSelfContexts = selectActiveSelfContexts([
112
+ ...(input.page?.selfContexts ?? []),
113
+ ...(input.selfContexts ?? []),
114
+ ], activeSelfContextId);
115
+ const groupContext = buildCanonGroupContext({
116
+ conversation: input.conversation,
117
+ messages: [
118
+ ...(input.page?.messages ?? []),
119
+ input.message,
120
+ ],
121
+ agentId: input.agentId,
122
+ ownerId: input.ownerId,
123
+ ownerName: input.ownerName,
124
+ membershipChange: input.membershipChange,
125
+ });
105
126
  return {
106
127
  participantContext: {
107
128
  conversationType: input.conversation?.type ?? 'unknown',
@@ -110,6 +131,8 @@ export function buildHydratedInboundContext(input) {
110
131
  senderName: input.senderName,
111
132
  isOwner: input.isOwner,
112
133
  mentionedAgent: Array.isArray(input.message.mentions) && input.message.mentions.includes(input.agentId),
134
+ ...(groupContext ? { groupContext } : {}),
135
+ ...(groupContext && input.groupContextMode ? { groupContextMode: input.groupContextMode } : {}),
113
136
  recentSenderTypes: history.recentSenderTypes,
114
137
  recentHumanCount: history.recentHumanCount,
115
138
  recentAgentCount: history.recentAgentCount,
@@ -117,7 +140,8 @@ export function buildHydratedInboundContext(input) {
117
140
  currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
118
141
  },
119
142
  behavior: input.page?.behavior ?? input.conversation?.behavior,
120
- selfContexts: input.page?.selfContexts ?? [],
143
+ activeSelfContextId: activeSelfContexts.length > 0 ? activeSelfContextId : null,
144
+ selfContexts: activeSelfContexts,
121
145
  hydratedFromPage: input.page != null,
122
146
  };
123
147
  }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
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, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, 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, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, 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, 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, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
3
3
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
4
- export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
4
+ export { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
5
5
  export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
6
6
  export type { ConfiguredWorkspaceRoot, WorkspaceDiscoveryResult, } from './workspace-discovery.js';
7
7
  export { CanonClient, CanonApiError } from './client.js';
8
8
  export { buildAgentSessionSnapshot } from './agent-session.js';
9
- export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
9
+ export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
10
10
  export { CanonStream } from './stream.js';
11
11
  export type { StreamHandler } from './stream.js';
12
12
  export type { PolicyRole, ParticipationStyle, RepresentationMode, PermissionLevel, ConversationScope, AgentBehaviorSettings, Participant, Relationship, ContextOverlay, BehaviorProfile, AdmissionPolicy, RuntimeControlPolicy, ActionApprovalPolicy, ParticipationPolicy, ResolvedPolicy, ResolvedTurnEligibility, ResolvedAgentBehaviorPolicy, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationDecisionInput, ParticipationDecision, } from './policy.js';
@@ -34,6 +34,7 @@ export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRunt
34
34
  export type { AgentSessionSnapshotPatch, RTDBClientHandle, RuntimeInfoPayloadData, SessionStatePayload, TurnStatePayload, } from './rtdb-rest.js';
35
35
  export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
36
36
  export type { HostInboundParticipantContext, } from './host-runtime.js';
37
+ export { buildCanonGroupContext, buildCanonKnownRecentParticipants, buildCompactGroupContextLines, diffCanonMemberIds, } from './group-context.js';
37
38
  export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
38
39
  export type { RuntimeStatePublisher, RuntimeStatePublisherOptions, RuntimeStreamingPayload, } from './runtime-state-publisher.js';
39
40
  export { formatCanonMessageAsText } from './message-format.js';
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // Types
2
2
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
3
- export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
3
+ export { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
4
4
  export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
5
5
  // Client
6
6
  export { CanonClient, CanonApiError } from './client.js';
7
7
  export { buildAgentSessionSnapshot } from './agent-session.js';
8
- export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
8
+ export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
9
9
  // Stream
10
10
  export { CanonStream } from './stream.js';
11
11
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
@@ -32,6 +32,7 @@ export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, build
32
32
  export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
33
33
  // Runtime host plumbing
34
34
  export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
35
+ export { buildCanonGroupContext, buildCanonKnownRecentParticipants, buildCompactGroupContextLines, diffCanonMemberIds, } from './group-context.js';
35
36
  export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
36
37
  // Message formatting (LLM-facing text projection)
37
38
  export { formatCanonMessageAsText } from './message-format.js';
@@ -80,6 +80,7 @@ export interface AgentSessionSnapshotPatch {
80
80
  state?: null;
81
81
  turnState?: TurnLifecycleState | null;
82
82
  supportsQueue?: boolean | null;
83
+ supportsInputInterrupt?: boolean | null;
83
84
  queueDepth?: number;
84
85
  waitingForInput?: boolean;
85
86
  contextUsage?: {
package/dist/rtdb-rest.js CHANGED
@@ -198,6 +198,9 @@ function createRTDBClientHandle(client, options) {
198
198
  ...(state.capabilities?.supportsQueue !== undefined
199
199
  ? { supportsQueue: state.capabilities.supportsQueue }
200
200
  : {}),
201
+ ...(state.capabilities?.supportsInterrupt !== undefined
202
+ ? { supportsInputInterrupt: state.capabilities.supportsInterrupt }
203
+ : {}),
201
204
  queueDepth: state.queueDepth,
202
205
  waitingForInput: state.state === 'waiting_input',
203
206
  lastHeartbeatAt: { '.sv': 'timestamp' },
@@ -36,6 +36,9 @@ export declare function buildRuntimePermissionModeControl(input: {
36
36
  defaultValue?: string | null;
37
37
  }): CanonControlDescriptor;
38
38
  export declare function buildRuntimeEffortControl(): CanonControlDescriptor;
39
+ export declare const RUNTIME_STOP_ACTION: CanonRuntimeActionDescriptor;
40
+ export declare const RUNTIME_STOP_AND_DROP_ACTION: CanonRuntimeActionDescriptor;
41
+ export declare const RUNTIME_NEW_SESSION_ACTION: CanonRuntimeActionDescriptor;
39
42
  export declare function buildFirstPartyCodingRuntimeDescriptor(input: {
40
43
  clientType: Extract<AgentClientType, 'claude-code' | 'codex'>;
41
44
  models: ReadonlyArray<ModelOption>;
@@ -93,6 +93,36 @@ export function buildRuntimeEffortControl() {
93
93
  selectionPolicy: 'inherit',
94
94
  };
95
95
  }
96
+ export const RUNTIME_STOP_ACTION = {
97
+ id: 'stop',
98
+ label: 'Stop',
99
+ description: 'Interrupt the current turn.',
100
+ aliases: ['stop'],
101
+ category: 'turn',
102
+ placements: ['composer_slash', 'command_palette'],
103
+ availability: ['busy'],
104
+ dispatch: { kind: 'signal', signal: 'interrupt' },
105
+ };
106
+ export const RUNTIME_STOP_AND_DROP_ACTION = {
107
+ id: 'stop-and-clear-queue',
108
+ label: 'Stop & clear queue',
109
+ description: 'Interrupt the current turn and drop queued Canon messages.',
110
+ aliases: ['stop-clear', 'clear-queue'],
111
+ category: 'turn',
112
+ placements: ['composer_slash', 'command_palette', 'session_strip'],
113
+ availability: ['busy_with_queue'],
114
+ dispatch: { kind: 'signal', signal: 'stop_and_drop' },
115
+ };
116
+ export const RUNTIME_NEW_SESSION_ACTION = {
117
+ id: 'new-session',
118
+ label: 'New session',
119
+ description: 'Reset this runtime session inside the current Canon chat.',
120
+ aliases: ['new'],
121
+ category: 'session',
122
+ placements: ['composer_slash', 'command_palette', 'session_strip'],
123
+ availability: ['always'],
124
+ dispatch: { kind: 'signal', signal: 'new_session' },
125
+ };
96
126
  export function buildFirstPartyCodingRuntimeDescriptor(input) {
97
127
  const permissionModes = input.permissionModes
98
128
  ?? (input.clientType === 'claude-code' ? CLAUDE_PERMISSION_MODE_OPTIONS : []);
@@ -122,6 +152,7 @@ export function buildFirstPartyCodingRuntimeDescriptor(input) {
122
152
  actions: input.actions,
123
153
  workspaceRoots: input.workspaceRoots,
124
154
  supportsInterrupt: true,
155
+ supportsInputInterrupt: true,
125
156
  streamingTextMode: input.streamingTextMode,
126
157
  admissionActions: input.admissionActions,
127
158
  };
@@ -28,6 +28,7 @@ export type SendContextualMessageResult = {
28
28
  } | {
29
29
  status: 'requested' | 'pending';
30
30
  requestId: string | null;
31
+ deferredIntentId?: string | null;
31
32
  } | {
32
33
  status: 'setup_required' | 'blocked' | 'unavailable';
33
34
  reason: string;
@@ -36,4 +37,10 @@ export interface SelfContextPromptRenderOptions {
36
37
  maxSelfContexts?: number;
37
38
  }
38
39
  export declare function normalizeSelfContexts(selfContexts?: CanonSelfContext[] | null, options?: SelfContextPromptRenderOptions): CanonSelfContext[];
40
+ export declare function resolveMessageActiveSelfContextId(input: {
41
+ messageId?: string | null;
42
+ activeSelfContextId?: string | null;
43
+ activeSelfContextIdByMessageId?: Record<string, string> | null;
44
+ }): string | null;
45
+ export declare function selectActiveSelfContexts(selfContexts?: CanonSelfContext[] | null, activeSelfContextId?: string | null): CanonSelfContext[];
39
46
  export declare function buildSelfContextPromptLines(selfContexts?: CanonSelfContext[] | null, options?: SelfContextPromptRenderOptions): string[];
@@ -49,6 +49,23 @@ export function normalizeSelfContexts(selfContexts, options) {
49
49
  .sort((left, right) => contextSortTime(right) - contextSortTime(left))
50
50
  .slice(0, promptOptions.maxSelfContexts);
51
51
  }
52
+ export function resolveMessageActiveSelfContextId(input) {
53
+ const direct = normalizeString(input.activeSelfContextId);
54
+ if (direct)
55
+ return direct;
56
+ const messageId = normalizeString(input.messageId);
57
+ if (!messageId || !input.activeSelfContextIdByMessageId)
58
+ return null;
59
+ return normalizeString(input.activeSelfContextIdByMessageId[messageId]);
60
+ }
61
+ export function selectActiveSelfContexts(selfContexts, activeSelfContextId) {
62
+ const id = normalizeString(activeSelfContextId);
63
+ if (!id)
64
+ return [];
65
+ return normalizeSelfContexts(Array.isArray(selfContexts)
66
+ ? selfContexts.filter((selfContext) => normalizeString(selfContext?.id) === id)
67
+ : []);
68
+ }
52
69
  export function buildSelfContextPromptLines(selfContexts, options) {
53
70
  const resolved = normalizeSelfContexts(selfContexts, options);
54
71
  if (resolved.length === 0)
package/dist/stream.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentContext, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload } from './types.js';
1
+ import type { AgentContext, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ConversationUpdatedPayload, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload } from './types.js';
2
2
  export type StreamHandler = {
3
3
  onMessage: (payload: MessageCreatedPayload) => void;
4
4
  onAgentContext?: (ctx: AgentContext) => void;
@@ -14,10 +14,7 @@ export type StreamHandler = {
14
14
  conversationId: string;
15
15
  messageId: string;
16
16
  }) => void;
17
- onConversationUpdated?: (payload: {
18
- conversationId: string;
19
- changes: Record<string, unknown>;
20
- }) => void;
17
+ onConversationUpdated?: (payload: ConversationUpdatedPayload) => void;
21
18
  onConnected?: () => void;
22
19
  onDisconnected?: () => void;
23
20
  onError?: (error: Error) => void;
@@ -47,6 +44,8 @@ export declare class CanonStream {
47
44
  start(): Promise<void>;
48
45
  stop(): void;
49
46
  isRunning(): boolean;
47
+ private requestedEventFamilies;
48
+ private streamEndpoint;
50
49
  private connect;
51
50
  private readStream;
52
51
  private processFrame;
package/dist/stream.js CHANGED
@@ -42,6 +42,27 @@ export class CanonStream {
42
42
  isRunning() {
43
43
  return this.running;
44
44
  }
45
+ requestedEventFamilies() {
46
+ const families = new Set(['messages']);
47
+ if (this.handler.onContactRequest
48
+ || this.handler.onContactApproved
49
+ || this.handler.onContactAdded
50
+ || this.handler.onContactRemoved) {
51
+ families.add('contacts');
52
+ }
53
+ if (this.handler.onTyping || this.handler.onPresence) {
54
+ families.add('typing_presence');
55
+ }
56
+ if (this.handler.onRuntimeUpdated || this.handler.onTurnUpdated) {
57
+ families.add('runtime_turn');
58
+ }
59
+ return [...families];
60
+ }
61
+ streamEndpoint() {
62
+ const base = `${this.streamUrl.replace(/\/$/, '')}/agents/stream`;
63
+ const events = encodeURIComponent(this.requestedEventFamilies().join(','));
64
+ return `${base}?events=${events}`;
65
+ }
45
66
  // ── SSE connection ────────────────────────────────────────────────────
46
67
  async connect() {
47
68
  if (!this.running)
@@ -55,7 +76,7 @@ export class CanonStream {
55
76
  headers['Last-Event-ID'] = this.lastEventId;
56
77
  }
57
78
  try {
58
- const res = await fetch(`${this.streamUrl}/agents/stream`, {
79
+ const res = await fetch(this.streamEndpoint(), {
59
80
  headers,
60
81
  signal: this.abortController.signal,
61
82
  });
package/dist/types.d.ts CHANGED
@@ -40,6 +40,7 @@ export interface ContactCardPayload {
40
40
  export interface CanonMessage {
41
41
  id: string;
42
42
  senderId: string;
43
+ senderName?: string;
43
44
  senderType: 'human' | 'ai_agent';
44
45
  /** Whether the sender is this agent's owner (server-computed, trusted) */
45
46
  isOwner: boolean;
@@ -77,6 +78,7 @@ export interface CanonConversation {
77
78
  export interface CanonMessagesPage {
78
79
  messages: CanonMessage[];
79
80
  behavior?: ResolvedAgentBehaviorPolicy;
81
+ activeSelfContextIdByMessageId?: Record<string, string>;
80
82
  selfContexts?: CanonSelfContext[];
81
83
  }
82
84
  export type CanonContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired';
@@ -235,7 +237,7 @@ export type CanonRuntimeActionDispatch = {
235
237
  value: CanonControlValue;
236
238
  } | {
237
239
  kind: 'signal';
238
- signal: 'interrupt' | 'stop_and_drop';
240
+ signal: 'interrupt' | 'stop_and_drop' | 'new_session';
239
241
  } | {
240
242
  kind: 'compose';
241
243
  text: string;
@@ -278,6 +280,7 @@ export interface CanonRuntimeDescriptor {
278
280
  workspaceRoots?: ReadonlyArray<CanonWorkspaceRootMetadata>;
279
281
  writableRoots?: ReadonlyArray<CanonWorkspaceRootMetadata>;
280
282
  supportsInterrupt?: boolean;
283
+ supportsInputInterrupt?: boolean;
281
284
  /**
282
285
  * Fidelity of live text exposed through Canon's streaming bubble path.
283
286
  * `delta` means token/content deltas, `block` means chunked live previews,
@@ -354,6 +357,7 @@ export interface AgentContext {
354
357
  export interface MessageCreatedPayload {
355
358
  conversationId: string;
356
359
  behavior?: ResolvedAgentBehaviorPolicy;
360
+ activeSelfContextId?: string | null;
357
361
  selfContexts?: CanonSelfContext[];
358
362
  message: {
359
363
  id: string;
@@ -400,6 +404,33 @@ export interface TurnUpdatedPayload {
400
404
  agentId: string;
401
405
  turn: import('./turn-protocol.js').TurnState | null;
402
406
  }
407
+ export interface CanonMembershipChange {
408
+ addedMemberIds: string[];
409
+ removedMemberIds: string[];
410
+ memberCount: number;
411
+ }
412
+ export interface CanonKnownRecentParticipant {
413
+ id: string;
414
+ name: string;
415
+ userType: 'human' | 'ai_agent' | 'unknown';
416
+ isOwner: boolean;
417
+ isSelf: boolean;
418
+ }
419
+ export interface CanonGroupContext {
420
+ memberCount: number;
421
+ memberIds: string[];
422
+ ownerId: string;
423
+ ownerName: string;
424
+ ownerPresent: boolean;
425
+ knownRecentParticipants: CanonKnownRecentParticipant[];
426
+ membershipChange?: CanonMembershipChange;
427
+ }
428
+ export type CanonGroupContextMode = 'initial' | 'membership_change';
429
+ export interface ConversationUpdatedPayload {
430
+ conversationId: string;
431
+ changes: Record<string, unknown>;
432
+ membershipChange?: CanonMembershipChange;
433
+ }
403
434
  export type CanonStreamEvent = {
404
435
  type: 'agent.context';
405
436
  payload: AgentContext;
@@ -438,10 +469,7 @@ export type CanonStreamEvent = {
438
469
  };
439
470
  } | {
440
471
  type: 'conversation.updated';
441
- payload: {
442
- conversationId: string;
443
- changes: Record<string, unknown>;
444
- };
472
+ payload: ConversationUpdatedPayload;
445
473
  };
446
474
  export interface SendMessageOptions {
447
475
  messageId?: string;
@@ -451,7 +479,7 @@ export interface SendMessageOptions {
451
479
  attachments?: MediaAttachment[];
452
480
  contactCardUserId?: string;
453
481
  mentions?: string[];
454
- selfContextId?: string;
482
+ selfContextId?: string | null;
455
483
  /** Structured metadata for rich UI (approval cards, etc.) */
456
484
  metadata?: Record<string, unknown>;
457
485
  }
@@ -601,6 +629,7 @@ export interface AgentSessionSnapshot {
601
629
  executionFallbackReason?: string | null;
602
630
  turnState?: TurnLifecycleState;
603
631
  supportsQueue?: boolean;
632
+ supportsInputInterrupt?: boolean;
604
633
  queueDepth: number;
605
634
  waitingForInput: boolean;
606
635
  contextUsage?: SessionState['contextUsage'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.15.5",
3
+ "version": "0.17.1",
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",