@canonmsg/core 0.16.0 → 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.
@@ -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,7 +1,7 @@
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';
@@ -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,6 +1,6 @@
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';
@@ -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';
@@ -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';
@@ -355,6 +357,7 @@ export interface AgentContext {
355
357
  export interface MessageCreatedPayload {
356
358
  conversationId: string;
357
359
  behavior?: ResolvedAgentBehaviorPolicy;
360
+ activeSelfContextId?: string | null;
358
361
  selfContexts?: CanonSelfContext[];
359
362
  message: {
360
363
  id: string;
@@ -401,6 +404,33 @@ export interface TurnUpdatedPayload {
401
404
  agentId: string;
402
405
  turn: import('./turn-protocol.js').TurnState | null;
403
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
+ }
404
434
  export type CanonStreamEvent = {
405
435
  type: 'agent.context';
406
436
  payload: AgentContext;
@@ -439,10 +469,7 @@ export type CanonStreamEvent = {
439
469
  };
440
470
  } | {
441
471
  type: 'conversation.updated';
442
- payload: {
443
- conversationId: string;
444
- changes: Record<string, unknown>;
445
- };
472
+ payload: ConversationUpdatedPayload;
446
473
  };
447
474
  export interface SendMessageOptions {
448
475
  messageId?: string;
@@ -452,7 +479,7 @@ export interface SendMessageOptions {
452
479
  attachments?: MediaAttachment[];
453
480
  contactCardUserId?: string;
454
481
  mentions?: string[];
455
- selfContextId?: string;
482
+ selfContextId?: string | null;
456
483
  /** Structured metadata for rich UI (approval cards, etc.) */
457
484
  metadata?: Record<string, unknown>;
458
485
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.16.0",
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",