@canonmsg/core 0.18.0 → 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.
- package/dist/approval-format.d.ts +8 -1
- package/dist/approval-format.js +28 -6
- package/dist/approval-manager.d.ts +11 -2
- package/dist/approval-manager.js +51 -27
- package/dist/approval-types.d.ts +40 -0
- package/dist/approval-types.js +189 -0
- package/dist/browser.d.ts +4 -2
- package/dist/browser.js +2 -0
- package/dist/host-runtime.d.ts +29 -1
- package/dist/host-runtime.js +84 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +3 -2
- package/dist/message-format.js +18 -17
- package/dist/provenance.d.ts +31 -0
- package/dist/provenance.js +71 -0
- package/dist/runtime-descriptor.js +2 -2
- package/dist/types.d.ts +24 -0
- package/package.json +1 -1
|
@@ -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
|
}): {
|
package/dist/approval-format.js
CHANGED
|
@@ -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
|
-
`
|
|
53
|
+
`Action Approval Required [${approvalId}]`,
|
|
39
54
|
'',
|
|
40
55
|
`Tool: ${toolName}`,
|
|
41
56
|
summary,
|
|
42
57
|
'',
|
|
43
58
|
];
|
|
44
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/approval-manager.js
CHANGED
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
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
|
-
|
|
166
|
-
if
|
|
167
|
-
|
|
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 (
|
|
181
|
-
const ruleDesc = this.describeRule(
|
|
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((
|
|
203
|
-
if (!
|
|
226
|
+
this.rules = this.rules.filter(({ rule }) => {
|
|
227
|
+
if (!rule.expiresAt)
|
|
204
228
|
return true;
|
|
205
|
-
return new Date(
|
|
229
|
+
return new Date(rule.expiresAt).getTime() > now;
|
|
206
230
|
});
|
|
207
231
|
}
|
|
208
232
|
summarizeTool(toolName, toolInput) {
|
package/dist/approval-types.d.ts
CHANGED
|
@@ -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;
|
package/dist/approval-types.js
CHANGED
|
@@ -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';
|
package/dist/host-runtime.d.ts
CHANGED
|
@@ -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';
|
|
@@ -23,13 +23,31 @@ export interface HostInboundParticipantContext {
|
|
|
23
23
|
}
|
|
24
24
|
type HostInboundMessage = {
|
|
25
25
|
id?: string | null;
|
|
26
|
+
senderId?: string | null;
|
|
27
|
+
senderName?: string | null;
|
|
26
28
|
text?: string | null;
|
|
27
29
|
contentType?: CanonMessage['contentType'] | null;
|
|
28
30
|
attachments?: CanonMessage['attachments'];
|
|
29
31
|
senderType?: CanonMessage['senderType'];
|
|
30
32
|
mentions?: string[] | null;
|
|
31
33
|
contactCard?: CanonMessage['contactCard'];
|
|
34
|
+
replyTo?: string | null;
|
|
35
|
+
replyToPosition?: number | null;
|
|
36
|
+
deleted?: boolean | null;
|
|
32
37
|
};
|
|
38
|
+
export interface CanonReplyContext {
|
|
39
|
+
messageId: string;
|
|
40
|
+
senderId?: string | null;
|
|
41
|
+
senderName?: string | null;
|
|
42
|
+
senderType?: CanonMessage['senderType'] | null;
|
|
43
|
+
text?: string | null;
|
|
44
|
+
contentType?: CanonMessage['contentType'] | null;
|
|
45
|
+
attachments?: CanonMessage['attachments'];
|
|
46
|
+
contactCard?: CanonMessage['contactCard'];
|
|
47
|
+
body: string;
|
|
48
|
+
replyToPosition?: number | null;
|
|
49
|
+
found: boolean;
|
|
50
|
+
}
|
|
33
51
|
export declare function buildCanonHostPrompt(input: {
|
|
34
52
|
hostLabel: string;
|
|
35
53
|
content: string;
|
|
@@ -37,9 +55,11 @@ export declare function buildCanonHostPrompt(input: {
|
|
|
37
55
|
participantContext: HostInboundParticipantContext;
|
|
38
56
|
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
39
57
|
selfContexts?: MessageCreatedPayload['selfContexts'];
|
|
58
|
+
replyContext?: CanonReplyContext | null;
|
|
40
59
|
buildInboundContextLines: (context: HostInboundParticipantContext) => string[];
|
|
41
60
|
sessionContextLines?: string[];
|
|
42
61
|
}): string;
|
|
62
|
+
export declare function buildCanonReplyContextLines(replyContext: CanonReplyContext | null): string[];
|
|
43
63
|
/**
|
|
44
64
|
* Render the **text portion** of an inbound Canon message. Images are
|
|
45
65
|
* referenced by short placeholders — their actual bytes are delivered to the
|
|
@@ -60,12 +80,18 @@ export declare function renderCanonHostInboundContent(message: HostInboundMessag
|
|
|
60
80
|
durationMs?: number;
|
|
61
81
|
index: number;
|
|
62
82
|
}>): string;
|
|
83
|
+
export declare function resolveCanonReplyContext(input: {
|
|
84
|
+
message: HostInboundMessage;
|
|
85
|
+
messages?: ReadonlyArray<HostInboundMessage> | null;
|
|
86
|
+
}): CanonReplyContext | null;
|
|
63
87
|
export declare function buildHydratedInboundContext(input: {
|
|
64
88
|
agentId: string;
|
|
89
|
+
conversationId: string;
|
|
65
90
|
conversation: CanonConversation | null;
|
|
66
91
|
page?: CanonMessagesPage | null;
|
|
67
92
|
activeSelfContextId?: string | null;
|
|
68
93
|
selfContexts?: MessageCreatedPayload['selfContexts'];
|
|
94
|
+
provenance?: MessageCreatedPayload['provenance'];
|
|
69
95
|
message: HostInboundMessage;
|
|
70
96
|
senderName: string;
|
|
71
97
|
isOwner: boolean;
|
|
@@ -78,6 +104,8 @@ export declare function buildHydratedInboundContext(input: {
|
|
|
78
104
|
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
79
105
|
activeSelfContextId: string | null;
|
|
80
106
|
selfContexts: NonNullable<MessageCreatedPayload['selfContexts']>;
|
|
107
|
+
provenance: CanonRuntimeProvenance;
|
|
108
|
+
replyContext: CanonReplyContext | null;
|
|
81
109
|
hydratedFromPage: boolean;
|
|
82
110
|
};
|
|
83
111
|
export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
|
package/dist/host-runtime.js
CHANGED
|
@@ -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({
|
|
@@ -29,11 +30,32 @@ export function buildCanonHostPrompt(input) {
|
|
|
29
30
|
? ['Canon session state:', ...input.sessionContextLines]
|
|
30
31
|
: []),
|
|
31
32
|
`Conversation ID: ${input.conversationId}`,
|
|
33
|
+
...buildCanonReplyContextLines(input.replyContext ?? null),
|
|
32
34
|
'',
|
|
33
35
|
'New Canon message:',
|
|
34
36
|
input.content,
|
|
35
37
|
].join('\n');
|
|
36
38
|
}
|
|
39
|
+
export function buildCanonReplyContextLines(replyContext) {
|
|
40
|
+
if (!replyContext)
|
|
41
|
+
return [];
|
|
42
|
+
const sender = replyContext.senderName || replyContext.senderId || 'unknown sender';
|
|
43
|
+
const senderType = replyContext.senderType ? `, ${replyContext.senderType}` : '';
|
|
44
|
+
const position = replyContext.replyToPosition != null
|
|
45
|
+
? ` at ${formatReplyPosition(replyContext.replyToPosition)}`
|
|
46
|
+
: '';
|
|
47
|
+
const header = replyContext.found
|
|
48
|
+
? `This message is replying to ${sender}${senderType} (message ${replyContext.messageId}${position}).`
|
|
49
|
+
: `This message is replying to message ${replyContext.messageId}${position}, but that message was not present in fetched history.`;
|
|
50
|
+
return [
|
|
51
|
+
'',
|
|
52
|
+
'Reply context:',
|
|
53
|
+
header,
|
|
54
|
+
...(replyContext.found
|
|
55
|
+
? ['Replied message:', replyContext.body]
|
|
56
|
+
: []),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
37
59
|
/**
|
|
38
60
|
* Render the **text portion** of an inbound Canon message. Images are
|
|
39
61
|
* referenced by short placeholders — their actual bytes are delivered to the
|
|
@@ -62,6 +84,35 @@ export function renderCanonHostInboundContent(message, materialized) {
|
|
|
62
84
|
const rendered = [...placeholders, body].filter(Boolean).join('\n');
|
|
63
85
|
return rendered || '[Empty message]';
|
|
64
86
|
}
|
|
87
|
+
export function resolveCanonReplyContext(input) {
|
|
88
|
+
const replyTo = normalizeOptionalString(input.message.replyTo);
|
|
89
|
+
if (!replyTo)
|
|
90
|
+
return null;
|
|
91
|
+
const referenced = input.messages
|
|
92
|
+
?.find((message) => message.id === replyTo && message.deleted !== true)
|
|
93
|
+
?? null;
|
|
94
|
+
if (!referenced) {
|
|
95
|
+
return {
|
|
96
|
+
messageId: replyTo,
|
|
97
|
+
body: '',
|
|
98
|
+
replyToPosition: input.message.replyToPosition ?? null,
|
|
99
|
+
found: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
messageId: replyTo,
|
|
104
|
+
senderId: referenced.senderId ?? null,
|
|
105
|
+
senderName: referenced.senderName ?? null,
|
|
106
|
+
senderType: referenced.senderType ?? null,
|
|
107
|
+
text: referenced.text ?? null,
|
|
108
|
+
contentType: referenced.contentType ?? null,
|
|
109
|
+
attachments: referenced.attachments ?? [],
|
|
110
|
+
...(referenced.contactCard ? { contactCard: referenced.contactCard } : {}),
|
|
111
|
+
body: renderCanonHostInboundContent(referenced),
|
|
112
|
+
replyToPosition: input.message.replyToPosition ?? null,
|
|
113
|
+
found: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
65
116
|
function describeContactCard(card) {
|
|
66
117
|
const parts = [`${card.userType} · userId: ${card.userId}`];
|
|
67
118
|
if (card.ownerName)
|
|
@@ -88,7 +139,8 @@ function describeContactCard(card) {
|
|
|
88
139
|
}
|
|
89
140
|
function describeAttachment(attachment, materialized) {
|
|
90
141
|
if (attachment.kind === 'image') {
|
|
91
|
-
|
|
142
|
+
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
143
|
+
return `[Image attached${ref}]`;
|
|
92
144
|
}
|
|
93
145
|
if (attachment.kind === 'audio') {
|
|
94
146
|
const durationMs = materialized?.durationMs ?? attachment.durationMs;
|
|
@@ -101,6 +153,16 @@ function describeAttachment(attachment, materialized) {
|
|
|
101
153
|
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
102
154
|
return `[File: ${label}${ref}]`;
|
|
103
155
|
}
|
|
156
|
+
function formatReplyPosition(positionMs) {
|
|
157
|
+
if (!Number.isFinite(positionMs) || positionMs < 0)
|
|
158
|
+
return `${positionMs}ms`;
|
|
159
|
+
const totalSeconds = Math.floor(positionMs / 1000);
|
|
160
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
161
|
+
const seconds = totalSeconds % 60;
|
|
162
|
+
return minutes > 0
|
|
163
|
+
? `${minutes}:${seconds.toString().padStart(2, '0')}`
|
|
164
|
+
: `${seconds}s`;
|
|
165
|
+
}
|
|
104
166
|
export function buildHydratedInboundContext(input) {
|
|
105
167
|
const history = buildParticipationHistorySnapshot(input.page?.messages ?? [], input.agentId);
|
|
106
168
|
const activeSelfContextId = resolveMessageActiveSelfContextId({
|
|
@@ -112,6 +174,21 @@ export function buildHydratedInboundContext(input) {
|
|
|
112
174
|
...(input.page?.selfContexts ?? []),
|
|
113
175
|
...(input.selfContexts ?? []),
|
|
114
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
|
+
});
|
|
115
192
|
const groupContext = buildCanonGroupContext({
|
|
116
193
|
conversation: input.conversation,
|
|
117
194
|
messages: [
|
|
@@ -140,8 +217,13 @@ export function buildHydratedInboundContext(input) {
|
|
|
140
217
|
currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
|
|
141
218
|
},
|
|
142
219
|
behavior: input.page?.behavior ?? input.conversation?.behavior,
|
|
143
|
-
activeSelfContextId:
|
|
220
|
+
activeSelfContextId: resolvedActiveSelfContextId,
|
|
144
221
|
selfContexts: activeSelfContexts,
|
|
222
|
+
provenance,
|
|
223
|
+
replyContext: resolveCanonReplyContext({
|
|
224
|
+
message: input.message,
|
|
225
|
+
messages: input.page?.messages ?? [],
|
|
226
|
+
}),
|
|
145
227
|
hydratedFromPage: input.page != null,
|
|
146
228
|
};
|
|
147
229
|
}
|
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';
|
|
@@ -32,8 +33,8 @@ export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, build
|
|
|
32
33
|
export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecutionEnvironment, SessionWorkspaceConfig, } from './execution-environment.js';
|
|
33
34
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, readRuntimeActivity, writeRuntimeActivity, removeRuntimeActivityItem, clearRuntimeActivity, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
34
35
|
export type { AgentSessionSnapshotPatch, RTDBClientHandle, RuntimeActivityPayloadData, RuntimeInfoPayloadData, SessionStatePayload, TurnStatePayload, } from './rtdb-rest.js';
|
|
35
|
-
export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
36
|
-
export type { HostInboundParticipantContext, } from './host-runtime.js';
|
|
36
|
+
export { buildCanonHostPrompt, buildCanonReplyContextLines, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveCanonReplyContext, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
37
|
+
export type { CanonReplyContext, HostInboundParticipantContext, } from './host-runtime.js';
|
|
37
38
|
export { buildCanonGroupContext, buildCanonKnownRecentParticipants, buildCompactGroupContextLines, diffCanonMemberIds, } from './group-context.js';
|
|
38
39
|
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
39
40
|
export type { RuntimeStatePublisher, RuntimeStatePublisherOptions, RuntimeStreamingPayload, ClearRuntimeActivityOptions, } from './runtime-state-publisher.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';
|
|
@@ -31,7 +32,7 @@ export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, build
|
|
|
31
32
|
// RTDB REST helpers (token exchange, session state, generic read/write)
|
|
32
33
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, readRuntimeActivity, writeRuntimeActivity, removeRuntimeActivityItem, clearRuntimeActivity, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
33
34
|
// Runtime host plumbing
|
|
34
|
-
export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
35
|
+
export { buildCanonHostPrompt, buildCanonReplyContextLines, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveCanonReplyContext, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
35
36
|
export { buildCanonGroupContext, buildCanonKnownRecentParticipants, buildCompactGroupContextLines, diffCanonMemberIds, } from './group-context.js';
|
|
36
37
|
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
37
38
|
// Message formatting (LLM-facing text projection)
|
package/dist/message-format.js
CHANGED
|
@@ -15,26 +15,14 @@ export function formatCanonMessageAsText(message) {
|
|
|
15
15
|
const cardText = formatContactCard(message.contactCard);
|
|
16
16
|
return trimmedText ? `${cardText}\n${trimmedText}` : cardText;
|
|
17
17
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const seconds = typeof attachment.durationMs === 'number'
|
|
24
|
-
? ` ${Math.round(attachment.durationMs / 1000)}s`
|
|
25
|
-
: '';
|
|
26
|
-
return trimmedText ? `[audio${seconds}] ${trimmedText}` : `[audio${seconds}]`;
|
|
27
|
-
}
|
|
28
|
-
if (attachment?.kind === 'file') {
|
|
29
|
-
const label = attachment.fileName?.trim() || 'file';
|
|
30
|
-
return trimmedText ? `[${label}] ${trimmedText}` : `[${label}]`;
|
|
18
|
+
const attachmentLabels = (message.attachments ?? [])
|
|
19
|
+
.filter((attachment) => Boolean(attachment.url))
|
|
20
|
+
.map(formatAttachment);
|
|
21
|
+
if (attachmentLabels.length > 0) {
|
|
22
|
+
return [...attachmentLabels, trimmedText].filter(Boolean).join('\n');
|
|
31
23
|
}
|
|
32
24
|
return trimmedText || '[message]';
|
|
33
25
|
}
|
|
34
|
-
function pickPrimaryAttachment(attachments) {
|
|
35
|
-
const first = attachments?.[0];
|
|
36
|
-
return first?.url ? first : null;
|
|
37
|
-
}
|
|
38
26
|
function formatContactCard(card) {
|
|
39
27
|
const displayName = card.displayName?.trim() || 'Unknown';
|
|
40
28
|
const parts = [card.userType, `userId: ${card.userId}`];
|
|
@@ -44,3 +32,16 @@ function formatContactCard(card) {
|
|
|
44
32
|
parts.push(`about: ${card.about}`);
|
|
45
33
|
return `[Contact card] "${displayName}" — ${parts.join(' · ')}`;
|
|
46
34
|
}
|
|
35
|
+
function formatAttachment(attachment) {
|
|
36
|
+
if (attachment.kind === 'image') {
|
|
37
|
+
return '[image]';
|
|
38
|
+
}
|
|
39
|
+
if (attachment.kind === 'audio') {
|
|
40
|
+
const seconds = typeof attachment.durationMs === 'number'
|
|
41
|
+
? ` ${Math.round(attachment.durationMs / 1000)}s`
|
|
42
|
+
: '';
|
|
43
|
+
return `[audio${seconds}]`;
|
|
44
|
+
}
|
|
45
|
+
const label = attachment.fileName?.trim() || 'file';
|
|
46
|
+
return `[${label}]`;
|
|
47
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -8,12 +8,12 @@ export const EXECUTION_MODE_CONTROL_OPTIONS = [
|
|
|
8
8
|
{
|
|
9
9
|
value: 'worktree',
|
|
10
10
|
label: 'Isolated worktree',
|
|
11
|
-
description: '
|
|
11
|
+
description: 'Best-effort git worktree for this conversation. This is not a security sandbox; if unavailable, Canon may fall back to the shared project.',
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
value: 'locked',
|
|
15
15
|
label: 'Use shared project',
|
|
16
|
-
description: 'Runs directly in the selected project folder.
|
|
16
|
+
description: 'Runs directly in the selected project folder. File changes happen there.',
|
|
17
17
|
},
|
|
18
18
|
];
|
|
19
19
|
export function buildRuntimeWorkspaceControlOptions(workspaces) {
|
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;
|
|
@@ -616,6 +639,7 @@ export interface SessionConfig {
|
|
|
616
639
|
export interface PermissionModeOption {
|
|
617
640
|
value: string;
|
|
618
641
|
label: string;
|
|
642
|
+
description?: string;
|
|
619
643
|
}
|
|
620
644
|
export declare const CLAUDE_PERMISSION_MODE_OPTIONS: readonly [{
|
|
621
645
|
readonly value: "default";
|