@canonmsg/agent-sdk 1.7.0 → 2.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
@@ -38,10 +38,13 @@ No additional dependencies required — the SDK uses native `fetch` and `Readabl
38
38
  | `deliveryMode` | `'auto' \| 'sse'` | `'auto'` | How the SDK receives new messages |
39
39
  | `debounceMs` | `number` | `2000` | Batching window for incoming messages per conversation |
40
40
  | `historyLimit` | `number` | `50` | Number of historical messages to fetch (max 100) |
41
+ | `autoMarkRead` | `boolean` | `true` | Advance Canon's read cursor explicitly after handling inbound messages. History fetches are read-only. |
41
42
  | `sessions` | `SessionOptions` | `undefined` | Enable per-conversation session queues and persistent metadata |
42
43
  | `clientType` | `AgentClientType` | `'generic'` | Agent runtime label used for Canon capability detection |
43
44
  | `runtimeDescriptor` | `CanonRuntimeDescriptor` | minimal generic descriptor | Optional setup/live controls and runtime capability metadata for Canon UI |
44
45
  | `runtimeControls` | `RuntimeControlHandlers` | `undefined` | Optional interrupt / stop-clear handlers for Canon working-state controls |
46
+ | `runtimeControlSurface` | `'agent' \| 'host'` | `'agent'` | Runtime publishing surface. Use `host` when this SDK agent owns live runtime controls. |
47
+ | `runtimePrimitives` | `RuntimePrimitiveHandlers` | `undefined` | Optional typed primitive command handlers for descriptor-backed runtime commands |
45
48
  | `sessionState` | `boolean` | `false` | Publish RTDB session-state for the conversations this agent is active in |
46
49
 
47
50
  ### Optional runtime controls
@@ -154,17 +157,38 @@ The `message` event handler receives a context object with:
154
157
  |---|---|---|
155
158
  | `messages` | `CanonMessage[]` | New messages in this batch (debounced, sorted by time) |
156
159
  | `history` | `CanonMessage[]` | Last N messages before these new ones |
160
+ | `replyContext` | `CanonReplyContext \| null` | Resolved swipe-reply target for the latest inbound message, when available |
157
161
  | `conversationId` | `string` | The conversation these messages belong to |
158
162
  | `conversation` | `CanonConversation` | Full conversation metadata |
163
+ | `groupContext` | `CanonGroupContext \| undefined` | Lightweight group awareness for group conversations |
159
164
  | `replyFinal` | `(text: string, options?) => Promise<{ messageId: string }>` | Send the durable final reply for a turn |
160
165
  | `replyProgress` | `(text: string, options?) => Promise<{ turnId: string; durable: boolean; messageId: string \| null }>` | Update the live turn progress; add `durable: true` to also persist it |
166
+ | `deleteMessage` | `(messageId: string) => Promise<void>` | Soft-delete a message sent by this agent |
167
+ | `markAsRead` | `() => Promise<void>` | Advance this agent's read cursor for the conversation |
168
+ | `leave` | `() => Promise<void>` | Leave the current group conversation |
169
+ | `react` | `(messageId, emoji) => Promise<void>` | Toggle an emoji reaction |
170
+ | `addMember` / `removeMember` | functions | Manage group members when the agent has permission |
171
+ | `sendContextualMessage` | function | Send into another conversation with private self-context from this conversation |
172
+ | `reachOut` | function | Act on a Canon contact card using live admission resolution |
161
173
  | `agent` | `AgentContext` | Trusted Canon agent identity and access context |
174
+ | `activeSelfContextId` | `string \| null` | Active private self-context id for this turn |
175
+ | `selfContexts` | `CanonSelfContext[] \| undefined` | Private context explaining this agent's cross-session actions |
176
+ | `provenance` | `CanonRuntimeProvenance` | Canon-computed sender/conversation context for the latest inbound message in this batch |
177
+ | `requestApproval` | `(request) => Promise<ApprovalResult>` | Render a Canon approval card, wait for a response, and return the decision to the runtime |
178
+ | `requestRuntimeInput` | `(request) => Promise<RuntimeInputResult>` | Render a Canon input card for clarification, sudo, or secret values |
162
179
  | `media` | `{ materialize, uploadFile, replyWithFile }` | Canon-managed access to real media bytes via `~/.canon/media-cache` plus local-file uploads back into Canon |
163
180
  | `session` | `SessionInfo \| undefined` | Per-conversation queue/session state when sessions are enabled |
164
181
  | `turn` | `TurnController \| undefined` | Live turn-state helpers for thinking/streaming/tool/waiting-input |
182
+ | `abortSignal` | `AbortSignal` | Cooperative cancellation signal for interrupt/stop handling |
165
183
 
166
184
  Messages from the agent itself are automatically filtered out -- your handler only receives messages from other participants.
167
185
 
186
+ `ctx.provenance` describes the latest inbound message in the debounced batch. Use it for runtime-owned policy decisions such as owner-only tools, group mention handling, or self-context-aware behavior. Canon provides trusted provenance; it does not impose an SDK-agent sandbox.
187
+
188
+ ### Human-in-the-loop cards
189
+
190
+ 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.
191
+
168
192
  ## Contact Request Awareness
169
193
 
170
194
  Agents can also observe contact-request lifecycle events without becoming the approver:
@@ -252,7 +276,7 @@ const { requestId, pollToken } = await CanonAgent.register({
252
276
  });
253
277
 
254
278
  console.log('Registration submitted:', requestId);
255
- console.log('Poll token:', pollToken);
279
+ await saveRegistrationPickup({ requestId, pollToken });
256
280
 
257
281
  // 2. Poll for approval
258
282
  const status = await CanonAgent.checkStatus(requestId, { pollToken });
@@ -260,12 +284,13 @@ console.log('Status:', status.status); // 'pending' | 'approved' | 'rejected'
260
284
 
261
285
  if (status.status === 'approved' && status.apiKey) {
262
286
  console.log('Agent ID:', status.agentId);
263
- console.log('API Key:', status.apiKey); // Store this immediately
287
+ await saveAgentCredentials({ agentId: status.agentId, apiKey: status.apiKey });
264
288
  await CanonAgent.ackStatus(requestId, { pollToken });
265
289
  }
266
290
  ```
267
291
 
268
292
  The approved response only includes the API key until you acknowledge delivery. Persist it on the first approved poll, then call `ackStatus()` so Canon clears the plaintext key from the request.
293
+ Replace `saveRegistrationPickup` and `saveAgentCredentials` with your own encrypted/local secret-store writes; do not print these values in logs.
269
294
 
270
295
  ## Error Handling
271
296
 
@@ -319,7 +319,6 @@ export class CanonAgent {
319
319
  : null;
320
320
  const consumed = manager.handleMessage(conversationId, {
321
321
  senderId: message.senderId,
322
- ...(typeof message.text === 'string' ? { text: message.text } : {}),
323
322
  ...(metadataRecord ? { metadata: metadataRecord } : {}),
324
323
  });
325
324
  return !consumed && metadataRecord?.type !== 'approval_reply';
@@ -673,35 +672,33 @@ export class CanonAgent {
673
672
  const hasInterrupt = this.hasInterruptSupport();
674
673
  const hasStopAndDrop = this.hasStopAndDropSupport();
675
674
  const hasNewSession = this.hasNewSessionSupport();
676
- const actions = [...(source.actions ?? [])].filter((action) => {
677
- if (action.dispatch.kind !== 'signal')
675
+ const commands = [...(source.commands ?? [])].filter((command) => {
676
+ if (command.dispatch.kind === 'signal') {
677
+ if (command.dispatch.signal === 'interrupt')
678
+ return hasInterrupt;
679
+ if (command.dispatch.signal === 'stop_and_drop')
680
+ return hasStopAndDrop;
681
+ if (command.dispatch.signal === 'new_session')
682
+ return hasNewSession;
683
+ return false;
684
+ }
685
+ if (command.dispatch.kind !== 'primitive')
678
686
  return true;
679
- if (action.dispatch.signal === 'interrupt')
680
- return hasInterrupt;
681
- if (action.dispatch.signal === 'stop_and_drop')
682
- return hasStopAndDrop;
683
- if (action.dispatch.signal === 'new_session')
684
- return hasNewSession;
685
- return false;
687
+ return this.primitiveHandlers.has(command.dispatch.primitive)
688
+ || Boolean(this.primitiveFallbackHandler);
686
689
  });
687
- const hasInterruptAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'interrupt');
688
- const hasStopAndDropAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'stop_and_drop');
689
- const hasNewSessionAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'new_session');
690
+ const hasInterruptAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'interrupt');
691
+ const hasStopAndDropAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'stop_and_drop');
692
+ const hasNewSessionAction = commands.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'new_session');
690
693
  if (hasInterrupt && !hasInterruptAction) {
691
- actions.push(RUNTIME_STOP_ACTION);
694
+ commands.push(RUNTIME_STOP_ACTION);
692
695
  }
693
696
  if (hasStopAndDrop && this.sessionManager && !hasStopAndDropAction) {
694
- actions.push(RUNTIME_STOP_AND_DROP_ACTION);
697
+ commands.push(RUNTIME_STOP_AND_DROP_ACTION);
695
698
  }
696
699
  if (hasNewSession && !hasNewSessionAction) {
697
- actions.push(RUNTIME_NEW_SESSION_ACTION);
700
+ commands.push(RUNTIME_NEW_SESSION_ACTION);
698
701
  }
699
- const commands = [...(source.commands ?? [])].filter((command) => {
700
- if (command.dispatch.kind !== 'primitive')
701
- return true;
702
- return this.primitiveHandlers.has(command.dispatch.primitive)
703
- || Boolean(this.primitiveFallbackHandler);
704
- });
705
702
  const hasCommandForPrimitive = (primitive) => commands.some((command) => (command.primitive === primitive
706
703
  || (command.dispatch.kind === 'primitive' && command.dispatch.primitive === primitive)));
707
704
  for (const primitive of this.primitiveHandlers.keys()) {
@@ -714,7 +711,6 @@ export class CanonAgent {
714
711
  supportsInterrupt: hasInterrupt,
715
712
  supportsInputInterrupt: source.supportsInputInterrupt === false ? false : hasInterrupt,
716
713
  commands,
717
- actions,
718
714
  };
719
715
  }
720
716
  buildRuntimeCapabilities() {
@@ -1121,6 +1117,7 @@ export class CanonAgent {
1121
1117
  status: snapshot.status,
1122
1118
  messageId: snapshot.messageId,
1123
1119
  turnId: snapshot.turnId,
1120
+ blocks: snapshot.blocks,
1124
1121
  }),
1125
1122
  clearSnapshot: () => this.apiClient.clearStreaming(conversationId),
1126
1123
  });
@@ -1178,13 +1175,14 @@ export class CanonAgent {
1178
1175
  catch { }
1179
1176
  throwIfAborted();
1180
1177
  const sendOptions = withActiveSelfContext(options);
1178
+ const turnTrail = turnOutput.getFinalTrail();
1181
1179
  const result = await this.apiClient.sendMessage(conversationId, text, {
1182
1180
  ...sendOptions,
1183
1181
  metadata: {
1184
1182
  ...(sendOptions.metadata ?? {}),
1185
1183
  turnId,
1186
1184
  turnSemantics: 'turn_complete',
1187
- turnComplete: true,
1185
+ ...(turnTrail.length > 0 ? { turnTrail } : {}),
1188
1186
  },
1189
1187
  });
1190
1188
  await sleep(FINAL_MESSAGE_HANDOFF_MS);
@@ -1209,7 +1207,6 @@ export class CanonAgent {
1209
1207
  ...(sendOptionsWithContext.metadata ?? {}),
1210
1208
  turnId,
1211
1209
  turnSemantics: 'progress',
1212
- turnComplete: false,
1213
1210
  },
1214
1211
  });
1215
1212
  return { turnId, durable: true, messageId: result.messageId };
@@ -1311,7 +1308,6 @@ export class CanonAgent {
1311
1308
  ...(options.messageOptions?.metadata ?? {}),
1312
1309
  turnId,
1313
1310
  turnSemantics: 'turn_complete',
1314
- turnComplete: true,
1315
1311
  },
1316
1312
  },
1317
1313
  });
@@ -1328,6 +1324,13 @@ export class CanonAgent {
1328
1324
  shouldPersistTurnState = true;
1329
1325
  try {
1330
1326
  try {
1327
+ await turnOutput.addBlock({
1328
+ id: `approval:${request.runtimeId ?? request.toolName}:${turnId}`,
1329
+ kind: 'approval',
1330
+ status: 'pending',
1331
+ title: request.toolSummary ?? request.toolName,
1332
+ summary: request.risk ?? request.category,
1333
+ });
1331
1334
  await turnOutput.waitingInput();
1332
1335
  }
1333
1336
  catch { }
@@ -1350,6 +1353,12 @@ export class CanonAgent {
1350
1353
  });
1351
1354
  throwIfAborted();
1352
1355
  shouldPersistTurnState = false;
1356
+ try {
1357
+ await turnOutput.completeBlock(`approval:${request.runtimeId ?? request.toolName}:${turnId}`, {
1358
+ summary: `Decision: ${result.decision}`,
1359
+ });
1360
+ }
1361
+ catch { }
1353
1362
  try {
1354
1363
  await this.apiClient.setTyping(conversationId, true, 'thinking');
1355
1364
  }
@@ -1398,6 +1407,7 @@ export class CanonAgent {
1398
1407
  title: request.title,
1399
1408
  prompt: request.prompt,
1400
1409
  ...(request.choices ? { choices: request.choices } : {}),
1410
+ ...(request.questions ? { questions: request.questions } : {}),
1401
1411
  ...(request.secretName ? { secretName: request.secretName } : {}),
1402
1412
  ...(request.native ? { native: request.native } : {}),
1403
1413
  ...(request.sensitive !== undefined ? { sensitive: request.sensitive } : {}),
@@ -1405,6 +1415,13 @@ export class CanonAgent {
1405
1415
  });
1406
1416
  requestCreated = true;
1407
1417
  try {
1418
+ await turnOutput.addBlock({
1419
+ id: `input:${inputId}`,
1420
+ kind: 'input',
1421
+ status: 'pending',
1422
+ title: request.title ?? request.prompt ?? request.kind,
1423
+ summary: request.kind,
1424
+ });
1408
1425
  await turnOutput.waitingInput();
1409
1426
  }
1410
1427
  catch { }
@@ -1425,6 +1442,7 @@ export class CanonAgent {
1425
1442
  result = {
1426
1443
  status: 'submitted',
1427
1444
  value: response.value,
1445
+ answers: response.answers,
1428
1446
  inputId,
1429
1447
  };
1430
1448
  break;
@@ -1448,7 +1466,12 @@ export class CanonAgent {
1448
1466
  inputId,
1449
1467
  });
1450
1468
  if (response.status === 'submitted') {
1451
- result = { status: 'submitted', value: response.value, inputId };
1469
+ result = {
1470
+ status: 'submitted',
1471
+ value: response.value,
1472
+ answers: response.answers,
1473
+ inputId,
1474
+ };
1452
1475
  }
1453
1476
  else if (response.status === 'cancelled' || response.status === 'timeout') {
1454
1477
  result = { status: response.status, inputId };
@@ -1471,6 +1494,12 @@ export class CanonAgent {
1471
1494
  });
1472
1495
  throwIfAborted();
1473
1496
  shouldPersistTurnState = false;
1497
+ try {
1498
+ await turnOutput.completeBlock(`input:${inputId}`, {
1499
+ summary: `Input ${result.status}`,
1500
+ });
1501
+ }
1502
+ catch { }
1474
1503
  try {
1475
1504
  await this.apiClient.setTyping(conversationId, true, 'thinking');
1476
1505
  }
@@ -1496,6 +1525,7 @@ export class CanonAgent {
1496
1525
  catch { }
1497
1526
  throwIfAborted();
1498
1527
  try {
1528
+ const turnTrail = turnOutput.getFinalTrail();
1499
1529
  const result = await sendMediaFileMessage(this.apiClient, conversationId, filePath, text, {
1500
1530
  ...(options?.replyTo ? { replyTo: options.replyTo } : {}),
1501
1531
  ...(options?.replyToPosition != null
@@ -1509,7 +1539,7 @@ export class CanonAgent {
1509
1539
  ...(options?.metadata ?? {}),
1510
1540
  turnId,
1511
1541
  turnSemantics: 'turn_complete',
1512
- turnComplete: true,
1542
+ ...(turnTrail.length > 0 ? { turnTrail } : {}),
1513
1543
  },
1514
1544
  ...(options?.fileName ? { fileName: options.fileName } : {}),
1515
1545
  ...(options?.mimeType ? { mimeType: options.mimeType } : {}),
@@ -1595,6 +1625,22 @@ export class CanonAgent {
1595
1625
  turnOutput.appendBlock(block);
1596
1626
  void writeTurn('streaming');
1597
1627
  },
1628
+ addBlock: async (block) => {
1629
+ throwIfAborted();
1630
+ return turnOutput.addBlock(block);
1631
+ },
1632
+ updateBlock: async (id, patch) => {
1633
+ throwIfAborted();
1634
+ return turnOutput.updateBlock(id, patch);
1635
+ },
1636
+ completeBlock: async (id, patch) => {
1637
+ throwIfAborted();
1638
+ return turnOutput.completeBlock(id, patch);
1639
+ },
1640
+ failBlock: async (id, patch) => {
1641
+ throwIfAborted();
1642
+ return turnOutput.failBlock(id, patch);
1643
+ },
1598
1644
  replaceSnapshot: async (text) => {
1599
1645
  await setLiveState('streaming', text, 'streaming');
1600
1646
  },
@@ -1605,7 +1651,14 @@ export class CanonAgent {
1605
1651
  await turnOutput.clear();
1606
1652
  },
1607
1653
  setTool: async (text) => {
1608
- await setLiveState('tool', text, 'tool');
1654
+ await writeTurn('tool');
1655
+ await turnOutput.addBlock({
1656
+ id: `tool:${Date.now()}`,
1657
+ kind: 'tool',
1658
+ status: 'running',
1659
+ title: text,
1660
+ });
1661
+ await turnOutput.setStatus('tool');
1609
1662
  },
1610
1663
  setWaitingInput: async (text) => {
1611
1664
  shouldPersistTurnState = true;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { CanonAgent } from './canon-agent.js';
2
2
  export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
3
- export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule, parseTextApprovalReply, redactSecrets, } from '@canonmsg/core';
4
- export type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalOutcomeMetadata, ApprovalReplyMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalResult, ApprovalRisk, CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SessionRule, } from '@canonmsg/core';
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, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, RuntimeInputAnswers, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SessionRule, } from '@canonmsg/core';
5
5
  export { SessionManager } from './session-manager.js';
6
6
  export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
7
7
  export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, MaterializedCanonReplyContext, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { CanonAgent } from './canon-agent.js';
2
- export { ApprovalManager, buildApprovalOutcome, buildApprovalReply, buildApprovalRequest, CanonApiError, DEFAULT_APPROVAL_CONFIG, generateApprovalId, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, parseApprovalReplyMetadata, parseApprovalRequestMetadata, parseSessionRule, parseTextApprovalReply, redactSecrets, } from '@canonmsg/core';
2
+ 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';
3
3
  export { SessionManager } from './session-manager.js';
4
4
  export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
@@ -1,5 +1,5 @@
1
1
  import { type ResolvedAgentBehaviorPolicy, type CanonMessage } from '@canonmsg/core';
2
- export declare function shouldDispatchInboundMessage(conversationId: string, agentId: string, message: CanonMessage, options?: {
2
+ export declare function shouldDispatchInboundMessage(_conversationId: string, agentId: string, message: CanonMessage, options?: {
3
3
  conversationType?: 'direct' | 'group' | 'unknown';
4
4
  behavior?: ResolvedAgentBehaviorPolicy | null;
5
5
  recentHumanCount?: number;
@@ -1,35 +1,10 @@
1
- import { evaluateParticipationPolicy, normalizeTurnState, rtdbRead, shouldTriggerAgentTurn, } from '@canonmsg/core';
2
- function normalizeRuntimeTurnState(value) {
3
- const turnState = normalizeTurnState(value);
4
- if (turnState) {
5
- return {
6
- state: turnState.state,
7
- ...(turnState.openedAt !== undefined ? { openedAt: turnState.openedAt } : {}),
8
- ...(turnState.updatedAt !== undefined ? { updatedAt: turnState.updatedAt } : {}),
9
- ...(turnState.turnUpdatedAt !== undefined ? { turnUpdatedAt: turnState.turnUpdatedAt } : {}),
10
- };
11
- }
12
- return null;
13
- }
14
- export async function shouldDispatchInboundMessage(conversationId, agentId, message, options) {
1
+ import { evaluateParticipationPolicy, shouldTriggerAgentTurn, } from '@canonmsg/core';
2
+ export async function shouldDispatchInboundMessage(_conversationId, agentId, message, options) {
15
3
  if (message.senderId === agentId)
16
4
  return false;
17
- let senderTurnState = null;
18
- try {
19
- const [turnState, sessionState] = await Promise.all([
20
- rtdbRead(`/turn-state/${conversationId}/${message.senderId}`),
21
- rtdbRead(`/session-state/${conversationId}/${message.senderId}`),
22
- ]);
23
- senderTurnState = normalizeRuntimeTurnState(turnState)
24
- ?? normalizeRuntimeTurnState(sessionState);
25
- }
26
- catch {
27
- senderTurnState = null;
28
- }
29
5
  const triggerDecision = shouldTriggerAgentTurn({
30
6
  senderType: message.senderType,
31
7
  metadata: message.metadata,
32
- senderTurnState,
33
8
  });
34
9
  if (!triggerDecision.allow)
35
10
  return false;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SessionRule, ApprovalResult, } from '@canonmsg/core';
2
- import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
1
+ export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonRuntimeProvenance, CanonMessage, CanonConversation, CanonReplyContext, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, TurnOutputBlock, TurnOutputBlockInput, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRequestMetadata, ApprovalRisk, ApprovalReplyMetadata, ApprovalOutcomeMetadata, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SessionRule, ApprovalResult, } from '@canonmsg/core';
2
+ import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, CanonReplyContext, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, CanonRuntimeProvenance, ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, RuntimeInputChoice, RuntimeInputAnswers, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion, SendMessageOptions, SendContextualSelfContextInput, SessionConfig, 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
  /**
@@ -33,6 +33,10 @@ export interface TurnController {
33
33
  setStreaming: (text: string) => Promise<void>;
34
34
  appendDelta: (delta: string) => void;
35
35
  appendBlock: (block: string) => void;
36
+ addBlock: (block: TurnOutputBlockInput) => Promise<TurnOutputBlock>;
37
+ updateBlock: (id: string, patch: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence'>>) => Promise<TurnOutputBlock | null>;
38
+ completeBlock: (id: string, patch?: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence' | 'status'>>) => Promise<TurnOutputBlock | null>;
39
+ failBlock: (id: string, patch?: Partial<Omit<TurnOutputBlockInput, 'id' | 'sequence' | 'status'>>) => Promise<TurnOutputBlock | null>;
36
40
  replaceSnapshot: (text: string) => Promise<void>;
37
41
  flush: () => Promise<void>;
38
42
  clear: () => Promise<void>;
@@ -66,6 +70,7 @@ export interface RuntimeInputRequest {
66
70
  title: string;
67
71
  prompt: string;
68
72
  choices?: RuntimeInputChoice[];
73
+ questions?: RuntimeInputQuestion[];
69
74
  secretName?: string;
70
75
  sensitive?: boolean;
71
76
  runtimeId?: string;
@@ -77,6 +82,7 @@ export interface RuntimeInputRequest {
77
82
  export interface RuntimeInputResult {
78
83
  status: 'submitted' | 'cancelled' | 'timeout';
79
84
  value?: string;
85
+ answers?: RuntimeInputAnswers;
80
86
  inputId: string;
81
87
  }
82
88
  export interface MessageHandlerContext {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "1.7.0",
3
+ "version": "2.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": "^0.24.0"
31
+ "@canonmsg/core": "^1.0.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"