@canonmsg/core 0.15.3 → 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,5 @@
1
1
  import type { AgentRuntime, AgentSessionSnapshot, SessionConfig, SessionState } from './types.js';
2
2
  import type { TurnState } from './turn-protocol.js';
3
- import { type CanonWorkSessionContext } from './work-session.js';
4
3
  export interface BuildAgentSessionSnapshotInput {
5
4
  conversationId: string;
6
5
  agentId: string;
@@ -8,8 +7,6 @@ export interface BuildAgentSessionSnapshotInput {
8
7
  sessionConfig?: SessionConfig | null;
9
8
  sessionState?: SessionState | null;
10
9
  turnState?: TurnState | null;
11
- workSession?: CanonWorkSessionContext | null;
12
- workSessions?: CanonWorkSessionContext[] | null;
13
10
  lastHeartbeatAt?: number | null;
14
11
  updatedAt?: number | null;
15
12
  }
@@ -1,4 +1,3 @@
1
- import { mergeWorkSessionContexts, } from './work-session.js';
2
1
  function listDescriptorControls(runtime) {
3
2
  const descriptor = runtime?.runtimeDescriptor;
4
3
  return [
@@ -16,20 +15,6 @@ function descriptorDefaultValue(runtime, controlId) {
16
15
  .find((candidate) => candidate.id === controlId);
17
16
  return control?.defaultValue ?? undefined;
18
17
  }
19
- function buildWorkSessionSummary(workSession, workSessions) {
20
- const merged = mergeWorkSessionContexts(workSession, workSessions);
21
- const primary = merged[0];
22
- if (!primary?.id) {
23
- return null;
24
- }
25
- return {
26
- count: merged.length,
27
- id: primary.id,
28
- title: primary.title ?? null,
29
- objective: primary.objective ?? null,
30
- status: primary.status ?? null,
31
- };
32
- }
33
18
  function setControlState(controlState, id, value, source, appliedAt) {
34
19
  if (value === undefined)
35
20
  return;
@@ -154,7 +139,6 @@ export function buildAgentSessionSnapshot(input) {
154
139
  lastHeartbeatAt: input.lastHeartbeatAt
155
140
  ?? input.runtime?.updatedAt
156
141
  ?? input.sessionState?.updatedAt,
157
- workSession: buildWorkSessionSummary(input.workSession, input.workSessions),
158
142
  updatedAt: input.updatedAt
159
143
  ?? input.turnState?.updatedAt
160
144
  ?? input.sessionState?.updatedAt
package/dist/browser.d.ts CHANGED
@@ -1,11 +1,11 @@
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, AgentSessionWorkSessionSummary, 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, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, 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
- export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
8
- export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
7
+ export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
8
+ export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
9
9
  export { buildAgentSessionSnapshot } from './agent-session.js';
10
10
  export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
11
11
  export type { AgentBehaviorSettings, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationStyle, ResolvedAgentBehaviorPolicy, } from './policy.js';
package/dist/browser.js CHANGED
@@ -2,7 +2,7 @@ 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
4
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
5
- export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
5
+ export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
6
6
  export { buildAgentSessionSnapshot } from './agent-session.js';
7
7
  export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, 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';
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type CanonMessage, type CanonConversation, type CanonContact, type CanonContactRequest, type CanonMessagesPage, type CanonResolveAdmissionResult, type AgentContext, type AddMemberResult, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
2
- import type { CanonResolvedWorkSession, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions } from './work-session.js';
2
+ import type { SendContextualMessageOptions, SendContextualMessageResult } from './self-context.js';
3
3
  import type { InboundDisposition } from './turn-protocol.js';
4
4
  /**
5
5
  * Thin REST client for Canon's agent API.
@@ -25,10 +25,7 @@ export declare class CanonClient {
25
25
  sendMessage(conversationId: string, text: string, options?: SendMessageOptions): Promise<{
26
26
  messageId: string;
27
27
  }>;
28
- createWorkSession(options: CreateWorkSessionOptions): Promise<CanonResolvedWorkSession>;
29
- getWorkSession(workSessionId: string, conversationId: string): Promise<CanonResolvedWorkSession>;
30
- upsertWorkSessionConversation(workSessionId: string, conversationId: string, options?: UpdateWorkSessionConversationOptions): Promise<CanonResolvedWorkSession>;
31
- sendLinkedMessage(options: SendLinkedMessageOptions): Promise<SendLinkedMessageResult>;
28
+ sendContextualMessage(options: SendContextualMessageOptions): Promise<SendContextualMessageResult>;
32
29
  createConversation(options: CreateConversationOptions): Promise<{
33
30
  conversationId: string;
34
31
  }>;
package/dist/client.js CHANGED
@@ -82,8 +82,8 @@ export class CanonClient {
82
82
  throw new CanonApiError(res.status, await res.text());
83
83
  return res.json();
84
84
  }
85
- async createWorkSession(options) {
86
- const res = await fetch(`${this.baseUrl}/work-sessions`, {
85
+ async sendContextualMessage(options) {
86
+ const res = await fetch(`${this.baseUrl}/messages/send-contextual`, {
87
87
  method: 'POST',
88
88
  headers: this.authHeaders(),
89
89
  body: JSON.stringify(options),
@@ -92,44 +92,6 @@ export class CanonClient {
92
92
  throw new CanonApiError(res.status, await res.text());
93
93
  return res.json();
94
94
  }
95
- async getWorkSession(workSessionId, conversationId) {
96
- const params = new URLSearchParams({ conversationId });
97
- const res = await fetch(`${this.baseUrl}/work-sessions/${workSessionId}?${params}`, { headers: this.authHeaders() });
98
- if (!res.ok)
99
- throw new CanonApiError(res.status, await res.text());
100
- return res.json();
101
- }
102
- async upsertWorkSessionConversation(workSessionId, conversationId, options) {
103
- const res = await fetch(`${this.baseUrl}/work-sessions/${workSessionId}/conversations/${conversationId}`, {
104
- method: 'PUT',
105
- headers: this.authHeaders(),
106
- body: JSON.stringify(options ?? {}),
107
- });
108
- if (!res.ok)
109
- throw new CanonApiError(res.status, await res.text());
110
- return res.json();
111
- }
112
- async sendLinkedMessage(options) {
113
- const res = await fetch(`${this.baseUrl}/messages/send-linked`, {
114
- method: 'POST',
115
- headers: this.authHeaders(),
116
- body: JSON.stringify({
117
- sourceConversationId: options.sourceConversationId,
118
- targetConversationId: options.targetConversationId,
119
- text: options.text,
120
- ...(options.workSessionId ? { workSessionId: options.workSessionId } : {}),
121
- ...(options.createWorkSession
122
- ? { createWorkSession: options.createWorkSession }
123
- : {}),
124
- ...(options.sourceContext ? { sourceContext: options.sourceContext } : {}),
125
- ...(options.targetContext ? { targetContext: options.targetContext } : {}),
126
- ...(options.messageOptions ? { messageOptions: options.messageOptions } : {}),
127
- }),
128
- });
129
- if (!res.ok)
130
- throw new CanonApiError(res.status, await res.text());
131
- return res.json();
132
- }
133
95
  async createConversation(options) {
134
96
  const res = await fetch(`${this.baseUrl}/conversations/create`, {
135
97
  method: 'POST',
@@ -33,8 +33,7 @@ export declare function buildCanonHostPrompt(input: {
33
33
  conversationId: string;
34
34
  participantContext: HostInboundParticipantContext;
35
35
  behavior?: ResolvedAgentBehaviorPolicy | null;
36
- workSession?: MessageCreatedPayload['message']['workSession'];
37
- workSessions?: MessageCreatedPayload['workSessions'];
36
+ selfContexts?: MessageCreatedPayload['selfContexts'];
38
37
  buildInboundContextLines: (context: HostInboundParticipantContext) => string[];
39
38
  sessionContextLines?: string[];
40
39
  }): string;
@@ -68,7 +67,7 @@ export declare function buildHydratedInboundContext(input: {
68
67
  }): {
69
68
  participantContext: HostInboundParticipantContext;
70
69
  behavior?: ResolvedAgentBehaviorPolicy | null;
71
- workSessions: NonNullable<MessageCreatedPayload['workSessions']>;
70
+ selfContexts: NonNullable<MessageCreatedPayload['selfContexts']>;
72
71
  hydratedFromPage: boolean;
73
72
  };
74
73
  export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { buildAgentSessionSnapshot } from './agent-session.js';
2
2
  import { buildConversationWorktreeSpec, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, } from './execution-environment.js';
3
3
  import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, } from './policy.js';
4
- import { buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
4
+ import { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
5
5
  import { rtdbRead } from './rtdb-rest.js';
6
6
  import { createRuntimeStatePublisher } from './runtime-state-publisher.js';
7
7
  const HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES = Object.freeze({
@@ -11,18 +11,18 @@ const HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES = Object.freeze({
11
11
  canRejectPendingContactRequests: false,
12
12
  });
13
13
  export function buildCanonHostPrompt(input) {
14
- const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
14
+ const resolvedSelfContexts = normalizeSelfContexts(input.selfContexts);
15
15
  return [
16
16
  `You are connected to Canon messaging through a ${input.hostLabel} host wrapper.`,
17
17
  'Only the last assistant message from this turn will be delivered as the permanent Canon reply.',
18
18
  'Short intermediate assistant messages may be shown as ephemeral status while you work.',
19
19
  ...input.buildInboundContextLines(input.participantContext),
20
20
  ...buildBehaviorPolicyLines(input.behavior),
21
- ...buildWorkSessionsPromptLines(resolvedWorkSessions),
21
+ ...buildSelfContextPromptLines(resolvedSelfContexts),
22
22
  'Canon participants may be humans or AI agents.',
23
23
  'Honor the Canon behavior policy above when deciding how proactively to participate.',
24
- ...(resolvedWorkSessions.length > 0
25
- ? ['Honor the Canon work-session context above within its stated disclosure limits.']
24
+ ...(resolvedSelfContexts.length > 0
25
+ ? ['Honor the Canon self-context above when continuing your own cross-session action.']
26
26
  : []),
27
27
  ...(input.sessionContextLines?.length
28
28
  ? ['Canon session state:', ...input.sessionContextLines]
@@ -117,7 +117,7 @@ export function buildHydratedInboundContext(input) {
117
117
  currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
118
118
  },
119
119
  behavior: input.page?.behavior ?? input.conversation?.behavior,
120
- workSessions: input.page?.workSessions ?? [],
120
+ selfContexts: input.page?.selfContexts ?? [],
121
121
  hydratedFromPage: input.page != null,
122
122
  };
123
123
  }
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, AgentSessionWorkSessionSummary, 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
- export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CreateWorkSessionOptions, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
4
- export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.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';
3
+ export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
4
+ export { buildSelfContextPromptLines, normalizeSelfContexts, } 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';
@@ -9,7 +9,7 @@ export { buildAgentSessionSnapshot } from './agent-session.js';
9
9
  export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, 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
- export type { PolicyRole, ParticipationStyle, RepresentationMode, PermissionLevel, ConversationScope, AgentBehaviorSettings, Participant, Relationship, ContextOverlay, BehaviorProfile, AdmissionPolicy, RuntimeControlPolicy, ActionApprovalPolicy, ParticipationPolicy, WorkSession, ResolvedPolicy, ResolvedTurnEligibility, ResolvedAgentBehaviorPolicy, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationDecisionInput, ParticipationDecision, } from './policy.js';
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';
13
13
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
14
14
  export { DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
15
15
  export type { DeliveryIntent, TurnMessageSemantics, InboundDisposition, TurnLifecycleState, RuntimeCapabilities, HostAdmissionActionCapabilities, TurnState, TurnMetadata, TriggerDecision, } from './turn-protocol.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 { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
3
+ export { buildSelfContextPromptLines, normalizeSelfContexts, } 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';
package/dist/policy.d.ts CHANGED
@@ -67,16 +67,6 @@ export interface ParticipationPolicy {
67
67
  requireMentionForGroupReplies: boolean;
68
68
  maxConsecutiveAgentTurns?: number | null;
69
69
  }
70
- export interface WorkSession {
71
- id: string;
72
- conversationId?: string | null;
73
- label?: string | null;
74
- objective?: string | null;
75
- participationStyle?: ParticipationStyle;
76
- activeParticipantIds: string[];
77
- overlayIds?: string[];
78
- status: "active" | "paused" | "completed";
79
- }
80
70
  export interface ResolvedPolicy {
81
71
  admission: AdmissionPolicy;
82
72
  runtime: RuntimeControlPolicy;
@@ -90,7 +80,6 @@ export interface ResolvedPolicy {
90
80
  globalDefault?: string;
91
81
  agentDefault?: string;
92
82
  relationship?: string;
93
- workSession?: string;
94
83
  messageDirective?: string;
95
84
  };
96
85
  }
@@ -1,4 +1,4 @@
1
- import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions } from './types.js';
1
+ import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions, SessionConfig } from './types.js';
2
2
  export interface CanonReachOutClient {
3
3
  resolveAdmission(targetUserId: string): Promise<CanonResolveAdmissionResult>;
4
4
  createConversation(options: CreateConversationOptions): Promise<{
@@ -14,6 +14,7 @@ export interface CanonReachOutOptions {
14
14
  text?: string | null;
15
15
  requestMessage?: string | null;
16
16
  sendMessageOptions?: SendMessageOptions;
17
+ sessionConfig?: SessionConfig | null;
17
18
  }
18
19
  export type CanonReachOutResult = {
19
20
  status: 'messaged';
@@ -25,6 +26,9 @@ export type CanonReachOutResult = {
25
26
  } | {
26
27
  status: 'pending';
27
28
  requestId: string | null;
29
+ } | {
30
+ status: 'setup_required';
31
+ reason: string;
28
32
  } | {
29
33
  status: 'blocked' | 'unavailable';
30
34
  reason: string;
package/dist/reach-out.js CHANGED
@@ -11,6 +11,12 @@ function normalizeContactRequestMessage(requestMessage, fallbackText) {
11
11
  return text;
12
12
  return `${text.slice(0, CONTACT_REQUEST_MESSAGE_LIMIT - 3)}...`;
13
13
  }
14
+ function optionsForResolvedTarget(options, targetUserType) {
15
+ if (options.sessionConfig && targetUserType === 'human') {
16
+ return { ...options, sessionConfig: null };
17
+ }
18
+ return options;
19
+ }
14
20
  function isConnectionRequiredError(error) {
15
21
  const status = error && typeof error === 'object' && 'status' in error
16
22
  ? Number(error.status)
@@ -18,10 +24,34 @@ function isConnectionRequiredError(error) {
18
24
  const message = error instanceof Error ? error.message : String(error ?? '');
19
25
  return status === 403 && /CONNECTION_REQUIRED|connection required/i.test(message);
20
26
  }
27
+ function isSessionSetupRequiredError(error) {
28
+ const status = error && typeof error === 'object' && 'status' in error
29
+ ? Number(error.status)
30
+ : null;
31
+ if (status !== 400 && status !== 403)
32
+ return null;
33
+ const rawMessage = error instanceof Error ? error.message : String(error ?? '');
34
+ const message = rawMessage.replace(/^Canon API error \d+:\s*/i, '').trim();
35
+ const parsed = (() => {
36
+ try {
37
+ const value = JSON.parse(message);
38
+ return typeof value.error === 'string' ? value.error : message;
39
+ }
40
+ catch {
41
+ return message;
42
+ }
43
+ })();
44
+ if (/session config|execution mode|coding session|worktree|workspace|permission mode|agent owner|runtime unavailable|unsupported control|invalid .*mode|invalid .*model/i
45
+ .test(parsed)) {
46
+ return parsed || 'Agent session setup is required.';
47
+ }
48
+ return null;
49
+ }
21
50
  async function openConversationAndMaybeMessage(client, options) {
22
51
  const { conversationId } = await client.createConversation({
23
52
  type: 'direct',
24
53
  targetUserId: options.targetUserId,
54
+ ...(options.sessionConfig ? { sessionConfig: options.sessionConfig } : {}),
25
55
  });
26
56
  const text = normalizeOptionalText(options.text);
27
57
  if (text) {
@@ -35,7 +65,16 @@ async function openConversationAndMaybeMessage(client, options) {
35
65
  async function requestContact(client, options) {
36
66
  const result = await client.createContactRequest(options.targetUserId, normalizeContactRequestMessage(options.requestMessage, options.text));
37
67
  if (result.status === 'open') {
38
- return openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
68
+ try {
69
+ return await openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
70
+ }
71
+ catch (error) {
72
+ const setupReason = isSessionSetupRequiredError(error);
73
+ if (setupReason) {
74
+ return { status: 'setup_required', reason: setupReason };
75
+ }
76
+ throw error;
77
+ }
39
78
  }
40
79
  if (result.status === 'duplicate') {
41
80
  return { status: 'pending', requestId: result.requestId };
@@ -48,14 +87,19 @@ async function requestContact(client, options) {
48
87
  * their product behavior is "message if reachable, otherwise request access."
49
88
  */
50
89
  export async function reachOutToCanonContact(client, options) {
51
- const { admission } = await client.resolveAdmission(options.targetUserId);
90
+ const { target, admission } = await client.resolveAdmission(options.targetUserId);
91
+ const resolvedOptions = optionsForResolvedTarget(options, target?.userType);
52
92
  if (admission.state === 'allowed' && admission.canMessage) {
53
93
  try {
54
- return await openConversationAndMaybeMessage(client, options);
94
+ return await openConversationAndMaybeMessage(client, resolvedOptions);
55
95
  }
56
96
  catch (error) {
97
+ const setupReason = isSessionSetupRequiredError(error);
98
+ if (setupReason) {
99
+ return { status: 'setup_required', reason: setupReason };
100
+ }
57
101
  if (isConnectionRequiredError(error)) {
58
- return requestContact(client, options);
102
+ return requestContact(client, resolvedOptions);
59
103
  }
60
104
  throw error;
61
105
  }
@@ -64,7 +108,7 @@ export async function reachOutToCanonContact(client, options) {
64
108
  return { status: 'pending', requestId: admission.pendingRequestId ?? null };
65
109
  }
66
110
  if (admission.state === 'request-required' && admission.canRequestContact) {
67
- return requestContact(client, options);
111
+ return requestContact(client, resolvedOptions);
68
112
  }
69
113
  if (admission.state === 'blocked') {
70
114
  return { status: 'blocked', reason: 'blocked' };
@@ -91,13 +91,6 @@ export interface AgentSessionSnapshotPatch {
91
91
  lastHeartbeatAt?: number | {
92
92
  '.sv': 'timestamp';
93
93
  };
94
- workSession?: {
95
- count: number;
96
- id: string;
97
- title?: string | null;
98
- objective?: string | null;
99
- status?: string | null;
100
- } | null;
101
94
  updatedAt?: {
102
95
  '.sv': 'timestamp';
103
96
  };
@@ -0,0 +1,39 @@
1
+ export type CanonSelfContextType = 'cross_session';
2
+ export interface CanonSelfContext {
3
+ id: string;
4
+ type: CanonSelfContextType;
5
+ context: string;
6
+ createdAt?: string | null;
7
+ updatedAt?: string | null;
8
+ }
9
+ export interface SendContextualSelfContextInput {
10
+ type: CanonSelfContextType;
11
+ context: string;
12
+ }
13
+ export interface SendContextualMessageOptions {
14
+ sourceConversationId: string;
15
+ targetConversationId?: string;
16
+ targetUserId?: string;
17
+ text: string;
18
+ selfContext: SendContextualSelfContextInput;
19
+ sessionConfig?: import('./types.js').SessionConfig | null;
20
+ requestMessage?: string | null;
21
+ messageOptions?: Omit<import('./types.js').SendMessageOptions, 'selfContextId' | 'selfContext'>;
22
+ }
23
+ export type SendContextualMessageResult = {
24
+ status: 'messaged';
25
+ messageId: string;
26
+ conversationId: string;
27
+ selfContextId: string;
28
+ } | {
29
+ status: 'requested' | 'pending';
30
+ requestId: string | null;
31
+ } | {
32
+ status: 'setup_required' | 'blocked' | 'unavailable';
33
+ reason: string;
34
+ };
35
+ export interface SelfContextPromptRenderOptions {
36
+ maxSelfContexts?: number;
37
+ }
38
+ export declare function normalizeSelfContexts(selfContexts?: CanonSelfContext[] | null, options?: SelfContextPromptRenderOptions): CanonSelfContext[];
39
+ export declare function buildSelfContextPromptLines(selfContexts?: CanonSelfContext[] | null, options?: SelfContextPromptRenderOptions): string[];
@@ -0,0 +1,66 @@
1
+ const DEFAULT_SELF_CONTEXT_PROMPT_OPTIONS = {
2
+ maxSelfContexts: 3,
3
+ };
4
+ function normalizeString(value) {
5
+ if (typeof value !== 'string')
6
+ return null;
7
+ const trimmed = value.trim();
8
+ return trimmed.length > 0 ? trimmed : null;
9
+ }
10
+ function resolvePromptOptions(options) {
11
+ return {
12
+ maxSelfContexts: Math.max(1, options?.maxSelfContexts ?? DEFAULT_SELF_CONTEXT_PROMPT_OPTIONS.maxSelfContexts),
13
+ };
14
+ }
15
+ function contextSortTime(context) {
16
+ const updatedAt = normalizeString(context.updatedAt);
17
+ if (updatedAt) {
18
+ const parsed = new Date(updatedAt).getTime();
19
+ if (Number.isFinite(parsed))
20
+ return parsed;
21
+ }
22
+ const createdAt = normalizeString(context.createdAt);
23
+ if (createdAt) {
24
+ const parsed = new Date(createdAt).getTime();
25
+ if (Number.isFinite(parsed))
26
+ return parsed;
27
+ }
28
+ return 0;
29
+ }
30
+ export function normalizeSelfContexts(selfContexts, options) {
31
+ if (!Array.isArray(selfContexts))
32
+ return [];
33
+ const promptOptions = resolvePromptOptions(options);
34
+ const byId = new Map();
35
+ for (const selfContext of selfContexts) {
36
+ const id = normalizeString(selfContext?.id);
37
+ const context = normalizeString(selfContext?.context);
38
+ if (!id || !context || selfContext.type !== 'cross_session')
39
+ continue;
40
+ byId.set(id, {
41
+ id,
42
+ type: 'cross_session',
43
+ context,
44
+ createdAt: normalizeString(selfContext.createdAt),
45
+ updatedAt: normalizeString(selfContext.updatedAt),
46
+ });
47
+ }
48
+ return [...byId.values()]
49
+ .sort((left, right) => contextSortTime(right) - contextSortTime(left))
50
+ .slice(0, promptOptions.maxSelfContexts);
51
+ }
52
+ export function buildSelfContextPromptLines(selfContexts, options) {
53
+ const resolved = normalizeSelfContexts(selfContexts, options);
54
+ if (resolved.length === 0)
55
+ return [];
56
+ const lines = ['Canon cross-session self-context for you:'];
57
+ resolved.forEach((selfContext, index) => {
58
+ if (resolved.length > 1) {
59
+ lines.push(`Self-context ${index + 1}:`);
60
+ }
61
+ lines.push(`Context type: ${selfContext.type}.`);
62
+ lines.push(`Why you are speaking in this conversation: ${selfContext.context}`);
63
+ });
64
+ lines.push('Canon rule: this self-context explains your own prior cross-session action; it does not grant access to the source transcript. Keep Canon sessions separate.');
65
+ return lines;
66
+ }
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
2
2
  import type { ResolvedAgentBehaviorPolicy } from './policy.js';
3
3
  import type { TurnLifecycleState } from './turn-protocol.js';
4
- import type { CanonWorkSessionContext } from './work-session.js';
4
+ import type { CanonSelfContext } from './self-context.js';
5
5
  export type { ExecutionEnvironmentMode };
6
6
  export type MediaAttachmentKind = 'image' | 'audio' | 'file';
7
7
  export interface MediaAttachment {
@@ -30,6 +30,7 @@ export interface ContactCardPayload {
30
30
  displayName: string;
31
31
  avatarUrl: string | null;
32
32
  userType: 'human' | 'ai_agent';
33
+ clientType?: AgentClientType;
33
34
  about?: string;
34
35
  isActive?: boolean;
35
36
  ownerId?: string;
@@ -50,7 +51,6 @@ export interface CanonMessage {
50
51
  replyToPosition: number | null;
51
52
  forwarded?: boolean;
52
53
  forwardedFrom?: ForwardedFrom;
53
- workSession?: CanonWorkSessionContext | null;
54
54
  metadata?: Record<string, unknown>;
55
55
  contactCard?: ContactCardPayload;
56
56
  status: 'sent' | 'read';
@@ -77,7 +77,7 @@ export interface CanonConversation {
77
77
  export interface CanonMessagesPage {
78
78
  messages: CanonMessage[];
79
79
  behavior?: ResolvedAgentBehaviorPolicy;
80
- workSessions?: CanonWorkSessionContext[];
80
+ selfContexts?: CanonSelfContext[];
81
81
  }
82
82
  export type CanonContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired';
83
83
  export interface CanonContactRequest {
@@ -354,7 +354,7 @@ export interface AgentContext {
354
354
  export interface MessageCreatedPayload {
355
355
  conversationId: string;
356
356
  behavior?: ResolvedAgentBehaviorPolicy;
357
- workSessions?: CanonWorkSessionContext[];
357
+ selfContexts?: CanonSelfContext[];
358
358
  message: {
359
359
  id: string;
360
360
  senderId: string;
@@ -371,7 +371,6 @@ export interface MessageCreatedPayload {
371
371
  forwardedFrom?: ForwardedFrom;
372
372
  mentions?: string[];
373
373
  createdAt?: string;
374
- workSession?: CanonWorkSessionContext | null;
375
374
  /** Structured metadata for rich UI (approval cards, etc.) */
376
375
  metadata?: Record<string, unknown>;
377
376
  /** Populated when `contentType === 'contact_card'`. */
@@ -452,7 +451,7 @@ export interface SendMessageOptions {
452
451
  attachments?: MediaAttachment[];
453
452
  contactCardUserId?: string;
454
453
  mentions?: string[];
455
- workSessionId?: string;
454
+ selfContextId?: string;
456
455
  /** Structured metadata for rich UI (approval cards, etc.) */
457
456
  metadata?: Record<string, unknown>;
458
457
  }
@@ -461,6 +460,8 @@ export interface CreateConversationOptions {
461
460
  targetUserId?: string;
462
461
  memberIds?: string[];
463
462
  name?: string;
463
+ /** Required when creating a direct conversation with a first-party coding agent. */
464
+ sessionConfig?: SessionConfig | null;
464
465
  }
465
466
  export type StreamingStatus = 'thinking' | 'streaming' | 'tool';
466
467
  export interface SetStreamingOptions {
@@ -574,13 +575,6 @@ export interface AgentRuntime {
574
575
  availableWorkspaces?: WorkspaceOption[];
575
576
  updatedAt?: number;
576
577
  }
577
- export interface AgentSessionWorkSessionSummary {
578
- count: number;
579
- id: string;
580
- title?: string | null;
581
- objective?: string | null;
582
- status?: CanonWorkSessionContext['status'] | null;
583
- }
584
578
  export interface AgentSessionSnapshot {
585
579
  conversationId: string;
586
580
  agentId: string;
@@ -612,7 +606,6 @@ export interface AgentSessionSnapshot {
612
606
  contextUsage?: SessionState['contextUsage'];
613
607
  lastError?: string;
614
608
  lastHeartbeatAt?: number;
615
- workSession?: AgentSessionWorkSessionSummary | null;
616
609
  updatedAt?: number;
617
610
  }
618
611
  export interface RegistrationInput {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
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",
@@ -1,88 +0,0 @@
1
- import type { RepresentationMode } from './policy.js';
2
- import type { SendMessageOptions } from './types.js';
3
- export type CanonWorkSessionStatus = 'active' | 'paused' | 'completed';
4
- export type CanonWorkSessionConversationRole = 'requester' | 'coordinator' | 'participant' | 'delegate' | 'observer';
5
- export type CanonWorkSessionDisclosureMode = 'none' | 'summary' | 'full';
6
- export interface CanonWorkSessionParticipant {
7
- conversationId?: string | null;
8
- participantId?: string | null;
9
- label?: string | null;
10
- role?: CanonWorkSessionConversationRole | null;
11
- }
12
- export interface CanonWorkSessionContext {
13
- id: string;
14
- title?: string | null;
15
- objective?: string | null;
16
- status?: CanonWorkSessionStatus | null;
17
- summary?: string | null;
18
- representation?: RepresentationMode | null;
19
- currentConversationRole?: CanonWorkSessionConversationRole | null;
20
- sourceConversationId?: string | null;
21
- sourceConversationLabel?: string | null;
22
- sourceParticipantId?: string | null;
23
- sourceParticipantLabel?: string | null;
24
- disclosure?: CanonWorkSessionDisclosureMode | null;
25
- disclosureNotes?: string[];
26
- visibleFacts?: string[];
27
- pendingQuestions?: string[];
28
- participants?: CanonWorkSessionParticipant[];
29
- updatedAt?: string | null;
30
- }
31
- export interface CanonWorkSession {
32
- id: string;
33
- title?: string | null;
34
- objective?: string | null;
35
- status: CanonWorkSessionStatus;
36
- createdAt?: string | null;
37
- updatedAt?: string | null;
38
- }
39
- export interface UpdateWorkSessionConversationOptions {
40
- summary?: string | null;
41
- representation?: RepresentationMode | null;
42
- currentConversationRole?: CanonWorkSessionConversationRole | null;
43
- sourceConversationId?: string | null;
44
- sourceConversationLabel?: string | null;
45
- sourceParticipantId?: string | null;
46
- sourceParticipantLabel?: string | null;
47
- disclosure?: CanonWorkSessionDisclosureMode | null;
48
- disclosureNotes?: string[];
49
- visibleFacts?: string[];
50
- pendingQuestions?: string[];
51
- participants?: CanonWorkSessionParticipant[];
52
- }
53
- export interface CreateWorkSessionOptions extends UpdateWorkSessionConversationOptions {
54
- conversationId: string;
55
- title?: string | null;
56
- objective?: string | null;
57
- status?: CanonWorkSessionStatus | null;
58
- }
59
- export interface CanonResolvedWorkSession {
60
- workSession: CanonWorkSession;
61
- context: CanonWorkSessionContext;
62
- }
63
- export interface SendLinkedMessageOptions {
64
- sourceConversationId: string;
65
- targetConversationId: string;
66
- text: string;
67
- workSessionId?: string;
68
- createWorkSession?: Omit<CreateWorkSessionOptions, 'conversationId'>;
69
- sourceContext?: UpdateWorkSessionConversationOptions;
70
- targetContext?: UpdateWorkSessionConversationOptions;
71
- messageOptions?: Omit<SendMessageOptions, 'workSessionId'>;
72
- }
73
- export interface SendLinkedMessageResult {
74
- messageId: string;
75
- workSessionId: string;
76
- workSessionCreated: boolean;
77
- }
78
- export interface WorkSessionPromptRenderOptions {
79
- maxVisibleFacts?: number;
80
- maxPendingQuestions?: number;
81
- maxDisclosureNotes?: number;
82
- maxParticipants?: number;
83
- maxDetailedWorkSessions?: number;
84
- maxOtherWorkSessions?: number;
85
- }
86
- export declare function mergeWorkSessionContexts(explicitWorkSession?: CanonWorkSessionContext | null, activeWorkSessions?: CanonWorkSessionContext[] | null): CanonWorkSessionContext[];
87
- export declare function buildWorkSessionPromptLines(workSession?: CanonWorkSessionContext | null, options?: WorkSessionPromptRenderOptions): string[];
88
- export declare function buildWorkSessionsPromptLines(workSessions?: CanonWorkSessionContext[] | null, options?: WorkSessionPromptRenderOptions): string[];
@@ -1,238 +0,0 @@
1
- const DEFAULT_WORK_SESSION_PROMPT_OPTIONS = {
2
- maxVisibleFacts: 3,
3
- maxPendingQuestions: 2,
4
- maxDisclosureNotes: 1,
5
- maxParticipants: 3,
6
- maxDetailedWorkSessions: 1,
7
- maxOtherWorkSessions: 3,
8
- };
9
- function normalizeString(value) {
10
- if (typeof value !== 'string')
11
- return null;
12
- const trimmed = value.trim();
13
- return trimmed.length > 0 ? trimmed : null;
14
- }
15
- function normalizeStringList(value) {
16
- if (!Array.isArray(value))
17
- return [];
18
- return value
19
- .map((entry) => normalizeString(entry))
20
- .filter((entry) => entry != null);
21
- }
22
- function mergeStringLists(primary, secondary) {
23
- const merged = [...normalizeStringList(primary), ...normalizeStringList(secondary)];
24
- const unique = Array.from(new Set(merged));
25
- return unique.length > 0 ? unique : undefined;
26
- }
27
- function mergeParticipants(primary, secondary) {
28
- const result = [];
29
- const seen = new Set();
30
- const pushParticipant = (participant) => {
31
- if (!participant)
32
- return;
33
- const key = [
34
- normalizeString(participant.conversationId),
35
- normalizeString(participant.participantId),
36
- normalizeString(participant.label),
37
- normalizeString(participant.role),
38
- ].join('::');
39
- if (seen.has(key))
40
- return;
41
- seen.add(key);
42
- result.push(participant);
43
- };
44
- for (const participant of primary ?? [])
45
- pushParticipant(participant);
46
- for (const participant of secondary ?? [])
47
- pushParticipant(participant);
48
- return result.length > 0 ? result : undefined;
49
- }
50
- function mergeTwoWorkSessions(primary, secondary) {
51
- const disclosureNotes = mergeStringLists(primary.disclosureNotes, secondary.disclosureNotes);
52
- const visibleFacts = mergeStringLists(primary.visibleFacts, secondary.visibleFacts);
53
- const pendingQuestions = mergeStringLists(primary.pendingQuestions, secondary.pendingQuestions);
54
- const participants = mergeParticipants(primary.participants, secondary.participants);
55
- return {
56
- id: primary.id,
57
- title: primary.title ?? secondary.title ?? null,
58
- objective: primary.objective ?? secondary.objective ?? null,
59
- status: primary.status ?? secondary.status ?? null,
60
- summary: primary.summary ?? secondary.summary ?? null,
61
- representation: primary.representation ?? secondary.representation ?? null,
62
- currentConversationRole: primary.currentConversationRole ?? secondary.currentConversationRole ?? null,
63
- sourceConversationId: primary.sourceConversationId ?? secondary.sourceConversationId ?? null,
64
- sourceConversationLabel: primary.sourceConversationLabel ?? secondary.sourceConversationLabel ?? null,
65
- sourceParticipantId: primary.sourceParticipantId ?? secondary.sourceParticipantId ?? null,
66
- sourceParticipantLabel: primary.sourceParticipantLabel ?? secondary.sourceParticipantLabel ?? null,
67
- disclosure: primary.disclosure ?? secondary.disclosure ?? null,
68
- ...(disclosureNotes ? { disclosureNotes } : {}),
69
- ...(visibleFacts ? { visibleFacts } : {}),
70
- ...(pendingQuestions ? { pendingQuestions } : {}),
71
- ...(participants ? { participants } : {}),
72
- updatedAt: primary.updatedAt ?? secondary.updatedAt ?? null,
73
- };
74
- }
75
- export function mergeWorkSessionContexts(explicitWorkSession, activeWorkSessions) {
76
- const merged = new Map();
77
- const add = (workSession) => {
78
- if (!workSession?.id)
79
- return;
80
- const existing = merged.get(workSession.id);
81
- merged.set(workSession.id, existing ? mergeTwoWorkSessions(existing, workSession) : workSession);
82
- };
83
- add(explicitWorkSession);
84
- for (const workSession of activeWorkSessions ?? []) {
85
- add(workSession);
86
- }
87
- return Array.from(merged.values());
88
- }
89
- function formatParticipantLabel(participant) {
90
- const label = normalizeString(participant.label);
91
- if (label)
92
- return label;
93
- const participantId = normalizeString(participant.participantId);
94
- if (participantId)
95
- return participantId;
96
- return normalizeString(participant.conversationId);
97
- }
98
- function resolvePromptOptions(options) {
99
- return {
100
- maxVisibleFacts: Math.max(0, options?.maxVisibleFacts
101
- ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxVisibleFacts),
102
- maxPendingQuestions: Math.max(0, options?.maxPendingQuestions
103
- ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxPendingQuestions),
104
- maxDisclosureNotes: Math.max(0, options?.maxDisclosureNotes
105
- ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxDisclosureNotes),
106
- maxParticipants: Math.max(0, options?.maxParticipants ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxParticipants),
107
- maxDetailedWorkSessions: Math.max(1, options?.maxDetailedWorkSessions
108
- ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxDetailedWorkSessions),
109
- maxOtherWorkSessions: Math.max(0, options?.maxOtherWorkSessions
110
- ?? DEFAULT_WORK_SESSION_PROMPT_OPTIONS.maxOtherWorkSessions),
111
- };
112
- }
113
- function takeWithHiddenCount(values, maxCount) {
114
- if (maxCount <= 0) {
115
- return { visible: [], hiddenCount: values.length };
116
- }
117
- return {
118
- visible: values.slice(0, maxCount),
119
- hiddenCount: Math.max(0, values.length - maxCount),
120
- };
121
- }
122
- function buildOriginLabel(workSession) {
123
- const sourceConversation = normalizeString(workSession.sourceConversationLabel)
124
- ?? normalizeString(workSession.sourceConversationId);
125
- const sourceParticipant = normalizeString(workSession.sourceParticipantLabel)
126
- ?? normalizeString(workSession.sourceParticipantId);
127
- if (sourceConversation && sourceParticipant) {
128
- return `${sourceParticipant} in ${sourceConversation}`;
129
- }
130
- return sourceParticipant ?? sourceConversation;
131
- }
132
- function formatWorkSessionReference(workSession) {
133
- const title = normalizeString(workSession.title);
134
- const objective = normalizeString(workSession.objective);
135
- if (title)
136
- return `${title} (${workSession.id})`;
137
- if (objective)
138
- return `${objective} (${workSession.id})`;
139
- return workSession.id;
140
- }
141
- export function buildWorkSessionPromptLines(workSession, options) {
142
- if (!workSession)
143
- return [];
144
- const promptOptions = resolvePromptOptions(options);
145
- const title = normalizeString(workSession.title);
146
- const objective = normalizeString(workSession.objective);
147
- const summary = normalizeString(workSession.summary);
148
- const origin = buildOriginLabel(workSession);
149
- const visibleFacts = takeWithHiddenCount(normalizeStringList(workSession.visibleFacts), promptOptions.maxVisibleFacts);
150
- const pendingQuestions = takeWithHiddenCount(normalizeStringList(workSession.pendingQuestions), promptOptions.maxPendingQuestions);
151
- const disclosureNotes = takeWithHiddenCount(normalizeStringList(workSession.disclosureNotes), promptOptions.maxDisclosureNotes);
152
- const participants = takeWithHiddenCount(Array.isArray(workSession.participants) ? workSession.participants : [], promptOptions.maxParticipants);
153
- return [
154
- `Canon work session ID: ${workSession.id}`,
155
- ...(title ? [`Canon work session title: ${title}`] : []),
156
- ...(objective ? [`Canon work session objective: ${objective}`] : []),
157
- ...(workSession.status
158
- ? [`Canon work session status: ${workSession.status}`]
159
- : []),
160
- ...(workSession.currentConversationRole
161
- ? [
162
- `This conversation's role in the work session: ${workSession.currentConversationRole}`,
163
- ]
164
- : []),
165
- ...(workSession.representation
166
- ? [`Participate in this conversation as: ${workSession.representation}`]
167
- : []),
168
- ...(origin ? [`Work session origin: ${origin}`] : []),
169
- ...(workSession.disclosure
170
- ? [`Allowed disclosure level in this conversation: ${workSession.disclosure}`]
171
- : []),
172
- ...(summary ? [`Shared work session summary: ${summary}`] : []),
173
- ...visibleFacts.visible.map((fact, index) => `Shared fact ${index + 1}: ${fact}`),
174
- ...(visibleFacts.hiddenCount > 0
175
- ? [`Additional shared facts omitted for brevity: ${visibleFacts.hiddenCount}`]
176
- : []),
177
- ...pendingQuestions.visible.map((question, index) => `Open question ${index + 1}: ${question}`),
178
- ...(pendingQuestions.hiddenCount > 0
179
- ? [
180
- `Additional open questions omitted for brevity: ${pendingQuestions.hiddenCount}`,
181
- ]
182
- : []),
183
- ...disclosureNotes.visible.map((note, index) => `Disclosure note ${index + 1}: ${note}`),
184
- ...(disclosureNotes.hiddenCount > 0
185
- ? [
186
- `Additional disclosure notes omitted for brevity: ${disclosureNotes.hiddenCount}`,
187
- ]
188
- : []),
189
- ...participants.visible.flatMap((participant, index) => {
190
- const label = formatParticipantLabel(participant);
191
- if (!label)
192
- return [];
193
- const role = normalizeString(participant.role);
194
- return [
195
- `Related participant ${index + 1}: ${label}${role ? ` (${role})` : ''}`,
196
- ];
197
- }),
198
- ...(participants.hiddenCount > 0
199
- ? [
200
- `Additional related participants omitted for brevity: ${participants.hiddenCount}`,
201
- ]
202
- : []),
203
- 'Canon rule: this shared work-session context is scoped to this conversation only and does not grant access to other conversation transcripts.',
204
- ];
205
- }
206
- export function buildWorkSessionsPromptLines(workSessions, options) {
207
- const promptOptions = resolvePromptOptions(options);
208
- if (!Array.isArray(workSessions) || workSessions.length === 0)
209
- return [];
210
- if (workSessions.length === 1) {
211
- return [
212
- 'Canon active work session for this conversation:',
213
- ...buildWorkSessionPromptLines(workSessions[0], promptOptions),
214
- ];
215
- }
216
- const detailedWorkSessions = workSessions.slice(0, promptOptions.maxDetailedWorkSessions);
217
- const hiddenWorkSessions = workSessions.slice(promptOptions.maxDetailedWorkSessions);
218
- const lines = ['Canon active work sessions for this conversation:'];
219
- detailedWorkSessions.forEach((workSession, index) => {
220
- lines.push(detailedWorkSessions.length === 1
221
- ? 'Primary active work session:'
222
- : `Detailed work session ${index + 1}:`);
223
- lines.push(...buildWorkSessionPromptLines(workSession, promptOptions));
224
- });
225
- if (hiddenWorkSessions.length > 0) {
226
- lines.push('Other active work sessions linked here:');
227
- hiddenWorkSessions
228
- .slice(0, promptOptions.maxOtherWorkSessions)
229
- .forEach((workSession, index) => {
230
- lines.push(`Other work session ${index + 1}: ${formatWorkSessionReference(workSession)}`);
231
- });
232
- if (hiddenWorkSessions.length > promptOptions.maxOtherWorkSessions) {
233
- lines.push(`Additional active work sessions omitted for brevity: ${hiddenWorkSessions.length - promptOptions.maxOtherWorkSessions}`);
234
- }
235
- lines.push('Canon rule: do not assume the current message refers to another linked work session unless the conversation clearly points to it.');
236
- }
237
- return lines;
238
- }