@canonmsg/core 0.7.3 → 0.7.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.
@@ -0,0 +1,16 @@
1
+ import type { AgentRuntime, AgentSessionSnapshot, SessionConfig, SessionState } from './types.js';
2
+ import type { TurnState } from './turn-protocol.js';
3
+ import { type CanonWorkSessionContext } from './work-session.js';
4
+ export interface BuildAgentSessionSnapshotInput {
5
+ conversationId: string;
6
+ agentId: string;
7
+ runtime?: AgentRuntime | null;
8
+ sessionConfig?: SessionConfig | null;
9
+ sessionState?: SessionState | null;
10
+ turnState?: TurnState | null;
11
+ workSession?: CanonWorkSessionContext | null;
12
+ workSessions?: CanonWorkSessionContext[] | null;
13
+ lastHeartbeatAt?: number | null;
14
+ updatedAt?: number | null;
15
+ }
16
+ export declare function buildAgentSessionSnapshot(input: BuildAgentSessionSnapshotInput): AgentSessionSnapshot;
@@ -0,0 +1,69 @@
1
+ import { mergeWorkSessionContexts, } from './work-session.js';
2
+ function buildWorkSessionSummary(workSession, workSessions) {
3
+ const merged = mergeWorkSessionContexts(workSession, workSessions);
4
+ const primary = merged[0];
5
+ if (!primary?.id) {
6
+ return null;
7
+ }
8
+ return {
9
+ count: merged.length,
10
+ id: primary.id,
11
+ title: primary.title ?? null,
12
+ objective: primary.objective ?? null,
13
+ status: primary.status ?? null,
14
+ };
15
+ }
16
+ export function buildAgentSessionSnapshot(input) {
17
+ const modelOptions = input.sessionState?.availableModels
18
+ ?? input.sessionConfig?.availableModels
19
+ ?? input.runtime?.availableModels
20
+ ?? [];
21
+ const workspaceOptions = input.sessionConfig?.workspaceOptions
22
+ ?? input.runtime?.availableWorkspaces
23
+ ?? [];
24
+ return {
25
+ conversationId: input.conversationId,
26
+ agentId: input.agentId,
27
+ clientType: input.sessionConfig?.clientType
28
+ ?? input.sessionState?.clientType
29
+ ?? input.runtime?.clientType,
30
+ hostMode: Boolean(input.sessionState?.hostMode
31
+ ?? input.sessionConfig?.hostMode
32
+ ?? input.runtime?.hostMode),
33
+ model: input.sessionState?.model
34
+ ?? input.sessionConfig?.model
35
+ ?? input.runtime?.defaultModel
36
+ ?? modelOptions[0]?.value,
37
+ modelOptions,
38
+ permissionMode: input.sessionState?.permissionMode
39
+ ?? input.sessionConfig?.permissionMode
40
+ ?? input.runtime?.defaultPermissionMode,
41
+ permissionModeOptions: input.runtime?.availablePermissionModes ?? [],
42
+ workspaceId: input.sessionConfig?.workspaceId
43
+ ?? input.runtime?.defaultWorkspaceId
44
+ ?? workspaceOptions[0]?.id,
45
+ workspaceOptions,
46
+ executionMode: input.sessionState?.executionMode
47
+ ?? input.sessionConfig?.executionMode,
48
+ availableExecutionModes: input.sessionConfig?.availableExecutionModes
49
+ ?? input.runtime?.availableExecutionModes
50
+ ?? [],
51
+ executionBranch: input.sessionState?.executionBranch,
52
+ state: input.sessionState?.state,
53
+ turnState: input.turnState?.state,
54
+ queueDepth: input.turnState?.queueDepth ?? 0,
55
+ waitingForInput: input.turnState?.state === 'waiting_input'
56
+ || input.sessionState?.state === 'requires_action',
57
+ contextUsage: input.sessionState?.contextUsage,
58
+ lastError: input.sessionState?.lastError,
59
+ lastHeartbeatAt: input.lastHeartbeatAt
60
+ ?? input.runtime?.updatedAt
61
+ ?? input.sessionState?.updatedAt,
62
+ workSession: buildWorkSessionSummary(input.workSession, input.workSessions),
63
+ updatedAt: input.updatedAt
64
+ ?? input.turnState?.updatedAt
65
+ ?? input.sessionState?.updatedAt
66
+ ?? input.sessionConfig?.updatedAt
67
+ ?? input.runtime?.updatedAt,
68
+ };
69
+ }
package/dist/browser.d.ts CHANGED
@@ -1,12 +1,17 @@
1
- export { AGENT_CAPABILITIES, } from './types.js';
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 { AgentCapabilities, AgentClientType, AgentRuntime, MediaAttachment, MediaAttachmentKind, ModelOption, SessionConfig, WorkspaceOption, } from './types.js';
4
+ export type { AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentSessionWorkSessionSummary, AgentRuntime, CanonContactRequest, CanonContactRequestStatus, ContactApprovedPayload, ContactRequestPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, RuntimeUpdatedPayload, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, } 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 { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
8
8
  export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
9
+ export { buildAgentSessionSnapshot } from './agent-session.js';
9
10
  export type { AgentBehaviorSettings, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationStyle, ResolvedAgentBehaviorPolicy, } from './policy.js';
10
11
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
11
12
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
12
13
  export type { DeliveryIntent, InboundDisposition, RuntimeCapabilities, TriggerDecision, TurnLifecycleState, TurnMessageSemantics, TurnMetadata, TurnState, } from './turn-protocol.js';
14
+ export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
15
+ export type { ApprovalRequestMetadata, ApprovalReplyMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
16
+ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
17
+ export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
package/dist/browser.js CHANGED
@@ -1,7 +1,10 @@
1
- export { AGENT_CAPABILITIES, } from './types.js';
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
4
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
5
5
  export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
6
+ export { buildAgentSessionSnapshot } from './agent-session.js';
6
7
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
7
8
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
9
+ export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
10
+ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type CanonMessage, type CanonConversation, type CanonMessagesPage, type AgentContext, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
1
+ import { type CanonMessage, type CanonConversation, type CanonContactRequest, type CanonMessagesPage, type AgentContext, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
2
2
  import type { CanonResolvedWorkSession, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions } from './work-session.js';
3
3
  import type { InboundDisposition } from './turn-protocol.js';
4
4
  /**
@@ -29,6 +29,8 @@ export declare class CanonClient {
29
29
  createConversation(options: CreateConversationOptions): Promise<{
30
30
  conversationId: string;
31
31
  }>;
32
+ createContactRequest(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
33
+ listContactRequests(): Promise<CanonContactRequest[]>;
32
34
  uploadMedia(conversationId: string, data: string, mimeType: string, fileName?: string): Promise<{
33
35
  url: string;
34
36
  attachment: MediaAttachment;
package/dist/client.js CHANGED
@@ -131,6 +131,42 @@ export class CanonClient {
131
131
  throw new CanonApiError(res.status, await res.text());
132
132
  return res.json();
133
133
  }
134
+ async createContactRequest(targetUserId, message) {
135
+ const res = await fetch(`${this.baseUrl}/contacts/request`, {
136
+ method: 'POST',
137
+ headers: this.authHeaders(),
138
+ body: JSON.stringify({
139
+ targetUserId,
140
+ ...(message !== undefined ? { message } : {}),
141
+ }),
142
+ });
143
+ const body = await res.text();
144
+ if (res.status === 200) {
145
+ return { status: 'open', requestId: null };
146
+ }
147
+ if (res.status === 201) {
148
+ const data = parseJsonBody(body);
149
+ return { status: 'created', requestId: data.requestId };
150
+ }
151
+ if (res.status === 409) {
152
+ const data = parseJsonBody(body);
153
+ if (typeof data.requestId === 'string' && data.requestId.length > 0) {
154
+ return { status: 'duplicate', requestId: data.requestId };
155
+ }
156
+ }
157
+ if (!res.ok)
158
+ throw new CanonApiError(res.status, body);
159
+ throw new CanonApiError(res.status, body || 'Unexpected contact-request response');
160
+ }
161
+ async listContactRequests() {
162
+ const res = await fetch(`${this.baseUrl}/contacts/requests`, {
163
+ headers: this.authHeaders(),
164
+ });
165
+ if (!res.ok)
166
+ throw new CanonApiError(res.status, await res.text());
167
+ const data = await res.json();
168
+ return data.requests;
169
+ }
134
170
  async uploadMedia(conversationId, data, mimeType, fileName) {
135
171
  const res = await fetch(`${this.baseUrl}/media/upload`, {
136
172
  method: 'POST',
@@ -273,3 +309,13 @@ export class CanonApiError extends Error {
273
309
  this.status = status;
274
310
  }
275
311
  }
312
+ function parseJsonBody(body) {
313
+ if (!body)
314
+ return {};
315
+ try {
316
+ return JSON.parse(body);
317
+ }
318
+ catch {
319
+ return {};
320
+ }
321
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export { AGENT_CAPABILITIES, } from './types.js';
2
- export type { AgentCapabilities, AgentClientType, CanonMessage, CanonConversation, CanonMessagesPage, AgentContext, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, ModelOption, WorkspaceOption, } from './types.js';
1
+ export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
2
+ export type { AgentCapabilities, AgentClientType, CanonContactRequest, CanonContactRequestStatus, ContactApprovedPayload, ContactRequestPayload, 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, ModelOption, PermissionModeOption, WorkspaceOption, } from './types.js';
3
3
  export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CreateWorkSessionOptions, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
4
4
  export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
5
5
  export { CanonClient, CanonApiError } from './client.js';
6
+ export { buildAgentSessionSnapshot } from './agent-session.js';
6
7
  export { CanonStream } from './stream.js';
7
8
  export type { StreamHandler } from './stream.js';
8
9
  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';
@@ -14,6 +15,8 @@ export { ApprovalManager } from './approval-manager.js';
14
15
  export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
15
16
  export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
16
17
  export type { ApprovalRequestMetadata, ApprovalReplyMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
18
+ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
19
+ export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
17
20
  export { createStreamingHelper } from './streaming.js';
18
21
  export type { RTDBHandle, RTDBRef, ServerTimestamp, StreamingHelperOptions, StreamingNode } from './streaming.js';
19
22
  export { loadProfiles, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
@@ -22,7 +25,7 @@ export { resolveCanonAgent, resolveCanonProfile, getActiveProfile } from './agen
22
25
  export type { ResolvedAgent } from './agent-resolver.js';
23
26
  export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
24
27
  export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecutionEnvironment, SessionWorkspaceConfig, } from './execution-environment.js';
25
- export { initRTDBAuth, rtdbWrite, rtdbRead, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
26
- export type { SessionStatePayload, TurnStatePayload } from './rtdb-rest.js';
28
+ export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
29
+ export type { AgentSessionSnapshotPatch, SessionStatePayload, TurnStatePayload, } from './rtdb-rest.js';
27
30
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
28
31
  export { resolveCanonBaseUrl } from './base-url.js';
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // Types
2
- export { AGENT_CAPABILITIES, } from './types.js';
2
+ export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
3
3
  export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
4
4
  // Client
5
5
  export { CanonClient, CanonApiError } from './client.js';
6
+ export { buildAgentSessionSnapshot } from './agent-session.js';
6
7
  // Stream
7
8
  export { CanonStream } from './stream.js';
8
9
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
@@ -14,6 +15,7 @@ export { registerAndWaitForApproval } from './registration.js';
14
15
  export { ApprovalManager } from './approval-manager.js';
15
16
  export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
16
17
  export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
18
+ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
17
19
  // Streaming (RTDB helpers)
18
20
  export { createStreamingHelper } from './streaming.js';
19
21
  // Agent profiles (loading, locking, resolution)
@@ -23,7 +25,7 @@ export { resolveCanonAgent, resolveCanonProfile, getActiveProfile } from './agen
23
25
  // Execution environments for host-mode coding sessions
24
26
  export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
25
27
  // RTDB REST helpers (token exchange, session state, generic read/write)
26
- export { initRTDBAuth, rtdbWrite, rtdbRead, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
28
+ export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
27
29
  // Constants
28
30
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
29
31
  // Base URL resolver
@@ -50,6 +50,51 @@ export interface TurnStatePayload {
50
50
  '.sv': 'timestamp';
51
51
  };
52
52
  }
53
+ export interface AgentSessionSnapshotPatch {
54
+ clientType?: string;
55
+ hostMode?: boolean;
56
+ model?: string | null;
57
+ modelOptions?: Array<{
58
+ value: string;
59
+ label: string;
60
+ }> | null;
61
+ permissionMode?: string | null;
62
+ permissionModeOptions?: Array<{
63
+ value: string;
64
+ label: string;
65
+ }> | null;
66
+ workspaceId?: string | null;
67
+ workspaceOptions?: Array<{
68
+ id: string;
69
+ label: string;
70
+ }> | null;
71
+ executionMode?: 'worktree' | 'locked' | null;
72
+ availableExecutionModes?: Array<'worktree' | 'locked'> | null;
73
+ executionBranch?: string | null;
74
+ state?: 'idle' | 'running' | 'requires_action' | null;
75
+ turnState?: TurnLifecycleState | null;
76
+ queueDepth?: number;
77
+ waitingForInput?: boolean;
78
+ contextUsage?: {
79
+ percentage: number;
80
+ totalTokens: number;
81
+ maxTokens: number;
82
+ } | null;
83
+ lastError?: string | null;
84
+ lastHeartbeatAt?: number | {
85
+ '.sv': 'timestamp';
86
+ };
87
+ workSession?: {
88
+ count: number;
89
+ id: string;
90
+ title?: string | null;
91
+ objective?: string | null;
92
+ status?: string | null;
93
+ } | null;
94
+ updatedAt?: {
95
+ '.sv': 'timestamp';
96
+ };
97
+ }
53
98
  interface RTDBAuthOptions {
54
99
  rtdbUrl?: string;
55
100
  firebaseApiKey?: string;
@@ -63,6 +108,7 @@ interface RTDBClientHandle {
63
108
  clearSessionState(conversationId: string, agentId: string): Promise<void>;
64
109
  writeTurnState(conversationId: string, agentId: string, state: Omit<TurnStatePayload, 'updatedAt'>): Promise<void>;
65
110
  clearTurnState(conversationId: string, agentId: string): Promise<void>;
111
+ patchAgentSessionSnapshot(conversationId: string, agentId: string, snapshot: Omit<AgentSessionSnapshotPatch, 'updatedAt'>): Promise<void>;
66
112
  }
67
113
  /**
68
114
  * Initializes the default RTDB helper and returns a scoped client for callers
@@ -84,4 +130,5 @@ export declare function writeSessionState(conversationId: string, agentId: strin
84
130
  export declare function clearSessionState(conversationId: string, agentId: string): Promise<void>;
85
131
  export declare function writeTurnState(conversationId: string, agentId: string, state: Omit<TurnStatePayload, 'updatedAt'>): Promise<void>;
86
132
  export declare function clearTurnState(conversationId: string, agentId: string): Promise<void>;
133
+ export declare function patchAgentSessionSnapshot(conversationId: string, agentId: string, snapshot: Omit<AgentSessionSnapshotPatch, 'updatedAt'>): Promise<void>;
87
134
  export {};
package/dist/rtdb-rest.js CHANGED
@@ -15,6 +15,9 @@ function normalizeRTDBBase(url) {
15
15
  function normalizeRTDBPath(path) {
16
16
  return path.startsWith('/') ? path : `/${path}`;
17
17
  }
18
+ function buildAgentSessionPath(conversationId, agentId) {
19
+ return `/agent-session/${conversationId}/${agentId}`;
20
+ }
18
21
  function createRTDBClientHandle(client, options) {
19
22
  const rtdbBase = normalizeRTDBBase(options?.rtdbUrl || DEFAULT_RTDB_BASE);
20
23
  const firebaseApiKey = options?.firebaseApiKey || DEFAULT_FIREBASE_API_KEY;
@@ -129,6 +132,21 @@ function createRTDBClientHandle(client, options) {
129
132
  ...state,
130
133
  updatedAt: { '.sv': 'timestamp' },
131
134
  });
135
+ await patch(buildAgentSessionPath(conversationId, agentId), {
136
+ ...(state.clientType ? { clientType: state.clientType } : {}),
137
+ ...(typeof state.hostMode === 'boolean' ? { hostMode: state.hostMode } : {}),
138
+ ...(state.model !== undefined ? { model: state.model } : {}),
139
+ ...(state.permissionMode !== undefined ? { permissionMode: state.permissionMode } : {}),
140
+ ...(state.availableModels !== undefined ? { modelOptions: state.availableModels } : {}),
141
+ ...(state.executionMode !== undefined ? { executionMode: state.executionMode } : {}),
142
+ ...(state.executionBranch !== undefined ? { executionBranch: state.executionBranch } : {}),
143
+ ...(state.state !== undefined ? { state: state.state } : {}),
144
+ waitingForInput: state.state === 'requires_action',
145
+ ...(state.contextUsage !== undefined ? { contextUsage: state.contextUsage } : {}),
146
+ ...(state.lastError !== undefined ? { lastError: state.lastError } : {}),
147
+ lastHeartbeatAt: { '.sv': 'timestamp' },
148
+ updatedAt: { '.sv': 'timestamp' },
149
+ });
132
150
  }
133
151
  async function clearSessionStateImpl(conversationId, agentId) {
134
152
  try {
@@ -136,6 +154,14 @@ function createRTDBClientHandle(client, options) {
136
154
  isActive: false,
137
155
  updatedAt: { '.sv': 'timestamp' },
138
156
  });
157
+ await patch(buildAgentSessionPath(conversationId, agentId), {
158
+ state: 'idle',
159
+ waitingForInput: false,
160
+ lastError: null,
161
+ executionBranch: null,
162
+ contextUsage: null,
163
+ updatedAt: { '.sv': 'timestamp' },
164
+ });
139
165
  }
140
166
  catch (error) {
141
167
  console.error('[canon] RTDB clear failed:', error);
@@ -146,6 +172,13 @@ function createRTDBClientHandle(client, options) {
146
172
  ...state,
147
173
  updatedAt: { '.sv': 'timestamp' },
148
174
  });
175
+ await patch(buildAgentSessionPath(conversationId, agentId), {
176
+ turnState: state.state,
177
+ queueDepth: state.queueDepth,
178
+ waitingForInput: state.state === 'waiting_input',
179
+ lastHeartbeatAt: { '.sv': 'timestamp' },
180
+ updatedAt: { '.sv': 'timestamp' },
181
+ });
149
182
  }
150
183
  async function clearTurnStateImpl(conversationId, agentId) {
151
184
  try {
@@ -155,11 +188,23 @@ function createRTDBClientHandle(client, options) {
155
188
  updatedAt: { '.sv': 'timestamp' },
156
189
  completedAt: { '.sv': 'timestamp' },
157
190
  });
191
+ await patch(buildAgentSessionPath(conversationId, agentId), {
192
+ turnState: 'idle',
193
+ queueDepth: 0,
194
+ waitingForInput: false,
195
+ updatedAt: { '.sv': 'timestamp' },
196
+ });
158
197
  }
159
198
  catch (error) {
160
199
  console.error('[canon] RTDB turn clear failed:', error);
161
200
  }
162
201
  }
202
+ async function patchAgentSessionSnapshotImpl(conversationId, agentId, snapshot) {
203
+ await patch(buildAgentSessionPath(conversationId, agentId), {
204
+ ...snapshot,
205
+ updatedAt: { '.sv': 'timestamp' },
206
+ });
207
+ }
163
208
  return {
164
209
  read,
165
210
  write,
@@ -169,6 +214,7 @@ function createRTDBClientHandle(client, options) {
169
214
  clearSessionState: clearSessionStateImpl,
170
215
  writeTurnState: writeTurnStateImpl,
171
216
  clearTurnState: clearTurnStateImpl,
217
+ patchAgentSessionSnapshot: patchAgentSessionSnapshotImpl,
172
218
  };
173
219
  }
174
220
  /**
@@ -211,3 +257,6 @@ export async function writeTurnState(conversationId, agentId, state) {
211
257
  export async function clearTurnState(conversationId, agentId) {
212
258
  await getDefaultRTDBClient()?.clearTurnState(conversationId, agentId);
213
259
  }
260
+ export async function patchAgentSessionSnapshot(conversationId, agentId, snapshot) {
261
+ await getDefaultRTDBClient()?.patchAgentSessionSnapshot(conversationId, agentId, snapshot);
262
+ }
@@ -0,0 +1,46 @@
1
+ export interface RuntimeQuestionOption {
2
+ label: string;
3
+ description?: string;
4
+ }
5
+ export interface RuntimeQuestionDefinition {
6
+ question: string;
7
+ header?: string;
8
+ options?: RuntimeQuestionOption[];
9
+ multiSelect?: boolean;
10
+ }
11
+ export interface ClaudeQuestionMetadata {
12
+ type: 'claude_question';
13
+ questionId: string;
14
+ questions: RuntimeQuestionDefinition[];
15
+ }
16
+ export interface ClaudeQuestionReplyMetadata {
17
+ type: 'question_reply';
18
+ questionId: string;
19
+ answers: Record<string, string>;
20
+ }
21
+ export interface PlanApprovalMetadata {
22
+ type: 'plan_approval';
23
+ planId: string;
24
+ }
25
+ export interface PlanApprovalReplyMetadata {
26
+ type: 'plan_approval_reply';
27
+ planId: string;
28
+ decision: 'approve' | 'revise';
29
+ feedback?: string;
30
+ }
31
+ export declare function buildQuestionRequest(questionId: string, questions: RuntimeQuestionDefinition[]): {
32
+ text: string;
33
+ metadata: ClaudeQuestionMetadata;
34
+ };
35
+ export declare function buildQuestionReply(questionId: string, answers: Record<string, string>): {
36
+ text: string;
37
+ metadata: ClaudeQuestionReplyMetadata;
38
+ };
39
+ export declare function buildPlanApprovalRequest(planId: string, text?: string): {
40
+ text: string;
41
+ metadata: PlanApprovalMetadata;
42
+ };
43
+ export declare function buildPlanApprovalReply(planId: string, decision: 'approve' | 'revise', feedback?: string): {
44
+ text: string;
45
+ metadata: PlanApprovalReplyMetadata;
46
+ };
@@ -0,0 +1,44 @@
1
+ export function buildQuestionRequest(questionId, questions) {
2
+ const text = questions.length === 1
3
+ ? questions[0]?.question || 'Question'
4
+ : `Please answer ${questions.length} questions.`;
5
+ return {
6
+ text,
7
+ metadata: {
8
+ type: 'claude_question',
9
+ questionId,
10
+ questions,
11
+ },
12
+ };
13
+ }
14
+ export function buildQuestionReply(questionId, answers) {
15
+ const text = Object.values(answers).filter(Boolean).join(', ') || 'Submitted';
16
+ return {
17
+ text,
18
+ metadata: {
19
+ type: 'question_reply',
20
+ questionId,
21
+ answers,
22
+ },
23
+ };
24
+ }
25
+ export function buildPlanApprovalRequest(planId, text = 'Plan ready for review.') {
26
+ return {
27
+ text,
28
+ metadata: {
29
+ type: 'plan_approval',
30
+ planId,
31
+ },
32
+ };
33
+ }
34
+ export function buildPlanApprovalReply(planId, decision, feedback) {
35
+ return {
36
+ text: decision === 'approve' ? 'Plan approved' : 'Requesting changes',
37
+ metadata: {
38
+ type: 'plan_approval_reply',
39
+ planId,
40
+ decision,
41
+ ...(feedback ? { feedback } : {}),
42
+ },
43
+ };
44
+ }
package/dist/stream.d.ts CHANGED
@@ -1,9 +1,13 @@
1
- import type { AgentContext, MessageCreatedPayload, TypingPayload, PresencePayload } from './types.js';
1
+ import type { AgentContext, ContactApprovedPayload, ContactRequestPayload, 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;
5
+ onContactRequest?: (payload: ContactRequestPayload) => void;
6
+ onContactApproved?: (payload: ContactApprovedPayload) => void;
5
7
  onTyping?: (data: TypingPayload) => void;
6
8
  onPresence?: (data: PresencePayload) => void;
9
+ onRuntimeUpdated?: (payload: RuntimeUpdatedPayload) => void;
10
+ onTurnUpdated?: (payload: TurnUpdatedPayload) => void;
7
11
  onMessageDeleted?: (payload: {
8
12
  conversationId: string;
9
13
  messageId: string;
@@ -48,6 +52,10 @@ export declare class CanonStream {
48
52
  private handleMessageCreated;
49
53
  private handleTyping;
50
54
  private handlePresence;
55
+ private handleContactRequest;
56
+ private handleContactApproved;
57
+ private handleRuntimeUpdated;
58
+ private handleTurnUpdated;
51
59
  private handleMessageDeleted;
52
60
  private handleConversationUpdated;
53
61
  private scheduleReconnect;
package/dist/stream.js CHANGED
@@ -140,6 +140,14 @@ export class CanonStream {
140
140
  this.reconnectAttempt = 0;
141
141
  this.handleMessageCreated(data);
142
142
  break;
143
+ case 'contact.request':
144
+ this.reconnectAttempt = 0;
145
+ this.handleContactRequest(data);
146
+ break;
147
+ case 'contact.approved':
148
+ this.reconnectAttempt = 0;
149
+ this.handleContactApproved(data);
150
+ break;
143
151
  case 'typing':
144
152
  this.reconnectAttempt = 0;
145
153
  this.handleTyping(data);
@@ -148,6 +156,14 @@ export class CanonStream {
148
156
  this.reconnectAttempt = 0;
149
157
  this.handlePresence(data);
150
158
  break;
159
+ case 'runtime.updated':
160
+ this.reconnectAttempt = 0;
161
+ this.handleRuntimeUpdated(data);
162
+ break;
163
+ case 'turn.updated':
164
+ this.reconnectAttempt = 0;
165
+ this.handleTurnUpdated(data);
166
+ break;
151
167
  case 'message.deleted':
152
168
  this.reconnectAttempt = 0;
153
169
  this.handleMessageDeleted(data);
@@ -161,6 +177,7 @@ export class CanonStream {
161
177
  break;
162
178
  case 'replay.expired':
163
179
  this.reconnectAttempt = 0;
180
+ this.lastEventId = null;
164
181
  this.handler.onError?.(new Error('Replay expired — some messages may have been missed'));
165
182
  break;
166
183
  case 'error':
@@ -210,6 +227,42 @@ export class CanonStream {
210
227
  // Ignore parse errors for non-critical events
211
228
  }
212
229
  }
230
+ handleContactRequest(raw) {
231
+ try {
232
+ const data = JSON.parse(raw);
233
+ this.handler.onContactRequest?.(data);
234
+ }
235
+ catch {
236
+ // Ignore parse errors for non-critical events
237
+ }
238
+ }
239
+ handleContactApproved(raw) {
240
+ try {
241
+ const data = JSON.parse(raw);
242
+ this.handler.onContactApproved?.(data);
243
+ }
244
+ catch {
245
+ // Ignore parse errors for non-critical events
246
+ }
247
+ }
248
+ handleRuntimeUpdated(raw) {
249
+ try {
250
+ const data = JSON.parse(raw);
251
+ this.handler.onRuntimeUpdated?.(data);
252
+ }
253
+ catch {
254
+ // Ignore parse errors for non-critical events
255
+ }
256
+ }
257
+ handleTurnUpdated(raw) {
258
+ try {
259
+ const data = JSON.parse(raw);
260
+ this.handler.onTurnUpdated?.(data);
261
+ }
262
+ catch {
263
+ // Ignore parse errors for non-critical events
264
+ }
265
+ }
213
266
  handleMessageDeleted(raw) {
214
267
  try {
215
268
  const data = JSON.parse(raw);
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
2
2
  import type { ResolvedAgentBehaviorPolicy } from './policy.js';
3
+ import type { TurnLifecycleState } from './turn-protocol.js';
3
4
  import type { CanonWorkSessionContext } from './work-session.js';
4
5
  export type { ExecutionEnvironmentMode };
5
6
  export type MediaAttachmentKind = 'image' | 'audio' | 'file';
@@ -79,7 +80,39 @@ export interface CanonMessagesPage {
79
80
  behavior?: ResolvedAgentBehaviorPolicy;
80
81
  workSessions?: CanonWorkSessionContext[];
81
82
  }
83
+ export type CanonContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired';
84
+ export interface CanonContactRequest {
85
+ id: string;
86
+ requesterId: string;
87
+ requesterName: string;
88
+ requesterAvatarUrl: string | null;
89
+ targetId: string;
90
+ approverId?: string | null;
91
+ message: string | null;
92
+ status: CanonContactRequestStatus;
93
+ createdAt: string | null;
94
+ resolvedAt?: string | null;
95
+ expiresAt?: string | null;
96
+ }
97
+ export type ContactRequestPayload = CanonContactRequest;
98
+ export type ContactApprovedPayload = CanonContactRequest;
99
+ export type CreateContactRequestResult = {
100
+ status: 'open';
101
+ requestId: null;
102
+ } | {
103
+ status: 'created';
104
+ requestId: string;
105
+ } | {
106
+ status: 'duplicate';
107
+ requestId: string;
108
+ };
82
109
  export type AgentClientType = 'claude-code' | 'openclaw' | 'codex' | 'generic';
110
+ export interface ResolvedAdmission {
111
+ accessLevel: 'open' | 'owner-only';
112
+ discoverable: boolean;
113
+ inboundPolicy: 'open' | 'approval-required' | 'private';
114
+ groupJoinPolicy: 'open' | 'approval-required' | 'private';
115
+ }
83
116
  /** Declares what session controls an agent type supports. */
84
117
  export interface AgentCapabilities {
85
118
  supportsModelSwitch: boolean;
@@ -156,6 +189,55 @@ export interface PresencePayload {
156
189
  userId: string;
157
190
  online: boolean;
158
191
  }
192
+ export interface RuntimeUpdatedPayload {
193
+ conversationId: string;
194
+ agentId: string;
195
+ runtime: AgentRuntime | null;
196
+ sessionState: SessionState | null;
197
+ sessionConfig: SessionConfig | null;
198
+ }
199
+ export interface TurnUpdatedPayload {
200
+ conversationId: string;
201
+ agentId: string;
202
+ turn: import('./turn-protocol.js').TurnState | null;
203
+ }
204
+ export type CanonStreamEvent = {
205
+ type: 'agent.context';
206
+ payload: AgentContext;
207
+ } | {
208
+ type: 'message.created';
209
+ payload: MessageCreatedPayload;
210
+ } | {
211
+ type: 'contact.request';
212
+ payload: ContactRequestPayload;
213
+ } | {
214
+ type: 'contact.approved';
215
+ payload: ContactApprovedPayload;
216
+ } | {
217
+ type: 'typing';
218
+ payload: TypingPayload;
219
+ } | {
220
+ type: 'presence';
221
+ payload: PresencePayload;
222
+ } | {
223
+ type: 'runtime.updated';
224
+ payload: RuntimeUpdatedPayload;
225
+ } | {
226
+ type: 'turn.updated';
227
+ payload: TurnUpdatedPayload;
228
+ } | {
229
+ type: 'message.deleted';
230
+ payload: {
231
+ conversationId: string;
232
+ messageId: string;
233
+ };
234
+ } | {
235
+ type: 'conversation.updated';
236
+ payload: {
237
+ conversationId: string;
238
+ changes: Record<string, unknown>;
239
+ };
240
+ };
159
241
  export interface SendMessageOptions {
160
242
  contentType?: 'text' | 'audio' | 'image' | 'file' | 'contact_card';
161
243
  replyTo?: string;
@@ -197,6 +279,7 @@ export interface SessionState {
197
279
  cwd?: string;
198
280
  executionMode?: 'worktree' | 'locked';
199
281
  executionBranch?: string;
282
+ clientType?: AgentClientType;
200
283
  /** True when the agent is running under the host wrapper (host.ts) which can apply control signals */
201
284
  hostMode?: boolean;
202
285
  isActive: boolean;
@@ -230,6 +313,22 @@ export interface PermissionModeOption {
230
313
  value: string;
231
314
  label: string;
232
315
  }
316
+ export declare const CLAUDE_PERMISSION_MODE_OPTIONS: readonly [{
317
+ readonly value: "default";
318
+ readonly label: "Default";
319
+ }, {
320
+ readonly value: "acceptEdits";
321
+ readonly label: "Auto-edit";
322
+ }, {
323
+ readonly value: "plan";
324
+ readonly label: "Plan";
325
+ }, {
326
+ readonly value: "bypassPermissions";
327
+ readonly label: "Bypass";
328
+ }, {
329
+ readonly value: "auto";
330
+ readonly label: "Auto";
331
+ }];
233
332
  export interface AgentRuntime {
234
333
  clientType?: AgentClientType;
235
334
  hostMode?: boolean;
@@ -252,6 +351,37 @@ export interface AgentRuntime {
252
351
  availableWorkspaces?: WorkspaceOption[];
253
352
  updatedAt?: number;
254
353
  }
354
+ export interface AgentSessionWorkSessionSummary {
355
+ count: number;
356
+ id: string;
357
+ title?: string | null;
358
+ objective?: string | null;
359
+ status?: CanonWorkSessionContext['status'] | null;
360
+ }
361
+ export interface AgentSessionSnapshot {
362
+ conversationId: string;
363
+ agentId: string;
364
+ clientType?: AgentClientType;
365
+ hostMode?: boolean;
366
+ model?: string;
367
+ modelOptions?: ModelOption[];
368
+ permissionMode?: string;
369
+ permissionModeOptions?: PermissionModeOption[];
370
+ workspaceId?: string;
371
+ workspaceOptions?: WorkspaceOption[];
372
+ executionMode?: ExecutionEnvironmentMode;
373
+ availableExecutionModes?: ExecutionEnvironmentMode[];
374
+ executionBranch?: string;
375
+ state?: SessionState['state'];
376
+ turnState?: TurnLifecycleState;
377
+ queueDepth: number;
378
+ waitingForInput: boolean;
379
+ contextUsage?: SessionState['contextUsage'];
380
+ lastError?: string;
381
+ lastHeartbeatAt?: number;
382
+ workSession?: AgentSessionWorkSessionSummary | null;
383
+ updatedAt?: number;
384
+ }
255
385
  export interface RegistrationInput {
256
386
  name: string;
257
387
  description: string;
package/dist/types.js CHANGED
@@ -32,3 +32,10 @@ export const AGENT_CAPABILITIES = {
32
32
  'openclaw': { ...DEFAULT_CAPABILITIES },
33
33
  'generic': { ...DEFAULT_CAPABILITIES },
34
34
  };
35
+ export const CLAUDE_PERMISSION_MODE_OPTIONS = [
36
+ { value: 'default', label: 'Default' },
37
+ { value: 'acceptEdits', label: 'Auto-edit' },
38
+ { value: 'plan', label: 'Plan' },
39
+ { value: 'bypassPermissions', label: 'Bypass' },
40
+ { value: 'auto', label: 'Auto' },
41
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.7.3",
3
+ "version": "0.7.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",