@canonmsg/core 0.13.0 → 0.15.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-session.js +29 -19
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +1 -0
- package/dist/host-runtime.d.ts +118 -0
- package/dist/host-runtime.js +256 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -0
- package/dist/rtdb-rest.d.ts +2 -7
- package/dist/rtdb-rest.js +3 -4
- 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 +0 -2
- package/package.json +1 -1
package/dist/agent-session.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { mergeWorkSessionContexts, } from './work-session.js';
|
|
2
|
+
function listDescriptorControls(runtime) {
|
|
3
|
+
const descriptor = runtime?.runtimeDescriptor;
|
|
4
|
+
return [
|
|
5
|
+
...(descriptor?.coreControls ?? []),
|
|
6
|
+
...(descriptor?.runtimeControls ?? []),
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
function descriptorOptions(runtime, controlId) {
|
|
10
|
+
return [...(listDescriptorControls(runtime)
|
|
11
|
+
.find((control) => control.id === controlId)
|
|
12
|
+
?.options ?? [])];
|
|
13
|
+
}
|
|
14
|
+
function descriptorDefaultValue(runtime, controlId) {
|
|
15
|
+
const control = listDescriptorControls(runtime)
|
|
16
|
+
.find((candidate) => candidate.id === controlId);
|
|
17
|
+
return control?.defaultValue ?? control?.options?.[0]?.value ?? undefined;
|
|
18
|
+
}
|
|
2
19
|
function buildWorkSessionSummary(workSession, workSessions) {
|
|
3
20
|
const merged = mergeWorkSessionContexts(workSession, workSessions);
|
|
4
21
|
const primary = merged[0];
|
|
@@ -14,13 +31,12 @@ function buildWorkSessionSummary(workSession, workSessions) {
|
|
|
14
31
|
};
|
|
15
32
|
}
|
|
16
33
|
export function buildAgentSessionSnapshot(input) {
|
|
17
|
-
const modelOptions = input.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
?? [];
|
|
34
|
+
const modelOptions = descriptorOptions(input.runtime, 'model');
|
|
35
|
+
const workspaceOptions = descriptorOptions(input.runtime, 'workspace')
|
|
36
|
+
.map((option) => ({ id: option.value, label: option.label }));
|
|
37
|
+
const availableExecutionModes = descriptorOptions(input.runtime, 'executionMode')
|
|
38
|
+
.map((option) => option.value)
|
|
39
|
+
.filter((value) => value === 'worktree' || value === 'locked');
|
|
24
40
|
return {
|
|
25
41
|
conversationId: input.conversationId,
|
|
26
42
|
agentId: input.agentId,
|
|
@@ -32,36 +48,30 @@ export function buildAgentSessionSnapshot(input) {
|
|
|
32
48
|
?? input.runtime?.hostMode),
|
|
33
49
|
model: input.sessionState?.model
|
|
34
50
|
?? input.sessionConfig?.model
|
|
35
|
-
?? input.runtime
|
|
36
|
-
?? modelOptions[0]?.value,
|
|
51
|
+
?? descriptorDefaultValue(input.runtime, 'model'),
|
|
37
52
|
modelOptions,
|
|
38
53
|
permissionMode: input.sessionState?.permissionMode
|
|
39
54
|
?? input.sessionConfig?.permissionMode
|
|
40
|
-
?? input.runtime
|
|
41
|
-
permissionModeOptions: input.runtime
|
|
55
|
+
?? descriptorDefaultValue(input.runtime, 'permissionMode'),
|
|
56
|
+
permissionModeOptions: descriptorOptions(input.runtime, 'permissionMode'),
|
|
42
57
|
effort: input.sessionState?.effort ?? input.sessionConfig?.effort,
|
|
43
58
|
runtimeControlValues: input.sessionState?.runtimeControlValues
|
|
44
59
|
?? input.sessionConfig?.runtimeControlValues,
|
|
45
60
|
workspaceId: input.sessionConfig?.workspaceId
|
|
46
|
-
?? input.runtime
|
|
47
|
-
?? workspaceOptions[0]?.id,
|
|
61
|
+
?? descriptorDefaultValue(input.runtime, 'workspace'),
|
|
48
62
|
workspaceOptions,
|
|
49
63
|
executionMode: input.sessionState?.executionMode
|
|
50
64
|
?? input.sessionConfig?.executionMode,
|
|
51
|
-
availableExecutionModes
|
|
52
|
-
?? input.runtime?.availableExecutionModes
|
|
53
|
-
?? [],
|
|
65
|
+
availableExecutionModes,
|
|
54
66
|
executionBranch: input.sessionState?.executionBranch,
|
|
55
67
|
resolvedWorkspaceLabel: undefined,
|
|
56
68
|
resolvedCwd: input.sessionState?.cwd,
|
|
57
69
|
worktreePath: input.sessionState?.worktreePath,
|
|
58
70
|
executionFallbackReason: input.sessionState?.executionFallbackReason,
|
|
59
|
-
state: input.sessionState?.state,
|
|
60
71
|
turnState: input.turnState?.state,
|
|
61
72
|
supportsQueue: input.turnState?.capabilities?.supportsQueue,
|
|
62
73
|
queueDepth: input.turnState?.queueDepth ?? 0,
|
|
63
|
-
waitingForInput: input.turnState?.state === 'waiting_input'
|
|
64
|
-
|| input.sessionState?.state === 'requires_action',
|
|
74
|
+
waitingForInput: input.turnState?.state === 'waiting_input',
|
|
65
75
|
contextUsage: input.sessionState?.contextUsage,
|
|
66
76
|
lastError: input.sessionState?.lastError,
|
|
67
77
|
lastHeartbeatAt: input.lastHeartbeatAt
|
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';
|
|
@@ -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 {};
|
|
@@ -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,6 +6,7 @@ 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';
|
|
@@ -30,7 +31,11 @@ export type { LocalRuntimeCatalogEntry, LocalRuntimeKind, LocalRuntimeReviveCapa
|
|
|
30
31
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
31
32
|
export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecutionEnvironment, SessionWorkspaceConfig, } from './execution-environment.js';
|
|
32
33
|
export { initRTDBAuth, rtdbWrite, rtdbRead, patchAgentSessionSnapshot, patchRuntimeInfo, writeRuntimeInfo, clearRuntimeInfo, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
33
|
-
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';
|
|
34
39
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
35
40
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
36
41
|
export { resolveCanonBaseUrl } from './base-url.js';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ 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';
|
|
@@ -29,6 +30,9 @@ export { RUNTIMES_DIR, buildLocalRuntimeId, clearRuntimeSessionState, describePr
|
|
|
29
30
|
export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, buildConversationWorktreeSpec, buildPublicWorkspaceOptions, buildWorkspaceOptionId, EXECUTION_ENVIRONMENT_MODES, isEnabledFlag, isExecutionEnvironmentMode, normalizeOptionalString, readSessionWorkspaceConfig, resolveConfiguredWorkspaceCwd, ExecutionEnvironmentError, prepareConversationEnvironment, releaseConversationEnvironment, } from './execution-environment.js';
|
|
30
31
|
// RTDB REST helpers (token exchange, session state, generic read/write)
|
|
31
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';
|
|
32
36
|
// Message formatting (LLM-facing text projection)
|
|
33
37
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
34
38
|
// Constants
|
package/dist/rtdb-rest.d.ts
CHANGED
|
@@ -22,16 +22,11 @@ export interface SessionStatePayload {
|
|
|
22
22
|
hostMode?: boolean;
|
|
23
23
|
clientType?: string;
|
|
24
24
|
isActive: boolean;
|
|
25
|
-
state?: 'idle' | 'running' | 'requires_action';
|
|
26
25
|
contextUsage?: {
|
|
27
26
|
percentage: number;
|
|
28
27
|
totalTokens: number;
|
|
29
28
|
maxTokens: number;
|
|
30
29
|
};
|
|
31
|
-
availableModels?: Array<{
|
|
32
|
-
value: string;
|
|
33
|
-
label: string;
|
|
34
|
-
}>;
|
|
35
30
|
updatedAt: {
|
|
36
31
|
'.sv': 'timestamp';
|
|
37
32
|
};
|
|
@@ -80,7 +75,7 @@ export interface AgentSessionSnapshotPatch {
|
|
|
80
75
|
resolvedCwd?: string | null;
|
|
81
76
|
worktreePath?: string | null;
|
|
82
77
|
executionFallbackReason?: string | null;
|
|
83
|
-
state?:
|
|
78
|
+
state?: null;
|
|
84
79
|
turnState?: TurnLifecycleState | null;
|
|
85
80
|
supportsQueue?: boolean | null;
|
|
86
81
|
queueDepth?: number;
|
|
@@ -114,7 +109,7 @@ interface RTDBAuthOptions {
|
|
|
114
109
|
rtdbUrl?: string;
|
|
115
110
|
firebaseApiKey?: string;
|
|
116
111
|
}
|
|
117
|
-
interface RTDBClientHandle {
|
|
112
|
+
export interface RTDBClientHandle {
|
|
118
113
|
read(path: string): Promise<unknown>;
|
|
119
114
|
write(path: string, data: unknown): Promise<void>;
|
|
120
115
|
patch(path: string, data: unknown): Promise<void>;
|
package/dist/rtdb-rest.js
CHANGED
|
@@ -144,7 +144,6 @@ function createRTDBClientHandle(client, options) {
|
|
|
144
144
|
...(state.runtimeControlValues !== undefined
|
|
145
145
|
? { runtimeControlValues: state.runtimeControlValues }
|
|
146
146
|
: {}),
|
|
147
|
-
...(state.availableModels !== undefined ? { modelOptions: state.availableModels } : {}),
|
|
148
147
|
...(state.cwd !== undefined ? { resolvedCwd: state.cwd } : {}),
|
|
149
148
|
...(state.executionMode !== undefined ? { executionMode: state.executionMode } : {}),
|
|
150
149
|
...(state.executionBranch !== undefined ? { executionBranch: state.executionBranch } : {}),
|
|
@@ -152,8 +151,8 @@ function createRTDBClientHandle(client, options) {
|
|
|
152
151
|
...(state.executionFallbackReason !== undefined
|
|
153
152
|
? { executionFallbackReason: state.executionFallbackReason }
|
|
154
153
|
: {}),
|
|
155
|
-
|
|
156
|
-
waitingForInput:
|
|
154
|
+
state: null,
|
|
155
|
+
waitingForInput: false,
|
|
157
156
|
...(state.contextUsage !== undefined ? { contextUsage: state.contextUsage } : {}),
|
|
158
157
|
...(state.lastError !== undefined ? { lastError: state.lastError } : {}),
|
|
159
158
|
lastHeartbeatAt: { '.sv': 'timestamp' },
|
|
@@ -167,7 +166,7 @@ function createRTDBClientHandle(client, options) {
|
|
|
167
166
|
updatedAt: { '.sv': 'timestamp' },
|
|
168
167
|
});
|
|
169
168
|
await patch(buildAgentSessionPath(conversationId, agentId), {
|
|
170
|
-
state:
|
|
169
|
+
state: null,
|
|
171
170
|
waitingForInput: false,
|
|
172
171
|
lastError: null,
|
|
173
172
|
executionBranch: null,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
|
|
2
|
+
import { type AgentClientType, type CanonControlDescriptor, type CanonRuntimeActionDescriptor, type CanonRuntimeDescriptor, type CanonRuntimeStreamingMode, type CanonWorkspaceRootMetadata, type ModelOption, type PermissionModeOption, type WorkspaceOption } from './types.js';
|
|
3
|
+
import type { HostAdmissionActionCapabilities } from './turn-protocol.js';
|
|
4
|
+
export declare const CLAUDE_EFFORT_OPTIONS: readonly [{
|
|
5
|
+
readonly value: "low";
|
|
6
|
+
readonly label: "Low";
|
|
7
|
+
}, {
|
|
8
|
+
readonly value: "medium";
|
|
9
|
+
readonly label: "Medium";
|
|
10
|
+
}, {
|
|
11
|
+
readonly value: "high";
|
|
12
|
+
readonly label: "High";
|
|
13
|
+
}];
|
|
14
|
+
export declare const EXECUTION_MODE_CONTROL_OPTIONS: ReadonlyArray<{
|
|
15
|
+
value: ExecutionEnvironmentMode;
|
|
16
|
+
label: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function buildRuntimeWorkspaceControlOptions(workspaces: ReadonlyArray<WorkspaceOption>): ModelOption[];
|
|
20
|
+
export declare function buildRuntimeExecutionModeOptions(modes: ReadonlyArray<ExecutionEnvironmentMode>): ModelOption[];
|
|
21
|
+
export declare function buildRuntimeModelControl(input: {
|
|
22
|
+
models: ReadonlyArray<ModelOption>;
|
|
23
|
+
liveBehavior: 'immediate' | 'next_turn';
|
|
24
|
+
defaultValue?: string | null;
|
|
25
|
+
}): CanonControlDescriptor;
|
|
26
|
+
export declare function buildRuntimeWorkspaceControl(input: {
|
|
27
|
+
workspaces: ReadonlyArray<WorkspaceOption>;
|
|
28
|
+
workspaceRoots?: ReadonlyArray<CanonWorkspaceRootMetadata>;
|
|
29
|
+
label?: string;
|
|
30
|
+
defaultValue?: string | null;
|
|
31
|
+
}): CanonControlDescriptor;
|
|
32
|
+
export declare function buildRuntimeExecutionModeControl(executionModes: ReadonlyArray<ExecutionEnvironmentMode>): CanonControlDescriptor;
|
|
33
|
+
export declare function buildRuntimePermissionModeControl(input: {
|
|
34
|
+
clientType: 'claude-code' | 'codex';
|
|
35
|
+
options: ReadonlyArray<PermissionModeOption>;
|
|
36
|
+
defaultValue?: string | null;
|
|
37
|
+
}): CanonControlDescriptor;
|
|
38
|
+
export declare function buildRuntimeEffortControl(): CanonControlDescriptor;
|
|
39
|
+
export declare function buildFirstPartyCodingRuntimeDescriptor(input: {
|
|
40
|
+
clientType: Extract<AgentClientType, 'claude-code' | 'codex'>;
|
|
41
|
+
models: ReadonlyArray<ModelOption>;
|
|
42
|
+
workspaces: ReadonlyArray<WorkspaceOption>;
|
|
43
|
+
workspaceRoots?: ReadonlyArray<CanonWorkspaceRootMetadata>;
|
|
44
|
+
executionModes: ReadonlyArray<ExecutionEnvironmentMode>;
|
|
45
|
+
permissionModes?: ReadonlyArray<PermissionModeOption>;
|
|
46
|
+
defaultPermissionMode?: string | null;
|
|
47
|
+
actions?: ReadonlyArray<CanonRuntimeActionDescriptor>;
|
|
48
|
+
streamingTextMode: CanonRuntimeStreamingMode;
|
|
49
|
+
admissionActions?: HostAdmissionActionCapabilities;
|
|
50
|
+
}): CanonRuntimeDescriptor;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { CLAUDE_PERMISSION_MODE_OPTIONS, } from './types.js';
|
|
2
|
+
export const CLAUDE_EFFORT_OPTIONS = [
|
|
3
|
+
{ value: 'low', label: 'Low' },
|
|
4
|
+
{ value: 'medium', label: 'Medium' },
|
|
5
|
+
{ value: 'high', label: 'High' },
|
|
6
|
+
];
|
|
7
|
+
export const EXECUTION_MODE_CONTROL_OPTIONS = [
|
|
8
|
+
{
|
|
9
|
+
value: 'worktree',
|
|
10
|
+
label: 'Isolated worktree',
|
|
11
|
+
description: 'Creates or reuses a per-conversation git worktree under ~/.canon/conversation-worktrees when the selected project is a git repo.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: 'locked',
|
|
15
|
+
label: 'Use shared project',
|
|
16
|
+
description: 'Runs directly in the selected project folder. Changes happen there.',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
export function buildRuntimeWorkspaceControlOptions(workspaces) {
|
|
20
|
+
return workspaces.map((workspace) => ({
|
|
21
|
+
value: workspace.id,
|
|
22
|
+
label: workspace.label,
|
|
23
|
+
...(workspace.description ? { description: workspace.description } : {}),
|
|
24
|
+
...(workspace.workspaceRootId ? { workspaceRootId: workspace.workspaceRootId } : {}),
|
|
25
|
+
...(workspace.workspaceRelativePath ? { workspaceRelativePath: workspace.workspaceRelativePath } : {}),
|
|
26
|
+
...(workspace.source ? { source: workspace.source } : {}),
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
export function buildRuntimeExecutionModeOptions(modes) {
|
|
30
|
+
return modes.map((mode) => {
|
|
31
|
+
const option = EXECUTION_MODE_CONTROL_OPTIONS.find((candidate) => candidate.value === mode);
|
|
32
|
+
return option
|
|
33
|
+
? { ...option }
|
|
34
|
+
: { value: mode, label: mode, description: mode };
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function buildRuntimeModelControl(input) {
|
|
38
|
+
return {
|
|
39
|
+
id: 'model',
|
|
40
|
+
label: 'Model',
|
|
41
|
+
options: input.models,
|
|
42
|
+
defaultValue: input.defaultValue ?? input.models[0]?.value ?? null,
|
|
43
|
+
availability: 'setup_and_live',
|
|
44
|
+
liveBehavior: input.liveBehavior,
|
|
45
|
+
selectionPolicy: 'inherit',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function buildRuntimeWorkspaceControl(input) {
|
|
49
|
+
return {
|
|
50
|
+
id: 'workspace',
|
|
51
|
+
label: input.label ?? 'Project',
|
|
52
|
+
options: buildRuntimeWorkspaceControlOptions(input.workspaces),
|
|
53
|
+
defaultValue: input.defaultValue ?? input.workspaces[0]?.id ?? null,
|
|
54
|
+
availability: 'setup',
|
|
55
|
+
liveBehavior: 'none',
|
|
56
|
+
selectionPolicy: 'inherit',
|
|
57
|
+
description: input.workspaceRoots?.length
|
|
58
|
+
? 'Choose one of the projects discovered inside the approved local roots for this host.'
|
|
59
|
+
: 'Choose one of the local projects advertised by this host.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function buildRuntimeExecutionModeControl(executionModes) {
|
|
63
|
+
return {
|
|
64
|
+
id: 'executionMode',
|
|
65
|
+
label: 'Execution mode',
|
|
66
|
+
options: buildRuntimeExecutionModeOptions(executionModes),
|
|
67
|
+
defaultValue: null,
|
|
68
|
+
availability: 'setup',
|
|
69
|
+
liveBehavior: 'none',
|
|
70
|
+
selectionPolicy: 'required_explicit',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function buildRuntimePermissionModeControl(input) {
|
|
74
|
+
const isCodex = input.clientType === 'codex';
|
|
75
|
+
return {
|
|
76
|
+
id: 'permissionMode',
|
|
77
|
+
label: isCodex ? 'Execution policy' : 'Permission mode',
|
|
78
|
+
options: input.options,
|
|
79
|
+
defaultValue: input.defaultValue ?? null,
|
|
80
|
+
availability: isCodex ? 'setup' : 'setup_and_live',
|
|
81
|
+
liveBehavior: isCodex ? 'none' : 'immediate',
|
|
82
|
+
selectionPolicy: 'inherit',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildRuntimeEffortControl() {
|
|
86
|
+
return {
|
|
87
|
+
id: 'effort',
|
|
88
|
+
label: 'Thinking level',
|
|
89
|
+
options: [...CLAUDE_EFFORT_OPTIONS],
|
|
90
|
+
defaultValue: 'medium',
|
|
91
|
+
availability: 'setup_and_live',
|
|
92
|
+
liveBehavior: 'immediate',
|
|
93
|
+
selectionPolicy: 'inherit',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function buildFirstPartyCodingRuntimeDescriptor(input) {
|
|
97
|
+
const permissionModes = input.permissionModes
|
|
98
|
+
?? (input.clientType === 'claude-code' ? CLAUDE_PERMISSION_MODE_OPTIONS : []);
|
|
99
|
+
return {
|
|
100
|
+
coreControls: [
|
|
101
|
+
buildRuntimeModelControl({
|
|
102
|
+
models: input.models,
|
|
103
|
+
liveBehavior: input.clientType === 'codex' ? 'next_turn' : 'immediate',
|
|
104
|
+
}),
|
|
105
|
+
buildRuntimeWorkspaceControl({
|
|
106
|
+
workspaces: input.workspaces,
|
|
107
|
+
workspaceRoots: input.workspaceRoots,
|
|
108
|
+
}),
|
|
109
|
+
buildRuntimeExecutionModeControl(input.executionModes),
|
|
110
|
+
],
|
|
111
|
+
runtimeControls: [
|
|
112
|
+
...(permissionModes.length > 0
|
|
113
|
+
? [buildRuntimePermissionModeControl({
|
|
114
|
+
clientType: input.clientType,
|
|
115
|
+
options: permissionModes,
|
|
116
|
+
defaultValue: input.defaultPermissionMode
|
|
117
|
+
?? (input.clientType === 'claude-code' ? 'default' : null),
|
|
118
|
+
})]
|
|
119
|
+
: []),
|
|
120
|
+
...(input.clientType === 'claude-code' ? [buildRuntimeEffortControl()] : []),
|
|
121
|
+
],
|
|
122
|
+
actions: input.actions,
|
|
123
|
+
workspaceRoots: input.workspaceRoots,
|
|
124
|
+
supportsInterrupt: true,
|
|
125
|
+
streamingTextMode: input.streamingTextMode,
|
|
126
|
+
admissionActions: input.admissionActions,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentClientType, AgentRuntime, RuntimeInfoPayload } from './types.js';
|
|
2
|
+
import { type AgentSessionSnapshotPatch, type RTDBClientHandle, type SessionStatePayload, type TurnStatePayload } from './rtdb-rest.js';
|
|
3
|
+
export interface RuntimeStatePublisherOptions {
|
|
4
|
+
agentId: string;
|
|
5
|
+
clientType: AgentClientType;
|
|
6
|
+
hostMode: boolean;
|
|
7
|
+
rtdb?: RTDBClientHandle;
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeStreamingPayload {
|
|
10
|
+
text: string;
|
|
11
|
+
status: 'thinking' | 'streaming' | 'tool';
|
|
12
|
+
messageId?: string;
|
|
13
|
+
updatedAt?: number | {
|
|
14
|
+
'.sv': 'timestamp';
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface RuntimeStatePublisher {
|
|
18
|
+
publishAgentRuntime(runtime: AgentRuntime): Promise<void>;
|
|
19
|
+
clearAgentRuntime(): Promise<void>;
|
|
20
|
+
writeSessionState(conversationId: string, state: Omit<SessionStatePayload, 'updatedAt'>): Promise<void>;
|
|
21
|
+
clearSessionState(conversationId: string): Promise<void>;
|
|
22
|
+
writeTurnState(conversationId: string, state: Omit<TurnStatePayload, 'updatedAt'>): Promise<void>;
|
|
23
|
+
clearTurnState(conversationId: string): Promise<void>;
|
|
24
|
+
patchAgentSessionSnapshot(conversationId: string, snapshot: Omit<AgentSessionSnapshotPatch, 'updatedAt'>): Promise<void>;
|
|
25
|
+
writeRuntimeInfo(conversationId: string, payload: Omit<RuntimeInfoPayload, 'updatedAt'>): Promise<void>;
|
|
26
|
+
patchRuntimeInfo(conversationId: string, payload: Partial<Omit<RuntimeInfoPayload, 'updatedAt'>>): Promise<void>;
|
|
27
|
+
clearRuntimeInfo(conversationId: string): Promise<void>;
|
|
28
|
+
writeStreaming(conversationId: string, payload: RuntimeStreamingPayload): Promise<void>;
|
|
29
|
+
clearStreaming(conversationId: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function createRuntimeStatePublisher(options: RuntimeStatePublisherOptions): RuntimeStatePublisher;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { clearRuntimeInfo, clearSessionState, clearTurnState, patchAgentSessionSnapshot, patchRuntimeInfo, rtdbWrite, writeRuntimeInfo, writeSessionState, writeTurnState, } from './rtdb-rest.js';
|
|
2
|
+
const SERVER_TIMESTAMP = { '.sv': 'timestamp' };
|
|
3
|
+
export function createRuntimeStatePublisher(options) {
|
|
4
|
+
const { agentId, clientType, hostMode, rtdb } = options;
|
|
5
|
+
const writePath = (path, data) => (rtdb ? rtdb.write(path, data) : rtdbWrite(path, data));
|
|
6
|
+
return {
|
|
7
|
+
async publishAgentRuntime(runtime) {
|
|
8
|
+
await writePath(`/agent-runtime/${agentId}`, {
|
|
9
|
+
clientType,
|
|
10
|
+
hostMode,
|
|
11
|
+
...runtime,
|
|
12
|
+
updatedAt: SERVER_TIMESTAMP,
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
async clearAgentRuntime() {
|
|
16
|
+
await writePath(`/agent-runtime/${agentId}`, null);
|
|
17
|
+
},
|
|
18
|
+
async writeSessionState(conversationId, state) {
|
|
19
|
+
await (rtdb
|
|
20
|
+
? rtdb.writeSessionState(conversationId, agentId, state)
|
|
21
|
+
: writeSessionState(conversationId, agentId, state));
|
|
22
|
+
},
|
|
23
|
+
async clearSessionState(conversationId) {
|
|
24
|
+
await (rtdb
|
|
25
|
+
? rtdb.clearSessionState(conversationId, agentId)
|
|
26
|
+
: clearSessionState(conversationId, agentId));
|
|
27
|
+
},
|
|
28
|
+
async writeTurnState(conversationId, state) {
|
|
29
|
+
await (rtdb
|
|
30
|
+
? rtdb.writeTurnState(conversationId, agentId, state)
|
|
31
|
+
: writeTurnState(conversationId, agentId, state));
|
|
32
|
+
},
|
|
33
|
+
async clearTurnState(conversationId) {
|
|
34
|
+
await (rtdb
|
|
35
|
+
? rtdb.clearTurnState(conversationId, agentId)
|
|
36
|
+
: clearTurnState(conversationId, agentId));
|
|
37
|
+
},
|
|
38
|
+
async patchAgentSessionSnapshot(conversationId, snapshot) {
|
|
39
|
+
await (rtdb
|
|
40
|
+
? rtdb.patchAgentSessionSnapshot(conversationId, agentId, snapshot)
|
|
41
|
+
: patchAgentSessionSnapshot(conversationId, agentId, snapshot));
|
|
42
|
+
},
|
|
43
|
+
async writeRuntimeInfo(conversationId, payload) {
|
|
44
|
+
await (rtdb
|
|
45
|
+
? rtdb.writeRuntimeInfo(conversationId, agentId, payload)
|
|
46
|
+
: writeRuntimeInfo(conversationId, agentId, payload));
|
|
47
|
+
},
|
|
48
|
+
async patchRuntimeInfo(conversationId, payload) {
|
|
49
|
+
await (rtdb
|
|
50
|
+
? rtdb.patchRuntimeInfo(conversationId, agentId, payload)
|
|
51
|
+
: patchRuntimeInfo(conversationId, agentId, payload));
|
|
52
|
+
},
|
|
53
|
+
async clearRuntimeInfo(conversationId) {
|
|
54
|
+
await (rtdb
|
|
55
|
+
? rtdb.clearRuntimeInfo(conversationId, agentId)
|
|
56
|
+
: clearRuntimeInfo(conversationId, agentId));
|
|
57
|
+
},
|
|
58
|
+
async writeStreaming(conversationId, payload) {
|
|
59
|
+
await writePath(`/streaming/${conversationId}/${agentId}`, {
|
|
60
|
+
...payload,
|
|
61
|
+
updatedAt: payload.updatedAt ?? SERVER_TIMESTAMP,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
async clearStreaming(conversationId) {
|
|
65
|
+
await writePath(`/streaming/${conversationId}/${agentId}`, null);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -482,7 +482,6 @@ export interface SessionState {
|
|
|
482
482
|
/** True when the agent is running under the host wrapper (host.ts) which can apply control signals */
|
|
483
483
|
hostMode?: boolean;
|
|
484
484
|
isActive: boolean;
|
|
485
|
-
state?: 'idle' | 'running' | 'requires_action';
|
|
486
485
|
contextUsage?: {
|
|
487
486
|
percentage: number;
|
|
488
487
|
totalTokens: number;
|
|
@@ -585,7 +584,6 @@ export interface AgentSessionSnapshot {
|
|
|
585
584
|
resolvedCwd?: string | null;
|
|
586
585
|
worktreePath?: string | null;
|
|
587
586
|
executionFallbackReason?: string | null;
|
|
588
|
-
state?: SessionState['state'];
|
|
589
587
|
turnState?: TurnLifecycleState;
|
|
590
588
|
supportsQueue?: boolean;
|
|
591
589
|
queueDepth: number;
|