@canonmsg/core 0.15.2 → 0.15.3
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.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/message-format.js +3 -2
- package/dist/reach-out.d.ts +37 -0
- package/dist/reach-out.js +73 -0
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
package/dist/host-runtime.js
CHANGED
|
@@ -82,7 +82,7 @@ function describeContactCard(card) {
|
|
|
82
82
|
? 'reject pending requests'
|
|
83
83
|
: null,
|
|
84
84
|
].filter(Boolean).join(', ');
|
|
85
|
-
const hint = `This host can inspect the card, but Canon admission actions
|
|
85
|
+
const hint = `This host can inspect the card, but this wrapper does not expose callable Canon admission actions yet. Missing capabilities here: ${missingCapabilities}. Use an SDK/OpenClaw reach-out surface or another Canon client for userId ${card.userId}.`;
|
|
86
86
|
return `${identity}\n${hint}`;
|
|
87
87
|
}
|
|
88
88
|
function describeAttachment(attachment, materialized) {
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export type { HostInboundParticipantContext, } from './host-runtime.js';
|
|
|
37
37
|
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
38
38
|
export type { RuntimeStatePublisher, RuntimeStatePublisherOptions, RuntimeStreamingPayload, } from './runtime-state-publisher.js';
|
|
39
39
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
40
|
+
export { reachOutToCanonContact } from './reach-out.js';
|
|
41
|
+
export type { CanonReachOutClient, CanonReachOutOptions, CanonReachOutResult, } from './reach-out.js';
|
|
40
42
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
41
43
|
export { resolveCanonBaseUrl } from './base-url.js';
|
|
42
44
|
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from './cli-metadata.js';
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,8 @@ export { buildCanonHostPrompt, buildHydratedInboundContext, createConversationMe
|
|
|
35
35
|
export { createRuntimeStatePublisher, } from './runtime-state-publisher.js';
|
|
36
36
|
// Message formatting (LLM-facing text projection)
|
|
37
37
|
export { formatCanonMessageAsText } from './message-format.js';
|
|
38
|
+
// Admission-aware reach-out helpers for runtime clients
|
|
39
|
+
export { reachOutToCanonContact } from './reach-out.js';
|
|
38
40
|
// Constants
|
|
39
41
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
40
42
|
// Base URL resolver
|
package/dist/message-format.js
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
* regression in every consumer plugin.
|
|
11
11
|
*/
|
|
12
12
|
export function formatCanonMessageAsText(message) {
|
|
13
|
+
const trimmedText = typeof message.text === 'string' ? message.text.trim() : '';
|
|
13
14
|
if (message.contentType === 'contact_card' && message.contactCard) {
|
|
14
|
-
|
|
15
|
+
const cardText = formatContactCard(message.contactCard);
|
|
16
|
+
return trimmedText ? `${cardText}\n${trimmedText}` : cardText;
|
|
15
17
|
}
|
|
16
18
|
const attachment = pickPrimaryAttachment(message.attachments);
|
|
17
|
-
const trimmedText = typeof message.text === 'string' ? message.text.trim() : '';
|
|
18
19
|
if (attachment?.kind === 'image') {
|
|
19
20
|
return trimmedText ? `[image] ${trimmedText}` : '[image]';
|
|
20
21
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions } from './types.js';
|
|
2
|
+
export interface CanonReachOutClient {
|
|
3
|
+
resolveAdmission(targetUserId: string): Promise<CanonResolveAdmissionResult>;
|
|
4
|
+
createConversation(options: CreateConversationOptions): Promise<{
|
|
5
|
+
conversationId: string;
|
|
6
|
+
}>;
|
|
7
|
+
sendMessage(conversationId: string, text: string, options?: SendMessageOptions): Promise<{
|
|
8
|
+
messageId: string;
|
|
9
|
+
}>;
|
|
10
|
+
createContactRequest(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
|
|
11
|
+
}
|
|
12
|
+
export interface CanonReachOutOptions {
|
|
13
|
+
targetUserId: string;
|
|
14
|
+
text?: string | null;
|
|
15
|
+
requestMessage?: string | null;
|
|
16
|
+
sendMessageOptions?: SendMessageOptions;
|
|
17
|
+
}
|
|
18
|
+
export type CanonReachOutResult = {
|
|
19
|
+
status: 'messaged';
|
|
20
|
+
conversationId: string;
|
|
21
|
+
messageId?: string;
|
|
22
|
+
} | {
|
|
23
|
+
status: 'requested';
|
|
24
|
+
requestId: string | null;
|
|
25
|
+
} | {
|
|
26
|
+
status: 'pending';
|
|
27
|
+
requestId: string | null;
|
|
28
|
+
} | {
|
|
29
|
+
status: 'blocked' | 'unavailable';
|
|
30
|
+
reason: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Admission-aware direct reach-out for runtime clients. It deliberately keeps
|
|
34
|
+
* backend conversation creation fail-closed; callers choose this helper when
|
|
35
|
+
* their product behavior is "message if reachable, otherwise request access."
|
|
36
|
+
*/
|
|
37
|
+
export declare function reachOutToCanonContact(client: CanonReachOutClient, options: CanonReachOutOptions): Promise<CanonReachOutResult>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const CONTACT_REQUEST_MESSAGE_LIMIT = 500;
|
|
2
|
+
function normalizeOptionalText(value) {
|
|
3
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
4
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
5
|
+
}
|
|
6
|
+
function normalizeContactRequestMessage(requestMessage, fallbackText) {
|
|
7
|
+
const text = normalizeOptionalText(requestMessage) ?? normalizeOptionalText(fallbackText);
|
|
8
|
+
if (!text)
|
|
9
|
+
return null;
|
|
10
|
+
if (text.length <= CONTACT_REQUEST_MESSAGE_LIMIT)
|
|
11
|
+
return text;
|
|
12
|
+
return `${text.slice(0, CONTACT_REQUEST_MESSAGE_LIMIT - 3)}...`;
|
|
13
|
+
}
|
|
14
|
+
function isConnectionRequiredError(error) {
|
|
15
|
+
const status = error && typeof error === 'object' && 'status' in error
|
|
16
|
+
? Number(error.status)
|
|
17
|
+
: null;
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
19
|
+
return status === 403 && /CONNECTION_REQUIRED|connection required/i.test(message);
|
|
20
|
+
}
|
|
21
|
+
async function openConversationAndMaybeMessage(client, options) {
|
|
22
|
+
const { conversationId } = await client.createConversation({
|
|
23
|
+
type: 'direct',
|
|
24
|
+
targetUserId: options.targetUserId,
|
|
25
|
+
});
|
|
26
|
+
const text = normalizeOptionalText(options.text);
|
|
27
|
+
if (text) {
|
|
28
|
+
const { messageId } = options.sendMessageOptions
|
|
29
|
+
? await client.sendMessage(conversationId, text, options.sendMessageOptions)
|
|
30
|
+
: await client.sendMessage(conversationId, text);
|
|
31
|
+
return { status: 'messaged', conversationId, messageId };
|
|
32
|
+
}
|
|
33
|
+
return { status: 'messaged', conversationId };
|
|
34
|
+
}
|
|
35
|
+
async function requestContact(client, options) {
|
|
36
|
+
const result = await client.createContactRequest(options.targetUserId, normalizeContactRequestMessage(options.requestMessage, options.text));
|
|
37
|
+
if (result.status === 'open') {
|
|
38
|
+
return openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
|
|
39
|
+
}
|
|
40
|
+
if (result.status === 'duplicate') {
|
|
41
|
+
return { status: 'pending', requestId: result.requestId };
|
|
42
|
+
}
|
|
43
|
+
return { status: 'requested', requestId: result.requestId };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Admission-aware direct reach-out for runtime clients. It deliberately keeps
|
|
47
|
+
* backend conversation creation fail-closed; callers choose this helper when
|
|
48
|
+
* their product behavior is "message if reachable, otherwise request access."
|
|
49
|
+
*/
|
|
50
|
+
export async function reachOutToCanonContact(client, options) {
|
|
51
|
+
const { admission } = await client.resolveAdmission(options.targetUserId);
|
|
52
|
+
if (admission.state === 'allowed' && admission.canMessage) {
|
|
53
|
+
try {
|
|
54
|
+
return await openConversationAndMaybeMessage(client, options);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (isConnectionRequiredError(error)) {
|
|
58
|
+
return requestContact(client, options);
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (admission.state === 'pending-outbound') {
|
|
64
|
+
return { status: 'pending', requestId: admission.pendingRequestId ?? null };
|
|
65
|
+
}
|
|
66
|
+
if (admission.state === 'request-required' && admission.canRequestContact) {
|
|
67
|
+
return requestContact(client, options);
|
|
68
|
+
}
|
|
69
|
+
if (admission.state === 'blocked') {
|
|
70
|
+
return { status: 'blocked', reason: 'blocked' };
|
|
71
|
+
}
|
|
72
|
+
return { status: 'unavailable', reason: admission.state };
|
|
73
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -21,9 +21,9 @@ export interface ForwardedFrom {
|
|
|
21
21
|
/**
|
|
22
22
|
* Server-serialized contact-card payload. Emitted on messages with
|
|
23
23
|
* `contentType: 'contact_card'` so agents receive the referenced user's
|
|
24
|
-
* identity alongside the card. Agents use the referenced `userId` with
|
|
25
|
-
*
|
|
26
|
-
* contact, they
|
|
24
|
+
* identity alongside the card. Agents use the referenced `userId` with an
|
|
25
|
+
* admission-aware reach-out path; if the target's inbound policy blocks cold
|
|
26
|
+
* contact, they create a contact request and retry after approval.
|
|
27
27
|
*/
|
|
28
28
|
export interface ContactCardPayload {
|
|
29
29
|
userId: string;
|