@canonmsg/core 0.18.1 → 0.19.0

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,8 +1,15 @@
1
- import type { ApprovalRequestMetadata, ApprovalReplyMetadata, SessionRule } from './approval-types.js';
1
+ import type { ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalNativeRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, SessionRule } from './approval-types.js';
2
2
  export declare function generateApprovalId(): string;
3
3
  export declare function redactSecrets(text: string, patterns: string[]): string;
4
4
  export declare function buildApprovalRequest(approvalId: string, toolName: string, toolInput: Record<string, unknown>, opts: {
5
5
  riskLevel?: 'normal' | 'destructive';
6
+ risk?: ApprovalRisk;
7
+ category?: ApprovalRequestCategory;
8
+ runtimeId?: string;
9
+ turnId?: string;
10
+ native?: ApprovalNativeRequestMetadata;
11
+ toolSummary?: string;
12
+ details?: ApprovalRequestDetail[];
6
13
  expiresAt: string;
7
14
  redactPatterns?: string[];
8
15
  }): {
@@ -27,22 +27,37 @@ export function redactSecrets(text, patterns) {
27
27
  }
28
28
  return result;
29
29
  }
30
+ function redactApprovalDetails(details, patterns) {
31
+ if (!details?.length)
32
+ return undefined;
33
+ return details.slice(0, 8).map((detail) => ({
34
+ ...detail,
35
+ label: detail.label.slice(0, 80),
36
+ value: patterns?.length
37
+ ? redactSecrets(detail.value.slice(0, 500), patterns)
38
+ : detail.value.slice(0, 500),
39
+ }));
40
+ }
30
41
  // ── Build approval request message ──────────────────────────────────
31
42
  export function buildApprovalRequest(approvalId, toolName, toolInput, opts) {
32
- let summary = summarizeToolInput(toolName, toolInput);
43
+ let summary = opts.toolSummary ?? summarizeToolInput(toolName, toolInput);
33
44
  if (opts.redactPatterns?.length) {
34
45
  summary = redactSecrets(summary, opts.redactPatterns);
35
46
  }
47
+ summary = summary.slice(0, 1000);
48
+ const risk = opts.risk ?? opts.riskLevel ?? 'normal';
49
+ const riskLevel = risk === 'destructive' ? 'destructive' : (opts.riskLevel ?? 'normal');
50
+ const details = redactApprovalDetails(opts.details, opts.redactPatterns);
36
51
  const timeoutMin = Math.round((new Date(opts.expiresAt).getTime() - Date.now()) / 60_000);
37
52
  const lines = [
38
- `Tool Approval Required [${approvalId}]`,
53
+ `Action Approval Required [${approvalId}]`,
39
54
  '',
40
55
  `Tool: ${toolName}`,
41
56
  summary,
42
57
  '',
43
58
  ];
44
- if (opts.riskLevel === 'destructive') {
45
- lines.push('This is a destructive operation');
59
+ if (risk === 'high' || risk === 'destructive') {
60
+ lines.push(risk === 'destructive' ? 'This is a destructive operation' : 'This is a high-risk operation');
46
61
  lines.push('');
47
62
  }
48
63
  lines.push(`Reply "approve" or "deny" (expires in ${timeoutMin}m)`);
@@ -51,7 +66,13 @@ export function buildApprovalRequest(approvalId, toolName, toolInput, opts) {
51
66
  approvalId,
52
67
  toolName,
53
68
  toolSummary: summary,
54
- riskLevel: opts.riskLevel ?? 'normal',
69
+ ...(opts.category ? { category: opts.category } : {}),
70
+ risk,
71
+ riskLevel,
72
+ ...(opts.runtimeId ? { runtimeId: opts.runtimeId } : {}),
73
+ ...(opts.turnId ? { turnId: opts.turnId } : {}),
74
+ ...(opts.native ? { native: opts.native } : {}),
75
+ ...(details ? { details } : {}),
55
76
  expiresAt: opts.expiresAt,
56
77
  };
57
78
  return { text: lines.join('\n'), metadata };
@@ -77,7 +98,8 @@ export function buildApprovalOutcome(approvalId, toolName, toolSummary, decision
77
98
  return `Expired [${approvalId}] -- ${toolName}: ${short} (auto-denied)`;
78
99
  }
79
100
  if (reason === 'session-rule') {
80
- return `Auto-approved (session rule) -- ${toolName}: ${short}`;
101
+ const label = decision === 'allow' ? 'Auto-approved' : 'Auto-denied';
102
+ return `${label} (session rule) -- ${toolName}: ${short}`;
81
103
  }
82
104
  return `${icon} [${approvalId}] -- ${toolName}: ${short}`;
83
105
  }
@@ -1,5 +1,5 @@
1
1
  import type { CanonClient } from './client.js';
2
- import type { ApprovalConfig, ApprovalResult, SessionRule } from './approval-types.js';
2
+ import type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalResult, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk, SessionRule } from './approval-types.js';
3
3
  /**
4
4
  * Platform-agnostic approval protocol for Canon.
5
5
  *
@@ -27,6 +27,15 @@ export declare class ApprovalManager {
27
27
  */
28
28
  requestApproval(conversationId: string, toolName: string, toolInput: Record<string, unknown>, opts?: {
29
29
  riskLevel?: 'normal' | 'destructive';
30
+ risk?: ApprovalRisk;
31
+ category?: ApprovalRequestCategory;
32
+ runtimeId?: string;
33
+ turnId?: string;
34
+ native?: ApprovalNativeRequestMetadata;
35
+ toolSummary?: string;
36
+ details?: ApprovalRequestDetail[];
37
+ ignoreSessionRules?: boolean;
38
+ allowSessionRule?: boolean;
30
39
  }): Promise<ApprovalResult>;
31
40
  /**
32
41
  * Feed an inbound message to the approval manager.
@@ -38,7 +47,7 @@ export declare class ApprovalManager {
38
47
  metadata?: Record<string, unknown>;
39
48
  }): boolean;
40
49
  /** Check if a tool would be auto-resolved by a session rule */
41
- checkSessionRules(toolName: string): SessionRule | null;
50
+ checkSessionRules(toolName: string, conversationId?: string): SessionRule | null;
42
51
  getSessionRules(): SessionRule[];
43
52
  clearSessionRules(): void;
44
53
  get pendingCount(): number;
@@ -1,5 +1,5 @@
1
- import { DEFAULT_APPROVAL_CONFIG } from './approval-types.js';
2
- import { generateApprovalId, buildApprovalRequest, buildApprovalOutcome, parseTextApprovalReply, } from './approval-format.js';
1
+ import { DEFAULT_APPROVAL_CONFIG, parseApprovalReplyMetadata } from './approval-types.js';
2
+ import { generateApprovalId, buildApprovalRequest, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
3
3
  // ── ApprovalManager ─────────────────────────────────────────────────
4
4
  /**
5
5
  * Platform-agnostic approval protocol for Canon.
@@ -34,11 +34,14 @@ export class ApprovalManager {
34
34
  */
35
35
  async requestApproval(conversationId, toolName, toolInput, opts) {
36
36
  // Check session rules first
37
- const matchingRule = this.checkSessionRules(toolName);
37
+ const matchingRule = opts?.ignoreSessionRules ? null : this.checkSessionRules(toolName, conversationId);
38
38
  if (matchingRule) {
39
39
  const decision = matchingRule.type === 'deny-tool' ? 'deny' : 'allow';
40
40
  // Send silent log (fire-and-forget)
41
- const summary = this.summarizeTool(toolName, toolInput);
41
+ const rawSummary = opts?.toolSummary ?? this.summarizeTool(toolName, toolInput);
42
+ const summary = this.config.redactPatterns.length
43
+ ? redactSecrets(rawSummary, this.config.redactPatterns)
44
+ : rawSummary;
42
45
  const logMsg = buildApprovalOutcome('', toolName, summary, decision, 'session-rule');
43
46
  this.client.sendMessage(conversationId, logMsg, {
44
47
  metadata: { type: 'approval_outcome', decision, reason: 'session-rule' },
@@ -49,6 +52,13 @@ export class ApprovalManager {
49
52
  const expiresAt = new Date(Date.now() + this.config.timeoutSeconds * 1000).toISOString();
50
53
  const { text, metadata } = buildApprovalRequest(approvalId, toolName, toolInput, {
51
54
  riskLevel: opts?.riskLevel,
55
+ risk: opts?.risk,
56
+ category: opts?.category,
57
+ runtimeId: opts?.runtimeId,
58
+ turnId: opts?.turnId,
59
+ native: opts?.native,
60
+ toolSummary: opts?.toolSummary,
61
+ details: opts?.details,
52
62
  expiresAt,
53
63
  redactPatterns: this.config.redactPatterns,
54
64
  });
@@ -61,8 +71,7 @@ export class ApprovalManager {
61
71
  return new Promise((resolve) => {
62
72
  const timer = setTimeout(() => {
63
73
  this.pending.delete(approvalId);
64
- const summary = this.summarizeTool(toolName, toolInput);
65
- const msg = buildApprovalOutcome(approvalId, toolName, summary, 'deny', 'timeout');
74
+ const msg = buildApprovalOutcome(approvalId, toolName, metadata.toolSummary, 'deny', 'timeout');
66
75
  this.client.sendMessage(conversationId, msg, {
67
76
  metadata: {
68
77
  type: 'approval_outcome',
@@ -77,7 +86,8 @@ export class ApprovalManager {
77
86
  approvalId,
78
87
  conversationId,
79
88
  toolName,
80
- toolSummary: this.summarizeTool(toolName, toolInput),
89
+ toolSummary: metadata.toolSummary,
90
+ allowSessionRule: opts?.allowSessionRule !== false,
81
91
  resolve,
82
92
  timer,
83
93
  });
@@ -95,11 +105,10 @@ export class ApprovalManager {
95
105
  return false;
96
106
  // Try structured metadata first (from card UI)
97
107
  if (message.metadata?.type === 'approval_reply') {
98
- const m = message.metadata;
99
- const approvalId = m.approvalId;
100
- const decision = m.decision;
101
- const sessionRule = m.sessionRule;
102
- return this.resolveApproval(approvalId, decision, sessionRule, conversationId);
108
+ const parsed = parseApprovalReplyMetadata(message.metadata);
109
+ if (!parsed)
110
+ return false;
111
+ return this.resolveApproval(parsed.approvalId, parsed.decision, parsed.sessionRule, conversationId);
103
112
  }
104
113
  // Fall back to text parsing
105
114
  if (message.text) {
@@ -119,23 +128,31 @@ export class ApprovalManager {
119
128
  return false;
120
129
  }
121
130
  /** Check if a tool would be auto-resolved by a session rule */
122
- checkSessionRules(toolName) {
131
+ checkSessionRules(toolName, conversationId) {
123
132
  this.pruneExpiredRules();
124
- for (const rule of this.rules) {
133
+ for (const stored of this.rules) {
134
+ if (conversationId && stored.conversationId !== conversationId)
135
+ continue;
136
+ const { rule } = stored;
125
137
  if (rule.type === 'approve-all')
126
138
  return rule;
127
139
  if ((rule.type === 'approve-tool' || rule.type === 'deny-tool') &&
128
140
  rule.toolPattern) {
129
- const re = new RegExp(`^${rule.toolPattern}$`, 'i');
130
- if (re.test(toolName))
131
- return rule;
141
+ try {
142
+ const re = new RegExp(`^${rule.toolPattern}$`, 'i');
143
+ if (re.test(toolName))
144
+ return rule;
145
+ }
146
+ catch {
147
+ continue;
148
+ }
132
149
  }
133
150
  }
134
151
  return null;
135
152
  }
136
153
  getSessionRules() {
137
154
  this.pruneExpiredRules();
138
- return [...this.rules];
155
+ return this.rules.map((stored) => stored.rule);
139
156
  }
140
157
  clearSessionRules() {
141
158
  this.rules = [];
@@ -160,11 +177,18 @@ export class ApprovalManager {
160
177
  }
161
178
  return false;
162
179
  }
180
+ if (entry.conversationId !== conversationId) {
181
+ return false;
182
+ }
163
183
  clearTimeout(entry.timer);
164
184
  this.pending.delete(approvalId);
165
- // Store session rule if provided
166
- if (sessionRule) {
167
- this.rules.push(sessionRule);
185
+ const acceptedSessionRule = entry.allowSessionRule ? sessionRule : undefined;
186
+ // Store session rule if provided and allowed for this request
187
+ if (acceptedSessionRule) {
188
+ this.rules.push({
189
+ conversationId: entry.conversationId,
190
+ rule: acceptedSessionRule,
191
+ });
168
192
  }
169
193
  // Send confirmation (fire-and-forget)
170
194
  const msg = buildApprovalOutcome(approvalId, entry.toolName, entry.toolSummary, decision, 'replied');
@@ -177,15 +201,15 @@ export class ApprovalManager {
177
201
  },
178
202
  }).catch(() => { });
179
203
  // If session rule was set, log that too
180
- if (sessionRule) {
181
- const ruleDesc = this.describeRule(sessionRule);
204
+ if (acceptedSessionRule) {
205
+ const ruleDesc = this.describeRule(acceptedSessionRule);
182
206
  this.client
183
207
  .sendMessage(conversationId, `Session rule set: ${ruleDesc}`, {
184
208
  metadata: { type: 'approval_outcome', decision, reason: 'session-rule' },
185
209
  })
186
210
  .catch(() => { });
187
211
  }
188
- entry.resolve({ decision, sessionRule });
212
+ entry.resolve({ decision, ...(acceptedSessionRule ? { sessionRule: acceptedSessionRule } : {}) });
189
213
  return true;
190
214
  }
191
215
  findMostRecentPending(conversationId) {
@@ -199,10 +223,10 @@ export class ApprovalManager {
199
223
  }
200
224
  pruneExpiredRules() {
201
225
  const now = Date.now();
202
- this.rules = this.rules.filter((r) => {
203
- if (!r.expiresAt)
226
+ this.rules = this.rules.filter(({ rule }) => {
227
+ if (!rule.expiresAt)
204
228
  return true;
205
- return new Date(r.expiresAt).getTime() > now;
229
+ return new Date(rule.expiresAt).getTime() > now;
206
230
  });
207
231
  }
208
232
  summarizeTool(toolName, toolInput) {
@@ -4,7 +4,17 @@ export interface ApprovalRequestMetadata {
4
4
  toolName: string;
5
5
  /** Pre-computed, redacted summary — raw toolInput is never stored */
6
6
  toolSummary: string;
7
+ /** Broad action family for richer Canon approval cards. */
8
+ category?: ApprovalRequestCategory;
9
+ /** Preferred v2 risk signal. `riskLevel` remains for older clients. */
10
+ risk?: ApprovalRisk;
7
11
  riskLevel?: 'normal' | 'destructive';
12
+ runtimeId?: string;
13
+ turnId?: string;
14
+ /** Native runtime correlation handle. Canon stores this opaquely for plugins/SDK bridges. */
15
+ native?: ApprovalNativeRequestMetadata;
16
+ /** Redacted, normalized details. Raw tool input must not be stored here. */
17
+ details?: ApprovalRequestDetail[];
8
18
  expiresAt: string;
9
19
  }
10
20
  export interface ApprovalOutcomeMetadata {
@@ -19,6 +29,33 @@ export interface ApprovalReplyMetadata {
19
29
  decision: 'allow' | 'deny';
20
30
  sessionRule?: SessionRule;
21
31
  }
32
+ export type ApprovalRequestCategory = 'command' | 'file' | 'network' | 'browser' | 'mcp' | 'plugin' | 'canon' | 'tool';
33
+ export type ApprovalRisk = 'low' | 'normal' | 'high' | 'destructive';
34
+ export interface ApprovalRequestDetail {
35
+ label: string;
36
+ value: string;
37
+ monospace?: boolean;
38
+ }
39
+ export interface ApprovalNativeRequestMetadata {
40
+ runtime?: string;
41
+ method?: string;
42
+ requestId?: string;
43
+ provider?: string;
44
+ origin?: string;
45
+ surface?: string;
46
+ threadId?: string;
47
+ turnId?: string;
48
+ runId?: string;
49
+ itemId?: string;
50
+ toolCallId?: string;
51
+ approvalId?: string;
52
+ pluginId?: string;
53
+ sessionKey?: string;
54
+ cwd?: string;
55
+ model?: string;
56
+ nodeId?: string;
57
+ handles?: Record<string, string>;
58
+ }
22
59
  export interface SessionRule {
23
60
  type: 'approve-all' | 'approve-tool' | 'deny-tool';
24
61
  /** Tool name pattern (regex) — only for approve-tool/deny-tool */
@@ -37,3 +74,6 @@ export interface ApprovalConfig {
37
74
  redactPatterns: string[];
38
75
  }
39
76
  export declare const DEFAULT_APPROVAL_CONFIG: ApprovalConfig;
77
+ export declare function parseSessionRule(value: unknown): SessionRule | null;
78
+ export declare function parseApprovalReplyMetadata(value: unknown): ApprovalReplyMetadata | null;
79
+ export declare function parseApprovalRequestMetadata(value: unknown): ApprovalRequestMetadata | null;
@@ -7,3 +7,192 @@ export const DEFAULT_APPROVAL_CONFIG = {
7
7
  '(?:api[_-]?key|token|secret|password)\\s*[:=]\\s*\\S+',
8
8
  ],
9
9
  };
10
+ function isRecord(value) {
11
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
12
+ }
13
+ function normalizeString(value, maxLength) {
14
+ if (typeof value !== 'string')
15
+ return null;
16
+ const trimmed = value.trim();
17
+ if (!trimmed || trimmed.length > maxLength)
18
+ return null;
19
+ return trimmed;
20
+ }
21
+ function normalizeCategory(value) {
22
+ return value === 'command'
23
+ || value === 'file'
24
+ || value === 'network'
25
+ || value === 'browser'
26
+ || value === 'mcp'
27
+ || value === 'plugin'
28
+ || value === 'canon'
29
+ || value === 'tool'
30
+ ? value
31
+ : undefined;
32
+ }
33
+ function normalizeRisk(value) {
34
+ return value === 'low'
35
+ || value === 'normal'
36
+ || value === 'high'
37
+ || value === 'destructive'
38
+ ? value
39
+ : undefined;
40
+ }
41
+ function normalizeDetails(value) {
42
+ if (!Array.isArray(value))
43
+ return undefined;
44
+ const details = value.slice(0, 8).flatMap((entry) => {
45
+ if (!isRecord(entry))
46
+ return [];
47
+ const label = normalizeString(entry.label, 80);
48
+ const detailValue = normalizeString(entry.value, 500);
49
+ if (!label || !detailValue)
50
+ return [];
51
+ return [{
52
+ label,
53
+ value: detailValue,
54
+ ...(entry.monospace === true ? { monospace: true } : {}),
55
+ }];
56
+ });
57
+ return details.length > 0 ? details : undefined;
58
+ }
59
+ function normalizeNativeRequest(value) {
60
+ if (!isRecord(value))
61
+ return undefined;
62
+ const native = {};
63
+ for (const key of [
64
+ 'runtime',
65
+ 'method',
66
+ 'requestId',
67
+ 'provider',
68
+ 'origin',
69
+ 'surface',
70
+ 'threadId',
71
+ 'turnId',
72
+ 'runId',
73
+ 'itemId',
74
+ 'toolCallId',
75
+ 'approvalId',
76
+ 'pluginId',
77
+ 'sessionKey',
78
+ 'cwd',
79
+ 'model',
80
+ 'nodeId',
81
+ ]) {
82
+ const normalized = normalizeString(value[key], 256);
83
+ if (normalized)
84
+ native[key] = normalized;
85
+ }
86
+ const handles = isRecord(value.handles) ? value.handles : null;
87
+ if (handles) {
88
+ const normalizedHandles = {};
89
+ for (const [key, raw] of Object.entries(handles).slice(0, 16)) {
90
+ const normalizedKey = /^[a-zA-Z0-9_.:-]{1,80}$/.test(key) ? key : null;
91
+ const normalizedValue = normalizeString(raw, 256);
92
+ if (normalizedKey && normalizedValue) {
93
+ normalizedHandles[normalizedKey] = normalizedValue;
94
+ }
95
+ }
96
+ if (Object.keys(normalizedHandles).length > 0) {
97
+ native.handles = normalizedHandles;
98
+ }
99
+ }
100
+ return Object.keys(native).length > 0 ? native : undefined;
101
+ }
102
+ export function parseSessionRule(value) {
103
+ if (!isRecord(value))
104
+ return null;
105
+ const type = value.type;
106
+ if (type !== 'approve-all' && type !== 'approve-tool' && type !== 'deny-tool') {
107
+ return null;
108
+ }
109
+ let expiresAt;
110
+ if (value.expiresAt !== undefined) {
111
+ if (value.expiresAt === null) {
112
+ expiresAt = null;
113
+ }
114
+ else {
115
+ const normalizedExpiresAt = normalizeString(value.expiresAt, 128);
116
+ if (!normalizedExpiresAt)
117
+ return null;
118
+ const expiresMs = Date.parse(normalizedExpiresAt);
119
+ if (!Number.isFinite(expiresMs) || expiresMs <= Date.now())
120
+ return null;
121
+ expiresAt = new Date(expiresMs).toISOString();
122
+ }
123
+ }
124
+ if (type === 'approve-all') {
125
+ return {
126
+ type,
127
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
128
+ };
129
+ }
130
+ const toolPattern = normalizeString(value.toolPattern, 128);
131
+ if (!toolPattern || !/^[\w.*:-]+$/.test(toolPattern)) {
132
+ return null;
133
+ }
134
+ return {
135
+ type,
136
+ toolPattern,
137
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
138
+ };
139
+ }
140
+ export function parseApprovalReplyMetadata(value) {
141
+ if (!isRecord(value) || value.type !== 'approval_reply')
142
+ return null;
143
+ const approvalId = normalizeString(value.approvalId, 128);
144
+ if (!approvalId)
145
+ return null;
146
+ const decision = value.decision;
147
+ if (decision !== 'allow' && decision !== 'deny')
148
+ return null;
149
+ let sessionRule;
150
+ if (value.sessionRule !== undefined) {
151
+ const parsed = parseSessionRule(value.sessionRule);
152
+ if (!parsed)
153
+ return null;
154
+ sessionRule = parsed;
155
+ }
156
+ return {
157
+ type: 'approval_reply',
158
+ approvalId,
159
+ decision,
160
+ ...(sessionRule ? { sessionRule } : {}),
161
+ };
162
+ }
163
+ export function parseApprovalRequestMetadata(value) {
164
+ if (!isRecord(value) || value.type !== 'approval_request')
165
+ return null;
166
+ const approvalId = normalizeString(value.approvalId, 128);
167
+ const toolName = normalizeString(value.toolName, 128);
168
+ const toolSummary = normalizeString(value.toolSummary, 1000);
169
+ const expiresAt = normalizeString(value.expiresAt, 128);
170
+ if (!approvalId || !toolName || !toolSummary || !expiresAt)
171
+ return null;
172
+ const expiresMs = Date.parse(expiresAt);
173
+ if (!Number.isFinite(expiresMs))
174
+ return null;
175
+ const riskLevel = value.riskLevel === 'destructive' || value.riskLevel === 'normal'
176
+ ? value.riskLevel
177
+ : undefined;
178
+ const risk = normalizeRisk(value.risk) ?? (riskLevel === 'destructive' ? 'destructive' : undefined);
179
+ const category = normalizeCategory(value.category);
180
+ const runtimeId = normalizeString(value.runtimeId, 128) ?? undefined;
181
+ const turnId = normalizeString(value.turnId, 128) ?? undefined;
182
+ const native = normalizeNativeRequest(value.native);
183
+ const details = normalizeDetails(value.details);
184
+ return {
185
+ type: 'approval_request',
186
+ approvalId,
187
+ toolName,
188
+ toolSummary,
189
+ ...(category ? { category } : {}),
190
+ ...(risk ? { risk } : {}),
191
+ ...(riskLevel ? { riskLevel } : {}),
192
+ ...(runtimeId ? { runtimeId } : {}),
193
+ ...(turnId ? { turnId } : {}),
194
+ ...(native ? { native } : {}),
195
+ ...(details ? { details } : {}),
196
+ expiresAt: new Date(expiresMs).toISOString(),
197
+ };
198
+ }
package/dist/browser.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
2
2
  export { resolveCanonBaseUrl } from './base-url.js';
3
3
  export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
4
- export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
4
+ export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeProvenance, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
5
+ export { buildRuntimeProvenance, resolveRuntimeProvenance, } from './provenance.js';
5
6
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
6
7
  export type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
7
8
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
@@ -13,6 +14,7 @@ export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots,
13
14
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
14
15
  export type { DeliveryIntent, InboundDisposition, RuntimeCapabilities, TriggerDecision, TurnLifecycleState, TurnMessageSemantics, TurnMetadata, TurnState, } from './turn-protocol.js';
15
16
  export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
16
- export type { ApprovalRequestMetadata, ApprovalReplyMetadata, ApprovalOutcomeMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
17
+ export type { ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalNativeRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
18
+ export { DEFAULT_APPROVAL_CONFIG, parseApprovalRequestMetadata, parseApprovalReplyMetadata, parseSessionRule, } from './approval-types.js';
17
19
  export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
18
20
  export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
package/dist/browser.js CHANGED
@@ -1,6 +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 { buildRuntimeProvenance, resolveRuntimeProvenance, } from './provenance.js';
4
5
  export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
5
6
  export { buildSelfContextPromptLines, normalizeSelfContexts, } from './self-context.js';
6
7
  export { buildAgentSessionSnapshot } from './agent-session.js';
@@ -8,4 +9,5 @@ export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, RUNTIME_NEW_SESS
8
9
  export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
9
10
  export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
10
11
  export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
12
+ export { DEFAULT_APPROVAL_CONFIG, parseApprovalRequestMetadata, parseApprovalReplyMetadata, parseSessionRule, } from './approval-types.js';
11
13
  export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
@@ -1,4 +1,4 @@
1
- import { type AgentClientType, type AgentRuntime, type CanonConversation, type CanonGroupContext, type CanonGroupContextMode, type CanonMembershipChange, type CanonMessage, type CanonMessagesPage, type MessageCreatedPayload } from './types.js';
1
+ import { type AgentClientType, type AgentRuntime, type CanonConversation, type CanonGroupContext, type CanonGroupContextMode, type CanonMembershipChange, type CanonMessage, type CanonMessagesPage, type CanonRuntimeProvenance, type MessageCreatedPayload } from './types.js';
2
2
  import { type CanonClient } from './client.js';
3
3
  import { type SessionWorkspaceConfig } from './execution-environment.js';
4
4
  import { type ResolvedAgentBehaviorPolicy } from './policy.js';
@@ -86,10 +86,12 @@ export declare function resolveCanonReplyContext(input: {
86
86
  }): CanonReplyContext | null;
87
87
  export declare function buildHydratedInboundContext(input: {
88
88
  agentId: string;
89
+ conversationId: string;
89
90
  conversation: CanonConversation | null;
90
91
  page?: CanonMessagesPage | null;
91
92
  activeSelfContextId?: string | null;
92
93
  selfContexts?: MessageCreatedPayload['selfContexts'];
94
+ provenance?: MessageCreatedPayload['provenance'];
93
95
  message: HostInboundMessage;
94
96
  senderName: string;
95
97
  isOwner: boolean;
@@ -102,6 +104,7 @@ export declare function buildHydratedInboundContext(input: {
102
104
  behavior?: ResolvedAgentBehaviorPolicy | null;
103
105
  activeSelfContextId: string | null;
104
106
  selfContexts: NonNullable<MessageCreatedPayload['selfContexts']>;
107
+ provenance: CanonRuntimeProvenance;
105
108
  replyContext: CanonReplyContext | null;
106
109
  hydratedFromPage: boolean;
107
110
  };
@@ -3,6 +3,7 @@ import { buildAgentSessionSnapshot } from './agent-session.js';
3
3
  import { buildConversationWorktreeSpec, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, } from './execution-environment.js';
4
4
  import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, } from './policy.js';
5
5
  import { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
6
+ import { resolveRuntimeProvenance } from './provenance.js';
6
7
  import { rtdbRead } from './rtdb-rest.js';
7
8
  import { createRuntimeStatePublisher } from './runtime-state-publisher.js';
8
9
  const HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES = Object.freeze({
@@ -173,6 +174,21 @@ export function buildHydratedInboundContext(input) {
173
174
  ...(input.page?.selfContexts ?? []),
174
175
  ...(input.selfContexts ?? []),
175
176
  ], activeSelfContextId);
177
+ const resolvedActiveSelfContextId = activeSelfContexts.length > 0 ? activeSelfContextId : null;
178
+ const provenance = resolveRuntimeProvenance({
179
+ provenance: input.provenance,
180
+ conversationId: input.conversationId,
181
+ conversationType: input.conversation?.type ?? 'unknown',
182
+ memberCount: input.conversation?.memberIds?.length ?? null,
183
+ senderId: input.message.senderId ?? '',
184
+ senderName: input.senderName,
185
+ senderType: input.message.senderType ?? 'human',
186
+ isOwner: input.isOwner,
187
+ agentId: input.agentId,
188
+ mentions: input.message.mentions ?? [],
189
+ activeSelfContextId: resolvedActiveSelfContextId,
190
+ selfContexts: activeSelfContexts,
191
+ });
176
192
  const groupContext = buildCanonGroupContext({
177
193
  conversation: input.conversation,
178
194
  messages: [
@@ -201,8 +217,9 @@ export function buildHydratedInboundContext(input) {
201
217
  currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
202
218
  },
203
219
  behavior: input.page?.behavior ?? input.conversation?.behavior,
204
- activeSelfContextId: activeSelfContexts.length > 0 ? activeSelfContextId : null,
220
+ activeSelfContextId: resolvedActiveSelfContextId,
205
221
  selfContexts: activeSelfContexts,
222
+ provenance,
206
223
  replyContext: resolveCanonReplyContext({
207
224
  message: input.message,
208
225
  messages: input.page?.messages ?? [],
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
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, CanonGroupContext, CanonGroupContextMode, CanonKnownRecentParticipant, CanonMembershipChange, CanonResolveAdmissionResult, ConversationUpdatedPayload, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
2
+ export type { AddMemberResult, AgentCapabilities, AgentClientType, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonGroupContext, CanonGroupContextMode, CanonKnownRecentParticipant, CanonMembershipChange, CanonResolveAdmissionResult, ConversationUpdatedPayload, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, CanonRuntimeProvenance, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandDescriptor, CanonRuntimeDetailTier, CanonRuntimeExecutionMetadata, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimePrimitiveId, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, RuntimeControlError, RuntimeControlState, RuntimeControlValueSource, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
3
+ export { buildRuntimeProvenance, resolveRuntimeProvenance, } from './provenance.js';
3
4
  export type { CanonSelfContext, CanonSelfContextType, SelfContextPromptRenderOptions, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, } from './self-context.js';
4
5
  export { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
5
6
  export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
@@ -16,8 +17,8 @@ export type { DeliveryIntent, TurnMessageSemantics, InboundDisposition, TurnLife
16
17
  export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistrationRequest, waitForRegistrationApproval, } from './registration.js';
17
18
  export { ApprovalManager } from './approval-manager.js';
18
19
  export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
19
- export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
20
- export type { ApprovalRequestMetadata, ApprovalReplyMetadata, ApprovalOutcomeMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
20
+ export { DEFAULT_APPROVAL_CONFIG, parseApprovalRequestMetadata, parseApprovalReplyMetadata, parseSessionRule, } from './approval-types.js';
21
+ export type { ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalNativeRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, SessionRule, ApprovalResult, ApprovalConfig, } from './approval-types.js';
21
22
  export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
22
23
  export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
23
24
  export { createStreamingHelper } from './streaming.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Types
2
2
  export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
3
+ export { buildRuntimeProvenance, resolveRuntimeProvenance, } from './provenance.js';
3
4
  export { buildSelfContextPromptLines, normalizeSelfContexts, resolveMessageActiveSelfContextId, selectActiveSelfContexts, } from './self-context.js';
4
5
  export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
5
6
  // Client
@@ -16,7 +17,7 @@ export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistration
16
17
  // Approval
17
18
  export { ApprovalManager } from './approval-manager.js';
18
19
  export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
19
- export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
20
+ export { DEFAULT_APPROVAL_CONFIG, parseApprovalRequestMetadata, parseApprovalReplyMetadata, parseSessionRule, } from './approval-types.js';
20
21
  export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, buildQuestionRequest, } from './runtime-cards.js';
21
22
  // Streaming (RTDB helpers)
22
23
  export { createStreamingHelper } from './streaming.js';
@@ -0,0 +1,31 @@
1
+ import type { CanonConversation, CanonMessage, CanonRuntimeProvenance } from './types.js';
2
+ import type { CanonSelfContext } from './self-context.js';
3
+ export declare function buildRuntimeProvenance(input: {
4
+ conversationId: string;
5
+ conversationType?: CanonConversation['type'] | 'unknown' | string | null;
6
+ memberCount?: number | null;
7
+ senderId: string;
8
+ senderName?: string | null;
9
+ senderType?: CanonMessage['senderType'] | string | null;
10
+ isOwner?: boolean | null;
11
+ agentId?: string | null;
12
+ mentions?: readonly string[] | null;
13
+ mentionedAgent?: boolean | null;
14
+ activeSelfContextId?: string | null;
15
+ selfContexts?: readonly CanonSelfContext[] | null;
16
+ }): CanonRuntimeProvenance;
17
+ export declare function resolveRuntimeProvenance(input: {
18
+ provenance?: CanonRuntimeProvenance | null;
19
+ conversationId: string;
20
+ conversationType?: CanonConversation['type'] | 'unknown' | string | null;
21
+ memberCount?: number | null;
22
+ senderId: string;
23
+ senderName?: string | null;
24
+ senderType?: CanonMessage['senderType'] | string | null;
25
+ isOwner?: boolean | null;
26
+ agentId?: string | null;
27
+ mentions?: readonly string[] | null;
28
+ mentionedAgent?: boolean | null;
29
+ activeSelfContextId?: string | null;
30
+ selfContexts?: readonly CanonSelfContext[] | null;
31
+ }): CanonRuntimeProvenance;
@@ -0,0 +1,71 @@
1
+ function normalizeConversationType(value) {
2
+ return value === 'direct' || value === 'group' ? value : 'unknown';
3
+ }
4
+ function normalizeSenderType(value) {
5
+ return value === 'ai_agent' ? 'ai_agent' : 'human';
6
+ }
7
+ function normalizeMemberCount(value) {
8
+ if (value == null)
9
+ return value;
10
+ return Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
11
+ }
12
+ function normalizeName(value) {
13
+ if (typeof value !== 'string')
14
+ return value == null ? value : undefined;
15
+ const trimmed = value.trim();
16
+ return trimmed ? trimmed : null;
17
+ }
18
+ function findSelfContextType(selfContexts, activeSelfContextId) {
19
+ if (!activeSelfContextId)
20
+ return null;
21
+ return selfContexts?.find((entry) => entry.id === activeSelfContextId)?.type ?? null;
22
+ }
23
+ export function buildRuntimeProvenance(input) {
24
+ const activeSelfContextId = typeof input.activeSelfContextId === 'string' && input.activeSelfContextId.trim()
25
+ ? input.activeSelfContextId.trim()
26
+ : null;
27
+ const mentionedAgent = typeof input.mentionedAgent === 'boolean'
28
+ ? input.mentionedAgent
29
+ : Boolean(input.agentId && input.mentions?.includes(input.agentId));
30
+ return {
31
+ conversation: {
32
+ id: input.conversationId,
33
+ type: normalizeConversationType(input.conversationType),
34
+ ...(input.memberCount !== undefined
35
+ ? { memberCount: normalizeMemberCount(input.memberCount) }
36
+ : {}),
37
+ },
38
+ sender: {
39
+ id: input.senderId,
40
+ ...(input.senderName !== undefined ? { name: normalizeName(input.senderName) } : {}),
41
+ type: normalizeSenderType(input.senderType),
42
+ isOwner: input.isOwner === true,
43
+ },
44
+ mentionedAgent,
45
+ activeSelfContext: activeSelfContextId
46
+ ? {
47
+ id: activeSelfContextId,
48
+ type: findSelfContextType(input.selfContexts, activeSelfContextId),
49
+ }
50
+ : null,
51
+ };
52
+ }
53
+ export function resolveRuntimeProvenance(input) {
54
+ if (!input.provenance) {
55
+ return buildRuntimeProvenance(input);
56
+ }
57
+ return buildRuntimeProvenance({
58
+ conversationId: input.provenance.conversation?.id || input.conversationId,
59
+ conversationType: input.provenance.conversation?.type ?? input.conversationType,
60
+ memberCount: input.provenance.conversation?.memberCount ?? input.memberCount,
61
+ senderId: input.provenance.sender?.id || input.senderId,
62
+ senderName: input.provenance.sender?.name ?? input.senderName,
63
+ senderType: input.provenance.sender?.type ?? input.senderType,
64
+ isOwner: input.provenance.sender?.isOwner ?? input.isOwner,
65
+ agentId: input.agentId,
66
+ mentions: input.mentions,
67
+ mentionedAgent: input.provenance.mentionedAgent ?? input.mentionedAgent,
68
+ activeSelfContextId: input.provenance.activeSelfContext?.id ?? input.activeSelfContextId,
69
+ selfContexts: input.selfContexts,
70
+ });
71
+ }
package/dist/types.d.ts CHANGED
@@ -81,6 +81,24 @@ export interface CanonMessagesPage {
81
81
  activeSelfContextIdByMessageId?: Record<string, string>;
82
82
  selfContexts?: CanonSelfContext[];
83
83
  }
84
+ export interface CanonRuntimeProvenance {
85
+ conversation: {
86
+ id: string;
87
+ type: CanonConversation['type'] | 'unknown';
88
+ memberCount?: number | null;
89
+ };
90
+ sender: {
91
+ id: string;
92
+ name?: string | null;
93
+ type: CanonMessage['senderType'];
94
+ isOwner: boolean;
95
+ };
96
+ mentionedAgent: boolean;
97
+ activeSelfContext?: {
98
+ id: string;
99
+ type?: CanonSelfContext['type'] | string | null;
100
+ } | null;
101
+ }
84
102
  export type CanonContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired';
85
103
  export interface CanonContactRequest {
86
104
  id: string;
@@ -88,6 +106,10 @@ export interface CanonContactRequest {
88
106
  requesterName: string;
89
107
  requesterAvatarUrl: string | null;
90
108
  targetId: string;
109
+ targetName?: string | null;
110
+ targetAvatarUrl?: string | null;
111
+ targetUserType?: 'human' | 'ai_agent' | null;
112
+ targetOwnerId?: string | null;
91
113
  approverId: string;
92
114
  message: string | null;
93
115
  status: CanonContactRequestStatus;
@@ -415,6 +437,7 @@ export interface MessageCreatedPayload {
415
437
  behavior?: ResolvedAgentBehaviorPolicy;
416
438
  activeSelfContextId?: string | null;
417
439
  selfContexts?: CanonSelfContext[];
440
+ provenance?: CanonRuntimeProvenance;
418
441
  message: {
419
442
  id: string;
420
443
  senderId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
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",