@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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { buildAgentSessionSnapshot } from './agent-session.js';
|
|
2
|
+
import { buildConversationWorktreeSpec, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, } from './execution-environment.js';
|
|
3
|
+
import { buildBehaviorPolicyLines, buildParticipationHistorySnapshot, } from './policy.js';
|
|
4
|
+
import { buildWorkSessionsPromptLines, mergeWorkSessionContexts, } from './work-session.js';
|
|
5
|
+
import { rtdbRead } from './rtdb-rest.js';
|
|
6
|
+
import { createRuntimeStatePublisher } from './runtime-state-publisher.js';
|
|
7
|
+
const HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES = Object.freeze({
|
|
8
|
+
canStartDirectConversation: false,
|
|
9
|
+
canSendContactRequest: false,
|
|
10
|
+
canApprovePendingContactRequests: false,
|
|
11
|
+
canRejectPendingContactRequests: false,
|
|
12
|
+
});
|
|
13
|
+
export function buildCanonHostPrompt(input) {
|
|
14
|
+
const resolvedWorkSessions = mergeWorkSessionContexts(input.workSession, input.workSessions);
|
|
15
|
+
return [
|
|
16
|
+
`You are connected to Canon messaging through a ${input.hostLabel} host wrapper.`,
|
|
17
|
+
'Only the last assistant message from this turn will be delivered as the permanent Canon reply.',
|
|
18
|
+
'Short intermediate assistant messages may be shown as ephemeral status while you work.',
|
|
19
|
+
...input.buildInboundContextLines(input.participantContext),
|
|
20
|
+
...buildBehaviorPolicyLines(input.behavior),
|
|
21
|
+
...buildWorkSessionsPromptLines(resolvedWorkSessions),
|
|
22
|
+
'Canon participants may be humans or AI agents.',
|
|
23
|
+
'Honor the Canon behavior policy above when deciding how proactively to participate.',
|
|
24
|
+
...(resolvedWorkSessions.length > 0
|
|
25
|
+
? ['Honor the Canon work-session context above within its stated disclosure limits.']
|
|
26
|
+
: []),
|
|
27
|
+
`Conversation ID: ${input.conversationId}`,
|
|
28
|
+
'',
|
|
29
|
+
'New Canon message:',
|
|
30
|
+
input.content,
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render the **text portion** of an inbound Canon message. Images are
|
|
35
|
+
* referenced by short placeholders — their actual bytes are delivered to the
|
|
36
|
+
* host as native vision/media inputs (Codex `-i <file>`, Anthropic image
|
|
37
|
+
* blocks). URLs are intentionally *not* inlined, since the harness never
|
|
38
|
+
* needs to refetch and earlier `[Image: <url>]` inlining caused vision
|
|
39
|
+
* models to see a string about an image instead of the image itself.
|
|
40
|
+
*
|
|
41
|
+
* `materialized` may be passed so non-image attachments can reference a
|
|
42
|
+
* local path the agent can Read. Without it we fall back to an unadorned
|
|
43
|
+
* placeholder; the vision path still works because image args carry the
|
|
44
|
+
* file path directly.
|
|
45
|
+
*/
|
|
46
|
+
export function renderCanonHostInboundContent(message, materialized) {
|
|
47
|
+
const body = message.text || '';
|
|
48
|
+
const placeholders = [];
|
|
49
|
+
const attachments = message.attachments ?? [];
|
|
50
|
+
for (let i = 0; i < attachments.length; i += 1) {
|
|
51
|
+
const att = attachments[i];
|
|
52
|
+
const mat = materialized?.find((m) => m.index === i) ?? null;
|
|
53
|
+
placeholders.push(describeAttachment(att, mat));
|
|
54
|
+
}
|
|
55
|
+
if (message.contentType === 'contact_card' && message.contactCard) {
|
|
56
|
+
placeholders.push(describeContactCard(message.contactCard));
|
|
57
|
+
}
|
|
58
|
+
const rendered = [...placeholders, body].filter(Boolean).join('\n');
|
|
59
|
+
return rendered || '[Empty message]';
|
|
60
|
+
}
|
|
61
|
+
function describeContactCard(card) {
|
|
62
|
+
const parts = [`${card.userType} · userId: ${card.userId}`];
|
|
63
|
+
if (card.ownerName)
|
|
64
|
+
parts.push(`owner: ${card.ownerName}`);
|
|
65
|
+
if (card.about)
|
|
66
|
+
parts.push(`about: ${card.about}`);
|
|
67
|
+
const identity = `📇 Contact card: "${card.displayName}" (${parts.join(' · ')}).`;
|
|
68
|
+
const missingCapabilities = [
|
|
69
|
+
!HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES.canStartDirectConversation
|
|
70
|
+
? 'start a direct conversation'
|
|
71
|
+
: null,
|
|
72
|
+
!HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES.canSendContactRequest
|
|
73
|
+
? 'send a contact request'
|
|
74
|
+
: null,
|
|
75
|
+
!HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES.canApprovePendingContactRequests
|
|
76
|
+
? 'approve pending requests'
|
|
77
|
+
: null,
|
|
78
|
+
!HOST_INBOUND_CONTACT_CARD_ACTION_CAPABILITIES.canRejectPendingContactRequests
|
|
79
|
+
? 'reject pending requests'
|
|
80
|
+
: null,
|
|
81
|
+
].filter(Boolean).join(', ');
|
|
82
|
+
const hint = `This host can inspect the card, but Canon admission actions are missing here. Missing capabilities: ${missingCapabilities}. Use another Canon surface for userId ${card.userId}.`;
|
|
83
|
+
return `${identity}\n${hint}`;
|
|
84
|
+
}
|
|
85
|
+
function describeAttachment(attachment, materialized) {
|
|
86
|
+
if (attachment.kind === 'image') {
|
|
87
|
+
return '[Image attached]';
|
|
88
|
+
}
|
|
89
|
+
if (attachment.kind === 'audio') {
|
|
90
|
+
const durationMs = materialized?.durationMs ?? attachment.durationMs;
|
|
91
|
+
const duration = durationMs ? ` (${Math.round(durationMs / 1000)}s)` : '';
|
|
92
|
+
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
93
|
+
return `[Voice message${duration}${ref}]`;
|
|
94
|
+
}
|
|
95
|
+
// file
|
|
96
|
+
const label = materialized?.fileName ?? attachment.fileName ?? 'File';
|
|
97
|
+
const ref = materialized?.path ? ` ${materialized.path}` : '';
|
|
98
|
+
return `[File: ${label}${ref}]`;
|
|
99
|
+
}
|
|
100
|
+
export function buildHydratedInboundContext(input) {
|
|
101
|
+
const history = buildParticipationHistorySnapshot(input.page?.messages ?? [], input.agentId);
|
|
102
|
+
return {
|
|
103
|
+
participantContext: {
|
|
104
|
+
conversationType: input.conversation?.type ?? 'unknown',
|
|
105
|
+
memberCount: input.conversation?.memberIds?.length ?? null,
|
|
106
|
+
senderType: input.message.senderType ?? 'human',
|
|
107
|
+
senderName: input.senderName,
|
|
108
|
+
isOwner: input.isOwner,
|
|
109
|
+
mentionedAgent: Array.isArray(input.message.mentions) && input.message.mentions.includes(input.agentId),
|
|
110
|
+
recentSenderTypes: history.recentSenderTypes,
|
|
111
|
+
recentHumanCount: history.recentHumanCount,
|
|
112
|
+
recentAgentCount: history.recentAgentCount,
|
|
113
|
+
consecutiveAgentTurns: history.consecutiveAgentTurns,
|
|
114
|
+
currentAgentStreakStartedByHuman: history.currentAgentStreakStartedByHuman,
|
|
115
|
+
},
|
|
116
|
+
behavior: input.page?.behavior ?? input.conversation?.behavior,
|
|
117
|
+
workSessions: input.page?.workSessions ?? [],
|
|
118
|
+
hydratedFromPage: input.page != null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export async function publishHostAgentRuntime(agentId, clientType, runtime) {
|
|
122
|
+
await createRuntimeStatePublisher({ agentId, clientType, hostMode: true })
|
|
123
|
+
.publishAgentRuntime(runtime);
|
|
124
|
+
}
|
|
125
|
+
export async function publishHostSessionSnapshots(input) {
|
|
126
|
+
if (input.conversationIds.length === 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const publisher = createRuntimeStatePublisher({
|
|
130
|
+
agentId: input.agentId,
|
|
131
|
+
clientType: input.clientType,
|
|
132
|
+
hostMode: true,
|
|
133
|
+
});
|
|
134
|
+
await Promise.all(input.conversationIds.map(async (conversationId) => {
|
|
135
|
+
const persistedConfig = await loadHostSessionConfig({
|
|
136
|
+
conversationId,
|
|
137
|
+
agentId: input.agentId,
|
|
138
|
+
extraStringFields: input.extraSessionConfigFields ?? ['permissionMode'],
|
|
139
|
+
});
|
|
140
|
+
const liveConfig = input.liveSessionConfigByConversation?.get(conversationId) ?? null;
|
|
141
|
+
const mergedConfig = {
|
|
142
|
+
...(persistedConfig ?? {}),
|
|
143
|
+
...(liveConfig ?? {}),
|
|
144
|
+
};
|
|
145
|
+
const snapshot = buildAgentSessionSnapshot({
|
|
146
|
+
conversationId,
|
|
147
|
+
agentId: input.agentId,
|
|
148
|
+
runtime: {
|
|
149
|
+
...input.runtime,
|
|
150
|
+
clientType: input.clientType,
|
|
151
|
+
hostMode: true,
|
|
152
|
+
},
|
|
153
|
+
sessionConfig: {
|
|
154
|
+
...(mergedConfig.model ? { model: mergedConfig.model } : {}),
|
|
155
|
+
...(mergedConfig.permissionMode ? { permissionMode: mergedConfig.permissionMode } : {}),
|
|
156
|
+
...(mergedConfig.effort ? { effort: mergedConfig.effort } : {}),
|
|
157
|
+
...(mergedConfig.runtimeControlValues
|
|
158
|
+
? { runtimeControlValues: mergedConfig.runtimeControlValues }
|
|
159
|
+
: {}),
|
|
160
|
+
...(mergedConfig.workspaceId ? { workspaceId: mergedConfig.workspaceId } : {}),
|
|
161
|
+
...(mergedConfig.executionMode ? { executionMode: mergedConfig.executionMode } : {}),
|
|
162
|
+
},
|
|
163
|
+
lastHeartbeatAt: undefined,
|
|
164
|
+
});
|
|
165
|
+
let executionBranch = liveConfig?.executionBranch ?? null;
|
|
166
|
+
if (!executionBranch && snapshot.executionMode === 'worktree' && snapshot.workspaceId) {
|
|
167
|
+
const workspace = input.workspaceOptions.find((option) => option.id === snapshot.workspaceId);
|
|
168
|
+
if (workspace) {
|
|
169
|
+
executionBranch = buildConversationWorktreeSpec({
|
|
170
|
+
agentId: input.agentId,
|
|
171
|
+
conversationId,
|
|
172
|
+
workspaceCwd: workspace.cwd,
|
|
173
|
+
}).branch;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return publisher.patchAgentSessionSnapshot(conversationId, {
|
|
177
|
+
clientType: input.clientType,
|
|
178
|
+
hostMode: true,
|
|
179
|
+
model: snapshot.model ?? null,
|
|
180
|
+
permissionMode: snapshot.permissionMode ?? null,
|
|
181
|
+
effort: snapshot.effort ?? null,
|
|
182
|
+
runtimeControlValues: snapshot.runtimeControlValues ?? null,
|
|
183
|
+
workspaceId: snapshot.workspaceId ?? null,
|
|
184
|
+
executionMode: snapshot.executionMode ?? null,
|
|
185
|
+
executionBranch,
|
|
186
|
+
modelOptions: snapshot.modelOptions,
|
|
187
|
+
permissionModeOptions: snapshot.permissionModeOptions,
|
|
188
|
+
workspaceOptions: snapshot.workspaceOptions,
|
|
189
|
+
availableExecutionModes: snapshot.availableExecutionModes,
|
|
190
|
+
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
191
|
+
});
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
export function readHostSessionConfig(raw, extraStringFields = []) {
|
|
195
|
+
const baseConfig = readSessionWorkspaceConfig(raw);
|
|
196
|
+
if (!raw || typeof raw !== 'object') {
|
|
197
|
+
return baseConfig;
|
|
198
|
+
}
|
|
199
|
+
const data = raw;
|
|
200
|
+
const extraConfig = Object.fromEntries(extraStringFields.flatMap((field) => {
|
|
201
|
+
const value = normalizeOptionalString(data[field]);
|
|
202
|
+
return value ? [[field, value]] : [];
|
|
203
|
+
}));
|
|
204
|
+
const runtimeControlValues = Object.fromEntries(Object.entries(data.runtimeControlValues && typeof data.runtimeControlValues === 'object'
|
|
205
|
+
? data.runtimeControlValues
|
|
206
|
+
: {}).flatMap(([key, value]) => {
|
|
207
|
+
const normalizedValue = normalizeOptionalString(value);
|
|
208
|
+
return normalizedValue ? [[key, normalizedValue]] : [];
|
|
209
|
+
}));
|
|
210
|
+
return {
|
|
211
|
+
...(baseConfig ?? {}),
|
|
212
|
+
...extraConfig,
|
|
213
|
+
...(Object.keys(runtimeControlValues).length > 0 ? { runtimeControlValues } : {}),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export async function loadHostSessionConfig(input) {
|
|
217
|
+
const raw = await rtdbRead(`/session-config/${input.conversationId}/${input.agentId}`);
|
|
218
|
+
return readHostSessionConfig(raw, input.extraStringFields);
|
|
219
|
+
}
|
|
220
|
+
export function resolveHostWorkspaceCwd(input) {
|
|
221
|
+
return resolveConfiguredWorkspaceCwd(input);
|
|
222
|
+
}
|
|
223
|
+
export function createConversationMetadataLoader(input) {
|
|
224
|
+
const cacheTtlMs = input.cacheTtlMs ?? 10_000;
|
|
225
|
+
let conversationCacheLoadedAt = 0;
|
|
226
|
+
async function refreshConversationCache(force = false) {
|
|
227
|
+
if (!force
|
|
228
|
+
&& input.conversationCache.size > 0
|
|
229
|
+
&& Date.now() - conversationCacheLoadedAt < cacheTtlMs) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const conversations = await input.client.getConversations();
|
|
233
|
+
input.conversationCache.clear();
|
|
234
|
+
for (const conversation of conversations) {
|
|
235
|
+
input.conversationCache.set(conversation.id, conversation);
|
|
236
|
+
}
|
|
237
|
+
conversationCacheLoadedAt = Date.now();
|
|
238
|
+
}
|
|
239
|
+
async function getConversationMeta(conversationId) {
|
|
240
|
+
try {
|
|
241
|
+
await refreshConversationCache();
|
|
242
|
+
const cached = input.conversationCache.get(conversationId);
|
|
243
|
+
if (cached)
|
|
244
|
+
return cached;
|
|
245
|
+
await refreshConversationCache(true);
|
|
246
|
+
return input.conversationCache.get(conversationId) ?? null;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return input.conversationCache.get(conversationId) ?? null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
refreshConversationCache,
|
|
254
|
+
getConversationMeta,
|
|
255
|
+
};
|
|
256
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,13 +6,14 @@ export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, bu
|
|
|
6
6
|
export type { ConfiguredWorkspaceRoot, WorkspaceDiscoveryResult, } from './workspace-discovery.js';
|
|
7
7
|
export { CanonClient, CanonApiError } from './client.js';
|
|
8
8
|
export { buildAgentSessionSnapshot } from './agent-session.js';
|
|
9
|
+
export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
|
|
9
10
|
export { CanonStream } from './stream.js';
|
|
10
11
|
export type { StreamHandler } from './stream.js';
|
|
11
12
|
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
13
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
13
14
|
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
15
|
export type { DeliveryIntent, TurnMessageSemantics, InboundDisposition, TurnLifecycleState, RuntimeCapabilities, HostAdmissionActionCapabilities, TurnState, TurnMetadata, TriggerDecision, } from './turn-protocol.js';
|
|
15
|
-
export { registerAndWaitForApproval } from './registration.js';
|
|
16
|
+
export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistrationRequest, waitForRegistrationApproval, } from './registration.js';
|
|
16
17
|
export { ApprovalManager } from './approval-manager.js';
|
|
17
18
|
export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
|
|
18
19
|
export { DEFAULT_APPROVAL_CONFIG, } from './approval-types.js';
|
|
@@ -21,14 +22,20 @@ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, b
|
|
|
21
22
|
export type { ClaudeQuestionMetadata, ClaudeQuestionReplyMetadata, PlanApprovalMetadata, PlanApprovalReplyMetadata, RuntimeQuestionDefinition, RuntimeQuestionOption, } from './runtime-cards.js';
|
|
22
23
|
export { createStreamingHelper } from './streaming.js';
|
|
23
24
|
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';
|
|
25
|
+
export { clearPendingRegistration, getOrCreatePendingRegistration, loadPendingRegistrations, loadProfiles, savePendingRegistrations, saveProfiles, updatePendingRegistration, upsertAgentProfile, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.js';
|
|
26
|
+
export type { AgentProfile, PendingRegistration, ProfileLockHandle } from './agent-profiles.js';
|
|
27
|
+
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile, getActiveProfileLock } from './agent-resolver.js';
|
|
27
28
|
export type { ResolvedAgent } from './agent-resolver.js';
|
|
29
|
+
export { RUNTIMES_DIR, buildLocalRuntimeId, clearRuntimeSessionState, describeProfileLock, heartbeatLocalRuntimeEntry, listLocalRuntimeEntries, loadRuntimeSessionState, markLocalRuntimeStopped, readLocalRuntimeEntry, removeLocalRuntimeEntry, saveRuntimeSessionState, upsertLocalRuntimeEntry, } from './local-runtime-catalog.js';
|
|
30
|
+
export type { LocalRuntimeCatalogEntry, LocalRuntimeKind, LocalRuntimeReviveCapability, LocalRuntimeSessionState, LocalRuntimeStatus, } from './local-runtime-catalog.js';
|
|
28
31
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
29
32
|
export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecutionEnvironment, SessionWorkspaceConfig, } from './execution-environment.js';
|
|
30
33
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
31
|
-
export type { AgentSessionSnapshotPatch, RuntimeInfoPayloadData, SessionStatePayload, TurnStatePayload, } from './rtdb-rest.js';
|
|
34
|
+
export type { AgentSessionSnapshotPatch, RTDBClientHandle, RuntimeInfoPayloadData, SessionStatePayload, TurnStatePayload, } from './rtdb-rest.js';
|
|
35
|
+
export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
36
|
+
export type { HostInboundParticipantContext, } from './host-runtime.js';
|
|
37
|
+
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
38
|
+
export type { RuntimeStatePublisher, RuntimeStatePublisherOptions, RuntimeStreamingPayload, } from './runtime-state-publisher.js';
|
|
32
39
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
33
40
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
34
41
|
export { resolveCanonBaseUrl } from './base-url.js';
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,14 @@ export { buildConfiguredWorkspaceOptionsWithRoots, buildPublicWorkspaceRoots, bu
|
|
|
5
5
|
// Client
|
|
6
6
|
export { CanonClient, CanonApiError } from './client.js';
|
|
7
7
|
export { buildAgentSessionSnapshot } from './agent-session.js';
|
|
8
|
+
export { CLAUDE_EFFORT_OPTIONS, EXECUTION_MODE_CONTROL_OPTIONS, buildFirstPartyCodingRuntimeDescriptor, buildRuntimeEffortControl, buildRuntimeExecutionModeControl, buildRuntimeExecutionModeOptions, buildRuntimeModelControl, buildRuntimePermissionModeControl, buildRuntimeWorkspaceControl, buildRuntimeWorkspaceControlOptions, } from './runtime-descriptor.js';
|
|
8
9
|
// Stream
|
|
9
10
|
export { CanonStream } from './stream.js';
|
|
10
11
|
export { buildParticipationHistorySnapshot, buildParticipationHistorySnapshots, buildBehaviorPolicyLines, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, evaluateParticipationPolicy, getDefaultParticipationPolicy, resolveAgentBehaviorPolicy, } from './policy.js';
|
|
11
12
|
// Turn protocol
|
|
12
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';
|
|
13
14
|
// Registration
|
|
14
|
-
export { registerAndWaitForApproval } from './registration.js';
|
|
15
|
+
export { ackRegistrationApproval, registerAndWaitForApproval, submitRegistrationRequest, waitForRegistrationApproval, } from './registration.js';
|
|
15
16
|
// Approval
|
|
16
17
|
export { ApprovalManager } from './approval-manager.js';
|
|
17
18
|
export { generateApprovalId, buildApprovalRequest, buildApprovalReply, buildApprovalOutcome, parseTextApprovalReply, redactSecrets, } from './approval-format.js';
|
|
@@ -20,13 +21,18 @@ export { buildPlanApprovalReply, buildPlanApprovalRequest, buildQuestionReply, b
|
|
|
20
21
|
// Streaming (RTDB helpers)
|
|
21
22
|
export { createStreamingHelper } from './streaming.js';
|
|
22
23
|
// Agent profiles (loading, locking, resolution)
|
|
23
|
-
export { loadProfiles, isProfileLocked, acquireLock, releaseLock, isProcessAlive, CANON_DIR, AGENTS_PATH, LOCKS_DIR, } from './agent-profiles.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';
|
|
24
25
|
// Agent resolver
|
|
25
|
-
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile } from './agent-resolver.js';
|
|
26
|
+
export { resolveCanonAgent, resolveCanonProfile, getActiveProfile, getActiveProfileLock } from './agent-resolver.js';
|
|
27
|
+
// Local runtime catalog
|
|
28
|
+
export { RUNTIMES_DIR, buildLocalRuntimeId, clearRuntimeSessionState, describeProfileLock, heartbeatLocalRuntimeEntry, listLocalRuntimeEntries, loadRuntimeSessionState, markLocalRuntimeStopped, readLocalRuntimeEntry, removeLocalRuntimeEntry, saveRuntimeSessionState, upsertLocalRuntimeEntry, } from './local-runtime-catalog.js';
|
|
26
29
|
// Execution environments for host-mode coding sessions
|
|
27
30
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
28
31
|
// RTDB REST helpers (token exchange, session state, generic read/write)
|
|
29
32
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
33
|
+
// Runtime host plumbing
|
|
34
|
+
export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMetadataLoader, loadHostSessionConfig, publishHostAgentRuntime, publishHostSessionSnapshots, readHostSessionConfig, renderCanonHostInboundContent, resolveHostWorkspaceCwd, } from './host-runtime.js';
|
|
35
|
+
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
30
36
|
// Message formatting (LLM-facing text projection)
|
|
31
37
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
32
38
|
// Constants
|
|
@@ -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/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>;
|