@canonmsg/agent-sdk 9.0.0 → 10.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
@@ -190,7 +190,7 @@ The `message` event handler receives a context object with:
190
190
  | `leave` | `() => Promise<void>` | Leave the current group conversation |
191
191
  | `react` | `(messageId, emoji) => Promise<void>` | Toggle an emoji reaction |
192
192
  | `addMember` / `removeMember` | functions | Manage group members when the agent has permission |
193
- | `communicate` | function | Message an existing conversation, start a direct conversation, create a group, or forward an exact message |
193
+ | `communicate` | function | Message an existing conversation, start a direct conversation, create a group, forward an exact message, share a contact, or manage group members |
194
194
  | `agent` | `AgentContext` | Trusted Canon agent identity and access context |
195
195
  | `activeSelfContextId` | `string \| null` | Active private self-context id for this turn |
196
196
  | `selfContexts` | `CanonSelfContext[] \| undefined` | Private context explaining this agent's cross-session actions |
@@ -200,6 +200,7 @@ The `message` event handler receives a context object with:
200
200
  | `turnVerbosity` | `'verbose' \| 'quiet'` | Resolved emission mode for this turn — see [Turn verbosity](#turn-verbosity). Fixed for the whole turn |
201
201
  | `requestApproval` | `(request) => Promise<ApprovalResult>` | Render a Canon approval card and wait for the decision. Fail-closed: returns `{ decision: 'deny' }` on any non-abort failure instead of throwing |
202
202
  | `requestRuntimeInput` | `(request) => Promise<RuntimeInputResult>` | Render a Canon input card for clarification, sudo, or secret values |
203
+ | `requestPlanReview` | `(request) => Promise<RuntimePlanReviewResult>` | Render Canon's native plan-review card and wait for approve, revise, reject, cancellation, or timeout |
203
204
  | `requestCard` / `sendCard` | functions | Render a generic `canon.card.v1` rich card. `requestCard` blocks only on cards that carry an `actions` block; `sendCard` posts a display card |
204
205
  | `media` | `{ materialize, uploadFile, replyWithFile }` | Canon-managed access to real media bytes via `~/.canon/media-cache` plus local-file uploads back into Canon |
205
206
  | `session` | `SessionInfo \| undefined` | Per-conversation queue/session state when sessions are enabled |
@@ -212,16 +213,15 @@ Messages from the agent itself are automatically filtered out -- your handler on
212
213
 
213
214
  ## Events
214
215
 
215
- `agent.on(event, handler)` accepts eleven events. Each event holds a single handler; registering again replaces it.
216
+ `agent.on(event, handler)` accepts ten events. Each event holds a single handler; registering again replaces it.
216
217
 
217
218
  | Event | Payload | Notes |
218
219
  |---|---|---|
219
220
  | `message` | `MessageHandlerContext` | Debounced inbound batch for one conversation |
220
221
  | `messageUpdated` | `MessageUpdatedPayload` | Reaction/status changes; not a new turn |
221
- | `contactRequest` | `CanonContactRequest` | Awareness only — the owner still approves |
222
- | `contactApproved` | `CanonContactRequest` | Awareness only |
223
222
  | `contactAdded` | `ContactAddedPayload` | A contact edge now exists |
224
223
  | `contactRemoved` | `ContactRemovedPayload` | A contact edge was removed |
224
+ | `participationSuppressed` | `ParticipationSuppressedPayload` | Observe-only notice that policy withheld a turn |
225
225
  | `interrupt` | `RuntimeSignalContext` | Same signal as `runtimeControls.onInterrupt` |
226
226
  | `stopAndDrop` | `RuntimeSignalContext` | Same signal as `runtimeControls.onStopAndDrop` |
227
227
  | `newSession` | `RuntimeSignalContext` | Same signal as `runtimeControls.onNewSession` |
@@ -246,7 +246,7 @@ Reaction update events are interaction state, not new chat turns. They do not ca
246
246
 
247
247
  ### Human-in-the-loop cards
248
248
 
249
- Use `ctx.requestRuntimeInput(...)` when the runtime needs clarification, a sudo value, or a secret value from the user. Use `ctx.requestApproval(...)` when the runtime needs an allow/deny decision before taking an action. Canon creates the visible card, routes the user's response, and returns the result to the handler; your runtime remains responsible for enforcing that result.
249
+ Use `ctx.requestRuntimeInput(...)` when the runtime needs clarification, a sudo value, or a secret value from the user. Use `ctx.requestPlanReview(...)` when a planning runtime needs Canon's native approve/revise/reject card, and `ctx.requestApproval(...)` when the runtime needs an allow/deny decision before taking an action. Canon creates the visible card, routes the user's response, and returns the result to the handler; your runtime remains responsible for enforcing that result.
250
250
 
251
251
  `requestApproval` is fail-closed and never throws: it returns `{ decision: 'deny' }` when no approval manager can be built (no resolved agent identity or owner) and on any non-abort error. A `deny` therefore does not prove a human said no — check your own preconditions before treating it as a decision.
252
252
 
@@ -277,22 +277,6 @@ const review = await ctx.requestCard({
277
277
 
278
278
  The SDK does not validate card documents — it forwards `request.card` to Canon as-is, and a malformed card surfaces as a backend 400. Install [`@canonmsg/rich-cards`](https://www.npmjs.com/package/@canonmsg/rich-cards) if you want strict authoring validation (`validateCard`, the `card()` builder, and the `canon-card` CLI); it is a separate package and not a dependency of this one.
279
279
 
280
- ## Contact Request Awareness
281
-
282
- Agents can also observe contact-request lifecycle events without becoming the approver:
283
-
284
- ```typescript
285
- agent.on('contactRequest', (request) => {
286
- console.log('New request aimed at this agent:', request.requesterName);
287
- });
288
-
289
- agent.on('contactApproved', (request) => {
290
- console.log('Request approved:', request.id);
291
- });
292
- ```
293
-
294
- These are awareness callbacks only. Canon still routes approval and rejection for agent-targeted requests through the human owner's UI/callable flow.
295
-
296
280
  ### Turn-aware example
297
281
 
298
282
  ```typescript
@@ -1,5 +1,5 @@
1
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';
2
+ import type { CanonAgentConnectionOptions, CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, MessageHandler, MessageUpdatedHandler, ParticipationSuppressedHandler, 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
@@ -40,8 +40,6 @@ export declare class CanonAgent {
40
40
  private realtimeManager;
41
41
  private sessionManager;
42
42
  private handler;
43
- private contactRequestHandler;
44
- private contactApprovedHandler;
45
43
  private contactAddedHandler;
46
44
  private contactRemovedHandler;
47
45
  private messageUpdatedHandler;
@@ -101,8 +99,6 @@ export declare class CanonAgent {
101
99
  private filterApprovalReplyMessages;
102
100
  on(event: 'message', handler: MessageHandler): void;
103
101
  on(event: 'messageUpdated', handler: MessageUpdatedHandler): void;
104
- on(event: 'contactRequest', handler: ContactRequestHandler): void;
105
- on(event: 'contactApproved', handler: ContactRequestHandler): void;
106
102
  on(event: 'contactAdded', handler: ContactAddedHandler): void;
107
103
  on(event: 'contactRemoved', handler: ContactRemovedHandler): void;
108
104
  /**
@@ -151,12 +147,11 @@ export declare class CanonAgent {
151
147
  * Outcome depends on the target's `groupJoinPolicy` and the relationship
152
148
  * graph:
153
149
  * - `{ status: 'added' }` — the member was added immediately.
154
- * - `{ status: 'pending', requestId, requirements }` — the target needs
155
- * policy approval, owner session setup, or both. The server created one
156
- * `group_invite`; membership activates after every requirement is met
157
- * (you can listen for `contact.approved` SSE events to know when).
150
+ * - `{ status: 'pending', requestId }` — the target needs policy approval.
151
+ * The server created one `group_invite`; membership activates after approval
152
+ * The approved membership arrives through the normal conversation update.
158
153
  *
159
- * Throws `CanonApiError` for hard failures (block, inactive, owner-only,
154
+ * Throws `CanonApiError` for hard failures (block, inactive, closed policy,
160
155
  * member cap, requester not authorized).
161
156
  */
162
157
  addMember(conversationId: string, userId: string): Promise<AddMemberResult>;
@@ -165,7 +160,6 @@ export declare class CanonAgent {
165
160
  url: string;
166
161
  attachment: import('@canonmsg/core').MediaAttachment;
167
162
  }>;
168
- private handleContactRequestEvent;
169
163
  private handleContactGraphEvent;
170
164
  private handleParticipationSuppressedEvent;
171
165
  private handleMessageUpdatedEvent;
@@ -245,6 +245,33 @@ function isAbortLikeError(error) {
245
245
  return true;
246
246
  return typeof record.message === 'string' && /\babort(?:ed)?\b/i.test(record.message);
247
247
  }
248
+ function linkAbortSignals(turnSignal, requestSignal) {
249
+ if (!requestSignal || requestSignal === turnSignal) {
250
+ return { signal: turnSignal, dispose: () => { } };
251
+ }
252
+ const controller = new AbortController();
253
+ const abortFrom = (source) => {
254
+ if (!controller.signal.aborted)
255
+ controller.abort(source.reason);
256
+ };
257
+ const onTurnAbort = () => abortFrom(turnSignal);
258
+ const onRequestAbort = () => abortFrom(requestSignal);
259
+ if (turnSignal.aborted)
260
+ abortFrom(turnSignal);
261
+ else
262
+ turnSignal.addEventListener('abort', onTurnAbort, { once: true });
263
+ if (requestSignal.aborted)
264
+ abortFrom(requestSignal);
265
+ else
266
+ requestSignal.addEventListener('abort', onRequestAbort, { once: true });
267
+ return {
268
+ signal: controller.signal,
269
+ dispose: () => {
270
+ turnSignal.removeEventListener('abort', onTurnAbort);
271
+ requestSignal.removeEventListener('abort', onRequestAbort);
272
+ },
273
+ };
274
+ }
248
275
  export class CanonAgent {
249
276
  options;
250
277
  runtimeConnection;
@@ -254,8 +281,6 @@ export class CanonAgent {
254
281
  realtimeManager = null;
255
282
  sessionManager = null;
256
283
  handler = null;
257
- contactRequestHandler = null;
258
- contactApprovedHandler = null;
259
284
  contactAddedHandler = null;
260
285
  contactRemovedHandler = null;
261
286
  messageUpdatedHandler = null;
@@ -457,14 +482,6 @@ export class CanonAgent {
457
482
  this.messageUpdatedHandler = handler;
458
483
  return;
459
484
  }
460
- if (event === 'contactRequest') {
461
- this.contactRequestHandler = handler;
462
- return;
463
- }
464
- if (event === 'contactApproved') {
465
- this.contactApprovedHandler = handler;
466
- return;
467
- }
468
485
  if (event === 'contactAdded') {
469
486
  this.contactAddedHandler = handler;
470
487
  return;
@@ -607,7 +624,6 @@ export class CanonAgent {
607
624
  const runtimeState = this.createRuntimeStatePublisher();
608
625
  for (const id of this.cachedConversationIds) {
609
626
  runtimeState?.writeSessionState(id, {
610
- cwd: process.cwd(),
611
627
  isActive: true,
612
628
  ...(this.options.clientType ? { clientType: this.options.clientType } : {}),
613
629
  }).catch(() => { });
@@ -640,14 +656,6 @@ export class CanonAgent {
640
656
  this.agentContext = ctx;
641
657
  this.ensureApprovalManager(ctx);
642
658
  });
643
- rtm.setContactRequestHandlers({
644
- onContactRequest: (request) => {
645
- void this.handleContactRequestEvent(this.contactRequestHandler, request);
646
- },
647
- onContactApproved: (request) => {
648
- void this.handleContactRequestEvent(this.contactApprovedHandler, request);
649
- },
650
- });
651
659
  rtm.setContactGraphHandlers({
652
660
  onContactAdded: (payload) => {
653
661
  void this.handleContactGraphEvent(this.contactAddedHandler, payload);
@@ -726,12 +734,11 @@ export class CanonAgent {
726
734
  * Outcome depends on the target's `groupJoinPolicy` and the relationship
727
735
  * graph:
728
736
  * - `{ status: 'added' }` — the member was added immediately.
729
- * - `{ status: 'pending', requestId, requirements }` — the target needs
730
- * policy approval, owner session setup, or both. The server created one
731
- * `group_invite`; membership activates after every requirement is met
732
- * (you can listen for `contact.approved` SSE events to know when).
737
+ * - `{ status: 'pending', requestId }` — the target needs policy approval.
738
+ * The server created one `group_invite`; membership activates after approval
739
+ * The approved membership arrives through the normal conversation update.
733
740
  *
734
- * Throws `CanonApiError` for hard failures (block, inactive, owner-only,
741
+ * Throws `CanonApiError` for hard failures (block, inactive, closed policy,
735
742
  * member cap, requester not authorized).
736
743
  */
737
744
  async addMember(conversationId, userId) {
@@ -743,16 +750,6 @@ export class CanonAgent {
743
750
  async uploadMedia(conversationId, data, mimeType, fileName) {
744
751
  return this.apiClient.uploadMedia(conversationId, data, mimeType, fileName);
745
752
  }
746
- async handleContactRequestEvent(handler, request) {
747
- if (!handler)
748
- return;
749
- try {
750
- await handler(request);
751
- }
752
- catch (error) {
753
- console.error('[canon-sdk] Contact-request handler failed:', error instanceof Error ? error.message : error);
754
- }
755
- }
756
753
  async handleContactGraphEvent(handler, payload) {
757
754
  if (!handler)
758
755
  return;
@@ -1219,6 +1216,7 @@ export class CanonAgent {
1219
1216
  // The freshest message in the batch is the turn's trigger — same convention
1220
1217
  // as the provenance lookup for turn verbosity below.
1221
1218
  const triggeringMessageId = messages[messages.length - 1]?.id;
1219
+ const triggeringReplyAuthority = messages[messages.length - 1]?.replyAuthority;
1222
1220
  const agentId = this.agentId;
1223
1221
  const runtimeState = this.createRuntimeStatePublisher();
1224
1222
  const queueDepth = () => this.sessionManager?.getQueueDepth(conversationId) ?? 0;
@@ -1493,9 +1491,12 @@ export class CanonAgent {
1493
1491
  const activeSelfContextId = selfContexts.length > 0 ? resolvedActiveSelfContextId : null;
1494
1492
  const withActiveSelfContext = (options) => {
1495
1493
  const base = { ...(options ?? {}) };
1496
- if (base.selfContextId !== undefined)
1497
- return base;
1498
- return activeSelfContextId ? { ...base, selfContextId: activeSelfContextId } : base;
1494
+ const withContext = base.selfContextId !== undefined || !activeSelfContextId
1495
+ ? base
1496
+ : { ...base, selfContextId: activeSelfContextId };
1497
+ return withContext.replyAuthority !== undefined || !triggeringReplyAuthority
1498
+ ? withContext
1499
+ : { ...withContext, replyAuthority: triggeringReplyAuthority };
1499
1500
  };
1500
1501
  // Core's chunked sender walks the parts in a plain loop and knows nothing
1501
1502
  // about this turn's abort signal, so a stop landing after part 1 would
@@ -1706,6 +1707,7 @@ export class CanonAgent {
1706
1707
  };
1707
1708
  const requestRuntimeInput = async (request) => {
1708
1709
  throwIfAborted();
1710
+ const linkedSignal = linkAbortSignals(abortController.signal, request.signal);
1709
1711
  const inputId = safeRuntimeInputId(request.inputId, request.kind);
1710
1712
  const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
1711
1713
  const expiresAtMs = Date.now() + timeoutMs;
@@ -1761,7 +1763,7 @@ export class CanonAgent {
1761
1763
  result = await this.ensureRuntimeRequestManager().request('input', conversationId, { kind: request.kind }, {
1762
1764
  requestId: inputId,
1763
1765
  expiresAt: expiresAtMs,
1764
- signal: abortController.signal,
1766
+ signal: linkedSignal.signal,
1765
1767
  });
1766
1768
  const outcome = buildRuntimeInputOutcome(inputId, result.status, {
1767
1769
  kind: request.kind,
@@ -1787,7 +1789,7 @@ export class CanonAgent {
1787
1789
  return result;
1788
1790
  }
1789
1791
  catch (error) {
1790
- if (abortController.signal.aborted || isAbortLikeError(error)) {
1792
+ if (abortController.signal.aborted || request.signal?.aborted || isAbortLikeError(error)) {
1791
1793
  if (requestCreated) {
1792
1794
  // Abort landing before the manager wired its cancel (e.g. during
1793
1795
  // the pre-request writeTurn/typing round-trips) leaves the
@@ -1810,6 +1812,81 @@ export class CanonAgent {
1810
1812
  await resumeTurnFromWaiting();
1811
1813
  return result;
1812
1814
  }
1815
+ finally {
1816
+ linkedSignal.dispose();
1817
+ }
1818
+ };
1819
+ const requestPlanReview = async (request) => {
1820
+ throwIfAborted();
1821
+ const linkedSignal = linkAbortSignals(abortController.signal, request.signal);
1822
+ const planId = request.planId && RUNTIME_INPUT_ID_PATTERN.test(request.planId)
1823
+ ? request.planId
1824
+ : randomUUID();
1825
+ const timeoutMs = Math.max(1_000, request.timeoutMs ?? DEFAULT_RUNTIME_INPUT_TIMEOUT_MS);
1826
+ const expiresAtMs = Date.now() + timeoutMs;
1827
+ const responseUserId = normalizeResponseUserId(request.responseUserId)
1828
+ ?? triggeringHumanId
1829
+ ?? normalizeResponseUserId(agent.ownerId);
1830
+ let created = false;
1831
+ let result = { status: 'timeout', planId };
1832
+ shouldPersistTurnState = true;
1833
+ try {
1834
+ result = await this.ensureRuntimeRequestManager().request('plan', conversationId, {
1835
+ ...(request.title ? { title: request.title } : {}),
1836
+ ...(request.summary ? { summary: request.summary } : {}),
1837
+ ...(request.body ? { body: request.body } : {}),
1838
+ ...(request.allowedPrompts ? { allowedPrompts: request.allowedPrompts } : {}),
1839
+ ...(responseUserId ? { responseUserId } : {}),
1840
+ turnId: request.turnId ?? turnId,
1841
+ }, {
1842
+ requestId: planId,
1843
+ expiresAt: expiresAtMs,
1844
+ responderPolicy: 'infer',
1845
+ signal: linkedSignal.signal,
1846
+ onCreated: async () => {
1847
+ created = true;
1848
+ try {
1849
+ await turnOutput.addBlock({
1850
+ id: `plan:${planId}`,
1851
+ kind: 'input',
1852
+ status: 'pending',
1853
+ title: request.title ?? 'Plan review',
1854
+ summary: request.summary ?? 'Review requested',
1855
+ });
1856
+ await turnOutput.waitingInput();
1857
+ }
1858
+ catch { }
1859
+ await writeTurn('waiting_input');
1860
+ try {
1861
+ await this.typingSignals.clear(conversationId);
1862
+ }
1863
+ catch { }
1864
+ },
1865
+ });
1866
+ throwIfAborted();
1867
+ shouldPersistTurnState = false;
1868
+ try {
1869
+ await turnOutput.completeBlock(`plan:${planId}`, {
1870
+ summary: `Plan ${result.status}`,
1871
+ });
1872
+ }
1873
+ catch { }
1874
+ await resumeTurnFromWaiting();
1875
+ return result;
1876
+ }
1877
+ catch (error) {
1878
+ if (abortController.signal.aborted || request.signal?.aborted || isAbortLikeError(error)) {
1879
+ throw error;
1880
+ }
1881
+ shouldPersistTurnState = false;
1882
+ if (!created)
1883
+ throw error;
1884
+ await resumeTurnFromWaiting();
1885
+ return result;
1886
+ }
1887
+ finally {
1888
+ linkedSignal.dispose();
1889
+ }
1813
1890
  };
1814
1891
  const sendCard = async (request) => {
1815
1892
  throwIfAborted();
@@ -2020,6 +2097,7 @@ export class CanonAgent {
2020
2097
  turnVerbosity,
2021
2098
  requestApproval,
2022
2099
  requestRuntimeInput,
2100
+ requestPlanReview,
2023
2101
  requestCard,
2024
2102
  sendCard,
2025
2103
  abortSignal: abortController.signal,
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { CanonAgent } from './canon-agent.js';
2
2
  export type { AgentContactsAPI, AgentConversationsAPI, AgentUsersAPI } from './canon-agent.js';
3
3
  export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule, redactSecrets, } from '@canonmsg/core';
4
- export type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalOutcomeMetadata, ApprovalReplyMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalResult, ApprovalRisk, CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeTurnModeActivation, CanonRuntimeTurnModeDescriptor, CanonRuntimeTurnModeScope, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ParticipationSuppressedPayload, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, ResumableMediaUploadResult, MediaAttachment, VideoProcessingStatus, VideoProcessingErrorCode, RuntimeInputAnswers, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SessionRule, } from '@canonmsg/core';
4
+ export type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalOutcomeMetadata, ApprovalReplyMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalResult, ApprovalRisk, CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeTurnModeActivation, CanonRuntimeTurnModeDescriptor, CanonRuntimeTurnModeScope, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ParticipationSuppressedPayload, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, ResumableMediaUploadResult, MediaAttachment, VideoProcessingStatus, VideoProcessingErrorCode, RuntimeInputAnswers, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimePlanRequestPayload, RuntimePlanRequestResult, SessionRule, } from '@canonmsg/core';
5
5
  export { SessionManager } from './session-manager.js';
6
6
  export { DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES, DEFAULT_MEDIA_CACHE_DIR, DEFAULT_MEDIA_MATERIALIZATION_BYTES, MAX_ANTHROPIC_IMAGE_RAW_BYTES, MAX_ANTHROPIC_REQUEST_BYTES, MAX_CANON_MEDIA_BYTES, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, toAnthropicImageBlocksWithinBudget, uploadMediaFile, } from './media.js';
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, 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';
10
+ export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, 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, FinalMessageResult, MessageHandler, MessageHandlerContext, MessageUpdatedHandler, ParticipationSuppressedHandler, ProgressMessageOptions, ProgressMessageResult, RuntimeApprovalRequest, RuntimeInputRequest, RuntimeInputResult, RuntimePlanReviewRequest, RuntimePlanReviewResult, 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 ConversationUpdatedPayload, type MessageUpdatedPayload, type ParticipationSuppressedPayload, type VoiceSessionEventPayload } from '@canonmsg/core';
1
+ import { type AgentContext, type ContactAddedPayload, type ContactRemovedPayload, 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:
@@ -15,8 +15,6 @@ export declare class RealtimeManager {
15
15
  private lastSseErrorAt;
16
16
  private suppressedSseErrorCount;
17
17
  private onAgentContext;
18
- private onContactRequest;
19
- private onContactApproved;
20
18
  private onContactAdded;
21
19
  private onContactRemoved;
22
20
  private onConversationUpdated;
@@ -35,10 +33,6 @@ export declare class RealtimeManager {
35
33
  private pruneRecentInboundMessageIds;
36
34
  private logSseError;
37
35
  setOnAgentContext(cb: (ctx: AgentContext) => void): void;
38
- setContactRequestHandlers(handlers: {
39
- onContactRequest?: (payload: ContactRequestPayload) => void;
40
- onContactApproved?: (payload: ContactApprovedPayload) => void;
41
- }): void;
42
36
  setContactGraphHandlers(handlers: {
43
37
  onContactAdded?: (payload: ContactAddedPayload) => void;
44
38
  onContactRemoved?: (payload: ContactRemovedPayload) => void;
package/dist/realtime.js CHANGED
@@ -16,8 +16,6 @@ export class RealtimeManager {
16
16
  lastSseErrorAt = 0;
17
17
  suppressedSseErrorCount = 0;
18
18
  onAgentContext = null;
19
- onContactRequest = null;
20
- onContactApproved = null;
21
19
  onContactAdded = null;
22
20
  onContactRemoved = null;
23
21
  onConversationUpdated = null;
@@ -86,6 +84,7 @@ export class RealtimeManager {
86
84
  createdAt: m.createdAt ?? new Date().toISOString(),
87
85
  ...(m.contactCard ? { contactCard: m.contactCard } : {}),
88
86
  ...(m.metadata ? { metadata: m.metadata } : {}),
87
+ ...(payload.replyAuthority ? { replyAuthority: payload.replyAuthority } : {}),
89
88
  };
90
89
  this.debouncer.add(payload.conversationId, message, payload.provenance ?? null);
91
90
  },
@@ -99,12 +98,6 @@ export class RealtimeManager {
99
98
  onAgentContext: (ctx) => {
100
99
  this.onAgentContext?.(ctx);
101
100
  },
102
- onContactRequest: (payload) => {
103
- this.onContactRequest?.(payload);
104
- },
105
- onContactApproved: (payload) => {
106
- this.onContactApproved?.(payload);
107
- },
108
101
  onContactAdded: (payload) => {
109
102
  this.onContactAdded?.(payload);
110
103
  },
@@ -173,10 +166,6 @@ export class RealtimeManager {
173
166
  setOnAgentContext(cb) {
174
167
  this.onAgentContext = cb;
175
168
  }
176
- setContactRequestHandlers(handlers) {
177
- this.onContactRequest = handlers.onContactRequest ?? null;
178
- this.onContactApproved = handlers.onContactApproved ?? null;
179
- }
180
169
  setContactGraphHandlers(handlers) {
181
170
  this.onContactAdded = handlers.onContactAdded ?? null;
182
171
  this.onContactRemoved = handlers.onContactRemoved ?? null;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
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';
1
+ export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonTurnContextV2, CanonMessage, CanonConversation, CommunicateInput, CommunicateResult, CanonReplyContext, CanonContact, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolveAdmissionTargetInput, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, CreateGroupOptions, CreateGroupResult, SendMessageOptions, TurnLifecycleState, TurnOutputBlock, TurnOutputBlockInput, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, RuntimePlanRequestPayload, RuntimePlanRequestResult, 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, RuntimePlanRequestPayload, RuntimePlanRequestResult, 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
  /**
@@ -119,6 +119,8 @@ export interface RuntimeInputRequest {
119
119
  native?: RuntimeInputNativeMetadata;
120
120
  inputId?: string;
121
121
  timeoutMs?: number;
122
+ /** Aborts the pending input request without waiting for the enclosing turn to stop. */
123
+ signal?: AbortSignal;
122
124
  /** Human conversation member who should answer. Secret and sudo prompts remain owner-only. */
123
125
  responseUserId?: string;
124
126
  }
@@ -128,6 +130,14 @@ export interface RuntimeInputResult {
128
130
  answers?: RuntimeInputAnswers;
129
131
  inputId: string;
130
132
  }
133
+ /** Native Canon plan-review card backed by Core's existing runtime-plan interaction. */
134
+ export interface RuntimePlanReviewRequest extends RuntimePlanRequestPayload {
135
+ planId?: string;
136
+ timeoutMs?: number;
137
+ /** Aborts the pending plan review without waiting for the enclosing turn to stop. */
138
+ signal?: AbortSignal;
139
+ }
140
+ export type RuntimePlanReviewResult = RuntimePlanRequestResult;
131
141
  export interface RuntimeCardRequest {
132
142
  card: RuntimeCardV1;
133
143
  cardId?: string;
@@ -210,6 +220,12 @@ export interface MessageHandlerContext {
210
220
  * are never persisted in Canon message metadata.
211
221
  */
212
222
  requestRuntimeInput: (request: RuntimeInputRequest) => Promise<RuntimeInputResult>;
223
+ /**
224
+ * Present a native Canon plan-review card and wait for approve, revise,
225
+ * reject, cancellation, or timeout. Canon owns presentation and response
226
+ * routing; the runtime remains responsible for enforcing the decision.
227
+ */
228
+ requestPlanReview: (request: RuntimePlanReviewRequest) => Promise<RuntimePlanReviewResult>;
213
229
  /**
214
230
  * Ask the triggering human to review/respond to a generic rich card. The
215
231
  * visible card document is redacted for presentation; raw response values
@@ -329,7 +345,6 @@ export interface CanonAgentOptions extends CanonAgentConnectionOptions {
329
345
  */
330
346
  turnVerbosity?: import('./turn-verbosity-option.js').CanonAgentTurnVerbosityOption;
331
347
  }
332
- export type ContactRequestHandler = (request: import('@canonmsg/core').CanonContactRequest) => void | Promise<void>;
333
348
  export type ContactAddedHandler = (contact: import('@canonmsg/core').ContactAddedPayload) => void | Promise<void>;
334
349
  export type ContactRemovedHandler = (payload: import('@canonmsg/core').ContactRemovedPayload) => void | Promise<void>;
335
350
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "9.0.0",
3
+ "version": "10.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": "^11.0.0"
31
+ "@canonmsg/core": "^12.0.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"