@canonmsg/agent-sdk 5.1.0 → 5.1.2

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.
@@ -167,6 +167,9 @@ function safeRuntimeCardId(value) {
167
167
  ? normalized
168
168
  : `card_${randomUUID()}`;
169
169
  }
170
+ function normalizeResponseUserId(value) {
171
+ return value?.trim() || undefined;
172
+ }
170
173
  function buildSdkMessageId(parts) {
171
174
  const raw = parts
172
175
  .map((part) => part == null ? '' : String(part))
@@ -1310,6 +1313,9 @@ export class CanonAgent {
1310
1313
  }
1311
1314
  }
1312
1315
  const latestMessage = hydratedMessages[hydratedMessages.length - 1] ?? null;
1316
+ const triggeringHumanId = latestMessage?.senderType === 'human'
1317
+ ? normalizeResponseUserId(latestMessage.senderId)
1318
+ : undefined;
1313
1319
  let replyContext = latestMessage
1314
1320
  ? resolveCanonReplyContext({ message: latestMessage, messages: history })
1315
1321
  : null;
@@ -1473,6 +1479,11 @@ export class CanonAgent {
1473
1479
  await this.typingSignals.clear(conversationId);
1474
1480
  }
1475
1481
  catch { }
1482
+ const responseUserId = normalizeResponseUserId(request.responseUserId)
1483
+ ?? triggeringHumanId;
1484
+ // Session-wide rules are owner-scoped. The effective responder, not
1485
+ // merely the member who triggered the turn, determines that scope.
1486
+ const mustIsolateSessionRules = Boolean(responseUserId && responseUserId !== agent.ownerId);
1476
1487
  const result = await manager.requestApproval(conversationId, request.toolName, request.toolInput ?? {}, {
1477
1488
  riskLevel: request.riskLevel,
1478
1489
  risk: request.risk,
@@ -1482,8 +1493,9 @@ export class CanonAgent {
1482
1493
  native: request.native,
1483
1494
  toolSummary: request.toolSummary,
1484
1495
  details: request.details,
1485
- ignoreSessionRules: request.ignoreSessionRules,
1486
- allowSessionRule: request.allowSessionRule,
1496
+ ...(responseUserId ? { responseUserId } : {}),
1497
+ ignoreSessionRules: mustIsolateSessionRules ? true : request.ignoreSessionRules,
1498
+ allowSessionRule: mustIsolateSessionRules ? false : request.allowSessionRule,
1487
1499
  });
1488
1500
  throwIfAborted();
1489
1501
  shouldPersistTurnState = false;
@@ -1516,6 +1528,15 @@ export class CanonAgent {
1516
1528
  const expiresAt = new Date(expiresAtMs).toISOString();
1517
1529
  let result = { status: 'timeout', inputId };
1518
1530
  let requestCreated = false;
1531
+ const ownerOnly = request.kind === 'secret'
1532
+ || request.kind === 'sudo'
1533
+ || request.sensitive === true
1534
+ || Boolean(request.questions?.some((question) => question.isSecret));
1535
+ const responseUserId = ownerOnly
1536
+ ? normalizeResponseUserId(agent.ownerId)
1537
+ : normalizeResponseUserId(request.responseUserId)
1538
+ ?? triggeringHumanId
1539
+ ?? normalizeResponseUserId(agent.ownerId);
1519
1540
  shouldPersistTurnState = true;
1520
1541
  try {
1521
1542
  await this.apiClient.createRuntimeInputRequest({
@@ -1523,7 +1544,7 @@ export class CanonAgent {
1523
1544
  inputId,
1524
1545
  kind: request.kind,
1525
1546
  expiresAt: expiresAtMs,
1526
- responseUserId: agent.ownerId,
1547
+ ...(responseUserId ? { responseUserId } : {}),
1527
1548
  title: request.title,
1528
1549
  prompt: request.prompt,
1529
1550
  ...(request.choices ? { choices: request.choices } : {}),
@@ -1621,9 +1642,20 @@ export class CanonAgent {
1621
1642
  const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
1622
1643
  ? explicitExpiresAt
1623
1644
  : Date.now() + timeoutMs;
1645
+ const responseUserId = normalizeResponseUserId(request.responseUserId)
1646
+ ?? triggeringHumanId;
1647
+ const routedRequest = responseUserId
1648
+ ? { ...request, responseUserId }
1649
+ : request;
1624
1650
  // Fire-and-forget: post the durable card and return. The backend treats an
1625
1651
  // action-less card as display (no pending state, no response expected).
1626
- await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
1652
+ await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
1653
+ request: routedRequest,
1654
+ conversationId,
1655
+ cardId,
1656
+ fallbackTurnId: turnId,
1657
+ expiresAtMs,
1658
+ }));
1627
1659
  try {
1628
1660
  await turnOutput.addBlock({
1629
1661
  id: `card:${cardId}`,
@@ -1655,12 +1687,23 @@ export class CanonAgent {
1655
1687
  const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
1656
1688
  ? explicitExpiresAt
1657
1689
  : Date.now() + timeoutMs;
1690
+ const responseUserId = normalizeResponseUserId(request.responseUserId)
1691
+ ?? triggeringHumanId;
1692
+ const routedRequest = responseUserId
1693
+ ? { ...request, responseUserId }
1694
+ : request;
1658
1695
  let result = { status: 'timeout', cardId };
1659
1696
  let requestCreated = false;
1660
1697
  let requestResolved = false;
1661
1698
  shouldPersistTurnState = true;
1662
1699
  try {
1663
- await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({ request, conversationId, cardId, fallbackTurnId: turnId, expiresAtMs }));
1700
+ await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
1701
+ request: routedRequest,
1702
+ conversationId,
1703
+ cardId,
1704
+ fallbackTurnId: turnId,
1705
+ expiresAtMs,
1706
+ }));
1664
1707
  requestCreated = true;
1665
1708
  try {
1666
1709
  await turnOutput.addBlock({
package/dist/types.d.ts CHANGED
@@ -59,6 +59,8 @@ export interface RuntimeApprovalRequest {
59
59
  turnId?: string;
60
60
  native?: ApprovalNativeRequestMetadata;
61
61
  details?: ApprovalRequestDetail[];
62
+ /** Human conversation member who should answer. Defaults to the triggering human. */
63
+ responseUserId?: string;
62
64
  /**
63
65
  * Ignore in-memory session rules for this approval request. Useful when the
64
66
  * action was triggered by someone other than the agent owner.
@@ -80,6 +82,8 @@ export interface RuntimeInputRequest {
80
82
  native?: RuntimeInputNativeMetadata;
81
83
  inputId?: string;
82
84
  timeoutMs?: number;
85
+ /** Human conversation member who should answer. Secret and sudo prompts remain owner-only. */
86
+ responseUserId?: string;
83
87
  }
84
88
  export interface RuntimeInputResult {
85
89
  status: 'submitted' | 'cancelled' | 'timeout';
@@ -91,6 +95,7 @@ export interface RuntimeCardRequest {
91
95
  card: RuntimeCardV1;
92
96
  cardId?: string;
93
97
  expiresAt?: string | number | Date;
98
+ /** Human conversation member who should answer. Defaults to the triggering human. */
94
99
  responseUserId?: string;
95
100
  timeoutMs?: number;
96
101
  runtimeId?: string;
@@ -102,6 +107,8 @@ export interface RuntimeCardResult {
102
107
  cardId: string;
103
108
  actionId?: string;
104
109
  values?: Record<string, unknown>;
110
+ /** Canon-authenticated human who submitted this card response. */
111
+ respondedBy?: string;
105
112
  }
106
113
  export interface MessageHandlerContext {
107
114
  messages: CanonMessage[];
@@ -155,21 +162,21 @@ export interface MessageHandlerContext {
155
162
  /** Runtime turn mode requested by the sender for this inbound turn, if any. */
156
163
  requestedTurnMode: string | null;
157
164
  /**
158
- * Ask the conversation owner to approve a native runtime action. This only
165
+ * Ask the triggering human to approve a native runtime action. This only
159
166
  * renders Canon's inline approval card; runtimes must explicitly wait for
160
167
  * and enforce the returned decision in their own approval hook.
161
168
  */
162
169
  requestApproval: (request: RuntimeApprovalRequest) => Promise<ApprovalResult>;
163
170
  /**
164
- * Ask the agent owner for runtime input such as clarification, sudo, or a
165
- * secret. Sensitive values are returned only to the calling runtime and are
166
- * never persisted in Canon message metadata.
171
+ * Ask the triggering human for runtime input. Secret and sudo prompts remain
172
+ * owner-only. Sensitive values are returned only to the calling runtime and
173
+ * are never persisted in Canon message metadata.
167
174
  */
168
175
  requestRuntimeInput: (request: RuntimeInputRequest) => Promise<RuntimeInputResult>;
169
176
  /**
170
- * Ask the agent owner to review/respond to a generic rich card. The visible
171
- * card document is redacted for presentation; raw response values return
172
- * only to the calling runtime through Canon's control path.
177
+ * Ask the triggering human to review/respond to a generic rich card. The
178
+ * visible card document is redacted for presentation; raw response values
179
+ * return only to the calling runtime through Canon's control path.
173
180
  */
174
181
  requestCard: (request: RuntimeCardRequest) => Promise<RuntimeCardResult>;
175
182
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "5.1.0",
3
+ "version": "5.1.2",
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": "^4.2.0"
31
+ "@canonmsg/core": "^4.3.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"