@canonmsg/core 0.16.0 → 0.17.2

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/browser.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
2
2
  export { resolveCanonBaseUrl } from './base-url.js';
3
3
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
4
- export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
4
+ export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
5
5
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
6
6
  export type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
7
7
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.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,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, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, 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';
@@ -1,5 +1,5 @@
1
1
  import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
2
- import { type AgentClientType, type CanonControlDescriptor, type CanonRuntimeActionDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeStreamingMode, type CanonWorkspaceRootMetadata, type ModelOption, type PermissionModeOption, type WorkspaceOption } from './types.js';
2
+ import { type AgentClientType, type CanonControlDescriptor, type CanonRuntimeActionDescriptor, type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeStreamingMode, type CanonWorkspaceRootMetadata, type ModelOption, type PermissionModeOption, type WorkspaceOption } from './types.js';
3
3
  import type { HostAdmissionActionCapabilities } from './turn-protocol.js';
4
4
  export declare const CLAUDE_EFFORT_OPTIONS: readonly [{
5
5
  readonly value: "low";
@@ -48,6 +48,7 @@ export declare function buildFirstPartyCodingRuntimeDescriptor(input: {
48
48
  permissionModes?: ReadonlyArray<PermissionModeOption>;
49
49
  defaultPermissionMode?: string | null;
50
50
  actions?: ReadonlyArray<CanonRuntimeActionDescriptor>;
51
+ commands?: ReadonlyArray<CanonRuntimeCommandDescriptor>;
51
52
  streamingTextMode: CanonRuntimeStreamingMode;
52
53
  admissionActions?: HostAdmissionActionCapabilities;
53
54
  }): CanonRuntimeDescriptor;
@@ -149,6 +149,7 @@ export function buildFirstPartyCodingRuntimeDescriptor(input) {
149
149
  : []),
150
150
  ...(input.clientType === 'claude-code' ? [buildRuntimeEffortControl()] : []),
151
151
  ],
152
+ commands: input.commands,
152
153
  actions: input.actions,
153
154
  workspaceRoots: input.workspaceRoots,
154
155
  supportsInterrupt: true,
@@ -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';
@@ -228,14 +230,35 @@ export type CanonRuntimeInventoryStatus = 'ready' | 'auth_needed' | 'unknown' |
228
230
  export type CanonRuntimeStatusTone = 'default' | 'success' | 'warning' | 'danger';
229
231
  export type CanonRuntimeActionAvailability = 'idle' | 'busy' | 'busy_with_queue' | 'waiting_input' | 'always';
230
232
  export type CanonRuntimeActionPlacement = 'composer_slash' | 'command_palette' | 'session_strip';
231
- export type CanonRuntimeActionCategory = 'plan' | 'turn' | 'session' | 'details' | 'custom';
233
+ export type CanonRuntimeActionCategory = 'plan' | 'turn' | 'session' | 'runtime' | 'details' | 'custom';
234
+ export type CanonRuntimePrimitiveId = 'runtime.status' | 'runtime.reasoning.set' | 'runtime.verbosity.set' | 'runtime.usage' | 'context.compact' | 'session.new' | 'session.reset';
235
+ export type CanonRuntimeCommandArgumentKind = 'string' | 'enum' | 'boolean';
236
+ export interface CanonRuntimeCommandArgumentChoice {
237
+ value: string;
238
+ label: string;
239
+ description?: string;
240
+ }
241
+ export interface CanonRuntimeCommandArgumentDescriptor {
242
+ id: string;
243
+ label: string;
244
+ kind: CanonRuntimeCommandArgumentKind;
245
+ required?: boolean;
246
+ captureRemaining?: boolean;
247
+ choices?: ReadonlyArray<CanonRuntimeCommandArgumentChoice>;
248
+ }
232
249
  export type CanonRuntimeActionDispatch = {
233
250
  kind: 'control';
234
251
  controlId: string;
235
- value: CanonControlValue;
252
+ value?: CanonControlValue;
236
253
  } | {
237
254
  kind: 'signal';
238
255
  signal: 'interrupt' | 'stop_and_drop' | 'new_session';
256
+ } | {
257
+ kind: 'primitive';
258
+ primitive: CanonRuntimePrimitiveId;
259
+ } | {
260
+ kind: 'text_passthrough';
261
+ template: string;
239
262
  } | {
240
263
  kind: 'compose';
241
264
  text: string;
@@ -247,6 +270,7 @@ export interface CanonRuntimeActionDescriptor {
247
270
  id: string;
248
271
  label: string;
249
272
  description?: string;
273
+ primitive?: CanonRuntimePrimitiveId;
250
274
  aliases?: ReadonlyArray<string>;
251
275
  category?: CanonRuntimeActionCategory;
252
276
  placements?: ReadonlyArray<CanonRuntimeActionPlacement>;
@@ -254,8 +278,13 @@ export interface CanonRuntimeActionDescriptor {
254
278
  ownerOnly?: boolean;
255
279
  disabledReason?: string | null;
256
280
  trailingTextBehavior?: 'ignore' | 'send_as_prompt';
281
+ args?: ReadonlyArray<CanonRuntimeCommandArgumentDescriptor>;
257
282
  dispatch: CanonRuntimeActionDispatch;
258
283
  }
284
+ export interface CanonRuntimeCommandDescriptor extends CanonRuntimeActionDescriptor {
285
+ primitive?: CanonRuntimePrimitiveId;
286
+ args?: ReadonlyArray<CanonRuntimeCommandArgumentDescriptor>;
287
+ }
259
288
  export interface CanonControlDescriptor {
260
289
  id: string;
261
290
  label: string;
@@ -269,6 +298,7 @@ export interface CanonControlDescriptor {
269
298
  export interface CanonRuntimeDescriptor {
270
299
  coreControls: ReadonlyArray<CanonControlDescriptor>;
271
300
  runtimeControls?: ReadonlyArray<CanonControlDescriptor>;
301
+ commands?: ReadonlyArray<CanonRuntimeCommandDescriptor>;
272
302
  actions?: ReadonlyArray<CanonRuntimeActionDescriptor>;
273
303
  /**
274
304
  * Optional setup-time local roots advertised by a runtime. These are
@@ -355,6 +385,7 @@ export interface AgentContext {
355
385
  export interface MessageCreatedPayload {
356
386
  conversationId: string;
357
387
  behavior?: ResolvedAgentBehaviorPolicy;
388
+ activeSelfContextId?: string | null;
358
389
  selfContexts?: CanonSelfContext[];
359
390
  message: {
360
391
  id: string;
@@ -401,6 +432,33 @@ export interface TurnUpdatedPayload {
401
432
  agentId: string;
402
433
  turn: import('./turn-protocol.js').TurnState | null;
403
434
  }
435
+ export interface CanonMembershipChange {
436
+ addedMemberIds: string[];
437
+ removedMemberIds: string[];
438
+ memberCount: number;
439
+ }
440
+ export interface CanonKnownRecentParticipant {
441
+ id: string;
442
+ name: string;
443
+ userType: 'human' | 'ai_agent' | 'unknown';
444
+ isOwner: boolean;
445
+ isSelf: boolean;
446
+ }
447
+ export interface CanonGroupContext {
448
+ memberCount: number;
449
+ memberIds: string[];
450
+ ownerId: string;
451
+ ownerName: string;
452
+ ownerPresent: boolean;
453
+ knownRecentParticipants: CanonKnownRecentParticipant[];
454
+ membershipChange?: CanonMembershipChange;
455
+ }
456
+ export type CanonGroupContextMode = 'initial' | 'membership_change';
457
+ export interface ConversationUpdatedPayload {
458
+ conversationId: string;
459
+ changes: Record<string, unknown>;
460
+ membershipChange?: CanonMembershipChange;
461
+ }
404
462
  export type CanonStreamEvent = {
405
463
  type: 'agent.context';
406
464
  payload: AgentContext;
@@ -439,10 +497,7 @@ export type CanonStreamEvent = {
439
497
  };
440
498
  } | {
441
499
  type: 'conversation.updated';
442
- payload: {
443
- conversationId: string;
444
- changes: Record<string, unknown>;
445
- };
500
+ payload: ConversationUpdatedPayload;
446
501
  };
447
502
  export interface SendMessageOptions {
448
503
  messageId?: string;
@@ -452,7 +507,7 @@ export interface SendMessageOptions {
452
507
  attachments?: MediaAttachment[];
453
508
  contactCardUserId?: string;
454
509
  mentions?: string[];
455
- selfContextId?: string;
510
+ selfContextId?: string | null;
456
511
  /** Structured metadata for rich UI (approval cards, etc.) */
457
512
  metadata?: Record<string, unknown>;
458
513
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.16.0",
3
+ "version": "0.17.2",
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",