@canonmsg/core 0.12.0 → 0.14.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 -0
- package/dist/browser.js +1 -0
- package/dist/client.d.ts +7 -1
- package/dist/client.js +25 -2
- package/dist/host-runtime.d.ts +118 -0
- package/dist/host-runtime.js +256 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.js +9 -3
- package/dist/local-runtime-catalog.d.ts +65 -0
- package/dist/local-runtime-catalog.js +200 -0
- package/dist/registration.d.ts +9 -1
- package/dist/registration.js +27 -4
- package/dist/rtdb-rest.d.ts +1 -1
- package/dist/runtime-descriptor.d.ts +50 -0
- package/dist/runtime-descriptor.js +128 -0
- package/dist/runtime-state-publisher.d.ts +31 -0
- package/dist/runtime-state-publisher.js +68 -0
- package/dist/types.d.ts +4 -1
- 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
|
@@ -7,6 +7,7 @@ export type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
|
|
|
7
7
|
export type { CanonResolvedWorkSession, CanonWorkSession, CanonWorkSessionContext, CanonWorkSessionConversationRole, CanonWorkSessionDisclosureMode, CanonWorkSessionParticipant, CanonWorkSessionStatus, CreateWorkSessionOptions, SendLinkedMessageOptions, SendLinkedMessageResult, UpdateWorkSessionConversationOptions, WorkSessionPromptRenderOptions, } from './work-session.js';
|
|
8
8
|
export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
|
|
9
9
|
export { buildAgentSessionSnapshot } from './agent-session.js';
|
|
10
|
+
export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
|
|
10
11
|
export type { AgentBehaviorSettings, ParticipationHistoryMessage, ParticipationHistorySnapshot, ParticipationStyle, ResolvedAgentBehaviorPolicy, } from './policy.js';
|
|
11
12
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
12
13
|
export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
package/dist/browser.js
CHANGED
|
@@ -4,6 +4,7 @@ export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_AP
|
|
|
4
4
|
export { EXECUTION_ENVIRONMENT_MODES, isExecutionEnvironmentMode, } from './execution-environment-mode.js';
|
|
5
5
|
export { buildWorkSessionPromptLines, buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
|
|
6
6
|
export { buildAgentSessionSnapshot } from './agent-session.js';
|
|
7
|
+
export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
|
|
7
8
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
8
9
|
export { DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, isTurnOpen, normalizeTurnMetadata, normalizeTurnState, resolveTurnMessageSemantics, shouldPromoteConversationMessage, shouldTriggerAgentTurn, } from './turn-protocol.js';
|
|
9
10
|
export { buildApprovalReply, buildApprovalRequest, buildApprovalOutcome, generateApprovalId, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
|
package/dist/client.d.ts
CHANGED
|
@@ -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>;
|
|
@@ -61,10 +64,13 @@ export declare class CanonClient {
|
|
|
61
64
|
avatarUrl?: string;
|
|
62
65
|
clientType?: string;
|
|
63
66
|
requestedAgentId?: string;
|
|
67
|
+
localRegistrationId?: string;
|
|
64
68
|
}): Promise<{
|
|
65
69
|
requestId: string;
|
|
70
|
+
pollToken?: string;
|
|
66
71
|
}>;
|
|
67
|
-
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>;
|
|
68
74
|
}
|
|
69
75
|
export declare class CanonApiError extends Error {
|
|
70
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(),
|
|
@@ -360,13 +369,27 @@ export class CanonClient {
|
|
|
360
369
|
throw new CanonApiError(res.status, await res.text());
|
|
361
370
|
return res.json();
|
|
362
371
|
}
|
|
363
|
-
static async checkStatus(baseUrl, requestId) {
|
|
372
|
+
static async checkStatus(baseUrl, requestId, pollToken) {
|
|
364
373
|
const url = baseUrl || DEFAULT_BASE_URL;
|
|
365
|
-
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
|
+
});
|
|
366
377
|
if (!res.ok)
|
|
367
378
|
throw new CanonApiError(res.status, await res.text());
|
|
368
379
|
return res.json();
|
|
369
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
|
+
}
|
|
370
393
|
}
|
|
371
394
|
export class CanonApiError extends Error {
|
|
372
395
|
status;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type AgentClientType, type AgentRuntime, type CanonConversation, type CanonMessage, type CanonMessagesPage, type MessageCreatedPayload } from './types.js';
|
|
2
|
+
import { type CanonClient } from './client.js';
|
|
3
|
+
import { type SessionWorkspaceConfig } from './execution-environment.js';
|
|
4
|
+
import { type ResolvedAgentBehaviorPolicy } from './policy.js';
|
|
5
|
+
interface HostWorkspaceResolverOption {
|
|
6
|
+
id: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HostInboundParticipantContext {
|
|
10
|
+
conversationType: CanonConversation['type'] | 'unknown';
|
|
11
|
+
memberCount: number | null;
|
|
12
|
+
senderType: 'human' | 'ai_agent';
|
|
13
|
+
senderName: string;
|
|
14
|
+
isOwner: boolean;
|
|
15
|
+
mentionedAgent: boolean;
|
|
16
|
+
recentSenderTypes: Array<'human' | 'ai_agent'>;
|
|
17
|
+
recentHumanCount: number;
|
|
18
|
+
recentAgentCount: number;
|
|
19
|
+
consecutiveAgentTurns: number;
|
|
20
|
+
currentAgentStreakStartedByHuman: boolean;
|
|
21
|
+
}
|
|
22
|
+
type HostInboundMessage = {
|
|
23
|
+
text?: string | null;
|
|
24
|
+
contentType?: CanonMessage['contentType'] | null;
|
|
25
|
+
attachments?: CanonMessage['attachments'];
|
|
26
|
+
senderType?: CanonMessage['senderType'];
|
|
27
|
+
mentions?: string[] | null;
|
|
28
|
+
contactCard?: CanonMessage['contactCard'];
|
|
29
|
+
};
|
|
30
|
+
export declare function buildCanonHostPrompt(input: {
|
|
31
|
+
hostLabel: string;
|
|
32
|
+
content: string;
|
|
33
|
+
conversationId: string;
|
|
34
|
+
participantContext: HostInboundParticipantContext;
|
|
35
|
+
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
36
|
+
workSession?: MessageCreatedPayload['message']['workSession'];
|
|
37
|
+
workSessions?: MessageCreatedPayload['workSessions'];
|
|
38
|
+
buildInboundContextLines: (context: HostInboundParticipantContext) => string[];
|
|
39
|
+
}): string;
|
|
40
|
+
/**
|
|
41
|
+
* Render the **text portion** of an inbound Canon message. Images are
|
|
42
|
+
* referenced by short placeholders — their actual bytes are delivered to the
|
|
43
|
+
* host as native vision/media inputs (Codex `-i <file>`, Anthropic image
|
|
44
|
+
* blocks). URLs are intentionally *not* inlined, since the harness never
|
|
45
|
+
* needs to refetch and earlier `[Image: <url>]` inlining caused vision
|
|
46
|
+
* models to see a string about an image instead of the image itself.
|
|
47
|
+
*
|
|
48
|
+
* `materialized` may be passed so non-image attachments can reference a
|
|
49
|
+
* local path the agent can Read. Without it we fall back to an unadorned
|
|
50
|
+
* placeholder; the vision path still works because image args carry the
|
|
51
|
+
* file path directly.
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderCanonHostInboundContent(message: HostInboundMessage, materialized?: ReadonlyArray<{
|
|
54
|
+
kind: 'image' | 'audio' | 'file';
|
|
55
|
+
path: string;
|
|
56
|
+
fileName?: string;
|
|
57
|
+
durationMs?: number;
|
|
58
|
+
index: number;
|
|
59
|
+
}>): string;
|
|
60
|
+
export declare function buildHydratedInboundContext(input: {
|
|
61
|
+
agentId: string;
|
|
62
|
+
conversation: CanonConversation | null;
|
|
63
|
+
page?: CanonMessagesPage | null;
|
|
64
|
+
message: HostInboundMessage;
|
|
65
|
+
senderName: string;
|
|
66
|
+
isOwner: boolean;
|
|
67
|
+
}): {
|
|
68
|
+
participantContext: HostInboundParticipantContext;
|
|
69
|
+
behavior?: ResolvedAgentBehaviorPolicy | null;
|
|
70
|
+
workSessions: NonNullable<MessageCreatedPayload['workSessions']>;
|
|
71
|
+
hydratedFromPage: boolean;
|
|
72
|
+
};
|
|
73
|
+
export declare function publishHostAgentRuntime(agentId: string, clientType: AgentClientType, runtime: AgentRuntime): Promise<void>;
|
|
74
|
+
export declare function publishHostSessionSnapshots(input: {
|
|
75
|
+
conversationIds: string[];
|
|
76
|
+
agentId: string;
|
|
77
|
+
clientType: AgentClientType;
|
|
78
|
+
runtime: AgentRuntime;
|
|
79
|
+
workspaceOptions: HostWorkspaceResolverOption[];
|
|
80
|
+
defaultCwd: string;
|
|
81
|
+
extraSessionConfigFields?: readonly string[];
|
|
82
|
+
liveSessionConfigByConversation?: ReadonlyMap<string, {
|
|
83
|
+
model?: string;
|
|
84
|
+
permissionMode?: string;
|
|
85
|
+
effort?: string;
|
|
86
|
+
runtimeControlValues?: Record<string, string>;
|
|
87
|
+
workspaceId?: string;
|
|
88
|
+
executionMode?: SessionWorkspaceConfig['executionMode'];
|
|
89
|
+
executionBranch?: string | null;
|
|
90
|
+
}>;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
export declare function readHostSessionConfig<TExtra extends string = never>(raw: unknown, extraStringFields?: readonly TExtra[]): (SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
93
|
+
runtimeControlValues?: Record<string, string>;
|
|
94
|
+
}) | null;
|
|
95
|
+
export declare function loadHostSessionConfig<TExtra extends string = never>(input: {
|
|
96
|
+
conversationId: string;
|
|
97
|
+
agentId: string;
|
|
98
|
+
extraStringFields?: readonly TExtra[];
|
|
99
|
+
}): Promise<(SessionWorkspaceConfig & Partial<Record<TExtra, string>> & {
|
|
100
|
+
runtimeControlValues?: Record<string, string>;
|
|
101
|
+
}) | null>;
|
|
102
|
+
export declare function resolveHostWorkspaceCwd(input: {
|
|
103
|
+
workspaceOptions: HostWorkspaceResolverOption[];
|
|
104
|
+
config: {
|
|
105
|
+
workspaceId?: string;
|
|
106
|
+
retiredWorkspaceConfig?: boolean;
|
|
107
|
+
} | null;
|
|
108
|
+
defaultCwd: string;
|
|
109
|
+
}): string;
|
|
110
|
+
export declare function createConversationMetadataLoader(input: {
|
|
111
|
+
client: CanonClient;
|
|
112
|
+
conversationCache: Map<string, CanonConversation>;
|
|
113
|
+
cacheTtlMs?: number;
|
|
114
|
+
}): {
|
|
115
|
+
refreshConversationCache(force?: boolean): Promise<void>;
|
|
116
|
+
getConversationMeta(conversationId: string): Promise<CanonConversation | null>;
|
|
117
|
+
};
|
|
118
|
+
export {};
|