@canonmsg/agent-sdk 5.0.0 → 5.1.1
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/dist/canon-agent.d.ts +19 -0
- package/dist/canon-agent.js +119 -136
- package/dist/types.d.ts +12 -7
- package/package.json +2 -2
package/dist/canon-agent.d.ts
CHANGED
|
@@ -53,6 +53,15 @@ export declare class CanonAgent {
|
|
|
53
53
|
private approvalManager;
|
|
54
54
|
private approvalManagerAgentId;
|
|
55
55
|
private approvalManagerOwnerId;
|
|
56
|
+
/**
|
|
57
|
+
* Shared poll/timeout/abort engine for interactive runtime input + card
|
|
58
|
+
* requests. The server request is still created inline (host-specific
|
|
59
|
+
* `native`/`responseUserId`), so the descriptors register a no-op `create`
|
|
60
|
+
* but keep the built-in `cancel`: passing the turn's abort `signal` lets the
|
|
61
|
+
* manager fire `consume({ cancel: true })` on interrupt, so the hosts no
|
|
62
|
+
* longer hand-roll a `cancelPendingRequest`.
|
|
63
|
+
*/
|
|
64
|
+
private runtimeRequestManager;
|
|
56
65
|
private cachedConversationIds;
|
|
57
66
|
private running;
|
|
58
67
|
private runtimeHeartbeatTimer;
|
|
@@ -66,6 +75,16 @@ export declare class CanonAgent {
|
|
|
66
75
|
private sseConnectedLogged;
|
|
67
76
|
constructor(options: CanonAgentOptions);
|
|
68
77
|
private ensureApprovalManager;
|
|
78
|
+
/**
|
|
79
|
+
* Shared engine for interactive runtime input + card polling. Unlike approval,
|
|
80
|
+
* these families do not need an owner (the request is created inline with the
|
|
81
|
+
* caller's own `responseUserId` policy), so the manager is always available.
|
|
82
|
+
* The registered descriptors keep the built-in poll/consume/timeout logic and
|
|
83
|
+
* the built-in `cancel` (best-effort `consume({ cancel: true })`), replacing
|
|
84
|
+
* only `create` with a no-op because the request is already created inline.
|
|
85
|
+
* On abort the manager runs that cancel via the passed `signal`.
|
|
86
|
+
*/
|
|
87
|
+
private ensureRuntimeRequestManager;
|
|
69
88
|
private filterApprovalReplyMessages;
|
|
70
89
|
on(event: 'message', handler: MessageHandler): void;
|
|
71
90
|
on(event: 'messageUpdated', handler: MessageUpdatedHandler): void;
|
package/dist/canon-agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApprovalManager, 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, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, renderCanonHostInboundContent, sendMessageWithRetry, } 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, normalizeRuntimeCommandDescriptors, normalizeTurnMetadata, reachOutToCanonContact, resolveCanonReplyContext, resolveMessageActiveSelfContextId, resolveRuntimeProvenance, selectActiveSelfContexts, renderCanonHostInboundContent, sendMessageWithRetry, } 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';
|
|
@@ -10,7 +10,6 @@ const RUNTIME_CONTROL_POLL_INTERVAL_MS = 2_000;
|
|
|
10
10
|
const RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
|
|
11
11
|
const RUNTIME_PRIMITIVE_DEDUPE_MAX = 1_000;
|
|
12
12
|
const DEFAULT_RUNTIME_INPUT_TIMEOUT_MS = 5 * 60_000;
|
|
13
|
-
const RUNTIME_INPUT_POLL_MS = 1_000;
|
|
14
13
|
const RUNTIME_INPUT_ID_PATTERN = /^[A-Za-z0-9_.:-]{1,160}$/;
|
|
15
14
|
const SDK_MESSAGE_ID_READABLE_MAX = 120;
|
|
16
15
|
const SDK_RUNTIME_CAPABILITIES = {
|
|
@@ -168,6 +167,9 @@ function safeRuntimeCardId(value) {
|
|
|
168
167
|
? normalized
|
|
169
168
|
: `card_${randomUUID()}`;
|
|
170
169
|
}
|
|
170
|
+
function normalizeResponseUserId(value) {
|
|
171
|
+
return value?.trim() || undefined;
|
|
172
|
+
}
|
|
171
173
|
function buildSdkMessageId(parts) {
|
|
172
174
|
const raw = parts
|
|
173
175
|
.map((part) => part == null ? '' : String(part))
|
|
@@ -268,6 +270,15 @@ export class CanonAgent {
|
|
|
268
270
|
approvalManager = null;
|
|
269
271
|
approvalManagerAgentId = null;
|
|
270
272
|
approvalManagerOwnerId = null;
|
|
273
|
+
/**
|
|
274
|
+
* Shared poll/timeout/abort engine for interactive runtime input + card
|
|
275
|
+
* requests. The server request is still created inline (host-specific
|
|
276
|
+
* `native`/`responseUserId`), so the descriptors register a no-op `create`
|
|
277
|
+
* but keep the built-in `cancel`: passing the turn's abort `signal` lets the
|
|
278
|
+
* manager fire `consume({ cancel: true })` on interrupt, so the hosts no
|
|
279
|
+
* longer hand-roll a `cancelPendingRequest`.
|
|
280
|
+
*/
|
|
281
|
+
runtimeRequestManager = null;
|
|
271
282
|
cachedConversationIds = [];
|
|
272
283
|
running = false;
|
|
273
284
|
runtimeHeartbeatTimer = null;
|
|
@@ -354,6 +365,33 @@ export class CanonAgent {
|
|
|
354
365
|
this.approvalManagerOwnerId = ownerId;
|
|
355
366
|
return this.approvalManager;
|
|
356
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Shared engine for interactive runtime input + card polling. Unlike approval,
|
|
370
|
+
* these families do not need an owner (the request is created inline with the
|
|
371
|
+
* caller's own `responseUserId` policy), so the manager is always available.
|
|
372
|
+
* The registered descriptors keep the built-in poll/consume/timeout logic and
|
|
373
|
+
* the built-in `cancel` (best-effort `consume({ cancel: true })`), replacing
|
|
374
|
+
* only `create` with a no-op because the request is already created inline.
|
|
375
|
+
* On abort the manager runs that cancel via the passed `signal`.
|
|
376
|
+
*/
|
|
377
|
+
ensureRuntimeRequestManager() {
|
|
378
|
+
if (!this.runtimeRequestManager) {
|
|
379
|
+
const manager = new RuntimeRequestManager(this.apiClient, {
|
|
380
|
+
agentId: this.agentId ?? '',
|
|
381
|
+
ownerId: '',
|
|
382
|
+
});
|
|
383
|
+
manager.register('input', {
|
|
384
|
+
...runtimeInputDescriptor,
|
|
385
|
+
create: async ({ requestId }) => ({ requestId }),
|
|
386
|
+
});
|
|
387
|
+
manager.register('card', {
|
|
388
|
+
...runtimeCardDescriptor,
|
|
389
|
+
create: async ({ requestId }) => ({ requestId }),
|
|
390
|
+
});
|
|
391
|
+
this.runtimeRequestManager = manager;
|
|
392
|
+
}
|
|
393
|
+
return this.runtimeRequestManager;
|
|
394
|
+
}
|
|
357
395
|
filterApprovalReplyMessages(conversationId, messages) {
|
|
358
396
|
const manager = this.ensureApprovalManager();
|
|
359
397
|
if (!manager) {
|
|
@@ -1275,6 +1313,9 @@ export class CanonAgent {
|
|
|
1275
1313
|
}
|
|
1276
1314
|
}
|
|
1277
1315
|
const latestMessage = hydratedMessages[hydratedMessages.length - 1] ?? null;
|
|
1316
|
+
const triggeringHumanId = latestMessage?.senderType === 'human'
|
|
1317
|
+
? normalizeResponseUserId(latestMessage.senderId)
|
|
1318
|
+
: undefined;
|
|
1278
1319
|
let replyContext = latestMessage
|
|
1279
1320
|
? resolveCanonReplyContext({ message: latestMessage, messages: history })
|
|
1280
1321
|
: null;
|
|
@@ -1438,6 +1479,11 @@ export class CanonAgent {
|
|
|
1438
1479
|
await this.typingSignals.clear(conversationId);
|
|
1439
1480
|
}
|
|
1440
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);
|
|
1441
1487
|
const result = await manager.requestApproval(conversationId, request.toolName, request.toolInput ?? {}, {
|
|
1442
1488
|
riskLevel: request.riskLevel,
|
|
1443
1489
|
risk: request.risk,
|
|
@@ -1447,8 +1493,9 @@ export class CanonAgent {
|
|
|
1447
1493
|
native: request.native,
|
|
1448
1494
|
toolSummary: request.toolSummary,
|
|
1449
1495
|
details: request.details,
|
|
1450
|
-
|
|
1451
|
-
|
|
1496
|
+
...(responseUserId ? { responseUserId } : {}),
|
|
1497
|
+
ignoreSessionRules: mustIsolateSessionRules ? true : request.ignoreSessionRules,
|
|
1498
|
+
allowSessionRule: mustIsolateSessionRules ? false : request.allowSessionRule,
|
|
1452
1499
|
});
|
|
1453
1500
|
throwIfAborted();
|
|
1454
1501
|
shouldPersistTurnState = false;
|
|
@@ -1481,20 +1528,15 @@ export class CanonAgent {
|
|
|
1481
1528
|
const expiresAt = new Date(expiresAtMs).toISOString();
|
|
1482
1529
|
let result = { status: 'timeout', inputId };
|
|
1483
1530
|
let requestCreated = false;
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
});
|
|
1494
|
-
requestResolved = true;
|
|
1495
|
-
}
|
|
1496
|
-
catch { }
|
|
1497
|
-
};
|
|
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);
|
|
1498
1540
|
shouldPersistTurnState = true;
|
|
1499
1541
|
try {
|
|
1500
1542
|
await this.apiClient.createRuntimeInputRequest({
|
|
@@ -1502,7 +1544,7 @@ export class CanonAgent {
|
|
|
1502
1544
|
inputId,
|
|
1503
1545
|
kind: request.kind,
|
|
1504
1546
|
expiresAt: expiresAtMs,
|
|
1505
|
-
responseUserId:
|
|
1547
|
+
...(responseUserId ? { responseUserId } : {}),
|
|
1506
1548
|
title: request.title,
|
|
1507
1549
|
prompt: request.prompt,
|
|
1508
1550
|
...(request.choices ? { choices: request.choices } : {}),
|
|
@@ -1529,56 +1571,15 @@ export class CanonAgent {
|
|
|
1529
1571
|
await this.typingSignals.clear(conversationId);
|
|
1530
1572
|
}
|
|
1531
1573
|
catch { }
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
result = {
|
|
1542
|
-
status: 'submitted',
|
|
1543
|
-
value: response.value,
|
|
1544
|
-
answers: response.answers,
|
|
1545
|
-
inputId,
|
|
1546
|
-
};
|
|
1547
|
-
break;
|
|
1548
|
-
}
|
|
1549
|
-
if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1550
|
-
requestResolved = true;
|
|
1551
|
-
result = { status: response.status, inputId };
|
|
1552
|
-
break;
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
catch {
|
|
1556
|
-
// Transient consume failures should not leak sensitive input or
|
|
1557
|
-
// break the runtime; keep waiting until the explicit timeout.
|
|
1558
|
-
}
|
|
1559
|
-
await sleepWithAbort(Math.min(RUNTIME_INPUT_POLL_MS, Math.max(1, expiresAtMs - Date.now())), abortController.signal);
|
|
1560
|
-
}
|
|
1561
|
-
if (!requestResolved) {
|
|
1562
|
-
try {
|
|
1563
|
-
const response = await this.apiClient.consumeRuntimeInputResponse({
|
|
1564
|
-
conversationId,
|
|
1565
|
-
inputId,
|
|
1566
|
-
});
|
|
1567
|
-
if (response.status === 'submitted') {
|
|
1568
|
-
result = {
|
|
1569
|
-
status: 'submitted',
|
|
1570
|
-
value: response.value,
|
|
1571
|
-
answers: response.answers,
|
|
1572
|
-
inputId,
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1576
|
-
result = { status: response.status, inputId };
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
catch { }
|
|
1580
|
-
requestResolved = true;
|
|
1581
|
-
}
|
|
1574
|
+
// The shared engine owns the poll-with-abort loop + post-deadline
|
|
1575
|
+
// consume; the request was already created above, so the descriptor's
|
|
1576
|
+
// create is a no-op. On abort the manager fires the descriptor's
|
|
1577
|
+
// built-in cancel (consume({ cancel: true })) via the passed signal.
|
|
1578
|
+
result = await this.ensureRuntimeRequestManager().request('input', conversationId, { kind: request.kind }, {
|
|
1579
|
+
requestId: inputId,
|
|
1580
|
+
expiresAt: expiresAtMs,
|
|
1581
|
+
signal: abortController.signal,
|
|
1582
|
+
});
|
|
1582
1583
|
const outcome = buildRuntimeInputOutcome(inputId, result.status, {
|
|
1583
1584
|
kind: request.kind,
|
|
1584
1585
|
reason: result.status,
|
|
@@ -1607,8 +1608,16 @@ export class CanonAgent {
|
|
|
1607
1608
|
return result;
|
|
1608
1609
|
}
|
|
1609
1610
|
catch (error) {
|
|
1610
|
-
await cancelPendingRequest();
|
|
1611
1611
|
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1612
|
+
if (requestCreated) {
|
|
1613
|
+
// Abort landing before the manager wired its cancel (e.g. during
|
|
1614
|
+
// the pre-request writeTurn/typing round-trips) leaves the
|
|
1615
|
+
// server-side request pending + responder-actionable; cancel it so
|
|
1616
|
+
// a late (possibly sensitive) answer can't resolve a dead request.
|
|
1617
|
+
await this.apiClient
|
|
1618
|
+
.consumeRuntimeInputResponse({ conversationId, inputId, cancel: true })
|
|
1619
|
+
.catch(() => { });
|
|
1620
|
+
}
|
|
1612
1621
|
throw error;
|
|
1613
1622
|
}
|
|
1614
1623
|
if (!requestCreated) {
|
|
@@ -1633,9 +1642,20 @@ export class CanonAgent {
|
|
|
1633
1642
|
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1634
1643
|
? explicitExpiresAt
|
|
1635
1644
|
: Date.now() + timeoutMs;
|
|
1645
|
+
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1646
|
+
?? triggeringHumanId;
|
|
1647
|
+
const routedRequest = responseUserId
|
|
1648
|
+
? { ...request, responseUserId }
|
|
1649
|
+
: request;
|
|
1636
1650
|
// Fire-and-forget: post the durable card and return. The backend treats an
|
|
1637
1651
|
// action-less card as display (no pending state, no response expected).
|
|
1638
|
-
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
1652
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
1653
|
+
request: routedRequest,
|
|
1654
|
+
conversationId,
|
|
1655
|
+
cardId,
|
|
1656
|
+
fallbackTurnId: turnId,
|
|
1657
|
+
expiresAtMs,
|
|
1658
|
+
}));
|
|
1639
1659
|
try {
|
|
1640
1660
|
await turnOutput.addBlock({
|
|
1641
1661
|
id: `card:${cardId}`,
|
|
@@ -1667,25 +1687,23 @@ export class CanonAgent {
|
|
|
1667
1687
|
const expiresAtMs = explicitExpiresAt && Number.isFinite(explicitExpiresAt)
|
|
1668
1688
|
? explicitExpiresAt
|
|
1669
1689
|
: Date.now() + timeoutMs;
|
|
1690
|
+
const responseUserId = normalizeResponseUserId(request.responseUserId)
|
|
1691
|
+
?? triggeringHumanId;
|
|
1692
|
+
const routedRequest = responseUserId
|
|
1693
|
+
? { ...request, responseUserId }
|
|
1694
|
+
: request;
|
|
1670
1695
|
let result = { status: 'timeout', cardId };
|
|
1671
1696
|
let requestCreated = false;
|
|
1672
1697
|
let requestResolved = false;
|
|
1673
|
-
const cancelPendingRequest = async () => {
|
|
1674
|
-
if (!requestCreated || requestResolved)
|
|
1675
|
-
return;
|
|
1676
|
-
try {
|
|
1677
|
-
await this.apiClient.consumeRuntimeCardResponse({
|
|
1678
|
-
conversationId,
|
|
1679
|
-
cardId,
|
|
1680
|
-
cancel: true,
|
|
1681
|
-
});
|
|
1682
|
-
requestResolved = true;
|
|
1683
|
-
}
|
|
1684
|
-
catch { }
|
|
1685
|
-
};
|
|
1686
1698
|
shouldPersistTurnState = true;
|
|
1687
1699
|
try {
|
|
1688
|
-
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
1700
|
+
await this.apiClient.createRuntimeCardRequest(buildRuntimeCardCreateArgs({
|
|
1701
|
+
request: routedRequest,
|
|
1702
|
+
conversationId,
|
|
1703
|
+
cardId,
|
|
1704
|
+
fallbackTurnId: turnId,
|
|
1705
|
+
expiresAtMs,
|
|
1706
|
+
}));
|
|
1689
1707
|
requestCreated = true;
|
|
1690
1708
|
try {
|
|
1691
1709
|
await turnOutput.addBlock({
|
|
@@ -1703,56 +1721,16 @@ export class CanonAgent {
|
|
|
1703
1721
|
await this.typingSignals.clear(conversationId);
|
|
1704
1722
|
}
|
|
1705
1723
|
catch { }
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
status: 'submitted',
|
|
1717
|
-
cardId,
|
|
1718
|
-
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1719
|
-
...(response.values ? { values: response.values } : {}),
|
|
1720
|
-
};
|
|
1721
|
-
break;
|
|
1722
|
-
}
|
|
1723
|
-
if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1724
|
-
requestResolved = true;
|
|
1725
|
-
result = { status: response.status, cardId };
|
|
1726
|
-
break;
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
catch {
|
|
1730
|
-
// Keep waiting; the control path is the authoritative source and
|
|
1731
|
-
// transient consume failures should not leak response values.
|
|
1732
|
-
}
|
|
1733
|
-
await sleepWithAbort(Math.min(RUNTIME_INPUT_POLL_MS, Math.max(1, expiresAtMs - Date.now())), abortController.signal);
|
|
1734
|
-
}
|
|
1735
|
-
if (!requestResolved) {
|
|
1736
|
-
try {
|
|
1737
|
-
const response = await this.apiClient.consumeRuntimeCardResponse({
|
|
1738
|
-
conversationId,
|
|
1739
|
-
cardId,
|
|
1740
|
-
});
|
|
1741
|
-
if (response.status === 'submitted') {
|
|
1742
|
-
result = {
|
|
1743
|
-
status: 'submitted',
|
|
1744
|
-
cardId,
|
|
1745
|
-
...(response.actionId ? { actionId: response.actionId } : {}),
|
|
1746
|
-
...(response.values ? { values: response.values } : {}),
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
else if (response.status === 'cancelled' || response.status === 'timeout') {
|
|
1750
|
-
result = { status: response.status, cardId };
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
catch { }
|
|
1754
|
-
requestResolved = true;
|
|
1755
|
-
}
|
|
1724
|
+
// The shared engine owns the poll-with-abort loop + post-deadline
|
|
1725
|
+
// consume; the request was already created above, so the descriptor's
|
|
1726
|
+
// create is a no-op. On abort the manager fires the descriptor's
|
|
1727
|
+
// built-in cancel (consume({ cancel: true })) via the passed signal.
|
|
1728
|
+
result = await this.ensureRuntimeRequestManager().request('card', conversationId, { card: request.card }, {
|
|
1729
|
+
requestId: cardId,
|
|
1730
|
+
expiresAt: expiresAtMs,
|
|
1731
|
+
signal: abortController.signal,
|
|
1732
|
+
});
|
|
1733
|
+
requestResolved = true;
|
|
1756
1734
|
// The interactive path never yields 'displayed' (that returns early via
|
|
1757
1735
|
// sendCard); narrow for buildRuntimeCardOutcome's resolution status.
|
|
1758
1736
|
const resolutionStatus = result.status === 'displayed' ? 'timeout' : result.status;
|
|
@@ -1784,9 +1762,14 @@ export class CanonAgent {
|
|
|
1784
1762
|
}
|
|
1785
1763
|
catch (error) {
|
|
1786
1764
|
const shouldSendInterruptedOutcome = requestCreated && !requestResolved;
|
|
1787
|
-
await cancelPendingRequest();
|
|
1788
1765
|
if (abortController.signal.aborted || isAbortLikeError(error)) {
|
|
1789
1766
|
if (shouldSendInterruptedOutcome) {
|
|
1767
|
+
// Cancel the server-side card if the abort landed before the
|
|
1768
|
+
// manager wired its cancel, so a late action can't resolve a dead
|
|
1769
|
+
// request (mirrors the runtime-input path).
|
|
1770
|
+
await this.apiClient
|
|
1771
|
+
.consumeRuntimeCardResponse({ conversationId, cardId, cancel: true })
|
|
1772
|
+
.catch(() => { });
|
|
1790
1773
|
const outcome = buildRuntimeCardOutcome(cardId, 'cancelled', { reason: 'interrupted' });
|
|
1791
1774
|
await sendDurableMessage(outcome.text, {
|
|
1792
1775
|
metadata: {
|
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;
|
|
@@ -155,21 +160,21 @@ export interface MessageHandlerContext {
|
|
|
155
160
|
/** Runtime turn mode requested by the sender for this inbound turn, if any. */
|
|
156
161
|
requestedTurnMode: string | null;
|
|
157
162
|
/**
|
|
158
|
-
* Ask the
|
|
163
|
+
* Ask the triggering human to approve a native runtime action. This only
|
|
159
164
|
* renders Canon's inline approval card; runtimes must explicitly wait for
|
|
160
165
|
* and enforce the returned decision in their own approval hook.
|
|
161
166
|
*/
|
|
162
167
|
requestApproval: (request: RuntimeApprovalRequest) => Promise<ApprovalResult>;
|
|
163
168
|
/**
|
|
164
|
-
* Ask the
|
|
165
|
-
*
|
|
166
|
-
* never persisted in Canon message metadata.
|
|
169
|
+
* Ask the triggering human for runtime input. Secret and sudo prompts remain
|
|
170
|
+
* owner-only. Sensitive values are returned only to the calling runtime and
|
|
171
|
+
* are never persisted in Canon message metadata.
|
|
167
172
|
*/
|
|
168
173
|
requestRuntimeInput: (request: RuntimeInputRequest) => Promise<RuntimeInputResult>;
|
|
169
174
|
/**
|
|
170
|
-
* Ask the
|
|
171
|
-
* card document is redacted for presentation; raw response values
|
|
172
|
-
* only to the calling runtime through Canon's control path.
|
|
175
|
+
* Ask the triggering human to review/respond to a generic rich card. The
|
|
176
|
+
* visible card document is redacted for presentation; raw response values
|
|
177
|
+
* return only to the calling runtime through Canon's control path.
|
|
173
178
|
*/
|
|
174
179
|
requestCard: (request: RuntimeCardRequest) => Promise<RuntimeCardResult>;
|
|
175
180
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/agent-sdk",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
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.
|
|
31
|
+
"@canonmsg/core": "^4.2.2"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|