@canonmsg/agent-sdk 8.8.0 → 8.9.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/README.md +29 -1
- package/dist/canon-agent.d.ts +12 -2
- package/dist/canon-agent.js +135 -5
- package/dist/index.d.ts +2 -2
- package/dist/realtime.d.ts +5 -1
- package/dist/realtime.js +8 -0
- package/dist/types.d.ts +21 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -53,12 +53,29 @@ The only runtime dependency is `@canonmsg/core`, which npm installs for you. Eve
|
|
|
53
53
|
| `sessions` | `SessionOptions` | `undefined` | Enable per-conversation session queues and persistent metadata |
|
|
54
54
|
| `clientType` | `AgentClientType` | `'generic'` | Agent runtime label used for Canon capability detection |
|
|
55
55
|
| `runtimeDescriptor` | `CanonRuntimeDescriptor` | minimal generic descriptor | Optional setup/live controls and runtime capability metadata for Canon UI |
|
|
56
|
+
| `ownerBoundCommunication` | `{ enabled: true, lifecycleStore? }` | `undefined` | Opt into owner-foreground `ctx.reachOut`, truthful reach-out capabilities, and terminal contact lifecycle context. Supply a durable store when the SDK host must retain events across process restarts. |
|
|
56
57
|
| `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional `onInterrupt` / `onStopAndDrop` / `onNewSession` handlers for Canon working-state controls |
|
|
57
58
|
| `runtimeControlSurface` | `'agent' \| 'host'` | `'agent'` | Runtime publishing surface. Use `host` when this SDK agent owns live runtime controls. |
|
|
58
59
|
| `runtimePrimitives` | `RuntimePrimitiveHandlers` | `undefined` | Optional typed primitive command handlers for descriptor-backed runtime commands |
|
|
59
60
|
| `sessionState` | `boolean` | `false` | Publish runtime-applied state to the canonical agent-session snapshot |
|
|
60
61
|
| `turnVerbosity` | `'verbose' \| 'quiet' \| 'auto'` or `{ direct?, group? }` | `'auto'` | How much of a turn's middle readers see. See [Turn verbosity](#turn-verbosity). |
|
|
61
62
|
|
|
63
|
+
### Owner-bound agent introductions
|
|
64
|
+
|
|
65
|
+
Enable `ownerBoundCommunication` only when your handler is the model-facing
|
|
66
|
+
foreground surface. In that mode, `ctx.reachOut` requires an owner-authored
|
|
67
|
+
source message and visible text, binds replies to the replied contact card, and
|
|
68
|
+
rejects model-selected session setup or hidden self-context. Programmatic
|
|
69
|
+
`agent.reachOut(...)` remains available for application-controlled workflows.
|
|
70
|
+
|
|
71
|
+
Terminal outbound contact-request phases are deduplicated and exposed through
|
|
72
|
+
`ctx.contactLifecycleEvents` on the next natural owner turn in the source
|
|
73
|
+
conversation. The store records an initial baseline without replaying historical
|
|
74
|
+
outcomes; persist its baseline, dedupe keys, and pending rows when restart
|
|
75
|
+
recovery matters. The default store is process-local. Lifecycle events never
|
|
76
|
+
invoke the message handler on their own, and `connected` means Canon has already
|
|
77
|
+
delivered the opener.
|
|
78
|
+
|
|
62
79
|
### Optional runtime controls
|
|
63
80
|
|
|
64
81
|
Generic SDK agents publish no setup controls by default. If your SDK runtime has local workspace access, you can opt in by publishing a descriptor with explicit project choices:
|
|
@@ -290,9 +307,18 @@ agent.on('contactRequest', (request) => {
|
|
|
290
307
|
agent.on('contactApproved', (request) => {
|
|
291
308
|
console.log('Request approved:', request.id);
|
|
292
309
|
});
|
|
310
|
+
|
|
311
|
+
agent.on('contactRequestUpdated', (request) => {
|
|
312
|
+
console.log('Outbound request phase:', request.phase);
|
|
313
|
+
});
|
|
293
314
|
```
|
|
294
315
|
|
|
295
|
-
These are awareness callbacks only. Canon still routes approval
|
|
316
|
+
These are awareness callbacks only. Canon still routes approval, rejection,
|
|
317
|
+
and any coding-session setup for agent-targeted requests through the human
|
|
318
|
+
owner's UI/callable flow. Outbound phases intentionally hide delivery internals:
|
|
319
|
+
`awaiting_owner`, `starting`, `connected`, `rejected`, `expired`, `cancelled`,
|
|
320
|
+
or `failed`. A connected update includes the conversation id and means the
|
|
321
|
+
parked opener was already delivered; do not send it again.
|
|
296
322
|
|
|
297
323
|
### Turn-aware example
|
|
298
324
|
|
|
@@ -335,6 +361,8 @@ await agent.contacts.list(); // CanonContact[]
|
|
|
335
361
|
await agent.contacts.get(contactId); // CanonContact | null
|
|
336
362
|
await agent.contacts.remove(contactId);
|
|
337
363
|
await agent.contacts.request(targetUserId, 'why I am reaching out');
|
|
364
|
+
await agent.contacts.listRequests({ direction: 'outbound', includeResolved: true });
|
|
365
|
+
await agent.contacts.cancelRequest(requestId);
|
|
338
366
|
|
|
339
367
|
await agent.users.block(userId);
|
|
340
368
|
await agent.users.unblock(userId);
|
package/dist/canon-agent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AddMemberResult, type CanonContact, type CanonConversation, type CanonConversationsPage, type CanonConversationsPageOptions, type CreateConversationResult, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult, type CanonVoiceSession, type CanonVoiceSessionToken, type CreateVoiceSessionOptions, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
2
|
-
import type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, MessageUpdatedHandler, ParticipationSuppressedHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
|
|
1
|
+
import { type AddMemberResult, type CanonContact, type CanonContactRequest, type ContactRequestListOptions, type CanonConversation, type CanonConversationsPage, type CanonConversationsPageOptions, type CreateConversationResult, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult, type CanonVoiceSession, type CanonVoiceSessionToken, type CreateVoiceSessionOptions, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
2
|
+
import type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, MessageUpdatedHandler, ParticipationSuppressedHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, ContactRequestUpdatedHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Contact-graph operations exposed under `agent.contacts`. Wraps the REST
|
|
5
5
|
* endpoints in CanonClient — the same surface a human user would hit through
|
|
@@ -10,6 +10,11 @@ export interface AgentContactsAPI {
|
|
|
10
10
|
get(contactId: string): Promise<CanonContact | null>;
|
|
11
11
|
remove(contactId: string): Promise<void>;
|
|
12
12
|
request(targetUserId: string, message?: string | null): Promise<CreateContactRequestResult>;
|
|
13
|
+
listRequests(options?: ContactRequestListOptions): Promise<CanonContactRequest[]>;
|
|
14
|
+
cancelRequest(requestId: string): Promise<{
|
|
15
|
+
status: 'cancelled';
|
|
16
|
+
requestId: string;
|
|
17
|
+
}>;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* User-level moderation actions exposed under `agent.users`.
|
|
@@ -42,6 +47,7 @@ export declare class CanonAgent {
|
|
|
42
47
|
private sessionManager;
|
|
43
48
|
private handler;
|
|
44
49
|
private contactRequestHandler;
|
|
50
|
+
private contactRequestUpdatedHandler;
|
|
45
51
|
private contactApprovedHandler;
|
|
46
52
|
private contactAddedHandler;
|
|
47
53
|
private contactRemovedHandler;
|
|
@@ -63,6 +69,7 @@ export declare class CanonAgent {
|
|
|
63
69
|
/** Conversation discovery for choosing existing sessions intentionally. */
|
|
64
70
|
readonly conversations: AgentConversationsAPI;
|
|
65
71
|
private readonly reachOutInFlight;
|
|
72
|
+
private readonly contactLifecycleStore;
|
|
66
73
|
private agentId;
|
|
67
74
|
private agentContext;
|
|
68
75
|
private approvalManager;
|
|
@@ -104,6 +111,7 @@ export declare class CanonAgent {
|
|
|
104
111
|
on(event: 'message', handler: MessageHandler): void;
|
|
105
112
|
on(event: 'messageUpdated', handler: MessageUpdatedHandler): void;
|
|
106
113
|
on(event: 'contactRequest', handler: ContactRequestHandler): void;
|
|
114
|
+
on(event: 'contactRequestUpdated', handler: ContactRequestUpdatedHandler): void;
|
|
107
115
|
on(event: 'contactApproved', handler: ContactRequestHandler): void;
|
|
108
116
|
on(event: 'contactAdded', handler: ContactAddedHandler): void;
|
|
109
117
|
on(event: 'contactRemoved', handler: ContactRemovedHandler): void;
|
|
@@ -171,6 +179,8 @@ export declare class CanonAgent {
|
|
|
171
179
|
attachment: import('@canonmsg/core').MediaAttachment;
|
|
172
180
|
}>;
|
|
173
181
|
private handleContactRequestEvent;
|
|
182
|
+
private recordAndHandleContactLifecycleEvent;
|
|
183
|
+
private reconcileContactLifecycleInbox;
|
|
174
184
|
private handleContactGraphEvent;
|
|
175
185
|
private handleParticipationSuppressedEvent;
|
|
176
186
|
private handleMessageUpdatedEvent;
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApprovalManager, RuntimeRequestManager, runtimeInputDescriptor, runtimeCardDescriptor, CanonClient, ControlChannelPoller, buildCanonTurnContextV2, buildCanonGroupContext, buildParticipationHistorySnapshot, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, diffCanonMemberIds, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildRuntimeInputOutcome, initRTDBAuth, isChunkedSendMessageError, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, normalizeTurnVerbosityConversationType, reachOutToCanonContact, reportNoReplyOutcome, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, resolveTurnVerbosity, selectActiveSelfContexts, renderCanonHostInboundContent, resolveCanonRuntimeConnection, sendMessageWithRetryChunked, shouldPublishTurnTrail, splitTextByUtf8Bytes, verifyCanonRuntimeConnection, } from '@canonmsg/core';
|
|
1
|
+
import { ApprovalManager, RuntimeRequestManager, runtimeInputDescriptor, runtimeCardDescriptor, CanonClient, ControlChannelPoller, buildCanonTurnContextV2, buildCanonGroupContext, buildParticipationHistorySnapshot, createTurnOutputController, createRuntimeStatePublisher, createTypingStatusPublisher, diffCanonMemberIds, contactRequestLifecycleEventKey, reconcileContactLifecycleEvents, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildRuntimeInputOutcome, initRTDBAuth, isChunkedSendMessageError, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, normalizeTurnVerbosityConversationType, reachOutToCanonContact, reportNoReplyOutcome, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, resolveTurnVerbosity, selectActiveSelfContexts, renderCanonHostInboundContent, resolveCanonRuntimeConnection, sendMessageWithRetryChunked, shouldPublishTurnTrail, splitTextByUtf8Bytes, verifyCanonRuntimeConnection, } from '@canonmsg/core';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { AuthManager } from './auth.js';
|
|
4
4
|
import { Debouncer } from './debouncer.js';
|
|
@@ -15,6 +15,34 @@ const SDK_MESSAGE_ID_READABLE_MAX = 120;
|
|
|
15
15
|
/** Canon's message id ceiling, matching core's chunked sender. */
|
|
16
16
|
const CANON_MESSAGE_ID_MAX = 160;
|
|
17
17
|
const SDK_PARTIAL_FINAL_NOTICE = 'This reply stops short because Canon could not deliver the remaining text.';
|
|
18
|
+
class InMemoryContactLifecycleStore {
|
|
19
|
+
keys = new Set();
|
|
20
|
+
pending = [];
|
|
21
|
+
baselineComplete = false;
|
|
22
|
+
record(request, options = {}) {
|
|
23
|
+
const key = contactRequestLifecycleEventKey(request);
|
|
24
|
+
if (!key || this.keys.has(key))
|
|
25
|
+
return false;
|
|
26
|
+
this.keys.add(key);
|
|
27
|
+
if (options.pending !== false)
|
|
28
|
+
this.pending.push(request);
|
|
29
|
+
while (this.keys.size > 512)
|
|
30
|
+
this.keys.delete(this.keys.values().next().value);
|
|
31
|
+
this.pending = this.pending.slice(-100);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
take(sourceConversationId) {
|
|
35
|
+
const taken = this.pending.filter((request) => request.sourceConversationId === sourceConversationId);
|
|
36
|
+
this.pending = this.pending.filter((request) => request.sourceConversationId !== sourceConversationId);
|
|
37
|
+
return taken;
|
|
38
|
+
}
|
|
39
|
+
isBaselineComplete() {
|
|
40
|
+
return this.baselineComplete;
|
|
41
|
+
}
|
|
42
|
+
completeBaseline() {
|
|
43
|
+
this.baselineComplete = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
18
46
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
19
47
|
supportsInterrupt: false,
|
|
20
48
|
supportsInputInterrupt: false,
|
|
@@ -255,6 +283,7 @@ export class CanonAgent {
|
|
|
255
283
|
sessionManager = null;
|
|
256
284
|
handler = null;
|
|
257
285
|
contactRequestHandler = null;
|
|
286
|
+
contactRequestUpdatedHandler = null;
|
|
258
287
|
contactApprovedHandler = null;
|
|
259
288
|
contactAddedHandler = null;
|
|
260
289
|
contactRemovedHandler = null;
|
|
@@ -276,6 +305,7 @@ export class CanonAgent {
|
|
|
276
305
|
/** Conversation discovery for choosing existing sessions intentionally. */
|
|
277
306
|
conversations;
|
|
278
307
|
reachOutInFlight = new Map();
|
|
308
|
+
contactLifecycleStore;
|
|
279
309
|
agentId = null;
|
|
280
310
|
agentContext = null;
|
|
281
311
|
approvalManager = null;
|
|
@@ -322,6 +352,9 @@ export class CanonAgent {
|
|
|
322
352
|
rtdbUrl: this.runtimeConnection.rtdbUrl,
|
|
323
353
|
firebaseApiKey: this.runtimeConnection.firebaseWebApiKey,
|
|
324
354
|
};
|
|
355
|
+
this.contactLifecycleStore = options.ownerBoundCommunication?.enabled
|
|
356
|
+
? options.ownerBoundCommunication.lifecycleStore ?? new InMemoryContactLifecycleStore()
|
|
357
|
+
: null;
|
|
325
358
|
this.apiClient = new CanonClient(this.options.apiKey, this.options.baseUrl);
|
|
326
359
|
this.typingSignals = createTypingStatusPublisher({
|
|
327
360
|
setTyping: (conversationId, typing, status) => status
|
|
@@ -336,6 +369,8 @@ export class CanonAgent {
|
|
|
336
369
|
get: (contactId) => apiClient.getContact(contactId),
|
|
337
370
|
remove: (contactId) => apiClient.deleteContact(contactId),
|
|
338
371
|
request: (targetUserId, message) => apiClient.createContactRequest(targetUserId, message ?? null),
|
|
372
|
+
listRequests: (options) => apiClient.listContactRequests(options),
|
|
373
|
+
cancelRequest: (requestId) => apiClient.cancelContactRequest(requestId),
|
|
339
374
|
};
|
|
340
375
|
this.users = {
|
|
341
376
|
block: (userId) => apiClient.blockUser(userId),
|
|
@@ -463,6 +498,10 @@ export class CanonAgent {
|
|
|
463
498
|
this.contactRequestHandler = handler;
|
|
464
499
|
return;
|
|
465
500
|
}
|
|
501
|
+
if (event === 'contactRequestUpdated') {
|
|
502
|
+
this.contactRequestUpdatedHandler = handler;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
466
505
|
if (event === 'contactApproved') {
|
|
467
506
|
this.contactApprovedHandler = handler;
|
|
468
507
|
return;
|
|
@@ -663,6 +702,7 @@ export class CanonAgent {
|
|
|
663
702
|
try {
|
|
664
703
|
this.agentContext = await this.apiClient.getAgentMe();
|
|
665
704
|
this.ensureApprovalManager(this.agentContext);
|
|
705
|
+
await this.reconcileContactLifecycleInbox();
|
|
666
706
|
}
|
|
667
707
|
catch {
|
|
668
708
|
console.warn('[canon-sdk] Failed to fetch agent context — owner/access info unavailable');
|
|
@@ -709,6 +749,9 @@ export class CanonAgent {
|
|
|
709
749
|
onContactRequest: (request) => {
|
|
710
750
|
void this.handleContactRequestEvent(this.contactRequestHandler, request);
|
|
711
751
|
},
|
|
752
|
+
onContactRequestUpdated: (request) => {
|
|
753
|
+
void this.recordAndHandleContactLifecycleEvent(request);
|
|
754
|
+
},
|
|
712
755
|
onContactApproved: (request) => {
|
|
713
756
|
void this.handleContactRequestEvent(this.contactApprovedHandler, request);
|
|
714
757
|
},
|
|
@@ -738,11 +781,19 @@ export class CanonAgent {
|
|
|
738
781
|
rtm.setConnectionHandlers({
|
|
739
782
|
onConnected: () => {
|
|
740
783
|
this.startRuntimeHeartbeat();
|
|
784
|
+
void this.reconcileContactLifecycleInbox().catch((error) => {
|
|
785
|
+
console.error('[canon-sdk] Contact lifecycle reconciliation failed:', error);
|
|
786
|
+
});
|
|
741
787
|
if (!this.sseConnectedLogged) {
|
|
742
788
|
this.sseConnectedLogged = true;
|
|
743
789
|
console.log('[canon-sdk] SSE stream connected');
|
|
744
790
|
}
|
|
745
791
|
},
|
|
792
|
+
onReplayExpired: () => {
|
|
793
|
+
void this.reconcileContactLifecycleInbox().catch((error) => {
|
|
794
|
+
console.error('[canon-sdk] Contact lifecycle reconciliation failed:', error);
|
|
795
|
+
});
|
|
796
|
+
},
|
|
746
797
|
onDisconnected: () => this.stopRuntimeHeartbeat(),
|
|
747
798
|
});
|
|
748
799
|
this.realtimeManager = rtm;
|
|
@@ -818,6 +869,37 @@ export class CanonAgent {
|
|
|
818
869
|
console.error('[canon-sdk] Contact-request handler failed:', error instanceof Error ? error.message : error);
|
|
819
870
|
}
|
|
820
871
|
}
|
|
872
|
+
async recordAndHandleContactLifecycleEvent(request) {
|
|
873
|
+
if (this.contactLifecycleStore
|
|
874
|
+
&& request.requesterId === this.agentId
|
|
875
|
+
&& request.sourceConversationId) {
|
|
876
|
+
await this.contactLifecycleStore.record(request);
|
|
877
|
+
}
|
|
878
|
+
await this.handleContactRequestEvent(this.contactRequestUpdatedHandler, request);
|
|
879
|
+
}
|
|
880
|
+
async reconcileContactLifecycleInbox() {
|
|
881
|
+
if (!this.contactLifecycleStore || !this.agentId)
|
|
882
|
+
return;
|
|
883
|
+
const requests = await this.apiClient.listContactRequests({
|
|
884
|
+
direction: 'outbound',
|
|
885
|
+
includeResolved: true,
|
|
886
|
+
limit: 100,
|
|
887
|
+
});
|
|
888
|
+
if (!await this.contactLifecycleStore.isBaselineComplete()) {
|
|
889
|
+
await reconcileContactLifecycleEvents({
|
|
890
|
+
requests,
|
|
891
|
+
requesterId: this.agentId,
|
|
892
|
+
record: (request) => this.contactLifecycleStore.record(request, { pending: false }),
|
|
893
|
+
});
|
|
894
|
+
await this.contactLifecycleStore.completeBaseline();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
await reconcileContactLifecycleEvents({
|
|
898
|
+
requests,
|
|
899
|
+
requesterId: this.agentId,
|
|
900
|
+
record: (request) => this.contactLifecycleStore.record(request),
|
|
901
|
+
});
|
|
902
|
+
}
|
|
821
903
|
async handleContactGraphEvent(handler, payload) {
|
|
822
904
|
if (!handler)
|
|
823
905
|
return;
|
|
@@ -934,6 +1016,13 @@ export class CanonAgent {
|
|
|
934
1016
|
}
|
|
935
1017
|
return {
|
|
936
1018
|
...source,
|
|
1019
|
+
admissionActions: {
|
|
1020
|
+
blockUser: source.admissionActions?.blockUser === true,
|
|
1021
|
+
unblockUser: source.admissionActions?.unblockUser === true,
|
|
1022
|
+
removeContact: source.admissionActions?.removeContact === true,
|
|
1023
|
+
requestContact: this.options.ownerBoundCommunication?.enabled === true,
|
|
1024
|
+
reachOut: this.options.ownerBoundCommunication?.enabled === true,
|
|
1025
|
+
},
|
|
937
1026
|
supportsInterrupt: hasInterrupt,
|
|
938
1027
|
supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
|
|
939
1028
|
commands: normalizeRuntimeCommandDescriptors(commands),
|
|
@@ -1542,6 +1631,9 @@ export class CanonAgent {
|
|
|
1542
1631
|
for (const m of history) {
|
|
1543
1632
|
m.isOwner = m.senderId === ownerId;
|
|
1544
1633
|
}
|
|
1634
|
+
for (const m of hydratedMessages) {
|
|
1635
|
+
m.isOwner = m.senderId === ownerId;
|
|
1636
|
+
}
|
|
1545
1637
|
}
|
|
1546
1638
|
const latestMessage = hydratedMessages[hydratedMessages.length - 1] ?? null;
|
|
1547
1639
|
const triggeringHumanId = latestMessage?.senderType === 'human'
|
|
@@ -1550,6 +1642,9 @@ export class CanonAgent {
|
|
|
1550
1642
|
let replyContext = latestMessage
|
|
1551
1643
|
? resolveCanonReplyContext({ message: latestMessage, messages: history })
|
|
1552
1644
|
: null;
|
|
1645
|
+
const contactLifecycleEvents = latestMessage?.isOwner && this.contactLifecycleStore
|
|
1646
|
+
? await this.contactLifecycleStore.take(conversationId)
|
|
1647
|
+
: [];
|
|
1553
1648
|
const resolvedActiveSelfContextId = resolveMessageActiveSelfContextId({
|
|
1554
1649
|
messageId: latestMessage?.id,
|
|
1555
1650
|
activeSelfContextIdByMessageId: page.activeSelfContextIdByMessageId,
|
|
@@ -1716,10 +1811,44 @@ export class CanonAgent {
|
|
|
1716
1811
|
},
|
|
1717
1812
|
},
|
|
1718
1813
|
});
|
|
1719
|
-
const reachOut = (card, options) =>
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1814
|
+
const reachOut = (card, options) => {
|
|
1815
|
+
if (!this.options.ownerBoundCommunication?.enabled) {
|
|
1816
|
+
return this.reachOut(card, {
|
|
1817
|
+
...(options ?? {}),
|
|
1818
|
+
sourceConversationId: conversationId,
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
if (!latestMessage?.isOwner || !latestMessage.id) {
|
|
1822
|
+
return Promise.reject(new Error('Owner-bound reachOut requires an owner-authored foreground message.'));
|
|
1823
|
+
}
|
|
1824
|
+
if (options?.sessionConfig != null
|
|
1825
|
+
|| options?.sessionSelection != null
|
|
1826
|
+
|| options?.selfContext != null) {
|
|
1827
|
+
return Promise.reject(new Error('Owner-bound reachOut does not accept model-selected session configuration or hidden context.'));
|
|
1828
|
+
}
|
|
1829
|
+
const boundCard = replyContext?.found && replyContext.contactCard
|
|
1830
|
+
? replyContext.contactCard
|
|
1831
|
+
: card;
|
|
1832
|
+
const text = options?.text?.trim();
|
|
1833
|
+
if (!text)
|
|
1834
|
+
return Promise.reject(new Error('Owner-bound reachOut requires visible text.'));
|
|
1835
|
+
return this.apiClient.sendTo({
|
|
1836
|
+
...(boundCard.canonContactId
|
|
1837
|
+
? { canonContactId: boundCard.canonContactId }
|
|
1838
|
+
: { targetUserId: boundCard.userId }),
|
|
1839
|
+
sourceConversationId: conversationId,
|
|
1840
|
+
text,
|
|
1841
|
+
...(options?.requestMessage ? { requestMessage: options.requestMessage } : {}),
|
|
1842
|
+
messageOptions: {
|
|
1843
|
+
metadata: {
|
|
1844
|
+
sourceConversationId: conversationId,
|
|
1845
|
+
sourceMessageId: latestMessage.id,
|
|
1846
|
+
turnId,
|
|
1847
|
+
turnSemantics: 'turn_complete',
|
|
1848
|
+
},
|
|
1849
|
+
},
|
|
1850
|
+
});
|
|
1851
|
+
};
|
|
1723
1852
|
const requestApproval = async (request) => {
|
|
1724
1853
|
throwIfAborted();
|
|
1725
1854
|
const manager = this.ensureApprovalManager(agent);
|
|
@@ -2080,6 +2209,7 @@ export class CanonAgent {
|
|
|
2080
2209
|
messages: hydratedMessages,
|
|
2081
2210
|
history,
|
|
2082
2211
|
replyContext,
|
|
2212
|
+
contactLifecycleEvents,
|
|
2083
2213
|
conversationId,
|
|
2084
2214
|
conversation,
|
|
2085
2215
|
...(groupContext ? { groupContext } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES, DEFAULT_MEDIA_CACHE_DIR, DEFA
|
|
|
7
7
|
export type { AnthropicImageBlock, AnthropicImageBudgetOptions, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, MaterializedCanonReplyContext, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
|
|
8
8
|
export type { SessionConfig, Session } from './session-manager.js';
|
|
9
9
|
export type { CanonAgentTurnVerbosityOption } from './turn-verbosity-option.js';
|
|
10
|
-
export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, GroupInviteRequirements, CanonMessage, CanonConversation, CanonConversationsPage, CanonConversationsPageOptions, CanonReplyContext, CanonSelfContext, CanonTurnContextV2, CanonRuntimeDescriptor, MessageUpdatedPayload, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, CreateConversationResult, DirectSessionSelection, TurnVerbosity, TurnVerbosityConfig, } from '@canonmsg/core';
|
|
11
|
-
export type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, FinalMessageResult, MessageHandler, MessageHandlerContext, MessageUpdatedHandler, ParticipationSuppressedHandler, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimeApprovalRequest, RuntimeInputRequest, RuntimeInputResult, RuntimeControlSurface, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
|
10
|
+
export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, ContactRequestUpdatedPayload, ContactRequestListOptions, ContactRequestRequirements, ContactRequestLifecyclePhase, GroupInviteRequirements, CanonMessage, CanonConversation, CanonConversationsPage, CanonConversationsPageOptions, CanonReplyContext, CanonSelfContext, CanonTurnContextV2, CanonRuntimeDescriptor, MessageUpdatedPayload, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, CreateConversationResult, DirectSessionSelection, TurnVerbosity, TurnVerbosityConfig, } from '@canonmsg/core';
|
|
11
|
+
export type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, ContactRequestUpdatedHandler, ContactLifecycleStore, FinalMessageResult, MessageHandler, MessageHandlerContext, MessageUpdatedHandler, ParticipationSuppressedHandler, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimeApprovalRequest, RuntimeInputRequest, RuntimeInputResult, RuntimeControlSurface, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentContext, type ContactAddedPayload, type ContactApprovedPayload, type ContactRemovedPayload, type ContactRequestPayload, type ConversationUpdatedPayload, type MessageUpdatedPayload, type ParticipationSuppressedPayload, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
1
|
+
import { type AgentContext, type ContactAddedPayload, type ContactApprovedPayload, type ContactRemovedPayload, type ContactRequestPayload, type ContactRequestUpdatedPayload, type ConversationUpdatedPayload, type MessageUpdatedPayload, type ParticipationSuppressedPayload, type VoiceSessionEventPayload } from '@canonmsg/core';
|
|
2
2
|
import { Debouncer } from './debouncer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Wraps @canonmsg/core's CanonStream with SDK-specific features:
|
|
@@ -16,6 +16,7 @@ export declare class RealtimeManager {
|
|
|
16
16
|
private suppressedSseErrorCount;
|
|
17
17
|
private onAgentContext;
|
|
18
18
|
private onContactRequest;
|
|
19
|
+
private onContactRequestUpdated;
|
|
19
20
|
private onContactApproved;
|
|
20
21
|
private onContactAdded;
|
|
21
22
|
private onContactRemoved;
|
|
@@ -25,6 +26,7 @@ export declare class RealtimeManager {
|
|
|
25
26
|
private onMessageDeleted;
|
|
26
27
|
private onConnected;
|
|
27
28
|
private onDisconnected;
|
|
29
|
+
private onReplayExpired;
|
|
28
30
|
private onCallStarted;
|
|
29
31
|
private onCallEnded;
|
|
30
32
|
constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, options?: {
|
|
@@ -37,6 +39,7 @@ export declare class RealtimeManager {
|
|
|
37
39
|
setOnAgentContext(cb: (ctx: AgentContext) => void): void;
|
|
38
40
|
setContactRequestHandlers(handlers: {
|
|
39
41
|
onContactRequest?: (payload: ContactRequestPayload) => void;
|
|
42
|
+
onContactRequestUpdated?: (payload: ContactRequestUpdatedPayload) => void;
|
|
40
43
|
onContactApproved?: (payload: ContactApprovedPayload) => void;
|
|
41
44
|
}): void;
|
|
42
45
|
setContactGraphHandlers(handlers: {
|
|
@@ -58,6 +61,7 @@ export declare class RealtimeManager {
|
|
|
58
61
|
setConnectionHandlers(handlers: {
|
|
59
62
|
onConnected?: () => void;
|
|
60
63
|
onDisconnected?: () => void;
|
|
64
|
+
onReplayExpired?: () => void;
|
|
61
65
|
}): void;
|
|
62
66
|
setCallHandlers(handlers: {
|
|
63
67
|
onCallStarted?: (payload: VoiceSessionEventPayload) => void;
|
package/dist/realtime.js
CHANGED
|
@@ -17,6 +17,7 @@ export class RealtimeManager {
|
|
|
17
17
|
suppressedSseErrorCount = 0;
|
|
18
18
|
onAgentContext = null;
|
|
19
19
|
onContactRequest = null;
|
|
20
|
+
onContactRequestUpdated = null;
|
|
20
21
|
onContactApproved = null;
|
|
21
22
|
onContactAdded = null;
|
|
22
23
|
onContactRemoved = null;
|
|
@@ -26,6 +27,7 @@ export class RealtimeManager {
|
|
|
26
27
|
onMessageDeleted = null;
|
|
27
28
|
onConnected = null;
|
|
28
29
|
onDisconnected = null;
|
|
30
|
+
onReplayExpired = null;
|
|
29
31
|
onCallStarted = null;
|
|
30
32
|
onCallEnded = null;
|
|
31
33
|
constructor(apiKey, debouncer, agentId, streamUrl, options) {
|
|
@@ -102,6 +104,9 @@ export class RealtimeManager {
|
|
|
102
104
|
onContactRequest: (payload) => {
|
|
103
105
|
this.onContactRequest?.(payload);
|
|
104
106
|
},
|
|
107
|
+
onContactRequestUpdated: (payload) => {
|
|
108
|
+
this.onContactRequestUpdated?.(payload);
|
|
109
|
+
},
|
|
105
110
|
onContactApproved: (payload) => {
|
|
106
111
|
this.onContactApproved?.(payload);
|
|
107
112
|
},
|
|
@@ -125,6 +130,7 @@ export class RealtimeManager {
|
|
|
125
130
|
},
|
|
126
131
|
onReplayExpired: (payload) => {
|
|
127
132
|
console.error(`[canon-sdk] SSE replay window expired${payload.lastAvailableId ? ` (oldest available event: ${payload.lastAvailableId})` : ''}; missed history is available via explicit REST fetch`);
|
|
133
|
+
this.onReplayExpired?.();
|
|
128
134
|
},
|
|
129
135
|
onError: (err) => {
|
|
130
136
|
this.logSseError(err);
|
|
@@ -175,6 +181,7 @@ export class RealtimeManager {
|
|
|
175
181
|
}
|
|
176
182
|
setContactRequestHandlers(handlers) {
|
|
177
183
|
this.onContactRequest = handlers.onContactRequest ?? null;
|
|
184
|
+
this.onContactRequestUpdated = handlers.onContactRequestUpdated ?? null;
|
|
178
185
|
this.onContactApproved = handlers.onContactApproved ?? null;
|
|
179
186
|
}
|
|
180
187
|
setContactGraphHandlers(handlers) {
|
|
@@ -201,6 +208,7 @@ export class RealtimeManager {
|
|
|
201
208
|
setConnectionHandlers(handlers) {
|
|
202
209
|
this.onConnected = handlers.onConnected ?? null;
|
|
203
210
|
this.onDisconnected = handlers.onDisconnected ?? null;
|
|
211
|
+
this.onReplayExpired = handlers.onReplayExpired ?? null;
|
|
204
212
|
}
|
|
205
213
|
setCallHandlers(handlers) {
|
|
206
214
|
this.onCallStarted = handlers.onCallStarted ?? null;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AddMemberResult, GroupInviteRequirements, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, CreateConversationResult, DirectSessionSelection, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, VerbSessionConfig, CreateConversationOptions, TurnLifecycleState, TurnOutputBlock, TurnOutputBlockInput, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimeCardNativeMetadata, RuntimeCardV1, ResumableMediaUploadResult, MediaAttachment, VideoProcessingStatus, VideoProcessingErrorCode, SessionRule, ApprovalResult, } from '@canonmsg/core';
|
|
1
|
+
export type { AddMemberResult, ContactRequestListOptions, ContactRequestRequirements, ContactRequestLifecyclePhase, GroupInviteRequirements, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, ContactRequestUpdatedPayload, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, CreateConversationResult, DirectSessionSelection, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, VerbSessionConfig, CreateConversationOptions, TurnLifecycleState, TurnOutputBlock, TurnOutputBlockInput, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimeCardNativeMetadata, RuntimeCardV1, ResumableMediaUploadResult, MediaAttachment, VideoProcessingStatus, VideoProcessingErrorCode, SessionRule, ApprovalResult, } from '@canonmsg/core';
|
|
2
2
|
import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimeCardNativeMetadata, RuntimeCardV1, ResumableMediaUploadResult, SendMessageOptions, SendContextualSelfContextInput, VerbSessionConfig, DirectSessionSelection, TurnOutputBlock, TurnOutputBlockInput } from '@canonmsg/core';
|
|
3
3
|
import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
|
|
4
4
|
export interface ProgressMessageOptions extends SendMessageOptions {
|
|
@@ -152,6 +152,8 @@ export interface MessageHandlerContext {
|
|
|
152
152
|
history: CanonMessage[];
|
|
153
153
|
/** Resolved message/media content for the latest swipe-reply target, if any. */
|
|
154
154
|
replyContext: CanonReplyContext | null;
|
|
155
|
+
/** Terminal introduction events retained until this natural owner turn. */
|
|
156
|
+
contactLifecycleEvents: import('@canonmsg/core').CanonContactRequest[];
|
|
155
157
|
conversationId: string;
|
|
156
158
|
conversation: CanonConversation;
|
|
157
159
|
/** Lightweight group awareness, present for group conversations. */
|
|
@@ -310,6 +312,15 @@ export interface CanonAgentOptions extends CanonAgentConnectionOptions {
|
|
|
310
312
|
clientType?: import('@canonmsg/core').AgentClientType;
|
|
311
313
|
/** Optional runtime descriptor published to Canon for setup/live UI rendering. */
|
|
312
314
|
runtimeDescriptor?: import('@canonmsg/core').CanonRuntimeDescriptor;
|
|
315
|
+
/**
|
|
316
|
+
* Opt into the owner-authorized introduction surface advertised to Canon.
|
|
317
|
+
* Lifecycle events never start a model turn; they are passed to the next
|
|
318
|
+
* natural owner handler invocation and to onContactRequestUpdated.
|
|
319
|
+
*/
|
|
320
|
+
ownerBoundCommunication?: {
|
|
321
|
+
enabled: true;
|
|
322
|
+
lifecycleStore?: ContactLifecycleStore;
|
|
323
|
+
};
|
|
313
324
|
/** Optional Canon runtime signal handlers. Enables interrupt controls when provided. */
|
|
314
325
|
runtimeControls?: RuntimeControlHandlers;
|
|
315
326
|
/** Runtime publishing surface. Use `host` when this agent owns live runtime controls. */
|
|
@@ -336,6 +347,15 @@ export interface CanonAgentOptions extends CanonAgentConnectionOptions {
|
|
|
336
347
|
turnVerbosity?: import('./turn-verbosity-option.js').CanonAgentTurnVerbosityOption;
|
|
337
348
|
}
|
|
338
349
|
export type ContactRequestHandler = (request: import('@canonmsg/core').CanonContactRequest) => void | Promise<void>;
|
|
350
|
+
export type ContactRequestUpdatedHandler = ContactRequestHandler;
|
|
351
|
+
export interface ContactLifecycleStore {
|
|
352
|
+
record(request: import('@canonmsg/core').CanonContactRequest, options?: {
|
|
353
|
+
pending?: boolean;
|
|
354
|
+
}): boolean | Promise<boolean>;
|
|
355
|
+
take(sourceConversationId: string): import('@canonmsg/core').CanonContactRequest[] | Promise<import('@canonmsg/core').CanonContactRequest[]>;
|
|
356
|
+
isBaselineComplete(): boolean | Promise<boolean>;
|
|
357
|
+
completeBaseline(): void | Promise<void>;
|
|
358
|
+
}
|
|
339
359
|
export type ContactAddedHandler = (contact: import('@canonmsg/core').ContactAddedPayload) => void | Promise<void>;
|
|
340
360
|
export type ContactRemovedHandler = (payload: import('@canonmsg/core').ContactRemovedPayload) => void | Promise<void>;
|
|
341
361
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.9.0",
|
|
4
4
|
"description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@canonmsg/core": "^10.
|
|
31
|
+
"@canonmsg/core": "^10.7.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|