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