@canonmsg/core 0.11.0 → 0.13.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/agent-profiles.d.ts +31 -2
- package/dist/agent-profiles.js +145 -10
- package/dist/agent-resolver.d.ts +12 -0
- package/dist/agent-resolver.js +36 -20
- package/dist/browser.d.ts +1 -1
- package/dist/client.d.ts +15 -3
- package/dist/client.js +94 -4
- package/dist/index.d.ts +9 -7
- package/dist/index.js +6 -4
- package/dist/local-runtime-catalog.d.ts +65 -0
- package/dist/local-runtime-catalog.js +200 -0
- package/dist/message-format.js +0 -2
- package/dist/registration.d.ts +9 -1
- package/dist/registration.js +27 -4
- package/dist/stream.d.ts +5 -1
- package/dist/stream.js +26 -0
- package/dist/turn-protocol.d.ts +18 -0
- package/dist/turn-protocol.js +14 -0
- package/dist/types.d.ts +85 -8
- package/package.json +1 -1
package/dist/agent-profiles.d.ts
CHANGED
|
@@ -2,20 +2,49 @@
|
|
|
2
2
|
* Shared agent profile management — loading, locking, and resolution.
|
|
3
3
|
* Used by both host.ts and server.ts.
|
|
4
4
|
*/
|
|
5
|
+
import type { AgentClientType } from './types.js';
|
|
5
6
|
export interface AgentProfile {
|
|
6
7
|
apiKey: string;
|
|
7
8
|
agentId: string;
|
|
8
9
|
agentName: string;
|
|
9
10
|
registeredAt: string;
|
|
11
|
+
clientType?: AgentClientType;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
streamUrl?: string;
|
|
14
|
+
rtdbUrl?: string;
|
|
15
|
+
runtimeId?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ProfileLockHandle {
|
|
18
|
+
profile: string;
|
|
19
|
+
pid: number;
|
|
20
|
+
token: string;
|
|
21
|
+
release(): void;
|
|
10
22
|
}
|
|
11
23
|
export declare const CANON_DIR: string;
|
|
12
24
|
export declare const AGENTS_PATH: string;
|
|
13
25
|
export declare const LOCKS_DIR: string;
|
|
26
|
+
export declare const PENDING_REGISTRATIONS_PATH: string;
|
|
14
27
|
export declare function loadProfiles(): Record<string, AgentProfile>;
|
|
28
|
+
export declare function saveProfiles(profiles: Record<string, AgentProfile>): void;
|
|
29
|
+
export declare function upsertAgentProfile(name: string, profile: AgentProfile): Record<string, AgentProfile>;
|
|
15
30
|
export declare function isProcessAlive(pid: number): boolean;
|
|
16
31
|
export declare function isProfileLocked(profile: string): {
|
|
17
32
|
locked: boolean;
|
|
18
33
|
pid?: number;
|
|
19
34
|
};
|
|
20
|
-
export declare function acquireLock(profile: string):
|
|
21
|
-
export declare function releaseLock(profile: string): void;
|
|
35
|
+
export declare function acquireLock(profile: string): ProfileLockHandle;
|
|
36
|
+
export declare function releaseLock(profile: string, token?: string): void;
|
|
37
|
+
export interface PendingRegistration {
|
|
38
|
+
profile: string;
|
|
39
|
+
clientType?: AgentClientType;
|
|
40
|
+
localRegistrationId: string;
|
|
41
|
+
requestId?: string;
|
|
42
|
+
pollToken?: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function loadPendingRegistrations(): Record<string, PendingRegistration>;
|
|
47
|
+
export declare function savePendingRegistrations(pending: Record<string, PendingRegistration>): void;
|
|
48
|
+
export declare function getOrCreatePendingRegistration(profile: string, clientType?: AgentClientType): PendingRegistration;
|
|
49
|
+
export declare function updatePendingRegistration(profile: string, patch: Partial<Pick<PendingRegistration, 'requestId' | 'pollToken' | 'clientType'>>): PendingRegistration;
|
|
50
|
+
export declare function clearPendingRegistration(profile: string): void;
|
package/dist/agent-profiles.js
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* Shared agent profile management — loading, locking, and resolution.
|
|
3
3
|
* Used by both host.ts and server.ts.
|
|
4
4
|
*/
|
|
5
|
-
import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
6
|
-
import {
|
|
5
|
+
import { readFileSync, renameSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { closeSync, openSync } from 'node:fs';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
7
9
|
import { homedir } from 'node:os';
|
|
8
10
|
// ── Paths ────────────────────────────────────────────────────────────
|
|
9
|
-
export const CANON_DIR =
|
|
11
|
+
export const CANON_DIR = process.env.CANON_HOME
|
|
12
|
+
? resolve(process.env.CANON_HOME)
|
|
13
|
+
: join(homedir(), '.canon');
|
|
10
14
|
export const AGENTS_PATH = join(CANON_DIR, 'agents.json');
|
|
11
15
|
export const LOCKS_DIR = join(CANON_DIR, 'locks');
|
|
16
|
+
export const PENDING_REGISTRATIONS_PATH = join(CANON_DIR, 'pending-registrations.json');
|
|
12
17
|
// ── Profile loading ──────────────────────────────────────────────────
|
|
13
18
|
export function loadProfiles() {
|
|
14
19
|
try {
|
|
@@ -18,6 +23,24 @@ export function loadProfiles() {
|
|
|
18
23
|
return {};
|
|
19
24
|
}
|
|
20
25
|
}
|
|
26
|
+
function writeJsonFile(path, value, mode = 0o600) {
|
|
27
|
+
mkdirSync(CANON_DIR, { recursive: true });
|
|
28
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
29
|
+
writeFileSync(tmpPath, JSON.stringify(value, null, 2), { mode });
|
|
30
|
+
renameSync(tmpPath, path);
|
|
31
|
+
}
|
|
32
|
+
export function saveProfiles(profiles) {
|
|
33
|
+
writeJsonFile(AGENTS_PATH, profiles, 0o600);
|
|
34
|
+
}
|
|
35
|
+
export function upsertAgentProfile(name, profile) {
|
|
36
|
+
const profileName = name.trim();
|
|
37
|
+
if (!profileName)
|
|
38
|
+
throw new Error('Profile name is required');
|
|
39
|
+
const profiles = loadProfiles();
|
|
40
|
+
profiles[profileName] = profile;
|
|
41
|
+
saveProfiles(profiles);
|
|
42
|
+
return profiles;
|
|
43
|
+
}
|
|
21
44
|
// ── Process locking ──────────────────────────────────────────────────
|
|
22
45
|
export function isProcessAlive(pid) {
|
|
23
46
|
try {
|
|
@@ -31,8 +54,9 @@ export function isProcessAlive(pid) {
|
|
|
31
54
|
export function isProfileLocked(profile) {
|
|
32
55
|
const lockPath = join(LOCKS_DIR, `${profile}.lock`);
|
|
33
56
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
57
|
+
const lock = parseLock(readFileSync(lockPath, 'utf-8'));
|
|
58
|
+
const pid = lock?.pid;
|
|
59
|
+
if (pid && isProcessAlive(pid))
|
|
36
60
|
return { locked: true, pid };
|
|
37
61
|
// Stale lock — clean it up
|
|
38
62
|
try {
|
|
@@ -45,19 +69,130 @@ export function isProfileLocked(profile) {
|
|
|
45
69
|
}
|
|
46
70
|
return { locked: false };
|
|
47
71
|
}
|
|
72
|
+
function parseLock(raw) {
|
|
73
|
+
const trimmed = raw.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
return null;
|
|
76
|
+
const legacyPid = Number.parseInt(trimmed, 10);
|
|
77
|
+
if (Number.isFinite(legacyPid) && String(legacyPid) === trimmed) {
|
|
78
|
+
return { pid: legacyPid };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const data = JSON.parse(trimmed);
|
|
82
|
+
return typeof data.pid === 'number'
|
|
83
|
+
? { pid: data.pid, token: typeof data.token === 'string' ? data.token : undefined }
|
|
84
|
+
: null;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
48
90
|
export function acquireLock(profile) {
|
|
49
91
|
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
50
92
|
const lockPath = join(LOCKS_DIR, `${profile}.lock`);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
93
|
+
const token = randomUUID();
|
|
94
|
+
const payload = JSON.stringify({
|
|
95
|
+
pid: process.pid,
|
|
96
|
+
token,
|
|
97
|
+
acquiredAt: new Date().toISOString(),
|
|
98
|
+
});
|
|
99
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
100
|
+
try {
|
|
101
|
+
const fd = openSync(lockPath, 'wx', 0o600);
|
|
102
|
+
try {
|
|
103
|
+
writeFileSync(fd, payload);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
closeSync(fd);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
profile,
|
|
110
|
+
pid: process.pid,
|
|
111
|
+
token,
|
|
112
|
+
release: () => releaseLock(profile, token),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error?.code !== 'EEXIST')
|
|
117
|
+
throw error;
|
|
118
|
+
const check = isProfileLocked(profile);
|
|
119
|
+
if (check.locked) {
|
|
120
|
+
throw new Error(`Agent "${profile}" is in use by another session (PID ${check.pid})`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
54
123
|
}
|
|
55
|
-
|
|
124
|
+
throw new Error(`Agent "${profile}" lock could not be acquired`);
|
|
56
125
|
}
|
|
57
|
-
export function releaseLock(profile) {
|
|
126
|
+
export function releaseLock(profile, token) {
|
|
58
127
|
const lockPath = join(LOCKS_DIR, `${profile}.lock`);
|
|
128
|
+
if (token) {
|
|
129
|
+
try {
|
|
130
|
+
const lock = parseLock(readFileSync(lockPath, 'utf-8'));
|
|
131
|
+
if (lock?.token && lock.token !== token)
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
59
138
|
try {
|
|
60
139
|
unlinkSync(lockPath);
|
|
61
140
|
}
|
|
62
141
|
catch { }
|
|
63
142
|
}
|
|
143
|
+
export function loadPendingRegistrations() {
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(readFileSync(PENDING_REGISTRATIONS_PATH, 'utf-8'));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function savePendingRegistrations(pending) {
|
|
152
|
+
writeJsonFile(PENDING_REGISTRATIONS_PATH, pending, 0o600);
|
|
153
|
+
}
|
|
154
|
+
export function getOrCreatePendingRegistration(profile, clientType) {
|
|
155
|
+
const now = new Date().toISOString();
|
|
156
|
+
const pending = loadPendingRegistrations();
|
|
157
|
+
const existing = pending[profile];
|
|
158
|
+
if (existing) {
|
|
159
|
+
const next = { ...existing, clientType, updatedAt: now };
|
|
160
|
+
pending[profile] = next;
|
|
161
|
+
savePendingRegistrations(pending);
|
|
162
|
+
return next;
|
|
163
|
+
}
|
|
164
|
+
const created = {
|
|
165
|
+
profile,
|
|
166
|
+
clientType,
|
|
167
|
+
localRegistrationId: randomUUID(),
|
|
168
|
+
createdAt: now,
|
|
169
|
+
updatedAt: now,
|
|
170
|
+
};
|
|
171
|
+
pending[profile] = created;
|
|
172
|
+
savePendingRegistrations(pending);
|
|
173
|
+
return created;
|
|
174
|
+
}
|
|
175
|
+
export function updatePendingRegistration(profile, patch) {
|
|
176
|
+
const pending = loadPendingRegistrations();
|
|
177
|
+
const existing = pending[profile] ?? {
|
|
178
|
+
profile,
|
|
179
|
+
localRegistrationId: randomUUID(),
|
|
180
|
+
createdAt: new Date().toISOString(),
|
|
181
|
+
updatedAt: new Date().toISOString(),
|
|
182
|
+
};
|
|
183
|
+
const next = {
|
|
184
|
+
...existing,
|
|
185
|
+
...patch,
|
|
186
|
+
updatedAt: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
pending[profile] = next;
|
|
189
|
+
savePendingRegistrations(pending);
|
|
190
|
+
return next;
|
|
191
|
+
}
|
|
192
|
+
export function clearPendingRegistration(profile) {
|
|
193
|
+
const pending = loadPendingRegistrations();
|
|
194
|
+
if (pending[profile]) {
|
|
195
|
+
delete pending[profile];
|
|
196
|
+
savePendingRegistrations(pending);
|
|
197
|
+
}
|
|
198
|
+
}
|
package/dist/agent-resolver.d.ts
CHANGED
|
@@ -10,16 +10,26 @@
|
|
|
10
10
|
* 2. CANON_AGENT env var (named profile with lock)
|
|
11
11
|
* 3. Auto-select from profiles (first unlocked, with lock)
|
|
12
12
|
*/
|
|
13
|
+
import { type ProfileLockHandle } from './agent-profiles.js';
|
|
14
|
+
import type { AgentClientType } from './types.js';
|
|
13
15
|
export interface ResolvedAgent {
|
|
14
16
|
apiKey: string;
|
|
15
17
|
agentId?: string;
|
|
16
18
|
profile: string | null;
|
|
19
|
+
agentName?: string;
|
|
20
|
+
clientType?: AgentClientType;
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
streamUrl?: string;
|
|
23
|
+
rtdbUrl?: string;
|
|
24
|
+
lockHandle?: ProfileLockHandle;
|
|
17
25
|
}
|
|
18
26
|
/** Get the currently locked profile name (for cleanup on shutdown). */
|
|
19
27
|
export declare function getActiveProfile(): string | null;
|
|
28
|
+
export declare function getActiveProfileLock(): ProfileLockHandle | null;
|
|
20
29
|
export declare function resolveCanonProfile(name: string, opts?: {
|
|
21
30
|
logPrefix?: string;
|
|
22
31
|
lock?: boolean;
|
|
32
|
+
expectedClientType?: AgentClientType;
|
|
23
33
|
}): ResolvedAgent;
|
|
24
34
|
/**
|
|
25
35
|
* Resolve Canon agent credentials.
|
|
@@ -30,4 +40,6 @@ export declare function resolveCanonProfile(name: string, opts?: {
|
|
|
30
40
|
*/
|
|
31
41
|
export declare function resolveCanonAgent(opts?: {
|
|
32
42
|
logPrefix?: string;
|
|
43
|
+
expectedClientType?: AgentClientType;
|
|
44
|
+
lock?: boolean;
|
|
33
45
|
}): ResolvedAgent;
|
package/dist/agent-resolver.js
CHANGED
|
@@ -12,10 +12,17 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { loadProfiles, isProfileLocked, acquireLock, } from './agent-profiles.js';
|
|
14
14
|
let activeResolvedProfile = null;
|
|
15
|
+
let activeLockHandle = null;
|
|
15
16
|
/** Get the currently locked profile name (for cleanup on shutdown). */
|
|
16
17
|
export function getActiveProfile() {
|
|
17
18
|
return activeResolvedProfile;
|
|
18
19
|
}
|
|
20
|
+
export function getActiveProfileLock() {
|
|
21
|
+
return activeLockHandle;
|
|
22
|
+
}
|
|
23
|
+
function profileMatches(profile, expectedClientType) {
|
|
24
|
+
return !expectedClientType || !profile.clientType || profile.clientType === expectedClientType;
|
|
25
|
+
}
|
|
19
26
|
export function resolveCanonProfile(name, opts) {
|
|
20
27
|
const prefix = opts?.logPrefix ?? '[canon]';
|
|
21
28
|
const profileName = name.trim();
|
|
@@ -27,13 +34,25 @@ export function resolveCanonProfile(name, opts) {
|
|
|
27
34
|
if (!profile) {
|
|
28
35
|
throw new Error(`${prefix} Profile "${profileName}" not found in ~/.canon/agents.json`);
|
|
29
36
|
}
|
|
37
|
+
if (!profileMatches(profile, opts?.expectedClientType)) {
|
|
38
|
+
throw new Error(`${prefix} Profile "${profileName}" is registered for ${profile.clientType}, not ${opts?.expectedClientType}`);
|
|
39
|
+
}
|
|
40
|
+
let lockHandle;
|
|
30
41
|
if (opts?.lock) {
|
|
31
|
-
acquireLock(profileName);
|
|
42
|
+
lockHandle = acquireLock(profileName);
|
|
43
|
+
activeResolvedProfile = profileName;
|
|
44
|
+
activeLockHandle = lockHandle;
|
|
32
45
|
}
|
|
33
46
|
return {
|
|
34
47
|
apiKey: profile.apiKey,
|
|
35
48
|
agentId: profile.agentId,
|
|
49
|
+
agentName: profile.agentName,
|
|
50
|
+
clientType: profile.clientType,
|
|
51
|
+
baseUrl: profile.baseUrl,
|
|
52
|
+
streamUrl: profile.streamUrl,
|
|
53
|
+
rtdbUrl: profile.rtdbUrl,
|
|
36
54
|
profile: profileName,
|
|
55
|
+
lockHandle,
|
|
37
56
|
};
|
|
38
57
|
}
|
|
39
58
|
/**
|
|
@@ -45,6 +64,7 @@ export function resolveCanonProfile(name, opts) {
|
|
|
45
64
|
*/
|
|
46
65
|
export function resolveCanonAgent(opts) {
|
|
47
66
|
const prefix = opts?.logPrefix ?? '[canon]';
|
|
67
|
+
const shouldLock = opts?.lock ?? true;
|
|
48
68
|
// 1. Explicit API key (highest priority, no lock)
|
|
49
69
|
// CANON_API_KEY is set by the host wrapper or user environment.
|
|
50
70
|
// CANON_PLUGIN_API_KEY is set by plugin.json from user_config.
|
|
@@ -58,34 +78,30 @@ export function resolveCanonAgent(opts) {
|
|
|
58
78
|
if (process.env.CANON_AGENT) {
|
|
59
79
|
const resolved = resolveCanonProfile(process.env.CANON_AGENT, {
|
|
60
80
|
logPrefix: prefix,
|
|
61
|
-
lock:
|
|
81
|
+
lock: shouldLock,
|
|
82
|
+
expectedClientType: opts?.expectedClientType,
|
|
62
83
|
});
|
|
63
|
-
activeResolvedProfile = resolved.profile;
|
|
64
84
|
return resolved;
|
|
65
85
|
}
|
|
66
86
|
// 3. Auto-select from profiles
|
|
67
87
|
if (names.length === 0) {
|
|
68
|
-
throw new Error(`${prefix} No agents registered. Run
|
|
88
|
+
throw new Error(`${prefix} No agents registered. Run a runtime register command first.`);
|
|
89
|
+
}
|
|
90
|
+
const viableNames = names.filter((name) => profileMatches(profiles[name], opts?.expectedClientType));
|
|
91
|
+
if (viableNames.length === 0) {
|
|
92
|
+
throw new Error(`${prefix} No ${opts?.expectedClientType ?? 'matching'} profiles found in ~/.canon/agents.json.`);
|
|
69
93
|
}
|
|
70
|
-
if (
|
|
71
|
-
const resolved = resolveCanonProfile(
|
|
94
|
+
if (viableNames.length === 1) {
|
|
95
|
+
const resolved = resolveCanonProfile(viableNames[0], {
|
|
72
96
|
logPrefix: prefix,
|
|
73
|
-
lock:
|
|
97
|
+
lock: shouldLock,
|
|
98
|
+
expectedClientType: opts?.expectedClientType,
|
|
74
99
|
});
|
|
75
|
-
activeResolvedProfile = resolved.profile;
|
|
76
100
|
return resolved;
|
|
77
101
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const resolved = resolveCanonProfile(name, {
|
|
82
|
-
logPrefix: prefix,
|
|
83
|
-
lock: true,
|
|
84
|
-
});
|
|
85
|
-
activeResolvedProfile = resolved.profile;
|
|
86
|
-
console.error(`${prefix} Auto-selected agent "${name}" (${profiles[name].agentName})`);
|
|
87
|
-
return resolved;
|
|
88
|
-
}
|
|
102
|
+
const unlocked = viableNames.filter((name) => !isProfileLocked(name).locked);
|
|
103
|
+
if (unlocked.length === 0) {
|
|
104
|
+
throw new Error(`${prefix} All matching agents are in use by other sessions. Run canon-necromance list to inspect them.`);
|
|
89
105
|
}
|
|
90
|
-
throw new Error(`${prefix}
|
|
106
|
+
throw new Error(`${prefix} Multiple profiles are available (${unlocked.join(', ')}). Set CANON_AGENT=<profile> or use canon-necromance revive <profile>.`);
|
|
91
107
|
}
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
|
|
2
2
|
export { resolveCanonBaseUrl } from './base-url.js';
|
|
3
3
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
4
|
-
export type { AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentSessionWorkSessionSummary, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContactRequest, CanonContactRequestStatus, ContactApprovedPayload, ContactRequestPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
|
|
4
|
+
export type { AddMemberResult, AgentCapabilities, AgentClientType, AgentSessionSnapshot, AgentSessionWorkSessionSummary, AgentRuntime, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonStreamEvent, CreateContactRequestResult, MediaAttachment, MediaAttachmentKind, ModelOption, PermissionModeOption, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, RuntimeUpdatedPayload, RuntimeInfoPayload, ResolvedAdmission, SessionConfig, TurnUpdatedPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
|
|
5
5
|
export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
|
|
6
6
|
export type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
|
|
7
7
|
export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CanonMessage, type CanonConversation, type CanonContactRequest, type CanonMessagesPage, type AgentContext, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
|
|
1
|
+
import { type CanonMessage, type CanonConversation, type CanonContact, type CanonContactRequest, type CanonMessagesPage, type CanonResolveAdmissionResult, type AgentContext, type AddMemberResult, type CreateContactRequestResult, type MediaAttachment, type SendMessageOptions, type CreateConversationOptions, type RegistrationStatus, type SetStreamingOptions } from './types.js';
|
|
2
2
|
import type { CanonResolvedWorkSession, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions } from './work-session.js';
|
|
3
3
|
import type { InboundDisposition } from './turn-protocol.js';
|
|
4
4
|
/**
|
|
@@ -16,6 +16,9 @@ export declare class CanonClient {
|
|
|
16
16
|
agentId: string;
|
|
17
17
|
}>;
|
|
18
18
|
getAgentMe(): Promise<AgentContext>;
|
|
19
|
+
rotateAgentKey(): Promise<{
|
|
20
|
+
apiKey: string;
|
|
21
|
+
}>;
|
|
19
22
|
getConversations(): Promise<CanonConversation[]>;
|
|
20
23
|
getMessages(conversationId: string, limit?: number, before?: string): Promise<CanonMessage[]>;
|
|
21
24
|
getMessagesPage(conversationId: string, limit?: number, before?: string): Promise<CanonMessagesPage>;
|
|
@@ -31,6 +34,12 @@ export declare class CanonClient {
|
|
|
31
34
|
}>;
|
|
32
35
|
createContactRequest(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
|
|
33
36
|
listContactRequests(): Promise<CanonContactRequest[]>;
|
|
37
|
+
listContacts(): Promise<CanonContact[]>;
|
|
38
|
+
getContact(contactId: string): Promise<CanonContact | null>;
|
|
39
|
+
deleteContact(contactId: string): Promise<void>;
|
|
40
|
+
blockUser(userId: string): Promise<void>;
|
|
41
|
+
unblockUser(userId: string): Promise<void>;
|
|
42
|
+
resolveAdmission(targetUserId: string): Promise<CanonResolveAdmissionResult>;
|
|
34
43
|
uploadMedia(conversationId: string, data: string, mimeType: string, fileName?: string): Promise<{
|
|
35
44
|
url: string;
|
|
36
45
|
attachment: MediaAttachment;
|
|
@@ -42,7 +51,7 @@ export declare class CanonClient {
|
|
|
42
51
|
leaveConversation(conversationId: string): Promise<void>;
|
|
43
52
|
react(conversationId: string, messageId: string, emoji: string): Promise<void>;
|
|
44
53
|
updateConversationName(conversationId: string, name: string): Promise<void>;
|
|
45
|
-
addMember(conversationId: string, userId: string): Promise<
|
|
54
|
+
addMember(conversationId: string, userId: string): Promise<AddMemberResult>;
|
|
46
55
|
removeMember(conversationId: string, userId: string): Promise<void>;
|
|
47
56
|
setStreaming(options: SetStreamingOptions): Promise<void>;
|
|
48
57
|
clearStreaming(conversationId: string): Promise<void>;
|
|
@@ -55,10 +64,13 @@ export declare class CanonClient {
|
|
|
55
64
|
avatarUrl?: string;
|
|
56
65
|
clientType?: string;
|
|
57
66
|
requestedAgentId?: string;
|
|
67
|
+
localRegistrationId?: string;
|
|
58
68
|
}): Promise<{
|
|
59
69
|
requestId: string;
|
|
70
|
+
pollToken?: string;
|
|
60
71
|
}>;
|
|
61
|
-
static checkStatus(baseUrl: string | undefined, requestId: string): Promise<RegistrationStatus>;
|
|
72
|
+
static checkStatus(baseUrl: string | undefined, requestId: string, pollToken?: string): Promise<RegistrationStatus>;
|
|
73
|
+
static ackRegistrationStatus(baseUrl: string | undefined, requestId: string, pollToken?: string): Promise<void>;
|
|
62
74
|
}
|
|
63
75
|
export declare class CanonApiError extends Error {
|
|
64
76
|
status: number;
|
package/dist/client.js
CHANGED
|
@@ -34,6 +34,15 @@ export class CanonClient {
|
|
|
34
34
|
throw new CanonApiError(res.status, await res.text());
|
|
35
35
|
return res.json();
|
|
36
36
|
}
|
|
37
|
+
async rotateAgentKey() {
|
|
38
|
+
const res = await fetch(`${this.baseUrl}/agents/keys/rotate`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: this.authHeaders(),
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
throw new CanonApiError(res.status, await res.text());
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
37
46
|
async getConversations() {
|
|
38
47
|
const res = await fetch(`${this.baseUrl}/conversations`, {
|
|
39
48
|
headers: this.authHeaders(),
|
|
@@ -142,7 +151,7 @@ export class CanonClient {
|
|
|
142
151
|
});
|
|
143
152
|
const body = await res.text();
|
|
144
153
|
if (res.status === 200) {
|
|
145
|
-
return { status: 'open'
|
|
154
|
+
return { status: 'open' };
|
|
146
155
|
}
|
|
147
156
|
if (res.status === 201) {
|
|
148
157
|
const data = parseJsonBody(body);
|
|
@@ -167,6 +176,61 @@ export class CanonClient {
|
|
|
167
176
|
const data = await res.json();
|
|
168
177
|
return data.requests;
|
|
169
178
|
}
|
|
179
|
+
async listContacts() {
|
|
180
|
+
const res = await fetch(`${this.baseUrl}/contacts`, {
|
|
181
|
+
headers: this.authHeaders(),
|
|
182
|
+
});
|
|
183
|
+
if (!res.ok)
|
|
184
|
+
throw new CanonApiError(res.status, await res.text());
|
|
185
|
+
const data = await res.json();
|
|
186
|
+
return data.contacts;
|
|
187
|
+
}
|
|
188
|
+
async getContact(contactId) {
|
|
189
|
+
const res = await fetch(`${this.baseUrl}/contacts/${contactId}`, {
|
|
190
|
+
headers: this.authHeaders(),
|
|
191
|
+
});
|
|
192
|
+
if (res.status === 404)
|
|
193
|
+
return null;
|
|
194
|
+
if (!res.ok)
|
|
195
|
+
throw new CanonApiError(res.status, await res.text());
|
|
196
|
+
return res.json();
|
|
197
|
+
}
|
|
198
|
+
async deleteContact(contactId) {
|
|
199
|
+
const res = await fetch(`${this.baseUrl}/contacts/${contactId}`, {
|
|
200
|
+
method: 'DELETE',
|
|
201
|
+
headers: this.authHeaders(),
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok)
|
|
204
|
+
throw new CanonApiError(res.status, await res.text());
|
|
205
|
+
}
|
|
206
|
+
async blockUser(userId) {
|
|
207
|
+
const res = await fetch(`${this.baseUrl}/users/block`, {
|
|
208
|
+
method: 'POST',
|
|
209
|
+
headers: this.authHeaders(),
|
|
210
|
+
body: JSON.stringify({ userId }),
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok)
|
|
213
|
+
throw new CanonApiError(res.status, await res.text());
|
|
214
|
+
}
|
|
215
|
+
async unblockUser(userId) {
|
|
216
|
+
const res = await fetch(`${this.baseUrl}/users/unblock`, {
|
|
217
|
+
method: 'POST',
|
|
218
|
+
headers: this.authHeaders(),
|
|
219
|
+
body: JSON.stringify({ userId }),
|
|
220
|
+
});
|
|
221
|
+
if (!res.ok)
|
|
222
|
+
throw new CanonApiError(res.status, await res.text());
|
|
223
|
+
}
|
|
224
|
+
async resolveAdmission(targetUserId) {
|
|
225
|
+
const res = await fetch(`${this.baseUrl}/admission/resolve`, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: this.authHeaders(),
|
|
228
|
+
body: JSON.stringify({ targetUserId }),
|
|
229
|
+
});
|
|
230
|
+
if (!res.ok)
|
|
231
|
+
throw new CanonApiError(res.status, await res.text());
|
|
232
|
+
return res.json();
|
|
233
|
+
}
|
|
170
234
|
async uploadMedia(conversationId, data, mimeType, fileName) {
|
|
171
235
|
const res = await fetch(`${this.baseUrl}/media/upload`, {
|
|
172
236
|
method: 'POST',
|
|
@@ -243,8 +307,20 @@ export class CanonClient {
|
|
|
243
307
|
headers: this.authHeaders(),
|
|
244
308
|
body: JSON.stringify({ userId }),
|
|
245
309
|
});
|
|
310
|
+
const body = await res.text();
|
|
246
311
|
if (!res.ok)
|
|
247
|
-
throw new CanonApiError(res.status,
|
|
312
|
+
throw new CanonApiError(res.status, body);
|
|
313
|
+
// 202 means the target requires approval — server created a contact
|
|
314
|
+
// request (kind: 'group_invite') routed to the approver. The actual
|
|
315
|
+
// group join happens when that request is approved.
|
|
316
|
+
if (res.status === 202) {
|
|
317
|
+
const data = parseJsonBody(body);
|
|
318
|
+
if (typeof data.requestId === 'string') {
|
|
319
|
+
return { status: 'pending', requestId: data.requestId };
|
|
320
|
+
}
|
|
321
|
+
throw new CanonApiError(202, 'Server returned 202 without a requestId');
|
|
322
|
+
}
|
|
323
|
+
return { status: 'added' };
|
|
248
324
|
}
|
|
249
325
|
async removeMember(conversationId, userId) {
|
|
250
326
|
const res = await fetch(`${this.baseUrl}/conversations/${conversationId}/members/${userId}`, {
|
|
@@ -293,13 +369,27 @@ export class CanonClient {
|
|
|
293
369
|
throw new CanonApiError(res.status, await res.text());
|
|
294
370
|
return res.json();
|
|
295
371
|
}
|
|
296
|
-
static async checkStatus(baseUrl, requestId) {
|
|
372
|
+
static async checkStatus(baseUrl, requestId, pollToken) {
|
|
297
373
|
const url = baseUrl || DEFAULT_BASE_URL;
|
|
298
|
-
const res = await fetch(`${url}/agents/status/${requestId}
|
|
374
|
+
const res = await fetch(`${url}/agents/status/${requestId}`, {
|
|
375
|
+
headers: pollToken ? { 'x-canon-poll-token': pollToken } : undefined,
|
|
376
|
+
});
|
|
299
377
|
if (!res.ok)
|
|
300
378
|
throw new CanonApiError(res.status, await res.text());
|
|
301
379
|
return res.json();
|
|
302
380
|
}
|
|
381
|
+
static async ackRegistrationStatus(baseUrl, requestId, pollToken) {
|
|
382
|
+
const url = baseUrl || DEFAULT_BASE_URL;
|
|
383
|
+
const res = await fetch(`${url}/agents/status/${requestId}/ack`, {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
headers: {
|
|
386
|
+
'Content-Type': 'application/json',
|
|
387
|
+
...(pollToken ? { 'x-canon-poll-token': pollToken } : {}),
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
if (!res.ok)
|
|
391
|
+
throw new CanonApiError(res.status, await res.text());
|
|
392
|
+
}
|
|
303
393
|
}
|
|
304
394
|
export class CanonApiError extends Error {
|
|
305
395
|
status;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AGENT_CAPABILITIES, CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
|
|
2
|
-
export type { AgentCapabilities, AgentClientType, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContactRequest, CanonContactRequestStatus, ContactApprovedPayload, ContactRequestPayload, CanonMessage, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, AgentSessionWorkSessionSummary, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
|
|
2
|
+
export type { AddMemberResult, AgentCapabilities, AgentClientType, CanonControlAvailability, CanonControlDescriptor, CanonControlLiveBehavior, CanonControlSelectionPolicy, CanonControlValue, CanonContact, CanonContactRequest, CanonContactRequestStatus, CanonResolveAdmissionResult, ContactAddedPayload, ContactApprovedPayload, ContactCardPayload, ContactRemovedPayload, ContactRequestPayload, ContactSource, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonMessage, CanonConversation, CanonMessagesPage, CreateContactRequestResult, AgentContext, CanonStreamEvent, AgentSessionSnapshot, AgentSessionWorkSessionSummary, ResolvedAdmission, MediaAttachment, MediaAttachmentKind, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload, SendMessageOptions, CreateConversationOptions, RegistrationInput, RegistrationResult, RegistrationStatus, StreamingStatus, SetStreamingOptions, SessionControl, SessionState, SessionConfig, AgentRuntime, CanonRuntimeDescriptor, CanonRuntimeActionAvailability, CanonRuntimeActionCategory, CanonRuntimeActionDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionPlacement, CanonRuntimeExecutionMetadata, CanonRuntimeInventory, CanonRuntimeInventoryEntry, CanonRuntimeStreamingMode, CanonRuntimeStatusItem, CanonRuntimeSurfaceMode, CanonWorkspaceRootMetadata, ModelOption, PermissionModeOption, RuntimeInfoPayload, WorkspaceOption, WorkspaceOptionSource, } from './types.js';
|
|
3
3
|
export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CreateWorkSessionOptions, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
|
|
4
4
|
export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
|
|
5
5
|
export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, buildWorkspaceRootId, discoverWorkspaceProjects, } from './workspace-discovery.js';
|
|
@@ -10,9 +10,9 @@ export { CanonStream } from './stream.js';
|
|
|
10
10
|
export type { StreamHandler } from './stream.js';
|
|
11
11
|
export type { PolicyRole, ParticipationStyle, RepresentationMode, PermissionLevel, ConversationScope, AgentBehaviorSettings, Participant, Relationship, ContextOverlay, BehaviorProfile, AdmissionPolicy, RuntimeControlPolicy, ActionApprovalPolicy, ParticipationPolicy, WorkSession, ResolvedPolicy, ResolvedTurnEligibility, ResolvedAgentBehaviorPolicy, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationDecisionInput, ParticipationDecision, } from './policy.js';
|
|
12
12
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
13
|
-
export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
|
14
|
-
export type { DeliveryIntent, TurnMessageSemantics, InboundDisposition, TurnLifecycleState, RuntimeCapabilities, TurnState, TurnMetadata, TriggerDecision, } from './turn-protocol.js';
|
|
15
|
-
export { registerAndWaitForApproval } from './registration.js';
|
|
13
|
+
export { DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
|
14
|
+
export type { DeliveryIntent, TurnMessageSemantics, InboundDisposition, TurnLifecycleState, RuntimeCapabilities, HostAdmissionActionCapabilities, TurnState, TurnMetadata, TriggerDecision, } from './turn-protocol.js';
|
|
15
|
+
export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistrationRequest, waitForRegistrationApproval, } from './registration.js';
|
|
16
16
|
export { ApprovalManager } from './approval-manager.js';
|
|
17
17
|
export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
|
|
18
18
|
export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
|
|
@@ -21,10 +21,12 @@ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, b
|
|
|
21
21
|
export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
|
|
22
22
|
export { createStreamingHelper } from './streaming.js';
|
|
23
23
|
export type { RTDBHandle, RTDBRef, ServerTimestamp, StreamingHelperOptions, StreamingNode } from './streaming.js';
|
|
24
|
-
export { loadProfiles, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
|
|
25
|
-
export type { AgentProfile } from './agent-profiles.js';
|
|
26
|
-
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile } from './agent-resolver.js';
|
|
24
|
+
export { clearPendingRegistration, getOrCreatePendingRegistration, loadPendingRegistrations, loadProfiles, savePendingRegistrations, saveProfiles, updatePendingRegistration, upsertAgentProfile, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
|
|
25
|
+
export type { AgentProfile, PendingRegistration, ProfileLockHandle } from './agent-profiles.js';
|
|
26
|
+
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile, getActiveProfileLock } from './agent-resolver.js';
|
|
27
27
|
export type { ResolvedAgent } from './agent-resolver.js';
|
|
28
|
+
export { RUNTIMES_DIR, buildLocalRuntimeId, clearRuntimeSessionState, describeProfileLock, heartbeatLocalRuntimeEntry, listLocalRuntimeEntries, loadRuntimeSessionState, markLocalRuntimeStopped, readLocalRuntimeEntry, removeLocalRuntimeEntry, saveRuntimeSessionState, upsertLocalRuntimeEntry, } from './local-runtime-catalog.js';
|
|
29
|
+
export type { LocalRuntimeCatalogEntry, LocalRuntimeKind, LocalRuntimeReviveCapability, LocalRuntimeSessionState, LocalRuntimeStatus, } from './local-runtime-catalog.js';
|
|
28
30
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
29
31
|
export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecutionEnvironment, SessionWorkspaceConfig, } from './execution-environment.js';
|
|
30
32
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
package/dist/index.js
CHANGED
|
@@ -9,9 +9,9 @@ export { buildAgentSessionSnapshot } from './agent-session.js';
|
|
|
9
9
|
export { CanonStream } from './stream.js';
|
|
10
10
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
11
11
|
// Turn protocol
|
|
12
|
-
export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
|
12
|
+
export { DEFAULT_RUNTIME_CAPABILITIES, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
|
13
13
|
// Registration
|
|
14
|
-
export { registerAndWaitForApproval } from './registration.js';
|
|
14
|
+
export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistrationRequest, waitForRegistrationApproval, } from './registration.js';
|
|
15
15
|
// Approval
|
|
16
16
|
export { ApprovalManager } from './approval-manager.js';
|
|
17
17
|
export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
|
|
@@ -20,9 +20,11 @@ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, b
|
|
|
20
20
|
// Streaming (RTDB helpers)
|
|
21
21
|
export { createStreamingHelper } from './streaming.js';
|
|
22
22
|
// Agent profiles (loading, locking, resolution)
|
|
23
|
-
export { loadProfiles, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
|
|
23
|
+
export { clearPendingRegistration, getOrCreatePendingRegistration, loadPendingRegistrations, loadProfiles, savePendingRegistrations, saveProfiles, updatePendingRegistration, upsertAgentProfile, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
|
|
24
24
|
// Agent resolver
|
|
25
|
-
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile } from './agent-resolver.js';
|
|
25
|
+
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile, getActiveProfileLock } from './agent-resolver.js';
|
|
26
|
+
// Local runtime catalog
|
|
27
|
+
export { RUNTIMES_DIR, buildLocalRuntimeId, clearRuntimeSessionState, describeProfileLock, heartbeatLocalRuntimeEntry, listLocalRuntimeEntries, loadRuntimeSessionState, markLocalRuntimeStopped, readLocalRuntimeEntry, removeLocalRuntimeEntry, saveRuntimeSessionState, upsertLocalRuntimeEntry, } from './local-runtime-catalog.js';
|
|
26
28
|
// Execution environments for host-mode coding sessions
|
|
27
29
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
28
30
|
// RTDB REST helpers (token exchange, session state, generic read/write)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { AgentClientType, ExecutionEnvironmentMode } from './types.js';
|
|
2
|
+
export type LocalRuntimeKind = AgentClientType | 'sdk';
|
|
3
|
+
export type LocalRuntimeStatus = 'running' | 'offline' | 'stale' | 'manual' | 'embedded';
|
|
4
|
+
export type LocalRuntimeReviveCapability = 'revivable' | 'manual' | 'embedded' | 'missing-profile';
|
|
5
|
+
export interface LocalRuntimeSessionState {
|
|
6
|
+
conversationId: string;
|
|
7
|
+
workspaceId?: string;
|
|
8
|
+
baseCwd: string;
|
|
9
|
+
executionMode?: ExecutionEnvironmentMode;
|
|
10
|
+
threadId?: string;
|
|
11
|
+
claudeSessionId?: string;
|
|
12
|
+
lastInboundMessageId?: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LocalRuntimeCatalogEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
runtime: LocalRuntimeKind;
|
|
18
|
+
profile: string | null;
|
|
19
|
+
agentId?: string;
|
|
20
|
+
agentName?: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
baseCwd?: string;
|
|
23
|
+
workspaceRoots?: string[];
|
|
24
|
+
workspaces?: string[];
|
|
25
|
+
launchCommand: string[];
|
|
26
|
+
pid?: number;
|
|
27
|
+
status: LocalRuntimeStatus;
|
|
28
|
+
reviveCapability: LocalRuntimeReviveCapability;
|
|
29
|
+
surfaceMode?: 'host' | 'channel' | 'embedded';
|
|
30
|
+
lastStartedAt?: string;
|
|
31
|
+
lastHeartbeatAt?: string;
|
|
32
|
+
lastStoppedAt?: string;
|
|
33
|
+
lastAuthError?: string;
|
|
34
|
+
lastError?: string;
|
|
35
|
+
sessions?: Record<string, LocalRuntimeSessionState>;
|
|
36
|
+
}
|
|
37
|
+
export declare const RUNTIMES_DIR: string;
|
|
38
|
+
export declare function buildLocalRuntimeId(input: {
|
|
39
|
+
runtime: LocalRuntimeKind;
|
|
40
|
+
profile?: string | null;
|
|
41
|
+
cwd: string;
|
|
42
|
+
launchCommand?: string[];
|
|
43
|
+
}): string;
|
|
44
|
+
export declare function readLocalRuntimeEntry(id: string): LocalRuntimeCatalogEntry | null;
|
|
45
|
+
export declare function upsertLocalRuntimeEntry(entry: LocalRuntimeCatalogEntry): LocalRuntimeCatalogEntry;
|
|
46
|
+
export declare function heartbeatLocalRuntimeEntry(id: string, patch?: Partial<LocalRuntimeCatalogEntry>): LocalRuntimeCatalogEntry | null;
|
|
47
|
+
export declare function markLocalRuntimeStopped(id: string, patch?: Partial<LocalRuntimeCatalogEntry>): LocalRuntimeCatalogEntry | null;
|
|
48
|
+
export declare function removeLocalRuntimeEntry(id: string): void;
|
|
49
|
+
export declare function listLocalRuntimeEntries(options?: {
|
|
50
|
+
runtime?: LocalRuntimeKind;
|
|
51
|
+
}): LocalRuntimeCatalogEntry[];
|
|
52
|
+
export declare function loadRuntimeSessionState(runtimeId: string, input: {
|
|
53
|
+
conversationId: string;
|
|
54
|
+
baseCwd: string;
|
|
55
|
+
executionMode?: ExecutionEnvironmentMode;
|
|
56
|
+
workspaceId?: string;
|
|
57
|
+
}): LocalRuntimeSessionState | null;
|
|
58
|
+
export declare function saveRuntimeSessionState(runtimeId: string, input: Omit<LocalRuntimeSessionState, 'updatedAt'>): LocalRuntimeSessionState;
|
|
59
|
+
export declare function clearRuntimeSessionState(runtimeId: string, input: {
|
|
60
|
+
conversationId: string;
|
|
61
|
+
baseCwd?: string;
|
|
62
|
+
executionMode?: ExecutionEnvironmentMode;
|
|
63
|
+
workspaceId?: string;
|
|
64
|
+
}): void;
|
|
65
|
+
export declare function describeProfileLock(profile: string): string;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { CANON_DIR, isProcessAlive, isProfileLocked, loadProfiles } from './agent-profiles.js';
|
|
5
|
+
export const RUNTIMES_DIR = join(CANON_DIR, 'runtimes');
|
|
6
|
+
const LEGACY_CODEX_SESSIONS_PATH = join(CANON_DIR, 'codex-sessions.json');
|
|
7
|
+
function shortHash(value) {
|
|
8
|
+
return createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
9
|
+
}
|
|
10
|
+
function safeRuntimeFileName(id) {
|
|
11
|
+
return `${shortHash(id)}.json`;
|
|
12
|
+
}
|
|
13
|
+
function runtimePath(id) {
|
|
14
|
+
return join(RUNTIMES_DIR, safeRuntimeFileName(id));
|
|
15
|
+
}
|
|
16
|
+
function writeJsonAtomic(path, value) {
|
|
17
|
+
mkdirSync(RUNTIMES_DIR, { recursive: true });
|
|
18
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
19
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
20
|
+
renameSync(tmp, path);
|
|
21
|
+
}
|
|
22
|
+
export function buildLocalRuntimeId(input) {
|
|
23
|
+
const profile = input.profile ?? 'manual';
|
|
24
|
+
return `${input.runtime}:${profile}:${shortHash(`${resolve(input.cwd)}:${(input.launchCommand ?? []).join('\0')}`)}`;
|
|
25
|
+
}
|
|
26
|
+
export function readLocalRuntimeEntry(id) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(readFileSync(runtimePath(id), 'utf-8'));
|
|
29
|
+
return parsed.id === id ? parsed : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function upsertLocalRuntimeEntry(entry) {
|
|
36
|
+
const existing = readLocalRuntimeEntry(entry.id);
|
|
37
|
+
const next = {
|
|
38
|
+
...existing,
|
|
39
|
+
...entry,
|
|
40
|
+
sessions: {
|
|
41
|
+
...(existing?.sessions ?? {}),
|
|
42
|
+
...(entry.sessions ?? {}),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
writeJsonAtomic(runtimePath(entry.id), next);
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
export function heartbeatLocalRuntimeEntry(id, patch = {}) {
|
|
49
|
+
const existing = readLocalRuntimeEntry(id);
|
|
50
|
+
if (!existing)
|
|
51
|
+
return null;
|
|
52
|
+
return upsertLocalRuntimeEntry({
|
|
53
|
+
...existing,
|
|
54
|
+
...patch,
|
|
55
|
+
id,
|
|
56
|
+
status: 'running',
|
|
57
|
+
pid: patch.pid ?? process.pid,
|
|
58
|
+
lastHeartbeatAt: new Date().toISOString(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function markLocalRuntimeStopped(id, patch = {}) {
|
|
62
|
+
const existing = readLocalRuntimeEntry(id);
|
|
63
|
+
if (!existing)
|
|
64
|
+
return null;
|
|
65
|
+
return upsertLocalRuntimeEntry({
|
|
66
|
+
...existing,
|
|
67
|
+
...patch,
|
|
68
|
+
id,
|
|
69
|
+
status: existing.reviveCapability === 'embedded' ? 'embedded' : 'offline',
|
|
70
|
+
pid: undefined,
|
|
71
|
+
lastStoppedAt: new Date().toISOString(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function removeLocalRuntimeEntry(id) {
|
|
75
|
+
try {
|
|
76
|
+
unlinkSync(runtimePath(id));
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
function deriveRuntimeState(entry) {
|
|
81
|
+
let status = entry.status;
|
|
82
|
+
if (status === 'running' && entry.pid && !isProcessAlive(entry.pid)) {
|
|
83
|
+
status = 'stale';
|
|
84
|
+
}
|
|
85
|
+
const profiles = loadProfiles();
|
|
86
|
+
let reviveCapability = entry.reviveCapability;
|
|
87
|
+
if (entry.profile && !profiles[entry.profile]) {
|
|
88
|
+
reviveCapability = 'missing-profile';
|
|
89
|
+
}
|
|
90
|
+
return { ...entry, status, reviveCapability };
|
|
91
|
+
}
|
|
92
|
+
export function listLocalRuntimeEntries(options = {}) {
|
|
93
|
+
if (!existsSync(RUNTIMES_DIR))
|
|
94
|
+
return [];
|
|
95
|
+
const entries = [];
|
|
96
|
+
for (const name of readdirSync(RUNTIMES_DIR)) {
|
|
97
|
+
if (!name.endsWith('.json'))
|
|
98
|
+
continue;
|
|
99
|
+
try {
|
|
100
|
+
const entry = JSON.parse(readFileSync(join(RUNTIMES_DIR, name), 'utf-8'));
|
|
101
|
+
if (options.runtime && entry.runtime !== options.runtime)
|
|
102
|
+
continue;
|
|
103
|
+
entries.push(deriveRuntimeState(entry));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Ignore corrupt breadcrumbs; they should not block the manager.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return entries.sort((a, b) => {
|
|
110
|
+
const at = a.lastHeartbeatAt ?? a.lastStartedAt ?? a.lastStoppedAt ?? '';
|
|
111
|
+
const bt = b.lastHeartbeatAt ?? b.lastStartedAt ?? b.lastStoppedAt ?? '';
|
|
112
|
+
return bt.localeCompare(at);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function sessionKey(input) {
|
|
116
|
+
return shortHash([
|
|
117
|
+
input.conversationId,
|
|
118
|
+
resolve(input.baseCwd),
|
|
119
|
+
input.executionMode ?? 'unknown',
|
|
120
|
+
input.workspaceId ?? '',
|
|
121
|
+
].join('\0'));
|
|
122
|
+
}
|
|
123
|
+
export function loadRuntimeSessionState(runtimeId, input) {
|
|
124
|
+
migrateLegacyCodexSessions(runtimeId);
|
|
125
|
+
const entry = readLocalRuntimeEntry(runtimeId);
|
|
126
|
+
return entry?.sessions?.[sessionKey(input)] ?? null;
|
|
127
|
+
}
|
|
128
|
+
export function saveRuntimeSessionState(runtimeId, input) {
|
|
129
|
+
const entry = readLocalRuntimeEntry(runtimeId);
|
|
130
|
+
if (!entry) {
|
|
131
|
+
throw new Error(`Runtime catalog entry not found: ${runtimeId}`);
|
|
132
|
+
}
|
|
133
|
+
const key = sessionKey(input);
|
|
134
|
+
const existingState = entry.sessions?.[key];
|
|
135
|
+
const nextState = {
|
|
136
|
+
...existingState,
|
|
137
|
+
...input,
|
|
138
|
+
updatedAt: new Date().toISOString(),
|
|
139
|
+
};
|
|
140
|
+
upsertLocalRuntimeEntry({
|
|
141
|
+
...entry,
|
|
142
|
+
sessions: {
|
|
143
|
+
...(entry.sessions ?? {}),
|
|
144
|
+
[key]: nextState,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
return nextState;
|
|
148
|
+
}
|
|
149
|
+
export function clearRuntimeSessionState(runtimeId, input) {
|
|
150
|
+
const entry = readLocalRuntimeEntry(runtimeId);
|
|
151
|
+
if (!entry?.sessions)
|
|
152
|
+
return;
|
|
153
|
+
const sessions = { ...entry.sessions };
|
|
154
|
+
if (input.baseCwd) {
|
|
155
|
+
delete sessions[sessionKey({
|
|
156
|
+
conversationId: input.conversationId,
|
|
157
|
+
baseCwd: input.baseCwd,
|
|
158
|
+
executionMode: input.executionMode,
|
|
159
|
+
workspaceId: input.workspaceId,
|
|
160
|
+
})];
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
for (const [key, state] of Object.entries(sessions)) {
|
|
164
|
+
if (state.conversationId === input.conversationId)
|
|
165
|
+
delete sessions[key];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
upsertLocalRuntimeEntry({ ...entry, sessions });
|
|
169
|
+
}
|
|
170
|
+
function migrateLegacyCodexSessions(runtimeId) {
|
|
171
|
+
if (!existsSync(LEGACY_CODEX_SESSIONS_PATH))
|
|
172
|
+
return;
|
|
173
|
+
const entry = readLocalRuntimeEntry(runtimeId);
|
|
174
|
+
if (!entry || entry.runtime !== 'codex')
|
|
175
|
+
return;
|
|
176
|
+
try {
|
|
177
|
+
const legacy = JSON.parse(readFileSync(LEGACY_CODEX_SESSIONS_PATH, 'utf-8'));
|
|
178
|
+
const agentSessions = entry.agentId ? legacy.agents?.[entry.agentId] : null;
|
|
179
|
+
if (!agentSessions)
|
|
180
|
+
return;
|
|
181
|
+
const sessions = { ...(entry.sessions ?? {}) };
|
|
182
|
+
for (const [conversationId, state] of Object.entries(agentSessions)) {
|
|
183
|
+
const baseCwd = state.cwd;
|
|
184
|
+
sessions[sessionKey({ conversationId, baseCwd })] = {
|
|
185
|
+
conversationId,
|
|
186
|
+
baseCwd,
|
|
187
|
+
threadId: state.threadId,
|
|
188
|
+
updatedAt: state.updatedAt,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
upsertLocalRuntimeEntry({ ...entry, sessions });
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function describeProfileLock(profile) {
|
|
198
|
+
const lock = isProfileLocked(profile);
|
|
199
|
+
return lock.locked ? `locked by PID ${lock.pid}` : 'available';
|
|
200
|
+
}
|
package/dist/message-format.js
CHANGED
|
@@ -39,8 +39,6 @@ function formatContactCard(card) {
|
|
|
39
39
|
const parts = [card.userType, `userId: ${card.userId}`];
|
|
40
40
|
if (card.ownerName)
|
|
41
41
|
parts.push(`owner: ${card.ownerName}`);
|
|
42
|
-
if (card.accessLevel)
|
|
43
|
-
parts.push(`access: ${card.accessLevel}`);
|
|
44
42
|
if (card.about)
|
|
45
43
|
parts.push(`about: ${card.about}`);
|
|
46
44
|
return `[Contact card] "${displayName}" — ${parts.join(' · ')}`;
|
package/dist/registration.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ import type { RegistrationInput, RegistrationResult, RegistrationStatus } from '
|
|
|
8
8
|
* 3. On approval, returns the API key
|
|
9
9
|
*/
|
|
10
10
|
export declare function registerAndWaitForApproval(input: RegistrationInput, callbacks?: {
|
|
11
|
-
onSubmitted?: (requestId: string) => void;
|
|
11
|
+
onSubmitted?: (requestId: string, pollToken?: string) => void;
|
|
12
12
|
onPollUpdate?: (status: RegistrationStatus) => void;
|
|
13
13
|
}): Promise<RegistrationResult>;
|
|
14
|
+
export declare function submitRegistrationRequest(input: RegistrationInput): Promise<{
|
|
15
|
+
requestId: string;
|
|
16
|
+
pollToken?: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function waitForRegistrationApproval(baseUrl: string | undefined, requestId: string, pollToken?: string, callbacks?: {
|
|
19
|
+
onPollUpdate?: (status: RegistrationStatus) => void;
|
|
20
|
+
}): Promise<RegistrationResult>;
|
|
21
|
+
export declare function ackRegistrationApproval(baseUrl: string | undefined, requestId: string, pollToken?: string): Promise<void>;
|
package/dist/registration.js
CHANGED
|
@@ -10,7 +10,12 @@ const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
10
10
|
* 3. On approval, returns the API key
|
|
11
11
|
*/
|
|
12
12
|
export async function registerAndWaitForApproval(input, callbacks) {
|
|
13
|
-
const
|
|
13
|
+
const submitted = await submitRegistrationRequest(input);
|
|
14
|
+
callbacks?.onSubmitted?.(submitted.requestId, submitted.pollToken);
|
|
15
|
+
return waitForRegistrationApproval(input.baseUrl, submitted.requestId, submitted.pollToken, callbacks);
|
|
16
|
+
}
|
|
17
|
+
export async function submitRegistrationRequest(input) {
|
|
18
|
+
return CanonClient.register(input.baseUrl, {
|
|
14
19
|
name: input.name,
|
|
15
20
|
description: input.description,
|
|
16
21
|
ownerPhone: input.ownerPhone,
|
|
@@ -18,29 +23,47 @@ export async function registerAndWaitForApproval(input, callbacks) {
|
|
|
18
23
|
avatarUrl: input.avatarUrl,
|
|
19
24
|
clientType: input.clientType,
|
|
20
25
|
requestedAgentId: input.requestedAgentId,
|
|
26
|
+
localRegistrationId: input.localRegistrationId,
|
|
21
27
|
});
|
|
22
|
-
|
|
28
|
+
}
|
|
29
|
+
export async function waitForRegistrationApproval(baseUrl, requestId, pollToken, callbacks) {
|
|
23
30
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
24
31
|
while (Date.now() < deadline) {
|
|
25
32
|
await sleep(POLL_INTERVAL_MS);
|
|
26
|
-
const result = await CanonClient.checkStatus(
|
|
33
|
+
const result = await CanonClient.checkStatus(baseUrl, requestId, pollToken);
|
|
27
34
|
callbacks?.onPollUpdate?.(result);
|
|
28
35
|
if (result.status === 'approved') {
|
|
36
|
+
if (!result.apiKey) {
|
|
37
|
+
return {
|
|
38
|
+
status: 'timeout',
|
|
39
|
+
requestId,
|
|
40
|
+
pollToken,
|
|
41
|
+
agentId: result.agentId,
|
|
42
|
+
agentName: result.agentName,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
29
45
|
return {
|
|
30
46
|
status: 'approved',
|
|
31
47
|
apiKey: result.apiKey,
|
|
32
48
|
agentId: result.agentId,
|
|
33
49
|
agentName: result.agentName,
|
|
50
|
+
requestId,
|
|
51
|
+
pollToken,
|
|
34
52
|
};
|
|
35
53
|
}
|
|
36
54
|
if (result.status === 'rejected') {
|
|
37
55
|
return {
|
|
38
56
|
status: 'rejected',
|
|
39
57
|
agentName: result.agentName,
|
|
58
|
+
requestId,
|
|
59
|
+
pollToken,
|
|
40
60
|
};
|
|
41
61
|
}
|
|
42
62
|
}
|
|
43
|
-
return { status: 'timeout' };
|
|
63
|
+
return { status: 'timeout', requestId, pollToken };
|
|
64
|
+
}
|
|
65
|
+
export async function ackRegistrationApproval(baseUrl, requestId, pollToken) {
|
|
66
|
+
await CanonClient.ackRegistrationStatus(baseUrl, requestId, pollToken);
|
|
44
67
|
}
|
|
45
68
|
function sleep(ms) {
|
|
46
69
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { AgentContext, ContactApprovedPayload, ContactRequestPayload, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload } from './types.js';
|
|
1
|
+
import type { AgentContext, ContactAddedPayload, ContactApprovedPayload, ContactRemovedPayload, ContactRequestPayload, MessageCreatedPayload, TypingPayload, PresencePayload, RuntimeUpdatedPayload, TurnUpdatedPayload } from './types.js';
|
|
2
2
|
export type StreamHandler = {
|
|
3
3
|
onMessage: (payload: MessageCreatedPayload) => void;
|
|
4
4
|
onAgentContext?: (ctx: AgentContext) => void;
|
|
5
5
|
onContactRequest?: (payload: ContactRequestPayload) => void;
|
|
6
6
|
onContactApproved?: (payload: ContactApprovedPayload) => void;
|
|
7
|
+
onContactAdded?: (payload: ContactAddedPayload) => void;
|
|
8
|
+
onContactRemoved?: (payload: ContactRemovedPayload) => void;
|
|
7
9
|
onTyping?: (data: TypingPayload) => void;
|
|
8
10
|
onPresence?: (data: PresencePayload) => void;
|
|
9
11
|
onRuntimeUpdated?: (payload: RuntimeUpdatedPayload) => void;
|
|
@@ -54,6 +56,8 @@ export declare class CanonStream {
|
|
|
54
56
|
private handlePresence;
|
|
55
57
|
private handleContactRequest;
|
|
56
58
|
private handleContactApproved;
|
|
59
|
+
private handleContactAdded;
|
|
60
|
+
private handleContactRemoved;
|
|
57
61
|
private handleRuntimeUpdated;
|
|
58
62
|
private handleTurnUpdated;
|
|
59
63
|
private handleMessageDeleted;
|
package/dist/stream.js
CHANGED
|
@@ -148,6 +148,14 @@ export class CanonStream {
|
|
|
148
148
|
this.reconnectAttempt = 0;
|
|
149
149
|
this.handleContactApproved(data);
|
|
150
150
|
break;
|
|
151
|
+
case 'contact.added':
|
|
152
|
+
this.reconnectAttempt = 0;
|
|
153
|
+
this.handleContactAdded(data);
|
|
154
|
+
break;
|
|
155
|
+
case 'contact.removed':
|
|
156
|
+
this.reconnectAttempt = 0;
|
|
157
|
+
this.handleContactRemoved(data);
|
|
158
|
+
break;
|
|
151
159
|
case 'typing':
|
|
152
160
|
this.reconnectAttempt = 0;
|
|
153
161
|
this.handleTyping(data);
|
|
@@ -245,6 +253,24 @@ export class CanonStream {
|
|
|
245
253
|
// Ignore parse errors for non-critical events
|
|
246
254
|
}
|
|
247
255
|
}
|
|
256
|
+
handleContactAdded(raw) {
|
|
257
|
+
try {
|
|
258
|
+
const data = JSON.parse(raw);
|
|
259
|
+
this.handler.onContactAdded?.(data);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Ignore parse errors for non-critical events
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
handleContactRemoved(raw) {
|
|
266
|
+
try {
|
|
267
|
+
const data = JSON.parse(raw);
|
|
268
|
+
this.handler.onContactRemoved?.(data);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Ignore parse errors for non-critical events
|
|
272
|
+
}
|
|
273
|
+
}
|
|
248
274
|
handleRuntimeUpdated(raw) {
|
|
249
275
|
try {
|
|
250
276
|
const data = JSON.parse(raw);
|
package/dist/turn-protocol.d.ts
CHANGED
|
@@ -36,6 +36,24 @@ export interface TriggerDecision {
|
|
|
36
36
|
reason: string;
|
|
37
37
|
}
|
|
38
38
|
export declare const DEFAULT_RUNTIME_CAPABILITIES: RuntimeCapabilities;
|
|
39
|
+
/**
|
|
40
|
+
* Contact-graph and admission actions a Canon host runtime exposes through the
|
|
41
|
+
* agent SDK. Plugins consult this to decide which tools to register on the
|
|
42
|
+
* LLM's behalf — e.g., whether to advertise a `block_user` tool. A flag is
|
|
43
|
+
* `true` only when the runtime has both the SDK wrapper *and* a tool surface
|
|
44
|
+
* wired up that calls it. Until plugin tool wiring ships, runtimes should
|
|
45
|
+
* advertise `HOST_ADMISSION_ACTIONS_DISABLED` so consumers don't render UI
|
|
46
|
+
* for actions the runtime can't actually perform.
|
|
47
|
+
*/
|
|
48
|
+
export interface HostAdmissionActionCapabilities {
|
|
49
|
+
blockUser: boolean;
|
|
50
|
+
unblockUser: boolean;
|
|
51
|
+
removeContact: boolean;
|
|
52
|
+
requestContact: boolean;
|
|
53
|
+
reachOut: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare const HOST_ADMISSION_ACTION_CAPABILITIES: HostAdmissionActionCapabilities;
|
|
56
|
+
export declare const HOST_ADMISSION_ACTIONS_DISABLED: HostAdmissionActionCapabilities;
|
|
39
57
|
export declare function normalizeTurnMetadata(metadata: unknown): TurnMetadata | null;
|
|
40
58
|
export declare function normalizeTurnState(value: unknown): TurnState | null;
|
|
41
59
|
export declare function isTurnOpen(turnState: Pick<TurnState, 'state'> | null | undefined): boolean;
|
package/dist/turn-protocol.js
CHANGED
|
@@ -26,6 +26,20 @@ export const DEFAULT_RUNTIME_CAPABILITIES = {
|
|
|
26
26
|
supportsRequiresAction: false,
|
|
27
27
|
supportsNonFinalPermanentMessages: false,
|
|
28
28
|
};
|
|
29
|
+
export const HOST_ADMISSION_ACTION_CAPABILITIES = {
|
|
30
|
+
blockUser: true,
|
|
31
|
+
unblockUser: true,
|
|
32
|
+
removeContact: true,
|
|
33
|
+
requestContact: true,
|
|
34
|
+
reachOut: true,
|
|
35
|
+
};
|
|
36
|
+
export const HOST_ADMISSION_ACTIONS_DISABLED = {
|
|
37
|
+
blockUser: false,
|
|
38
|
+
unblockUser: false,
|
|
39
|
+
removeContact: false,
|
|
40
|
+
requestContact: false,
|
|
41
|
+
reachOut: false,
|
|
42
|
+
};
|
|
29
43
|
function isRecord(value) {
|
|
30
44
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
31
45
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export interface ContactCardPayload {
|
|
|
34
34
|
isActive?: boolean;
|
|
35
35
|
ownerId?: string;
|
|
36
36
|
ownerName?: string;
|
|
37
|
-
accessLevel?: 'open' | 'owner-only';
|
|
38
37
|
lifecycleState?: string;
|
|
39
38
|
}
|
|
40
39
|
export interface CanonMessage {
|
|
@@ -87,18 +86,40 @@ export interface CanonContactRequest {
|
|
|
87
86
|
requesterName: string;
|
|
88
87
|
requesterAvatarUrl: string | null;
|
|
89
88
|
targetId: string;
|
|
90
|
-
approverId
|
|
89
|
+
approverId: string;
|
|
91
90
|
message: string | null;
|
|
92
91
|
status: CanonContactRequestStatus;
|
|
92
|
+
/** 'dm' for one-to-one contact request, 'group_invite' for stranger-add. */
|
|
93
|
+
kind: 'dm' | 'group_invite';
|
|
94
|
+
groupContext?: {
|
|
95
|
+
conversationId: string;
|
|
96
|
+
groupName: string | null;
|
|
97
|
+
};
|
|
93
98
|
createdAt: string | null;
|
|
94
99
|
resolvedAt?: string | null;
|
|
95
100
|
expiresAt?: string | null;
|
|
96
101
|
}
|
|
97
102
|
export type ContactRequestPayload = CanonContactRequest;
|
|
98
103
|
export type ContactApprovedPayload = CanonContactRequest;
|
|
104
|
+
/**
|
|
105
|
+
* Outcome of `CanonClient.addMember` / `agent.addMember`. The REST endpoint
|
|
106
|
+
* returns 201 on immediate add and 202 on approval-required group invite.
|
|
107
|
+
* Both are success from the protocol's perspective — distinct outcomes for
|
|
108
|
+
* the caller.
|
|
109
|
+
*/
|
|
110
|
+
export type AddMemberResult = {
|
|
111
|
+
status: 'added';
|
|
112
|
+
} | {
|
|
113
|
+
status: 'pending';
|
|
114
|
+
requestId: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Successful outcomes of `createContactRequest`. Errors (denied,
|
|
118
|
+
* malformed input) come back as Firebase `HttpsError`s and are not
|
|
119
|
+
* represented here.
|
|
120
|
+
*/
|
|
99
121
|
export type CreateContactRequestResult = {
|
|
100
122
|
status: 'open';
|
|
101
|
-
requestId: null;
|
|
102
123
|
} | {
|
|
103
124
|
status: 'created';
|
|
104
125
|
requestId: string;
|
|
@@ -106,12 +127,50 @@ export type CreateContactRequestResult = {
|
|
|
106
127
|
status: 'duplicate';
|
|
107
128
|
requestId: string;
|
|
108
129
|
};
|
|
130
|
+
export type ContactSource = 'direct_add' | 'phone_book' | 'contact_request' | 'link' | 'qr' | 'group' | 'open_inbound_message'
|
|
131
|
+
/** Sentinel value emitted by stream-service when a doc lacks a source field. */
|
|
132
|
+
| 'unknown';
|
|
133
|
+
/** A single entry from the agent's `users/{agentId}/contacts/` subcollection. */
|
|
134
|
+
export interface CanonContact {
|
|
135
|
+
/** The contact's userId. */
|
|
136
|
+
id: string;
|
|
137
|
+
source: ContactSource;
|
|
138
|
+
addedAt: string | null;
|
|
139
|
+
displayNameOverride: string | null;
|
|
140
|
+
}
|
|
141
|
+
export type ContactAddedPayload = CanonContact;
|
|
142
|
+
export type ContactRemovedPayload = {
|
|
143
|
+
id: string;
|
|
144
|
+
};
|
|
145
|
+
export type ResolvedAdmissionState = 'self' | 'not-found' | 'allowed' | 'request-required' | 'pending-outbound' | 'blocked' | 'inactive' | 'owner-only';
|
|
146
|
+
export interface ResolvedAdmissionTargetSummary {
|
|
147
|
+
id: string;
|
|
148
|
+
displayName?: string;
|
|
149
|
+
avatarUrl?: string | null;
|
|
150
|
+
about?: string;
|
|
151
|
+
userType?: 'human' | 'ai_agent';
|
|
152
|
+
discoverable?: boolean;
|
|
153
|
+
inboundPolicy?: 'open' | 'approval-required' | 'owner-only';
|
|
154
|
+
groupJoinPolicy?: 'open' | 'approval-required' | 'owner-only';
|
|
155
|
+
}
|
|
156
|
+
export interface ResolvedTargetAdmissionPayload {
|
|
157
|
+
state: ResolvedAdmissionState;
|
|
158
|
+
canMessage: boolean;
|
|
159
|
+
canRequestContact: boolean;
|
|
160
|
+
isContact: boolean;
|
|
161
|
+
pendingRequestId?: string;
|
|
162
|
+
blockedByViewer?: boolean;
|
|
163
|
+
blockedByTarget?: boolean;
|
|
164
|
+
}
|
|
165
|
+
export interface CanonResolveAdmissionResult {
|
|
166
|
+
target: ResolvedAdmissionTargetSummary | null;
|
|
167
|
+
admission: ResolvedTargetAdmissionPayload;
|
|
168
|
+
}
|
|
109
169
|
export type AgentClientType = 'claude-code' | 'openclaw' | 'codex' | 'generic';
|
|
110
170
|
export interface ResolvedAdmission {
|
|
111
|
-
accessLevel: 'open' | 'owner-only';
|
|
112
171
|
discoverable: boolean;
|
|
113
|
-
inboundPolicy: 'open' | 'approval-required' | '
|
|
114
|
-
groupJoinPolicy: 'open' | 'approval-required' | '
|
|
172
|
+
inboundPolicy: 'open' | 'approval-required' | 'owner-only';
|
|
173
|
+
groupJoinPolicy: 'open' | 'approval-required' | 'owner-only';
|
|
115
174
|
}
|
|
116
175
|
/** Declares what session controls an agent type supports. */
|
|
117
176
|
export interface AgentCapabilities {
|
|
@@ -219,6 +278,13 @@ export interface CanonRuntimeDescriptor {
|
|
|
219
278
|
* means activity/tool state without live assistant text.
|
|
220
279
|
*/
|
|
221
280
|
streamingTextMode?: CanonRuntimeStreamingMode;
|
|
281
|
+
/**
|
|
282
|
+
* Contact-graph and admission actions this runtime exposes through the
|
|
283
|
+
* agent SDK. Plugins set this to advertise which tools they will surface
|
|
284
|
+
* to the LLM (e.g., block-user, request-contact). When omitted, no
|
|
285
|
+
* admission actions are advertised.
|
|
286
|
+
*/
|
|
287
|
+
admissionActions?: import('./turn-protocol.js').HostAdmissionActionCapabilities;
|
|
222
288
|
}
|
|
223
289
|
export interface CanonRuntimeExecutionMetadata {
|
|
224
290
|
resolvedWorkspaceLabel?: string | null;
|
|
@@ -267,7 +333,9 @@ export interface AgentContext {
|
|
|
267
333
|
description?: string | null;
|
|
268
334
|
ownerId: string;
|
|
269
335
|
ownerName: string;
|
|
270
|
-
|
|
336
|
+
discoverable: boolean;
|
|
337
|
+
inboundPolicy: 'open' | 'approval-required' | 'owner-only';
|
|
338
|
+
groupJoinPolicy: 'open' | 'approval-required' | 'owner-only';
|
|
271
339
|
/** Identifies the agent's client platform for UI feature detection */
|
|
272
340
|
clientType?: AgentClientType;
|
|
273
341
|
defaultBehavior?: ResolvedAgentBehaviorPolicy;
|
|
@@ -334,6 +402,12 @@ export type CanonStreamEvent = {
|
|
|
334
402
|
} | {
|
|
335
403
|
type: 'contact.approved';
|
|
336
404
|
payload: ContactApprovedPayload;
|
|
405
|
+
} | {
|
|
406
|
+
type: 'contact.added';
|
|
407
|
+
payload: ContactAddedPayload;
|
|
408
|
+
} | {
|
|
409
|
+
type: 'contact.removed';
|
|
410
|
+
payload: ContactRemovedPayload;
|
|
337
411
|
} | {
|
|
338
412
|
type: 'typing';
|
|
339
413
|
payload: TypingPayload;
|
|
@@ -531,15 +605,18 @@ export interface RegistrationInput {
|
|
|
531
605
|
baseUrl?: string;
|
|
532
606
|
clientType?: AgentClientType;
|
|
533
607
|
requestedAgentId?: string;
|
|
608
|
+
localRegistrationId?: string;
|
|
534
609
|
}
|
|
535
610
|
export interface RegistrationResult {
|
|
536
611
|
status: 'approved' | 'rejected' | 'timeout';
|
|
537
612
|
apiKey?: string;
|
|
538
613
|
agentId?: string;
|
|
539
614
|
agentName?: string;
|
|
615
|
+
requestId?: string;
|
|
616
|
+
pollToken?: string;
|
|
540
617
|
}
|
|
541
618
|
export interface RegistrationStatus {
|
|
542
|
-
status: 'pending' | 'approved' | 'rejected';
|
|
619
|
+
status: 'pending' | 'approving' | 'approved' | 'rejected';
|
|
543
620
|
agentName: string;
|
|
544
621
|
agentId?: string;
|
|
545
622
|
apiKey?: string;
|