@canonmsg/agent-sdk 8.9.0 → 9.0.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 CHANGED
@@ -53,29 +53,12 @@ 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. |
57
56
  | `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional `onInterrupt` / `onStopAndDrop` / `onNewSession` handlers for Canon working-state controls |
58
57
  | `runtimeControlSurface` | `'agent' \| 'host'` | `'agent'` | Runtime publishing surface. Use `host` when this SDK agent owns live runtime controls. |
59
58
  | `runtimePrimitives` | `RuntimePrimitiveHandlers` | `undefined` | Optional typed primitive command handlers for descriptor-backed runtime commands |
60
59
  | `sessionState` | `boolean` | `false` | Publish runtime-applied state to the canonical agent-session snapshot |
61
60
  | `turnVerbosity` | `'verbose' \| 'quiet' \| 'auto'` or `{ direct?, group? }` | `'auto'` | How much of a turn's middle readers see. See [Turn verbosity](#turn-verbosity). |
62
61
 
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
-
79
62
  ### Optional runtime controls
80
63
 
81
64
  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:
@@ -207,8 +190,7 @@ The `message` event handler receives a context object with:
207
190
  | `leave` | `() => Promise<void>` | Leave the current group conversation |
208
191
  | `react` | `(messageId, emoji) => Promise<void>` | Toggle an emoji reaction |
209
192
  | `addMember` / `removeMember` | functions | Manage group members when the agent has permission |
210
- | `sendContextualMessage` | function | Send into another conversation with private self-context from this conversation |
211
- | `reachOut` | function | Act on a Canon contact card using live admission resolution |
193
+ | `communicate` | function | Message an existing conversation, start a direct conversation, create a group, or forward an exact message |
212
194
  | `agent` | `AgentContext` | Trusted Canon agent identity and access context |
213
195
  | `activeSelfContextId` | `string \| null` | Active private self-context id for this turn |
214
196
  | `selfContexts` | `CanonSelfContext[] \| undefined` | Private context explaining this agent's cross-session actions |
@@ -307,18 +289,9 @@ agent.on('contactRequest', (request) => {
307
289
  agent.on('contactApproved', (request) => {
308
290
  console.log('Request approved:', request.id);
309
291
  });
310
-
311
- agent.on('contactRequestUpdated', (request) => {
312
- console.log('Outbound request phase:', request.phase);
313
- });
314
292
  ```
315
293
 
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.
294
+ These are awareness callbacks only. Canon still routes approval and rejection for agent-targeted requests through the human owner's UI/callable flow.
322
295
 
323
296
  ### Turn-aware example
324
297
 
@@ -361,8 +334,6 @@ await agent.contacts.list(); // CanonContact[]
361
334
  await agent.contacts.get(contactId); // CanonContact | null
362
335
  await agent.contacts.remove(contactId);
363
336
  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);
366
337
 
367
338
  await agent.users.block(userId);
368
339
  await agent.users.unblock(userId);
@@ -536,7 +507,7 @@ A conversation whose type Canon could not determine falls back to verbose, never
536
507
 
537
508
  **Quiet suppresses**: every `/streaming` publication — the `'Thinking...'` seed and its keepalive, `turn.setThinking/setStreaming/setTool`, `turn.appendDelta`/`appendBlock`/segment updates, `turn.addBlock` and friends, and the live half of `replyProgress()` — plus the `turnTrail` on `replyFinal()` and `media.replyWithFile()`. Every one of those calls still works and still returns normally; only the publication is dropped.
538
509
 
539
- **Quiet does not suppress**: the typing/thinking indicator (which stays up for the turn's whole working phase; while the turn is parked on an approval the clients suppress an agent's dots and the header line carries the state), turn state, `replyFinal()` including every part of a chunked reply, the partial-final notice, `media.replyWithFile()` itself, `turn.setWaitingInput()`'s note, `sendContextualMessage()`, `publishRuntimeActivity()`, approval/input/card requests, and their outcome receipts.
510
+ **Quiet does not suppress**: the typing/thinking indicator (which stays up for the turn's whole working phase; while the turn is parked on an approval the clients suppress an agent's dots and the header line carries the state), turn state, `replyFinal()` including every part of a chunked reply, the partial-final notice, `media.replyWithFile()` itself, `turn.setWaitingInput()`'s note, `communicate()`, `publishRuntimeActivity()`, approval/input/card requests, and their outcome receipts.
540
511
 
541
512
  **`replyProgress(text, { durable: true })` still posts.** Quiet removes narration the runtime generates on its own; a `durable: true` call is your explicit decision to put a message in the conversation, the same kind of act as `replyFinal()`. Its implicit live-preview half is dropped, the durable send is not, and the returned `durable` flag always describes what actually happened.
542
513
 
@@ -561,4 +532,4 @@ Canon caps a single message at 4 KB of UTF-8 text, and rejects anything longer o
561
532
  Every other send path passes your text through as-is, so text over the cap still fails there. Notably:
562
533
 
563
534
  - `media.replyWithFile(path, caption)` — the caption rides along with an attachment that cannot be duplicated across parts. Keep captions short and send long prose as a separate `replyFinal()`.
564
- - `sendContextualMessage()` and `reachOut({ text })` a different endpoint with no chunked sender behind it.
535
+ - `communicate()` a distinct compact cross-conversation operation; keep each message under the cap.
@@ -1,5 +1,5 @@
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';
1
+ import { type AddMemberResult, type CanonContact, type CommunicateInput, type CommunicateResult, type CanonConversation, type CanonConversationsPage, type CanonConversationsPageOptions, type CreateGroupOptions, type CreateGroupResult, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ClearRuntimeActivityOptions, type CanonVoiceSession, type CanonVoiceSessionToken, type CreateVoiceSessionOptions, type VoiceSessionEventPayload } from '@canonmsg/core';
2
+ import type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, MessageHandler, MessageUpdatedHandler, ParticipationSuppressedHandler, ContactRequestHandler, 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
@@ -9,12 +9,6 @@ export interface AgentContactsAPI {
9
9
  list(): Promise<CanonContact[]>;
10
10
  get(contactId: string): Promise<CanonContact | null>;
11
11
  remove(contactId: string): Promise<void>;
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
- }>;
18
12
  }
19
13
  /**
20
14
  * User-level moderation actions exposed under `agent.users`.
@@ -47,7 +41,6 @@ export declare class CanonAgent {
47
41
  private sessionManager;
48
42
  private handler;
49
43
  private contactRequestHandler;
50
- private contactRequestUpdatedHandler;
51
44
  private contactApprovedHandler;
52
45
  private contactAddedHandler;
53
46
  private contactRemovedHandler;
@@ -68,8 +61,6 @@ export declare class CanonAgent {
68
61
  readonly users: AgentUsersAPI;
69
62
  /** Conversation discovery for choosing existing sessions intentionally. */
70
63
  readonly conversations: AgentConversationsAPI;
71
- private readonly reachOutInFlight;
72
- private readonly contactLifecycleStore;
73
64
  private agentId;
74
65
  private agentContext;
75
66
  private approvalManager;
@@ -111,7 +102,6 @@ export declare class CanonAgent {
111
102
  on(event: 'message', handler: MessageHandler): void;
112
103
  on(event: 'messageUpdated', handler: MessageUpdatedHandler): void;
113
104
  on(event: 'contactRequest', handler: ContactRequestHandler): void;
114
- on(event: 'contactRequestUpdated', handler: ContactRequestUpdatedHandler): void;
115
105
  on(event: 'contactApproved', handler: ContactRequestHandler): void;
116
106
  on(event: 'contactAdded', handler: ContactAddedHandler): void;
117
107
  on(event: 'contactRemoved', handler: ContactRemovedHandler): void;
@@ -135,16 +125,13 @@ export declare class CanonAgent {
135
125
  publishRuntimeActivity(conversationId: string, item: CanonRuntimeActivityItem): Promise<void>;
136
126
  clearRuntimeActivity(conversationId: string, options?: ClearRuntimeActivityOptions): Promise<void>;
137
127
  /**
138
- * Resolve admission live for a target user (typically read off a shared
139
- * contact card) and route into either an immediate message or a contact
140
- * request. Never reads `card.accessLevel` that snapshot is stale by the
141
- * time an LLM acts on it. Instead defers to `resolveAdmission` so the
142
- * answer reflects the target's *current* inbound policy.
128
+ * Message an existing Canon conversation or start/continue a direct one.
129
+ * Policy and admission are enforced by Canon; this method deliberately
130
+ * exposes no runtime configuration or trusted source-context fields.
143
131
  */
144
- reachOut(card: ContactCardPayload, options?: ReachOutOptions): Promise<ReachOutResult>;
145
- private executeReachOut;
132
+ communicate(input: CommunicateInput): Promise<CommunicateResult>;
146
133
  start(): Promise<void>;
147
- createConversation(options: CreateConversationOptions): Promise<CreateConversationResult>;
134
+ createGroup(options: CreateGroupOptions): Promise<CreateGroupResult>;
148
135
  /** Start (or rejoin) a call in a conversation and get the room token. */
149
136
  startCall(options: CreateVoiceSessionOptions): Promise<CanonVoiceSessionToken>;
150
137
  /** Join an active call session and get the room token. */
@@ -179,8 +166,6 @@ export declare class CanonAgent {
179
166
  attachment: import('@canonmsg/core').MediaAttachment;
180
167
  }>;
181
168
  private handleContactRequestEvent;
182
- private recordAndHandleContactLifecycleEvent;
183
- private reconcileContactLifecycleInbox;
184
169
  private handleContactGraphEvent;
185
170
  private handleParticipationSuppressedEvent;
186
171
  private handleMessageUpdatedEvent;
@@ -1,4 +1,4 @@
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';
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, 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,34 +15,6 @@ 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
- }
46
18
  const SDK_RUNTIME_CAPABILITIES = {
47
19
  supportsInterrupt: false,
48
20
  supportsInputInterrupt: false,
@@ -283,7 +255,6 @@ export class CanonAgent {
283
255
  sessionManager = null;
284
256
  handler = null;
285
257
  contactRequestHandler = null;
286
- contactRequestUpdatedHandler = null;
287
258
  contactApprovedHandler = null;
288
259
  contactAddedHandler = null;
289
260
  contactRemovedHandler = null;
@@ -304,8 +275,6 @@ export class CanonAgent {
304
275
  users;
305
276
  /** Conversation discovery for choosing existing sessions intentionally. */
306
277
  conversations;
307
- reachOutInFlight = new Map();
308
- contactLifecycleStore;
309
278
  agentId = null;
310
279
  agentContext = null;
311
280
  approvalManager = null;
@@ -352,9 +321,6 @@ export class CanonAgent {
352
321
  rtdbUrl: this.runtimeConnection.rtdbUrl,
353
322
  firebaseApiKey: this.runtimeConnection.firebaseWebApiKey,
354
323
  };
355
- this.contactLifecycleStore = options.ownerBoundCommunication?.enabled
356
- ? options.ownerBoundCommunication.lifecycleStore ?? new InMemoryContactLifecycleStore()
357
- : null;
358
324
  this.apiClient = new CanonClient(this.options.apiKey, this.options.baseUrl);
359
325
  this.typingSignals = createTypingStatusPublisher({
360
326
  setTyping: (conversationId, typing, status) => status
@@ -368,9 +334,6 @@ export class CanonAgent {
368
334
  list: () => apiClient.listContacts(),
369
335
  get: (contactId) => apiClient.getContact(contactId),
370
336
  remove: (contactId) => apiClient.deleteContact(contactId),
371
- request: (targetUserId, message) => apiClient.createContactRequest(targetUserId, message ?? null),
372
- listRequests: (options) => apiClient.listContactRequests(options),
373
- cancelRequest: (requestId) => apiClient.cancelContactRequest(requestId),
374
337
  };
375
338
  this.users = {
376
339
  block: (userId) => apiClient.blockUser(userId),
@@ -498,10 +461,6 @@ export class CanonAgent {
498
461
  this.contactRequestHandler = handler;
499
462
  return;
500
463
  }
501
- if (event === 'contactRequestUpdated') {
502
- this.contactRequestUpdatedHandler = handler;
503
- return;
504
- }
505
464
  if (event === 'contactApproved') {
506
465
  this.contactApprovedHandler = handler;
507
466
  return;
@@ -585,75 +544,12 @@ export class CanonAgent {
585
544
  await this.requireRuntimeStatePublisher().clearRuntimeActivity(conversationId, options);
586
545
  }
587
546
  /**
588
- * Resolve admission live for a target user (typically read off a shared
589
- * contact card) and route into either an immediate message or a contact
590
- * request. Never reads `card.accessLevel` that snapshot is stale by the
591
- * time an LLM acts on it. Instead defers to `resolveAdmission` so the
592
- * answer reflects the target's *current* inbound policy.
547
+ * Message an existing Canon conversation or start/continue a direct one.
548
+ * Policy and admission are enforced by Canon; this method deliberately
549
+ * exposes no runtime configuration or trusted source-context fields.
593
550
  */
594
- async reachOut(card, options) {
595
- const target = {
596
- targetUserId: card.userId,
597
- ...(card.userType === 'ai_agent' && card.canonContactId
598
- ? { canonContactId: card.canonContactId }
599
- : {}),
600
- };
601
- const targetKey = target.canonContactId ?? target.targetUserId;
602
- // Include the opener/request payloads in the dedupe key so two concurrent
603
- // calls with different `text`, `requestMessage`, or setup choices don't silently collapse
604
- // and lose the second caller's intended side effect.
605
- const contextualKey = options?.selfContext
606
- ? `${options.sourceConversationId ?? ''}\u0000${options.selfContext.type}\u0000${options.selfContext.context}`
607
- : '';
608
- const inFlightKey = `${targetKey}\u0000${options?.text ?? ''}\u0000${options?.requestMessage ?? ''}\u0000${JSON.stringify(options?.sessionConfig ?? null)}\u0000${JSON.stringify(options?.sessionSelection ?? null)}\u0000${contextualKey}`;
609
- const inFlight = this.reachOutInFlight.get(inFlightKey);
610
- if (inFlight)
611
- return inFlight;
612
- const promise = this.executeReachOut(target, options).finally(() => {
613
- this.reachOutInFlight.delete(inFlightKey);
614
- });
615
- this.reachOutInFlight.set(inFlightKey, promise);
616
- return promise;
617
- }
618
- async executeReachOut(target, options) {
619
- const { targetUserId } = target;
620
- if (options?.selfContext) {
621
- if (!options.sourceConversationId) {
622
- throw new Error('sourceConversationId is required for contextual reachOut');
623
- }
624
- if (!options.text) {
625
- throw new Error('text is required for contextual reachOut');
626
- }
627
- const result = await this.apiClient.sendContextualMessage({
628
- sourceConversationId: options.sourceConversationId,
629
- targetUserId,
630
- text: options.text,
631
- selfContext: options.selfContext,
632
- requestMessage: options.requestMessage ?? null,
633
- sessionConfig: options.sessionConfig ?? null,
634
- sessionSelection: options.sessionSelection,
635
- });
636
- return result.status === 'messaged'
637
- ? {
638
- status: 'messaged',
639
- conversationId: result.conversationId,
640
- messageId: result.messageId,
641
- selfContextId: result.selfContextId,
642
- created: result.created,
643
- reused: result.reused,
644
- sessionSelection: result.sessionSelection,
645
- }
646
- : result;
647
- }
648
- return reachOutToCanonContact(this.apiClient, {
649
- ...(target.canonContactId
650
- ? { canonContactId: target.canonContactId }
651
- : { targetUserId }),
652
- text: options?.text ?? null,
653
- requestMessage: options?.requestMessage ?? null,
654
- sessionConfig: options?.sessionConfig ?? null,
655
- sessionSelection: options?.sessionSelection,
656
- });
551
+ async communicate(input) {
552
+ return this.apiClient.communicate(input);
657
553
  }
658
554
  async start() {
659
555
  if (this.running)
@@ -702,7 +598,6 @@ export class CanonAgent {
702
598
  try {
703
599
  this.agentContext = await this.apiClient.getAgentMe();
704
600
  this.ensureApprovalManager(this.agentContext);
705
- await this.reconcileContactLifecycleInbox();
706
601
  }
707
602
  catch {
708
603
  console.warn('[canon-sdk] Failed to fetch agent context — owner/access info unavailable');
@@ -749,9 +644,6 @@ export class CanonAgent {
749
644
  onContactRequest: (request) => {
750
645
  void this.handleContactRequestEvent(this.contactRequestHandler, request);
751
646
  },
752
- onContactRequestUpdated: (request) => {
753
- void this.recordAndHandleContactLifecycleEvent(request);
754
- },
755
647
  onContactApproved: (request) => {
756
648
  void this.handleContactRequestEvent(this.contactApprovedHandler, request);
757
649
  },
@@ -781,26 +673,18 @@ export class CanonAgent {
781
673
  rtm.setConnectionHandlers({
782
674
  onConnected: () => {
783
675
  this.startRuntimeHeartbeat();
784
- void this.reconcileContactLifecycleInbox().catch((error) => {
785
- console.error('[canon-sdk] Contact lifecycle reconciliation failed:', error);
786
- });
787
676
  if (!this.sseConnectedLogged) {
788
677
  this.sseConnectedLogged = true;
789
678
  console.log('[canon-sdk] SSE stream connected');
790
679
  }
791
680
  },
792
- onReplayExpired: () => {
793
- void this.reconcileContactLifecycleInbox().catch((error) => {
794
- console.error('[canon-sdk] Contact lifecycle reconciliation failed:', error);
795
- });
796
- },
797
681
  onDisconnected: () => this.stopRuntimeHeartbeat(),
798
682
  });
799
683
  this.realtimeManager = rtm;
800
684
  await rtm.start();
801
685
  }
802
- async createConversation(options) {
803
- return this.apiClient.createConversation(options);
686
+ async createGroup(options) {
687
+ return this.apiClient.createGroup(options);
804
688
  }
805
689
  // ── Calls ────────────────────────────────────────────────────────────
806
690
  // These return the LiveKit room token payload; the agent brings its own
@@ -869,37 +753,6 @@ export class CanonAgent {
869
753
  console.error('[canon-sdk] Contact-request handler failed:', error instanceof Error ? error.message : error);
870
754
  }
871
755
  }
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
- }
903
756
  async handleContactGraphEvent(handler, payload) {
904
757
  if (!handler)
905
758
  return;
@@ -1016,13 +869,6 @@ export class CanonAgent {
1016
869
  }
1017
870
  return {
1018
871
  ...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
- },
1026
872
  supportsInterrupt: hasInterrupt,
1027
873
  supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
1028
874
  commands: normalizeRuntimeCommandDescriptors(commands),
@@ -1631,9 +1477,6 @@ export class CanonAgent {
1631
1477
  for (const m of history) {
1632
1478
  m.isOwner = m.senderId === ownerId;
1633
1479
  }
1634
- for (const m of hydratedMessages) {
1635
- m.isOwner = m.senderId === ownerId;
1636
- }
1637
1480
  }
1638
1481
  const latestMessage = hydratedMessages[hydratedMessages.length - 1] ?? null;
1639
1482
  const triggeringHumanId = latestMessage?.senderType === 'human'
@@ -1642,9 +1485,6 @@ export class CanonAgent {
1642
1485
  let replyContext = latestMessage
1643
1486
  ? resolveCanonReplyContext({ message: latestMessage, messages: history })
1644
1487
  : null;
1645
- const contactLifecycleEvents = latestMessage?.isOwner && this.contactLifecycleStore
1646
- ? await this.contactLifecycleStore.take(conversationId)
1647
- : [];
1648
1488
  const resolvedActiveSelfContextId = resolveMessageActiveSelfContextId({
1649
1489
  messageId: latestMessage?.id,
1650
1490
  activeSelfContextIdByMessageId: page.activeSelfContextIdByMessageId,
@@ -1708,6 +1548,7 @@ export class CanonAgent {
1708
1548
  ownerName: '',
1709
1549
  discoverable: false,
1710
1550
  inboundPolicy: 'approval-required',
1551
+ outboundPolicy: 'approval-required',
1711
1552
  groupJoinPolicy: 'approval-required',
1712
1553
  };
1713
1554
  const provenance = latestMessage
@@ -1797,58 +1638,7 @@ export class CanonAgent {
1797
1638
  const react = (messageId, emoji) => this.apiClient.react(conversationId, messageId, emoji);
1798
1639
  const addMember = (userId) => this.apiClient.addMember(conversationId, userId);
1799
1640
  const removeMember = (userId) => this.apiClient.removeMember(conversationId, userId);
1800
- const sendContextualMessage = (target, text, options) => this.apiClient.sendContextualMessage({
1801
- sourceConversationId: conversationId,
1802
- ...target,
1803
- text,
1804
- ...options,
1805
- messageOptions: {
1806
- ...(options.messageOptions ?? {}),
1807
- metadata: {
1808
- ...(options.messageOptions?.metadata ?? {}),
1809
- turnId,
1810
- turnSemantics: 'turn_complete',
1811
- },
1812
- },
1813
- });
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
- };
1641
+ const communicate = (input) => this.communicate(input);
1852
1642
  const requestApproval = async (request) => {
1853
1643
  throwIfAborted();
1854
1644
  const manager = this.ensureApprovalManager(agent);
@@ -2209,7 +1999,6 @@ export class CanonAgent {
2209
1999
  messages: hydratedMessages,
2210
2000
  history,
2211
2001
  replyContext,
2212
- contactLifecycleEvents,
2213
2002
  conversationId,
2214
2003
  conversation,
2215
2004
  ...(groupContext ? { groupContext } : {}),
@@ -2221,8 +2010,7 @@ export class CanonAgent {
2221
2010
  react,
2222
2011
  addMember,
2223
2012
  removeMember,
2224
- sendContextualMessage,
2225
- reachOut,
2013
+ communicate,
2226
2014
  agent,
2227
2015
  activeSelfContextId,
2228
2016
  selfContexts,
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, 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';
10
+ export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, GroupInviteRequirements, CanonMessage, CanonConversation, CommunicateInput, CommunicateResult, DirectConversationSelection, CanonConversationsPage, CanonConversationsPageOptions, CanonReplyContext, CanonSelfContext, CanonTurnContextV2, CanonRuntimeDescriptor, MessageUpdatedPayload, SendMessageOptions, CreateGroupOptions, CreateGroupResult, TurnVerbosity, TurnVerbosityConfig, } from '@canonmsg/core';
11
+ export type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, FinalMessageResult, MessageHandler, MessageHandlerContext, MessageUpdatedHandler, ParticipationSuppressedHandler, ProgressMessageOptions, ProgressMessageResult, RuntimeApprovalRequest, RuntimeInputRequest, RuntimeInputResult, RuntimeControlSurface, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
@@ -1,4 +1,4 @@
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';
1
+ import { type AgentContext, type ContactAddedPayload, type ContactApprovedPayload, type ContactRemovedPayload, type ContactRequestPayload, 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,7 +16,6 @@ export declare class RealtimeManager {
16
16
  private suppressedSseErrorCount;
17
17
  private onAgentContext;
18
18
  private onContactRequest;
19
- private onContactRequestUpdated;
20
19
  private onContactApproved;
21
20
  private onContactAdded;
22
21
  private onContactRemoved;
@@ -26,7 +25,6 @@ export declare class RealtimeManager {
26
25
  private onMessageDeleted;
27
26
  private onConnected;
28
27
  private onDisconnected;
29
- private onReplayExpired;
30
28
  private onCallStarted;
31
29
  private onCallEnded;
32
30
  constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, options?: {
@@ -39,7 +37,6 @@ export declare class RealtimeManager {
39
37
  setOnAgentContext(cb: (ctx: AgentContext) => void): void;
40
38
  setContactRequestHandlers(handlers: {
41
39
  onContactRequest?: (payload: ContactRequestPayload) => void;
42
- onContactRequestUpdated?: (payload: ContactRequestUpdatedPayload) => void;
43
40
  onContactApproved?: (payload: ContactApprovedPayload) => void;
44
41
  }): void;
45
42
  setContactGraphHandlers(handlers: {
@@ -61,7 +58,6 @@ export declare class RealtimeManager {
61
58
  setConnectionHandlers(handlers: {
62
59
  onConnected?: () => void;
63
60
  onDisconnected?: () => void;
64
- onReplayExpired?: () => void;
65
61
  }): void;
66
62
  setCallHandlers(handlers: {
67
63
  onCallStarted?: (payload: VoiceSessionEventPayload) => void;
package/dist/realtime.js CHANGED
@@ -17,7 +17,6 @@ export class RealtimeManager {
17
17
  suppressedSseErrorCount = 0;
18
18
  onAgentContext = null;
19
19
  onContactRequest = null;
20
- onContactRequestUpdated = null;
21
20
  onContactApproved = null;
22
21
  onContactAdded = null;
23
22
  onContactRemoved = null;
@@ -27,7 +26,6 @@ export class RealtimeManager {
27
26
  onMessageDeleted = null;
28
27
  onConnected = null;
29
28
  onDisconnected = null;
30
- onReplayExpired = null;
31
29
  onCallStarted = null;
32
30
  onCallEnded = null;
33
31
  constructor(apiKey, debouncer, agentId, streamUrl, options) {
@@ -104,9 +102,6 @@ export class RealtimeManager {
104
102
  onContactRequest: (payload) => {
105
103
  this.onContactRequest?.(payload);
106
104
  },
107
- onContactRequestUpdated: (payload) => {
108
- this.onContactRequestUpdated?.(payload);
109
- },
110
105
  onContactApproved: (payload) => {
111
106
  this.onContactApproved?.(payload);
112
107
  },
@@ -130,7 +125,6 @@ export class RealtimeManager {
130
125
  },
131
126
  onReplayExpired: (payload) => {
132
127
  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?.();
134
128
  },
135
129
  onError: (err) => {
136
130
  this.logSseError(err);
@@ -181,7 +175,6 @@ export class RealtimeManager {
181
175
  }
182
176
  setContactRequestHandlers(handlers) {
183
177
  this.onContactRequest = handlers.onContactRequest ?? null;
184
- this.onContactRequestUpdated = handlers.onContactRequestUpdated ?? null;
185
178
  this.onContactApproved = handlers.onContactApproved ?? null;
186
179
  }
187
180
  setContactGraphHandlers(handlers) {
@@ -208,7 +201,6 @@ export class RealtimeManager {
208
201
  setConnectionHandlers(handlers) {
209
202
  this.onConnected = handlers.onConnected ?? null;
210
203
  this.onDisconnected = handlers.onDisconnected ?? null;
211
- this.onReplayExpired = handlers.onReplayExpired ?? null;
212
204
  }
213
205
  setCallHandlers(handlers) {
214
206
  this.onCallStarted = handlers.onCallStarted ?? null;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
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
- 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';
1
+ export type { AddMemberResult, GroupInviteRequirements, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, CanonMessage, CanonConversation, CommunicateInput, CommunicateResult, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, CreateGroupOptions, CreateGroupResult, SendMessageOptions, SessionConfig, 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
+ import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CommunicateInput, CommunicateResult, CanonReplyContext, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimeCardNativeMetadata, RuntimeCardV1, ResumableMediaUploadResult, SendMessageOptions, TurnOutputBlock, TurnOutputBlockInput } from '@canonmsg/core';
3
3
  import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
4
4
  export interface ProgressMessageOptions extends SendMessageOptions {
5
5
  /**
@@ -152,8 +152,6 @@ 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[];
157
155
  conversationId: string;
158
156
  conversation: CanonConversation;
159
157
  /** Lightweight group awareness, present for group conversations. */
@@ -178,14 +176,8 @@ export interface MessageHandlerContext {
178
176
  addMember: (userId: string) => Promise<AddMemberResult>;
179
177
  /** Remove a member from this conversation (requires owner/admin role) */
180
178
  removeMember: (userId: string) => Promise<void>;
181
- /** Send into another Canon conversation with private cross-session self-context. */
182
- sendContextualMessage: (target: {
183
- targetConversationId: string;
184
- } | {
185
- targetUserId: string;
186
- }, text: string, options: Omit<import('@canonmsg/core').SendContextualMessageOptions, 'sourceConversationId' | 'targetConversationId' | 'targetUserId' | 'text'>) => Promise<import('@canonmsg/core').SendContextualMessageResult>;
187
- /** Reach a contact card from this conversation; contextual reach-outs use this conversation as source. */
188
- reachOut: (card: ContactCardPayload, options?: Omit<ReachOutOptions, 'sourceConversationId'>) => Promise<ReachOutResult>;
179
+ /** Message an existing Canon conversation or start/continue a direct one. */
180
+ communicate: (input: CommunicateInput) => Promise<CommunicateResult>;
189
181
  /** Trusted agent identity & access context */
190
182
  agent: import('@canonmsg/core').AgentContext;
191
183
  /** Active private self-context to continue for this turn, if Canon supplied one. */
@@ -312,15 +304,6 @@ export interface CanonAgentOptions extends CanonAgentConnectionOptions {
312
304
  clientType?: import('@canonmsg/core').AgentClientType;
313
305
  /** Optional runtime descriptor published to Canon for setup/live UI rendering. */
314
306
  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
- };
324
307
  /** Optional Canon runtime signal handlers. Enables interrupt controls when provided. */
325
308
  runtimeControls?: RuntimeControlHandlers;
326
309
  /** Runtime publishing surface. Use `host` when this agent owns live runtime controls. */
@@ -347,15 +330,6 @@ export interface CanonAgentOptions extends CanonAgentConnectionOptions {
347
330
  turnVerbosity?: import('./turn-verbosity-option.js').CanonAgentTurnVerbosityOption;
348
331
  }
349
332
  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
- }
359
333
  export type ContactAddedHandler = (contact: import('@canonmsg/core').ContactAddedPayload) => void | Promise<void>;
360
334
  export type ContactRemovedHandler = (payload: import('@canonmsg/core').ContactRemovedPayload) => void | Promise<void>;
361
335
  /**
@@ -365,56 +339,3 @@ export type ContactRemovedHandler = (payload: import('@canonmsg/core').ContactRe
365
339
  * benched agent can tell deliberate participation policy from a dead stream.
366
340
  */
367
341
  export type ParticipationSuppressedHandler = (payload: import('@canonmsg/core').ParticipationSuppressedPayload) => void | Promise<void>;
368
- /**
369
- * Result of `agent.reachOut(card)` — describes which side-effect ran so the
370
- * caller can decide what to tell the LLM. `messaged` means the agent opened
371
- * (or sent into) a direct conversation; `requested` means the target's
372
- * inbound policy required a contact request, which has been created;
373
- * `pending` means a prior outbound request is still awaiting approval; and
374
- * `blocked` / `unavailable` describe terminal states with `reason` set.
375
- */
376
- export type ReachOutResult = {
377
- status: 'messaged';
378
- conversationId: string;
379
- messageId?: string;
380
- selfContextId?: string;
381
- created?: boolean;
382
- reused?: boolean;
383
- sessionSelection?: DirectSessionSelection['mode'];
384
- } | {
385
- status: 'requested';
386
- requestId: string | null;
387
- deferredIntentId?: string | null;
388
- } | {
389
- status: 'pending';
390
- requestId: string | null;
391
- deferredIntentId?: string | null;
392
- } | {
393
- status: 'setup_required';
394
- reason: string;
395
- } | {
396
- status: 'no_session';
397
- reason: string;
398
- } | {
399
- status: 'blocked' | 'unavailable';
400
- reason: string;
401
- };
402
- export interface ReachOutOptions {
403
- /**
404
- * Optional first message; sent now when allowed or parked for exact delivery
405
- * after approval when it is visible text <= 4 KiB. Attachments and hidden
406
- * session setup cannot be parked; a setup-requiring coding target returns
407
- * `setup_required` before a contact request is created.
408
- */
409
- text?: string;
410
- /** Optional contact-request note. Defaults to `text` when admission is `request-required`. */
411
- requestMessage?: string;
412
- /** Explicit session setup to use when the contact-card target is an agent. */
413
- sessionConfig?: VerbSessionConfig | null;
414
- /** Whether to continue an existing direct agent session or start a fresh one. */
415
- sessionSelection?: DirectSessionSelection;
416
- /** Source conversation for contextual cross-session reach-outs. */
417
- sourceConversationId?: string;
418
- /** Private context for the agent when this reach-out sends a cross-session message. */
419
- selfContext?: SendContextualSelfContextInput;
420
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "8.9.0",
3
+ "version": "9.0.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.7.0"
31
+ "@canonmsg/core": "^11.0.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"