@canonmsg/core 0.15.1 → 0.15.3

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.
@@ -14,7 +14,7 @@ function descriptorOptions(runtime, controlId) {
14
14
  function descriptorDefaultValue(runtime, controlId) {
15
15
  const control = listDescriptorControls(runtime)
16
16
  .find((candidate) => candidate.id === controlId);
17
- return control?.defaultValue ?? control?.options?.[0]?.value ?? undefined;
17
+ return control?.defaultValue ?? undefined;
18
18
  }
19
19
  function buildWorkSessionSummary(workSession, workSessions) {
20
20
  const merged = mergeWorkSessionContexts(workSession, workSessions);
@@ -30,6 +30,70 @@ function buildWorkSessionSummary(workSession, workSessions) {
30
30
  status: primary.status ?? null,
31
31
  };
32
32
  }
33
+ function setControlState(controlState, id, value, source, appliedAt) {
34
+ if (value === undefined)
35
+ return;
36
+ controlState[id] = {
37
+ value,
38
+ source,
39
+ ...(source === 'applied' && appliedAt ? { appliedAt } : {}),
40
+ };
41
+ }
42
+ function buildControlState(input) {
43
+ const controlState = {};
44
+ const appliedAt = input.sessionState?.updatedAt ?? input.lastHeartbeatAt ?? input.updatedAt ?? undefined;
45
+ const modelDefault = descriptorDefaultValue(input.runtime, 'model');
46
+ if (input.sessionState?.model !== undefined) {
47
+ setControlState(controlState, 'model', input.sessionState.model, 'applied', appliedAt);
48
+ }
49
+ else if (input.sessionConfig?.model !== undefined) {
50
+ setControlState(controlState, 'model', input.sessionConfig.model, 'requested');
51
+ }
52
+ else if (modelDefault !== undefined) {
53
+ setControlState(controlState, 'model', modelDefault, 'host-default');
54
+ }
55
+ const permissionDefault = descriptorDefaultValue(input.runtime, 'permissionMode');
56
+ if (input.sessionState?.permissionMode !== undefined) {
57
+ setControlState(controlState, 'permissionMode', input.sessionState.permissionMode, 'applied', appliedAt);
58
+ }
59
+ else if (input.sessionConfig?.permissionMode !== undefined) {
60
+ setControlState(controlState, 'permissionMode', input.sessionConfig.permissionMode, 'requested');
61
+ }
62
+ else if (permissionDefault !== undefined) {
63
+ setControlState(controlState, 'permissionMode', permissionDefault, 'host-default');
64
+ }
65
+ const effortDefault = descriptorDefaultValue(input.runtime, 'effort');
66
+ if (input.sessionState?.effort !== undefined) {
67
+ setControlState(controlState, 'effort', input.sessionState.effort, 'applied', appliedAt);
68
+ }
69
+ else if (input.sessionConfig?.effort !== undefined) {
70
+ setControlState(controlState, 'effort', input.sessionConfig.effort, 'requested');
71
+ }
72
+ else if (effortDefault !== undefined) {
73
+ setControlState(controlState, 'effort', effortDefault, 'host-default');
74
+ }
75
+ const workspaceDefault = descriptorDefaultValue(input.runtime, 'workspace');
76
+ if (input.sessionConfig?.workspaceId !== undefined) {
77
+ setControlState(controlState, 'workspace', input.sessionConfig.workspaceId, 'requested');
78
+ }
79
+ else if (workspaceDefault !== undefined) {
80
+ setControlState(controlState, 'workspace', workspaceDefault, 'host-default');
81
+ }
82
+ if (input.sessionState?.executionMode !== undefined) {
83
+ setControlState(controlState, 'executionMode', input.sessionState.executionMode, 'applied', appliedAt);
84
+ }
85
+ else if (input.sessionConfig?.executionMode !== undefined) {
86
+ setControlState(controlState, 'executionMode', input.sessionConfig.executionMode, 'requested');
87
+ }
88
+ for (const [id, value] of Object.entries(input.sessionConfig?.runtimeControlValues ?? {})) {
89
+ setControlState(controlState, id, value, 'requested');
90
+ }
91
+ for (const [id, value] of Object.entries(input.sessionState?.runtimeControlValues ?? {})) {
92
+ setControlState(controlState, id, value, 'applied', appliedAt);
93
+ }
94
+ Object.assign(controlState, input.sessionState?.controlState ?? {});
95
+ return Object.keys(controlState).length > 0 ? controlState : undefined;
96
+ }
33
97
  export function buildAgentSessionSnapshot(input) {
34
98
  const modelOptions = input.sessionState?.availableModels?.length
35
99
  ? input.sessionState.availableModels
@@ -39,6 +103,22 @@ export function buildAgentSessionSnapshot(input) {
39
103
  const availableExecutionModes = descriptorOptions(input.runtime, 'executionMode')
40
104
  .map((option) => option.value)
41
105
  .filter((value) => value === 'worktree' || value === 'locked');
106
+ const model = input.sessionState?.model
107
+ ?? input.sessionConfig?.model
108
+ ?? descriptorDefaultValue(input.runtime, 'model');
109
+ const permissionMode = input.sessionState?.permissionMode
110
+ ?? input.sessionConfig?.permissionMode
111
+ ?? descriptorDefaultValue(input.runtime, 'permissionMode');
112
+ const effort = input.sessionState?.effort
113
+ ?? input.sessionConfig?.effort
114
+ ?? descriptorDefaultValue(input.runtime, 'effort');
115
+ const runtimeControlValues = input.sessionState?.runtimeControlValues
116
+ ?? input.sessionConfig?.runtimeControlValues;
117
+ const workspaceId = input.sessionConfig?.workspaceId
118
+ ?? descriptorDefaultValue(input.runtime, 'workspace');
119
+ const executionMode = input.sessionState?.executionMode
120
+ ?? input.sessionConfig?.executionMode;
121
+ const controlState = buildControlState(input);
42
122
  return {
43
123
  conversationId: input.conversationId,
44
124
  agentId: input.agentId,
@@ -48,23 +128,17 @@ export function buildAgentSessionSnapshot(input) {
48
128
  hostMode: Boolean(input.sessionState?.hostMode
49
129
  ?? input.sessionConfig?.hostMode
50
130
  ?? input.runtime?.hostMode),
51
- model: input.sessionState?.model
52
- ?? input.sessionConfig?.model
53
- ?? descriptorDefaultValue(input.runtime, 'model'),
131
+ model,
54
132
  modelOptions,
55
- permissionMode: input.sessionState?.permissionMode
56
- ?? input.sessionConfig?.permissionMode
57
- ?? descriptorDefaultValue(input.runtime, 'permissionMode'),
133
+ permissionMode,
58
134
  permissionModeOptions: descriptorOptions(input.runtime, 'permissionMode'),
59
- effort: input.sessionState?.effort ?? input.sessionConfig?.effort,
60
- runtimeControlValues: input.sessionState?.runtimeControlValues
61
- ?? input.sessionConfig?.runtimeControlValues,
135
+ effort,
136
+ runtimeControlValues,
137
+ controlState,
62
138
  runtimeControlErrors: input.sessionState?.runtimeControlErrors ?? undefined,
63
- workspaceId: input.sessionConfig?.workspaceId
64
- ?? descriptorDefaultValue(input.runtime, 'workspace'),
139
+ workspaceId,
65
140
  workspaceOptions,
66
- executionMode: input.sessionState?.executionMode
67
- ?? input.sessionConfig?.executionMode,
141
+ executionMode,
68
142
  availableExecutionModes,
69
143
  executionBranch: input.sessionState?.executionBranch,
70
144
  resolvedWorkspaceLabel: undefined,
package/dist/browser.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
2
2
  export { resolveCanonBaseUrl } from './base-url.js';
3
3
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
4
- export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, 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, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, RuntimeControlError, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.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';
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';
@@ -82,7 +82,7 @@ function describeContactCard(card) {
82
82
  ? 'reject pending requests'
83
83
  : null,
84
84
  ].filter(Boolean).join(', ');
85
- const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
85
+ const hint = `This host can inspect the card, but this wrapper does not expose callable Canon admission actions yet. Missing capabilities here: ${missingCapabilities}. Use an SDK/OpenClaw reach-out surface or another Canon client for userId ${card.userId}.`;
86
86
  return `${identity}\n${hint}`;
87
87
  }
88
88
  function describeAttachment(attachment, materialized) {
@@ -183,6 +183,7 @@ export async function publishHostSessionSnapshots(input) {
183
183
  permissionMode: snapshot.permissionMode ?? null,
184
184
  effort: snapshot.effort ?? null,
185
185
  runtimeControlValues: snapshot.runtimeControlValues ?? null,
186
+ controlState: snapshot.controlState ?? null,
186
187
  workspaceId: snapshot.workspaceId ?? null,
187
188
  executionMode: snapshot.executionMode ?? null,
188
189
  executionBranch,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, WorkspaceOption, WorkspaceOptionSource, } 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
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 { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
@@ -37,6 +37,8 @@ export type { HostInboundParticipantContext, } from './host-runtime.js';
37
37
  export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
38
38
  export type { RuntimeStatePublisher, RuntimeStatePublisherOptions, RuntimeStreamingPayload, } from './runtime-state-publisher.js';
39
39
  export { formatCanonMessageAsText } from './message-format.js';
40
+ export { reachOutToCanonContact } from './reach-out.js';
41
+ export type { CanonReachOutClient, CanonReachOutOptions, CanonReachOutResult, } from './reach-out.js';
40
42
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
41
43
  export { resolveCanonBaseUrl } from './base-url.js';
42
44
  export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from './cli-metadata.js';
package/dist/index.js CHANGED
@@ -35,6 +35,8 @@ export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMe
35
35
  export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
36
36
  // Message formatting (LLM-facing text projection)
37
37
  export { formatCanonMessageAsText } from './message-format.js';
38
+ // Admission-aware reach-out helpers for runtime clients
39
+ export { reachOutToCanonContact } from './reach-out.js';
38
40
  // Constants
39
41
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
40
42
  // Base URL resolver
@@ -10,11 +10,12 @@
10
10
  * regression in every consumer plugin.
11
11
  */
12
12
  export function formatCanonMessageAsText(message) {
13
+ const trimmedText = typeof message.text === 'string' ? message.text.trim() : '';
13
14
  if (message.contentType === 'contact_card' && message.contactCard) {
14
- return formatContactCard(message.contactCard);
15
+ const cardText = formatContactCard(message.contactCard);
16
+ return trimmedText ? `${cardText}\n${trimmedText}` : cardText;
15
17
  }
16
18
  const attachment = pickPrimaryAttachment(message.attachments);
17
- const trimmedText = typeof message.text === 'string' ? message.text.trim() : '';
18
19
  if (attachment?.kind === 'image') {
19
20
  return trimmedText ? `[image] ${trimmedText}` : '[image]';
20
21
  }
@@ -0,0 +1,37 @@
1
+ import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions } from './types.js';
2
+ export interface CanonReachOutClient {
3
+ resolveAdmission(targetUserId: string): Promise<CanonResolveAdmissionResult>;
4
+ createConversation(options: CreateConversationOptions): Promise<{
5
+ conversationId: string;
6
+ }>;
7
+ sendMessage(conversationId: string, text: string, options?: SendMessageOptions): Promise<{
8
+ messageId: string;
9
+ }>;
10
+ createContactRequest(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
11
+ }
12
+ export interface CanonReachOutOptions {
13
+ targetUserId: string;
14
+ text?: string | null;
15
+ requestMessage?: string | null;
16
+ sendMessageOptions?: SendMessageOptions;
17
+ }
18
+ export type CanonReachOutResult = {
19
+ status: 'messaged';
20
+ conversationId: string;
21
+ messageId?: string;
22
+ } | {
23
+ status: 'requested';
24
+ requestId: string | null;
25
+ } | {
26
+ status: 'pending';
27
+ requestId: string | null;
28
+ } | {
29
+ status: 'blocked' | 'unavailable';
30
+ reason: string;
31
+ };
32
+ /**
33
+ * Admission-aware direct reach-out for runtime clients. It deliberately keeps
34
+ * backend conversation creation fail-closed; callers choose this helper when
35
+ * their product behavior is "message if reachable, otherwise request access."
36
+ */
37
+ export declare function reachOutToCanonContact(client: CanonReachOutClient, options: CanonReachOutOptions): Promise<CanonReachOutResult>;
@@ -0,0 +1,73 @@
1
+ const CONTACT_REQUEST_MESSAGE_LIMIT = 500;
2
+ function normalizeOptionalText(value) {
3
+ const trimmed = typeof value === 'string' ? value.trim() : '';
4
+ return trimmed.length > 0 ? trimmed : null;
5
+ }
6
+ function normalizeContactRequestMessage(requestMessage, fallbackText) {
7
+ const text = normalizeOptionalText(requestMessage) ?? normalizeOptionalText(fallbackText);
8
+ if (!text)
9
+ return null;
10
+ if (text.length <= CONTACT_REQUEST_MESSAGE_LIMIT)
11
+ return text;
12
+ return `${text.slice(0, CONTACT_REQUEST_MESSAGE_LIMIT - 3)}...`;
13
+ }
14
+ function isConnectionRequiredError(error) {
15
+ const status = error && typeof error === 'object' && 'status' in error
16
+ ? Number(error.status)
17
+ : null;
18
+ const message = error instanceof Error ? error.message : String(error ?? '');
19
+ return status === 403 && /CONNECTION_REQUIRED|connection required/i.test(message);
20
+ }
21
+ async function openConversationAndMaybeMessage(client, options) {
22
+ const { conversationId } = await client.createConversation({
23
+ type: 'direct',
24
+ targetUserId: options.targetUserId,
25
+ });
26
+ const text = normalizeOptionalText(options.text);
27
+ if (text) {
28
+ const { messageId } = options.sendMessageOptions
29
+ ? await client.sendMessage(conversationId, text, options.sendMessageOptions)
30
+ : await client.sendMessage(conversationId, text);
31
+ return { status: 'messaged', conversationId, messageId };
32
+ }
33
+ return { status: 'messaged', conversationId };
34
+ }
35
+ async function requestContact(client, options) {
36
+ const result = await client.createContactRequest(options.targetUserId, normalizeContactRequestMessage(options.requestMessage, options.text));
37
+ if (result.status === 'open') {
38
+ return openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
39
+ }
40
+ if (result.status === 'duplicate') {
41
+ return { status: 'pending', requestId: result.requestId };
42
+ }
43
+ return { status: 'requested', requestId: result.requestId };
44
+ }
45
+ /**
46
+ * Admission-aware direct reach-out for runtime clients. It deliberately keeps
47
+ * backend conversation creation fail-closed; callers choose this helper when
48
+ * their product behavior is "message if reachable, otherwise request access."
49
+ */
50
+ export async function reachOutToCanonContact(client, options) {
51
+ const { admission } = await client.resolveAdmission(options.targetUserId);
52
+ if (admission.state === 'allowed' && admission.canMessage) {
53
+ try {
54
+ return await openConversationAndMaybeMessage(client, options);
55
+ }
56
+ catch (error) {
57
+ if (isConnectionRequiredError(error)) {
58
+ return requestContact(client, options);
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ if (admission.state === 'pending-outbound') {
64
+ return { status: 'pending', requestId: admission.pendingRequestId ?? null };
65
+ }
66
+ if (admission.state === 'request-required' && admission.canRequestContact) {
67
+ return requestContact(client, options);
68
+ }
69
+ if (admission.state === 'blocked') {
70
+ return { status: 'blocked', reason: 'blocked' };
71
+ }
72
+ return { status: 'unavailable', reason: admission.state };
73
+ }
@@ -7,13 +7,14 @@
7
7
  */
8
8
  import type { CanonClient } from './client.js';
9
9
  import type { DeliveryIntent, RuntimeCapabilities, TurnLifecycleState } from './turn-protocol.js';
10
- import type { CanonControlValue, ModelOption, RuntimeControlError, RuntimeInfoPayload } from './types.js';
10
+ import type { CanonControlValue, ModelOption, RuntimeControlError, RuntimeControlState, RuntimeInfoPayload } from './types.js';
11
11
  export interface SessionStatePayload {
12
12
  lastError?: string;
13
13
  model?: string;
14
14
  permissionMode?: string;
15
15
  effort?: string;
16
16
  runtimeControlValues?: Record<string, CanonControlValue>;
17
+ controlState?: Record<string, RuntimeControlState>;
17
18
  runtimeControlErrors?: Record<string, RuntimeControlError> | null;
18
19
  cwd?: string;
19
20
  executionMode?: 'worktree' | 'locked';
@@ -63,6 +64,7 @@ export interface AgentSessionSnapshotPatch {
63
64
  }> | null;
64
65
  effort?: string | null;
65
66
  runtimeControlValues?: Record<string, CanonControlValue> | null;
67
+ controlState?: Record<string, RuntimeControlState> | null;
66
68
  runtimeControlErrors?: Record<string, RuntimeControlError> | null;
67
69
  workspaceId?: string | null;
68
70
  workspaceOptions?: Array<{
package/dist/rtdb-rest.js CHANGED
@@ -144,6 +144,9 @@ function createRTDBClientHandle(client, options) {
144
144
  ...(state.runtimeControlValues !== undefined
145
145
  ? { runtimeControlValues: state.runtimeControlValues }
146
146
  : {}),
147
+ ...(state.controlState !== undefined
148
+ ? { controlState: state.controlState }
149
+ : {}),
147
150
  ...(state.runtimeControlErrors !== undefined
148
151
  ? { runtimeControlErrors: state.runtimeControlErrors }
149
152
  : {}),
@@ -173,6 +176,7 @@ function createRTDBClientHandle(client, options) {
173
176
  state: null,
174
177
  waitingForInput: false,
175
178
  lastError: null,
179
+ controlState: null,
176
180
  executionBranch: null,
177
181
  contextUsage: null,
178
182
  worktreePath: null,
package/dist/types.d.ts CHANGED
@@ -21,9 +21,9 @@ export interface ForwardedFrom {
21
21
  /**
22
22
  * Server-serialized contact-card payload. Emitted on messages with
23
23
  * `contentType: 'contact_card'` so agents receive the referenced user's
24
- * identity alongside the card. Agents use the referenced `userId` with the
25
- * standard send-message path; if the target's inbound policy blocks cold
26
- * contact, they first send a contact request and retry after approval.
24
+ * identity alongside the card. Agents use the referenced `userId` with an
25
+ * admission-aware reach-out path; if the target's inbound policy blocks cold
26
+ * contact, they create a contact request and retry after approval.
27
27
  */
28
28
  export interface ContactCardPayload {
29
29
  userId: string;
@@ -212,11 +212,18 @@ export interface CanonWorkspaceRootMetadata {
212
212
  defaultRelativePath?: string | null;
213
213
  }
214
214
  export type CanonControlValue = string;
215
+ export type RuntimeControlValueSource = 'applied' | 'requested' | 'host-default' | 'route-default' | 'unknown';
216
+ export interface RuntimeControlState {
217
+ value: CanonControlValue | null;
218
+ source: RuntimeControlValueSource;
219
+ appliedAt?: number;
220
+ }
215
221
  export type CanonControlAvailability = 'setup' | 'live' | 'setup_and_live';
216
222
  export type CanonControlLiveBehavior = 'immediate' | 'next_turn' | 'none';
217
223
  export type CanonControlSelectionPolicy = 'inherit' | 'required_explicit';
218
224
  export type CanonRuntimeStreamingMode = 'none' | 'status' | 'snapshot' | 'block' | 'delta';
219
225
  export type CanonRuntimeSurfaceMode = 'host' | 'channel' | 'limited_channel' | 'operator';
226
+ export type CanonRuntimeDetailTier = 'primary' | 'detail' | 'diagnostic';
220
227
  export type CanonRuntimeInventoryStatus = 'ready' | 'auth_needed' | 'unknown' | 'configured' | 'running' | 'error';
221
228
  export type CanonRuntimeStatusTone = 'default' | 'success' | 'warning' | 'danger';
222
229
  export type CanonRuntimeActionAvailability = 'idle' | 'busy' | 'busy_with_queue' | 'waiting_input' | 'always';
@@ -301,6 +308,9 @@ export interface CanonRuntimeStatusItem {
301
308
  label: string;
302
309
  value: string;
303
310
  tone?: CanonRuntimeStatusTone;
311
+ tier?: CanonRuntimeDetailTier;
312
+ sensitive?: boolean;
313
+ source?: RuntimeControlValueSource;
304
314
  }
305
315
  export interface CanonRuntimeInventoryEntry {
306
316
  id: string;
@@ -312,6 +322,7 @@ export interface CanonRuntimeInventory {
312
322
  id: string;
313
323
  label: string;
314
324
  entries: ReadonlyArray<CanonRuntimeInventoryEntry>;
325
+ tier?: CanonRuntimeDetailTier;
315
326
  }
316
327
  export interface RuntimeInfoPayload {
317
328
  descriptor: CanonRuntimeDescriptor;
@@ -479,6 +490,7 @@ export interface SessionState {
479
490
  permissionMode?: string;
480
491
  effort?: string;
481
492
  runtimeControlValues?: Record<string, CanonControlValue>;
493
+ controlState?: Record<string, RuntimeControlState>;
482
494
  runtimeControlErrors?: Record<string, RuntimeControlError> | null;
483
495
  cwd?: string;
484
496
  executionMode?: 'worktree' | 'locked';
@@ -580,6 +592,7 @@ export interface AgentSessionSnapshot {
580
592
  permissionModeOptions?: PermissionModeOption[];
581
593
  effort?: string;
582
594
  runtimeControlValues?: Record<string, CanonControlValue>;
595
+ controlState?: Record<string, RuntimeControlState>;
583
596
  runtimeControlErrors?: Record<string, RuntimeControlError> | null;
584
597
  runtimeDescriptor?: CanonRuntimeDescriptor | null;
585
598
  runtimeInfo?: RuntimeInfoPayload | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.15.1",
3
+ "version": "0.15.3",
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",