@vellumai/assistant 0.3.3 → 0.3.5
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/Dockerfile +2 -0
- package/README.md +45 -18
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +13 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +100 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +391 -1
- package/src/__tests__/call-routes-http.test.ts +27 -2
- package/src/__tests__/channel-approval-routes.test.ts +397 -135
- package/src/__tests__/channel-approvals.test.ts +99 -3
- package/src/__tests__/channel-delivery-store.test.ts +30 -4
- package/src/__tests__/channel-guardian.test.ts +261 -22
- package/src/__tests__/channel-readiness-service.test.ts +257 -0
- package/src/__tests__/config-schema.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-lifecycle.test.ts +636 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +19 -13
- package/src/__tests__/handlers-twilio-config.test.ts +480 -0
- package/src/__tests__/ipc-snapshot.test.ts +63 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +4 -0
- package/src/__tests__/run-orchestrator.test.ts +22 -0
- package/src/__tests__/secret-scanner.test.ts +223 -0
- package/src/__tests__/session-runtime-assembly.test.ts +85 -1
- package/src/__tests__/shell-parser-property.test.ts +357 -2
- package/src/__tests__/sms-messaging-provider.test.ts +125 -0
- package/src/__tests__/system-prompt.test.ts +25 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +34 -1
- package/src/__tests__/twilio-routes.test.ts +39 -3
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +68 -0
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/calls/call-domain.ts +8 -5
- package/src/calls/call-orchestrator.ts +85 -22
- package/src/calls/twilio-config.ts +17 -11
- package/src/calls/twilio-rest.ts +276 -0
- package/src/calls/twilio-routes.ts +39 -1
- package/src/cli/map.ts +6 -0
- package/src/commands/__tests__/cc-command-registry.test.ts +67 -0
- package/src/commands/cc-command-registry.ts +14 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +10 -3
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +199 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +320 -0
- package/src/config/bundled-skills/media-processing/services/capability-registry.ts +137 -0
- package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +280 -0
- package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +144 -0
- package/src/config/bundled-skills/media-processing/services/feedback-store.ts +136 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +261 -0
- package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +95 -0
- package/src/config/bundled-skills/media-processing/services/timeline-service.ts +267 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +301 -0
- package/src/config/bundled-skills/media-processing/tools/detect-events.ts +110 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +190 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +166 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +300 -0
- package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +235 -0
- package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +142 -0
- package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +150 -0
- package/src/config/bundled-skills/messaging/SKILL.md +24 -5
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/defaults.ts +2 -1
- package/src/config/schema.ts +9 -3
- package/src/config/skills.ts +5 -32
- package/src/config/system-prompt.ts +40 -0
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/user-reference.ts +29 -0
- package/src/config/vellum-skills/catalog.json +58 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +3 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +118 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +76 -6
- package/src/daemon/auth-manager.ts +103 -0
- package/src/daemon/computer-use-session.ts +8 -1
- package/src/daemon/config-watcher.ts +253 -0
- package/src/daemon/handlers/config.ts +819 -22
- package/src/daemon/handlers/dictation.ts +182 -0
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +2 -0
- package/src/daemon/handlers/sessions.ts +2 -0
- package/src/daemon/handlers/shared.ts +3 -0
- package/src/daemon/handlers/skills.ts +6 -7
- package/src/daemon/handlers/work-items.ts +15 -7
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +114 -4
- package/src/daemon/ipc-handler.ts +87 -0
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/ride-shotgun-handler.ts +11 -1
- package/src/daemon/server.ts +111 -504
- package/src/daemon/session-agent-loop.ts +10 -15
- package/src/daemon/session-runtime-assembly.ts +115 -44
- package/src/daemon/session-tool-setup.ts +2 -0
- package/src/daemon/session.ts +19 -2
- package/src/inbound/public-ingress-urls.ts +3 -3
- package/src/memory/channel-guardian-store.ts +2 -1
- package/src/memory/db-connection.ts +28 -0
- package/src/memory/db-init.ts +1163 -0
- package/src/memory/db.ts +2 -2007
- package/src/memory/embedding-backend.ts +79 -11
- package/src/memory/indexer.ts +2 -0
- package/src/memory/job-handlers/media-processing.ts +100 -0
- package/src/memory/job-utils.ts +64 -4
- package/src/memory/jobs-store.ts +2 -1
- package/src/memory/jobs-worker.ts +11 -1
- package/src/memory/media-store.ts +759 -0
- package/src/memory/recall-cache.ts +107 -0
- package/src/memory/retriever.ts +36 -2
- package/src/memory/schema-migration.ts +984 -0
- package/src/memory/schema.ts +99 -0
- package/src/memory/search/entity.ts +208 -25
- package/src/memory/search/ranking.ts +6 -1
- package/src/memory/search/types.ts +26 -0
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +204 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/permissions/checker.ts +16 -2
- package/src/permissions/prompter.ts +14 -3
- package/src/permissions/trust-store.ts +7 -0
- package/src/runtime/approval-message-composer.ts +143 -0
- package/src/runtime/channel-approvals.ts +29 -7
- package/src/runtime/channel-guardian-service.ts +44 -18
- package/src/runtime/channel-readiness-service.ts +292 -0
- package/src/runtime/channel-readiness-types.ts +29 -0
- package/src/runtime/gateway-client.ts +2 -1
- package/src/runtime/http-server.ts +65 -28
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-routes.ts +237 -103
- package/src/runtime/routes/run-routes.ts +7 -1
- package/src/runtime/run-orchestrator.ts +43 -3
- package/src/security/secret-scanner.ts +218 -0
- package/src/skills/frontmatter.ts +63 -0
- package/src/skills/slash-commands.ts +23 -0
- package/src/skills/vellum-catalog-remote.ts +107 -0
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/browser/auto-navigate.ts +132 -24
- package/src/tools/browser/browser-manager.ts +67 -61
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/claude-code/claude-code.ts +55 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +10 -2
- package/src/tools/network/web-search.ts +1 -1
- package/src/tools/skills/vellum-catalog.ts +61 -156
- package/src/tools/terminal/parser.ts +21 -5
- package/src/tools/types.ts +2 -0
- package/src/twitter/router.ts +1 -1
- package/src/util/platform.ts +43 -1
- package/src/util/retry.ts +4 -4
|
@@ -44,6 +44,8 @@ import type {
|
|
|
44
44
|
MessageProcessor,
|
|
45
45
|
RuntimeAttachmentMetadata,
|
|
46
46
|
} from '../http-types.js';
|
|
47
|
+
import type { GuardianRuntimeContext } from '../../daemon/session-runtime-assembly.js';
|
|
48
|
+
import { composeApprovalMessage } from '../approval-message-composer.js';
|
|
47
49
|
|
|
48
50
|
const log = getLogger('runtime-http');
|
|
49
51
|
|
|
@@ -110,6 +112,19 @@ export interface GuardianContext {
|
|
|
110
112
|
denialReason?: DenialReason;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
function toGuardianRuntimeContext(sourceChannel: string, ctx: GuardianContext): GuardianRuntimeContext {
|
|
116
|
+
return {
|
|
117
|
+
sourceChannel,
|
|
118
|
+
actorRole: ctx.actorRole,
|
|
119
|
+
guardianChatId: ctx.guardianChatId,
|
|
120
|
+
guardianExternalUserId: ctx.guardianExternalUserId,
|
|
121
|
+
requesterIdentifier: ctx.requesterIdentifier,
|
|
122
|
+
requesterExternalUserId: ctx.requesterExternalUserId,
|
|
123
|
+
requesterChatId: ctx.requesterChatId,
|
|
124
|
+
denialReason: ctx.denialReason,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
/** Guardian approval request expiry (30 minutes). */
|
|
114
129
|
const GUARDIAN_APPROVAL_TTL_MS = 30 * 60 * 1000;
|
|
115
130
|
|
|
@@ -127,12 +142,21 @@ function effectivePromptText(
|
|
|
127
142
|
return plainTextFallback;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Build contextual deny guidance for guardian-gated auto-deny paths.
|
|
147
|
+
* This is passed through the confirmation pipeline so the assistant can
|
|
148
|
+
* produce a single, user-facing message with next steps.
|
|
149
|
+
*/
|
|
150
|
+
function buildGuardianDenyContext(
|
|
151
|
+
toolName: string,
|
|
152
|
+
denialReason: DenialReason,
|
|
153
|
+
sourceChannel: string,
|
|
154
|
+
): string {
|
|
155
|
+
if (denialReason === 'no_identity') {
|
|
156
|
+
return `Permission denied: ${composeApprovalMessage({ scenario: 'guardian_deny_no_identity', toolName, channel: sourceChannel })} Do not retry yet. Ask the user to message from a verifiable direct account/chat, and then retry after identity is available.`;
|
|
157
|
+
}
|
|
133
158
|
|
|
134
|
-
|
|
135
|
-
return process.env.CHANNEL_APPROVALS_ENABLED === 'true';
|
|
159
|
+
return `Permission denied: ${composeApprovalMessage({ scenario: 'guardian_deny_no_binding', toolName, channel: sourceChannel })} Do not retry yet. Offer to set up guardian verification. The setup flow will provide a verification token to send as /guardian_verify <token>.`;
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
// ---------------------------------------------------------------------------
|
|
@@ -169,13 +193,22 @@ export async function handleDeleteConversation(req: Request, assistantId: string
|
|
|
169
193
|
return Response.json({ error: 'externalChatId is required' }, { status: 400 });
|
|
170
194
|
}
|
|
171
195
|
|
|
172
|
-
// Delete
|
|
173
|
-
//
|
|
196
|
+
// Delete the assistant-scoped key unconditionally. The legacy key is
|
|
197
|
+
// canonical for the self assistant and must not be deleted from non-self
|
|
198
|
+
// routes, otherwise a non-self reset can accidentally reset self state.
|
|
174
199
|
const legacyKey = `${sourceChannel}:${externalChatId}`;
|
|
175
200
|
const scopedKey = `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
|
|
176
|
-
deleteConversationKey(legacyKey);
|
|
177
201
|
deleteConversationKey(scopedKey);
|
|
178
|
-
|
|
202
|
+
if (assistantId === 'self') {
|
|
203
|
+
deleteConversationKey(legacyKey);
|
|
204
|
+
}
|
|
205
|
+
// external_conversation_bindings is currently assistant-agnostic
|
|
206
|
+
// (unique by sourceChannel + externalChatId). Restrict mutations to the
|
|
207
|
+
// canonical self-assistant route so multi-assistant legacy routes do not
|
|
208
|
+
// clobber each other's bindings.
|
|
209
|
+
if (assistantId === 'self') {
|
|
210
|
+
externalConversationStore.deleteBindingByChannelChat(sourceChannel, externalChatId);
|
|
211
|
+
}
|
|
179
212
|
|
|
180
213
|
return Response.json({ ok: true });
|
|
181
214
|
}
|
|
@@ -338,15 +371,19 @@ export async function handleChannelInbound(
|
|
|
338
371
|
{ sourceMessageId, assistantId },
|
|
339
372
|
);
|
|
340
373
|
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
374
|
+
// external_conversation_bindings is assistant-agnostic. Restrict writes to
|
|
375
|
+
// self so assistant-scoped legacy routes do not overwrite each other's
|
|
376
|
+
// channel binding metadata for the same chat.
|
|
377
|
+
if (assistantId === 'self') {
|
|
378
|
+
externalConversationStore.upsertBinding({
|
|
379
|
+
conversationId: result.conversationId,
|
|
380
|
+
sourceChannel,
|
|
381
|
+
externalChatId,
|
|
382
|
+
externalUserId: body.senderExternalUserId ?? null,
|
|
383
|
+
displayName: body.senderName ?? null,
|
|
384
|
+
username: body.senderUsername ?? null,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
350
387
|
|
|
351
388
|
const metadataHintsRaw = sourceMetadata?.hints;
|
|
352
389
|
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
@@ -374,16 +411,19 @@ export async function handleChannelInbound(
|
|
|
374
411
|
token,
|
|
375
412
|
body.senderExternalUserId,
|
|
376
413
|
externalChatId,
|
|
414
|
+
body.senderUsername,
|
|
415
|
+
body.senderName,
|
|
377
416
|
);
|
|
378
417
|
|
|
379
418
|
const replyText = verifyResult.success
|
|
380
|
-
?
|
|
381
|
-
:
|
|
419
|
+
? composeApprovalMessage({ scenario: 'guardian_verify_success' })
|
|
420
|
+
: verifyResult.reason;
|
|
382
421
|
|
|
383
422
|
try {
|
|
384
423
|
await deliverChannelReply(replyCallbackUrl, {
|
|
385
424
|
chatId: externalChatId,
|
|
386
425
|
text: replyText,
|
|
426
|
+
assistantId,
|
|
387
427
|
}, bearerToken);
|
|
388
428
|
} catch (err) {
|
|
389
429
|
log.error({ err, externalChatId }, 'Failed to deliver guardian verification reply');
|
|
@@ -403,18 +443,26 @@ export async function handleChannelInbound(
|
|
|
403
443
|
// When a guardian binding exists, non-guardian actors get stricter
|
|
404
444
|
// side-effect controls and their approvals route to the guardian's chat.
|
|
405
445
|
//
|
|
406
|
-
// Guardian
|
|
407
|
-
|
|
408
|
-
// actor-role resolution and fail-closed denial are always active.
|
|
409
|
-
let guardianCtx: GuardianContext = { actorRole: 'guardian' };
|
|
446
|
+
// Guardian actor-role resolution always runs.
|
|
447
|
+
let guardianCtx: GuardianContext;
|
|
410
448
|
if (body.senderExternalUserId) {
|
|
449
|
+
const requesterLabel = body.senderUsername
|
|
450
|
+
? `@${body.senderUsername}`
|
|
451
|
+
: body.senderExternalUserId;
|
|
411
452
|
const senderIsGuardian = isGuardian(assistantId, sourceChannel, body.senderExternalUserId);
|
|
412
|
-
if (
|
|
453
|
+
if (senderIsGuardian) {
|
|
454
|
+
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
455
|
+
guardianCtx = {
|
|
456
|
+
actorRole: 'guardian',
|
|
457
|
+
guardianChatId: binding?.guardianDeliveryChatId ?? externalChatId,
|
|
458
|
+
guardianExternalUserId: binding?.guardianExternalUserId ?? body.senderExternalUserId,
|
|
459
|
+
requesterIdentifier: requesterLabel,
|
|
460
|
+
requesterExternalUserId: body.senderExternalUserId,
|
|
461
|
+
requesterChatId: externalChatId,
|
|
462
|
+
};
|
|
463
|
+
} else {
|
|
413
464
|
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
414
465
|
if (binding) {
|
|
415
|
-
const requesterLabel = body.senderUsername
|
|
416
|
-
? `@${body.senderUsername}`
|
|
417
|
-
: body.senderExternalUserId;
|
|
418
466
|
guardianCtx = {
|
|
419
467
|
actorRole: 'non-guardian',
|
|
420
468
|
guardianChatId: binding.guardianDeliveryChatId,
|
|
@@ -429,29 +477,28 @@ export async function handleChannelInbound(
|
|
|
429
477
|
guardianCtx = {
|
|
430
478
|
actorRole: 'unverified_channel',
|
|
431
479
|
denialReason: 'no_binding',
|
|
480
|
+
requesterIdentifier: requesterLabel,
|
|
432
481
|
requesterExternalUserId: body.senderExternalUserId,
|
|
433
482
|
requesterChatId: externalChatId,
|
|
434
483
|
};
|
|
435
484
|
}
|
|
436
485
|
}
|
|
437
486
|
} else {
|
|
438
|
-
// No sender identity available —
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
};
|
|
449
|
-
}
|
|
487
|
+
// No sender identity available — treat as unverified and fail closed.
|
|
488
|
+
// Multi-actor channels must not grant default guardian permissions when
|
|
489
|
+
// the inbound actor cannot be identified.
|
|
490
|
+
guardianCtx = {
|
|
491
|
+
actorRole: 'unverified_channel',
|
|
492
|
+
denialReason: 'no_identity',
|
|
493
|
+
requesterIdentifier: body.senderUsername ? `@${body.senderUsername}` : undefined,
|
|
494
|
+
requesterExternalUserId: undefined,
|
|
495
|
+
requesterChatId: externalChatId,
|
|
496
|
+
};
|
|
450
497
|
}
|
|
451
498
|
|
|
452
|
-
// ── Approval interception
|
|
499
|
+
// ── Approval interception ──
|
|
500
|
+
// Keep this active whenever orchestrator + callback context are available.
|
|
453
501
|
if (
|
|
454
|
-
isChannelApprovalsEnabled() &&
|
|
455
502
|
runOrchestrator &&
|
|
456
503
|
replyCallbackUrl &&
|
|
457
504
|
!result.duplicate
|
|
@@ -506,7 +553,9 @@ export async function handleChannelInbound(
|
|
|
506
553
|
senderName: body.senderName,
|
|
507
554
|
senderExternalUserId: body.senderExternalUserId,
|
|
508
555
|
senderUsername: body.senderUsername,
|
|
556
|
+
guardianCtx,
|
|
509
557
|
replyCallbackUrl,
|
|
558
|
+
assistantId,
|
|
510
559
|
});
|
|
511
560
|
|
|
512
561
|
const contentToCheck = content ?? '';
|
|
@@ -522,13 +571,15 @@ export async function handleChannelInbound(
|
|
|
522
571
|
throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
|
|
523
572
|
}
|
|
524
573
|
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
const useApprovalPath =
|
|
529
|
-
|
|
574
|
+
// Use the approval-aware orchestrator path whenever orchestration and a
|
|
575
|
+
// callback delivery target are available. This keeps approval handling
|
|
576
|
+
// consistent across all channels and avoids silent prompt timeouts.
|
|
577
|
+
const useApprovalPath = Boolean(
|
|
578
|
+
runOrchestrator &&
|
|
579
|
+
replyCallbackUrl,
|
|
580
|
+
);
|
|
530
581
|
|
|
531
|
-
if (useApprovalPath) {
|
|
582
|
+
if (useApprovalPath && runOrchestrator && replyCallbackUrl) {
|
|
532
583
|
processChannelMessageWithApprovals({
|
|
533
584
|
orchestrator: runOrchestrator,
|
|
534
585
|
conversationId: result.conversationId,
|
|
@@ -541,6 +592,8 @@ export async function handleChannelInbound(
|
|
|
541
592
|
bearerToken,
|
|
542
593
|
guardianCtx,
|
|
543
594
|
assistantId,
|
|
595
|
+
metadataHints,
|
|
596
|
+
metadataUxBrief,
|
|
544
597
|
});
|
|
545
598
|
} else {
|
|
546
599
|
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
@@ -553,10 +606,12 @@ export async function handleChannelInbound(
|
|
|
553
606
|
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
554
607
|
sourceChannel,
|
|
555
608
|
externalChatId,
|
|
609
|
+
guardianCtx,
|
|
556
610
|
metadataHints,
|
|
557
611
|
metadataUxBrief,
|
|
558
612
|
replyCallbackUrl,
|
|
559
613
|
bearerToken,
|
|
614
|
+
assistantId,
|
|
560
615
|
});
|
|
561
616
|
}
|
|
562
617
|
}
|
|
@@ -576,10 +631,12 @@ interface BackgroundProcessingParams {
|
|
|
576
631
|
attachmentIds?: string[];
|
|
577
632
|
sourceChannel: string;
|
|
578
633
|
externalChatId: string;
|
|
634
|
+
guardianCtx: GuardianContext;
|
|
579
635
|
metadataHints: string[];
|
|
580
636
|
metadataUxBrief?: string;
|
|
581
637
|
replyCallbackUrl?: string;
|
|
582
638
|
bearerToken?: string;
|
|
639
|
+
assistantId?: string;
|
|
583
640
|
}
|
|
584
641
|
|
|
585
642
|
function processChannelMessageInBackground(params: BackgroundProcessingParams): void {
|
|
@@ -591,10 +648,12 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
591
648
|
attachmentIds,
|
|
592
649
|
sourceChannel,
|
|
593
650
|
externalChatId,
|
|
651
|
+
guardianCtx,
|
|
594
652
|
metadataHints,
|
|
595
653
|
metadataUxBrief,
|
|
596
654
|
replyCallbackUrl,
|
|
597
655
|
bearerToken,
|
|
656
|
+
assistantId,
|
|
598
657
|
} = params;
|
|
599
658
|
|
|
600
659
|
(async () => {
|
|
@@ -609,6 +668,8 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
609
668
|
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
610
669
|
uxBrief: metadataUxBrief,
|
|
611
670
|
},
|
|
671
|
+
assistantId,
|
|
672
|
+
guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
|
|
612
673
|
},
|
|
613
674
|
sourceChannel,
|
|
614
675
|
);
|
|
@@ -616,7 +677,13 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
616
677
|
channelDeliveryStore.markProcessed(eventId);
|
|
617
678
|
|
|
618
679
|
if (replyCallbackUrl) {
|
|
619
|
-
await deliverReplyViaCallback(
|
|
680
|
+
await deliverReplyViaCallback(
|
|
681
|
+
conversationId,
|
|
682
|
+
externalChatId,
|
|
683
|
+
replyCallbackUrl,
|
|
684
|
+
bearerToken,
|
|
685
|
+
assistantId,
|
|
686
|
+
);
|
|
620
687
|
}
|
|
621
688
|
} catch (err) {
|
|
622
689
|
log.error({ err, conversationId }, 'Background channel message processing failed');
|
|
@@ -665,6 +732,8 @@ interface ApprovalProcessingParams {
|
|
|
665
732
|
bearerToken?: string;
|
|
666
733
|
guardianCtx: GuardianContext;
|
|
667
734
|
assistantId: string;
|
|
735
|
+
metadataHints: string[];
|
|
736
|
+
metadataUxBrief?: string;
|
|
668
737
|
}
|
|
669
738
|
|
|
670
739
|
/**
|
|
@@ -692,6 +761,8 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
692
761
|
bearerToken,
|
|
693
762
|
guardianCtx,
|
|
694
763
|
assistantId,
|
|
764
|
+
metadataHints,
|
|
765
|
+
metadataUxBrief,
|
|
695
766
|
} = params;
|
|
696
767
|
|
|
697
768
|
const isNonGuardian = guardianCtx.actorRole === 'non-guardian';
|
|
@@ -706,6 +777,10 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
706
777
|
{
|
|
707
778
|
...((isNonGuardian || isUnverifiedChannel) ? { forceStrictSideEffects: true } : {}),
|
|
708
779
|
sourceChannel,
|
|
780
|
+
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
781
|
+
uxBrief: metadataUxBrief,
|
|
782
|
+
assistantId,
|
|
783
|
+
guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
|
|
709
784
|
},
|
|
710
785
|
);
|
|
711
786
|
|
|
@@ -714,6 +789,14 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
714
789
|
const startTime = Date.now();
|
|
715
790
|
const pollMaxWait = getEffectivePollMaxWait();
|
|
716
791
|
let lastStatus = run.status;
|
|
792
|
+
// Track whether a post-decision delivery path is guaranteed for this
|
|
793
|
+
// run. Set to true only when the approval prompt is successfully
|
|
794
|
+
// delivered (guardian or standard path), meaning
|
|
795
|
+
// handleApprovalInterception will schedule schedulePostDecisionDelivery
|
|
796
|
+
// when a decision arrives. Auto-deny paths (unverified channel, prompt
|
|
797
|
+
// delivery failures) do NOT set this flag because no post-decision
|
|
798
|
+
// delivery is scheduled in those cases.
|
|
799
|
+
let hasPostDecisionDelivery = false;
|
|
717
800
|
|
|
718
801
|
while (Date.now() - startTime < pollMaxWait) {
|
|
719
802
|
await new Promise((resolve) => setTimeout(resolve, RUN_POLL_INTERVAL_MS));
|
|
@@ -726,18 +809,16 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
726
809
|
|
|
727
810
|
if (isUnverifiedChannel && pending.length > 0) {
|
|
728
811
|
// Unverified channel — auto-deny the sensitive action (fail-closed).
|
|
729
|
-
handleChannelDecision(
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
log.error({ err, runId: run.id }, 'Failed to deliver unverified-channel denial notice');
|
|
740
|
-
}
|
|
812
|
+
handleChannelDecision(
|
|
813
|
+
conversationId,
|
|
814
|
+
{ action: 'reject', source: 'plain_text' },
|
|
815
|
+
orchestrator,
|
|
816
|
+
buildGuardianDenyContext(
|
|
817
|
+
pending[0].toolName,
|
|
818
|
+
guardianCtx.denialReason ?? 'no_binding',
|
|
819
|
+
sourceChannel,
|
|
820
|
+
),
|
|
821
|
+
);
|
|
741
822
|
} else if (isNonGuardian && guardianCtx.guardianChatId && pending.length > 0) {
|
|
742
823
|
// Non-guardian actor: route the approval prompt to the guardian's chat
|
|
743
824
|
const guardianPrompt = buildGuardianApprovalPrompt(
|
|
@@ -774,9 +855,11 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
774
855
|
guardianCtx.guardianChatId,
|
|
775
856
|
guardianText,
|
|
776
857
|
uiMetadata,
|
|
858
|
+
assistantId,
|
|
777
859
|
bearerToken,
|
|
778
860
|
);
|
|
779
861
|
guardianNotified = true;
|
|
862
|
+
hasPostDecisionDelivery = true;
|
|
780
863
|
} catch (err) {
|
|
781
864
|
log.error({ err, runId: run.id }, 'Failed to deliver guardian approval prompt');
|
|
782
865
|
// Deny the approval and the underlying run — fail-closed. If
|
|
@@ -788,7 +871,8 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
788
871
|
try {
|
|
789
872
|
await deliverChannelReply(replyCallbackUrl, {
|
|
790
873
|
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
791
|
-
text:
|
|
874
|
+
text: composeApprovalMessage({ scenario: 'guardian_delivery_failed', toolName: pending[0].toolName }),
|
|
875
|
+
assistantId,
|
|
792
876
|
}, bearerToken);
|
|
793
877
|
} catch (notifyErr) {
|
|
794
878
|
log.error({ err: notifyErr, runId: run.id }, 'Failed to notify requester of guardian delivery failure');
|
|
@@ -800,7 +884,8 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
800
884
|
try {
|
|
801
885
|
await deliverChannelReply(replyCallbackUrl, {
|
|
802
886
|
chatId: guardianCtx.requesterChatId ?? externalChatId,
|
|
803
|
-
text:
|
|
887
|
+
text: composeApprovalMessage({ scenario: 'guardian_request_forwarded', toolName: pending[0].toolName }),
|
|
888
|
+
assistantId,
|
|
804
889
|
}, bearerToken);
|
|
805
890
|
} catch (err) {
|
|
806
891
|
log.error({ err, runId: run.id }, 'Failed to notify requester of pending guardian approval');
|
|
@@ -823,8 +908,10 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
823
908
|
externalChatId,
|
|
824
909
|
promptTextForChannel,
|
|
825
910
|
uiMetadata,
|
|
911
|
+
assistantId,
|
|
826
912
|
bearerToken,
|
|
827
913
|
);
|
|
914
|
+
hasPostDecisionDelivery = true;
|
|
828
915
|
} catch (err) {
|
|
829
916
|
// Fail-closed: if we cannot deliver the approval prompt, the
|
|
830
917
|
// user will never see it and the run would hang indefinitely
|
|
@@ -867,7 +954,13 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
867
954
|
// rather than permanently losing the reply.
|
|
868
955
|
if (channelDeliveryStore.claimRunDelivery(run.id)) {
|
|
869
956
|
try {
|
|
870
|
-
await deliverReplyViaCallback(
|
|
957
|
+
await deliverReplyViaCallback(
|
|
958
|
+
conversationId,
|
|
959
|
+
externalChatId,
|
|
960
|
+
replyCallbackUrl,
|
|
961
|
+
bearerToken,
|
|
962
|
+
assistantId,
|
|
963
|
+
);
|
|
871
964
|
} catch (deliveryErr) {
|
|
872
965
|
channelDeliveryStore.resetRunDeliveryClaim(run.id);
|
|
873
966
|
throw deliveryErr;
|
|
@@ -884,23 +977,39 @@ function processChannelMessageWithApprovals(params: ApprovalProcessingParams): v
|
|
|
884
977
|
updateApprovalDecision(approvalReq.id, { status: outcomeStatus });
|
|
885
978
|
}
|
|
886
979
|
}
|
|
887
|
-
} else if (
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
//
|
|
980
|
+
} else if (
|
|
981
|
+
finalRun?.status === 'needs_confirmation' ||
|
|
982
|
+
(hasPostDecisionDelivery && finalRun?.status === 'running')
|
|
983
|
+
) {
|
|
984
|
+
// The run is either still waiting for an approval decision or was
|
|
985
|
+
// recently approved and has resumed execution. In both cases, mark
|
|
986
|
+
// the event as processed rather than failed:
|
|
987
|
+
//
|
|
988
|
+
// - needs_confirmation: the run will resume when the user clicks
|
|
989
|
+
// approve/reject, and `handleApprovalInterception` will deliver
|
|
990
|
+
// the reply via `schedulePostDecisionDelivery`.
|
|
991
|
+
//
|
|
992
|
+
// - running (after successful prompt delivery): an approval was
|
|
993
|
+
// applied near the poll deadline and the run resumed but hasn't
|
|
994
|
+
// reached terminal state yet. `handleApprovalInterception` has
|
|
995
|
+
// already scheduled post-decision delivery, so the final reply
|
|
996
|
+
// will be delivered. This condition is only true when the approval
|
|
997
|
+
// prompt was actually delivered (not in auto-deny paths), ensuring
|
|
998
|
+
// we don't suppress retry/dead-letter for cases where no
|
|
999
|
+
// post-decision delivery path exists.
|
|
1000
|
+
//
|
|
1001
|
+
// Marking either state as failed would cause the generic retry sweep
|
|
1002
|
+
// to replay through `processMessage`, which throws "Session is
|
|
894
1003
|
// already processing a message" and dead-letters a valid conversation.
|
|
895
1004
|
log.warn(
|
|
896
|
-
{ runId: run.id, status: finalRun.status, conversationId },
|
|
897
|
-
'Approval-path poll loop timed out while run
|
|
1005
|
+
{ runId: run.id, status: finalRun.status, conversationId, hasPostDecisionDelivery },
|
|
1006
|
+
'Approval-path poll loop timed out while run is in approval-related state; marking event as processed',
|
|
898
1007
|
);
|
|
899
1008
|
channelDeliveryStore.markProcessed(eventId);
|
|
900
1009
|
} else {
|
|
901
|
-
// The run is in a non-terminal, non-approval state (e.g. running
|
|
902
|
-
// needs_secret, or disappeared). Record a
|
|
903
|
-
// retry/dead-letter machinery can handle it.
|
|
1010
|
+
// The run is in a non-terminal, non-approval state (e.g. running
|
|
1011
|
+
// without prior approval, needs_secret, or disappeared). Record a
|
|
1012
|
+
// processing failure so the retry/dead-letter machinery can handle it.
|
|
904
1013
|
const timeoutErr = new Error(
|
|
905
1014
|
`Approval poll timeout: run did not reach terminal state within ${pollMaxWait}ms (status: ${finalRun?.status ?? 'null'})`,
|
|
906
1015
|
);
|
|
@@ -1002,7 +1111,8 @@ async function handleApprovalInterception(
|
|
|
1002
1111
|
try {
|
|
1003
1112
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1004
1113
|
chatId: externalChatId,
|
|
1005
|
-
text:
|
|
1114
|
+
text: composeApprovalMessage({ scenario: 'guardian_disambiguation', pendingCount: allPending.length }),
|
|
1115
|
+
assistantId,
|
|
1006
1116
|
}, bearerToken);
|
|
1007
1117
|
} catch (err) {
|
|
1008
1118
|
log.error({ err, externalChatId }, 'Failed to deliver disambiguation notice');
|
|
@@ -1034,7 +1144,8 @@ async function handleApprovalInterception(
|
|
|
1034
1144
|
try {
|
|
1035
1145
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1036
1146
|
chatId: externalChatId,
|
|
1037
|
-
text:
|
|
1147
|
+
text: composeApprovalMessage({ scenario: 'guardian_identity_mismatch' }),
|
|
1148
|
+
assistantId,
|
|
1038
1149
|
}, bearerToken);
|
|
1039
1150
|
} catch (err) {
|
|
1040
1151
|
log.error({ err, externalChatId }, 'Failed to deliver guardian identity rejection notice');
|
|
@@ -1067,14 +1178,16 @@ async function handleApprovalInterception(
|
|
|
1067
1178
|
|
|
1068
1179
|
if (result.applied) {
|
|
1069
1180
|
// Notify the requester's chat about the outcome with the tool name
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
:
|
|
1181
|
+
const outcomeText = composeApprovalMessage({
|
|
1182
|
+
scenario: 'guardian_decision_outcome',
|
|
1183
|
+
decision: decision.action === 'reject' ? 'denied' : 'approved',
|
|
1184
|
+
toolName: guardianApproval.toolName,
|
|
1185
|
+
});
|
|
1074
1186
|
try {
|
|
1075
1187
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1076
1188
|
chatId: guardianApproval.requesterChatId,
|
|
1077
1189
|
text: outcomeText,
|
|
1190
|
+
assistantId,
|
|
1078
1191
|
}, bearerToken);
|
|
1079
1192
|
} catch (err) {
|
|
1080
1193
|
log.error({ err, conversationId: guardianApproval.conversationId }, 'Failed to notify requester of guardian decision');
|
|
@@ -1090,6 +1203,7 @@ async function handleApprovalInterception(
|
|
|
1090
1203
|
guardianApproval.requesterChatId,
|
|
1091
1204
|
replyCallbackUrl,
|
|
1092
1205
|
bearerToken,
|
|
1206
|
+
assistantId,
|
|
1093
1207
|
);
|
|
1094
1208
|
}
|
|
1095
1209
|
}
|
|
@@ -1117,6 +1231,7 @@ async function handleApprovalInterception(
|
|
|
1117
1231
|
externalChatId,
|
|
1118
1232
|
reminderText,
|
|
1119
1233
|
uiMetadata,
|
|
1234
|
+
assistantId,
|
|
1120
1235
|
bearerToken,
|
|
1121
1236
|
);
|
|
1122
1237
|
} catch (err) {
|
|
@@ -1126,11 +1241,6 @@ async function handleApprovalInterception(
|
|
|
1126
1241
|
|
|
1127
1242
|
return { handled: true, type: 'reminder_sent' };
|
|
1128
1243
|
}
|
|
1129
|
-
|
|
1130
|
-
// Callback with a run ID that no longer has a pending approval — stale button
|
|
1131
|
-
if (decision?.runId) {
|
|
1132
|
-
return { handled: true, type: 'stale_ignored' };
|
|
1133
|
-
}
|
|
1134
1244
|
}
|
|
1135
1245
|
|
|
1136
1246
|
// ── Standard approval interception (existing flow) ──
|
|
@@ -1142,17 +1252,26 @@ async function handleApprovalInterception(
|
|
|
1142
1252
|
if (guardianCtx.actorRole === 'unverified_channel') {
|
|
1143
1253
|
const pending = getPendingConfirmationsByConversation(conversationId);
|
|
1144
1254
|
if (pending.length > 0) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1255
|
+
const denyResult = handleChannelDecision(
|
|
1256
|
+
conversationId,
|
|
1257
|
+
{ action: 'reject', source: 'plain_text' },
|
|
1258
|
+
orchestrator,
|
|
1259
|
+
buildGuardianDenyContext(
|
|
1260
|
+
pending[0].toolName,
|
|
1261
|
+
guardianCtx.denialReason ?? 'no_binding',
|
|
1262
|
+
sourceChannel,
|
|
1263
|
+
),
|
|
1264
|
+
);
|
|
1265
|
+
if (denyResult.applied && denyResult.runId) {
|
|
1266
|
+
schedulePostDecisionDelivery(
|
|
1267
|
+
orchestrator,
|
|
1268
|
+
denyResult.runId,
|
|
1269
|
+
conversationId,
|
|
1270
|
+
externalChatId,
|
|
1271
|
+
replyCallbackUrl,
|
|
1272
|
+
bearerToken,
|
|
1273
|
+
assistantId,
|
|
1274
|
+
);
|
|
1156
1275
|
}
|
|
1157
1276
|
return { handled: true, type: 'decision_applied' };
|
|
1158
1277
|
}
|
|
@@ -1169,7 +1288,8 @@ async function handleApprovalInterception(
|
|
|
1169
1288
|
try {
|
|
1170
1289
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1171
1290
|
chatId: externalChatId,
|
|
1172
|
-
text:
|
|
1291
|
+
text: composeApprovalMessage({ scenario: 'request_pending_guardian' }),
|
|
1292
|
+
assistantId,
|
|
1173
1293
|
}, bearerToken);
|
|
1174
1294
|
} catch (err) {
|
|
1175
1295
|
log.error({ err, conversationId }, 'Failed to deliver guardian-pending notice to requester');
|
|
@@ -1195,7 +1315,8 @@ async function handleApprovalInterception(
|
|
|
1195
1315
|
try {
|
|
1196
1316
|
await deliverChannelReply(replyCallbackUrl, {
|
|
1197
1317
|
chatId: externalChatId,
|
|
1198
|
-
text:
|
|
1318
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_requester', toolName: pending[0].toolName }),
|
|
1319
|
+
assistantId,
|
|
1199
1320
|
}, bearerToken);
|
|
1200
1321
|
} catch (err) {
|
|
1201
1322
|
log.error({ err, conversationId }, 'Failed to deliver guardian-expiry notice to requester');
|
|
@@ -1247,6 +1368,7 @@ async function handleApprovalInterception(
|
|
|
1247
1368
|
externalChatId,
|
|
1248
1369
|
replyCallbackUrl,
|
|
1249
1370
|
bearerToken,
|
|
1371
|
+
assistantId,
|
|
1250
1372
|
);
|
|
1251
1373
|
}
|
|
1252
1374
|
|
|
@@ -1269,6 +1391,7 @@ async function handleApprovalInterception(
|
|
|
1269
1391
|
externalChatId,
|
|
1270
1392
|
reminderText,
|
|
1271
1393
|
uiMetadata,
|
|
1394
|
+
assistantId,
|
|
1272
1395
|
bearerToken,
|
|
1273
1396
|
);
|
|
1274
1397
|
} catch (err) {
|
|
@@ -1296,6 +1419,7 @@ function schedulePostDecisionDelivery(
|
|
|
1296
1419
|
externalChatId: string,
|
|
1297
1420
|
replyCallbackUrl: string,
|
|
1298
1421
|
bearerToken?: string,
|
|
1422
|
+
assistantId?: string,
|
|
1299
1423
|
): void {
|
|
1300
1424
|
(async () => {
|
|
1301
1425
|
try {
|
|
@@ -1307,7 +1431,13 @@ function schedulePostDecisionDelivery(
|
|
|
1307
1431
|
if (current.status === 'completed' || current.status === 'failed') {
|
|
1308
1432
|
if (channelDeliveryStore.claimRunDelivery(runId)) {
|
|
1309
1433
|
try {
|
|
1310
|
-
await deliverReplyViaCallback(
|
|
1434
|
+
await deliverReplyViaCallback(
|
|
1435
|
+
conversationId,
|
|
1436
|
+
externalChatId,
|
|
1437
|
+
replyCallbackUrl,
|
|
1438
|
+
bearerToken,
|
|
1439
|
+
assistantId,
|
|
1440
|
+
);
|
|
1311
1441
|
} catch (deliveryErr) {
|
|
1312
1442
|
channelDeliveryStore.resetRunDeliveryClaim(runId);
|
|
1313
1443
|
throw deliveryErr;
|
|
@@ -1331,6 +1461,7 @@ async function deliverReplyViaCallback(
|
|
|
1331
1461
|
externalChatId: string,
|
|
1332
1462
|
callbackUrl: string,
|
|
1333
1463
|
bearerToken?: string,
|
|
1464
|
+
assistantId?: string,
|
|
1334
1465
|
): Promise<void> {
|
|
1335
1466
|
const msgs = conversationStore.getMessages(conversationId);
|
|
1336
1467
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
@@ -1353,6 +1484,7 @@ async function deliverReplyViaCallback(
|
|
|
1353
1484
|
chatId: externalChatId,
|
|
1354
1485
|
text: rendered.text || undefined,
|
|
1355
1486
|
attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
|
|
1487
|
+
assistantId,
|
|
1356
1488
|
}, bearerToken);
|
|
1357
1489
|
}
|
|
1358
1490
|
break;
|
|
@@ -1452,7 +1584,8 @@ export function sweepExpiredGuardianApprovals(
|
|
|
1452
1584
|
// Notify the requester that the approval expired
|
|
1453
1585
|
deliverChannelReply(deliverUrl, {
|
|
1454
1586
|
chatId: approval.requesterChatId,
|
|
1455
|
-
text:
|
|
1587
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_requester', toolName: approval.toolName }),
|
|
1588
|
+
assistantId: approval.assistantId,
|
|
1456
1589
|
}, bearerToken).catch((err) => {
|
|
1457
1590
|
log.error({ err, runId: approval.runId }, 'Failed to notify requester of guardian approval expiry');
|
|
1458
1591
|
});
|
|
@@ -1460,7 +1593,8 @@ export function sweepExpiredGuardianApprovals(
|
|
|
1460
1593
|
// Notify the guardian that the approval expired
|
|
1461
1594
|
deliverChannelReply(deliverUrl, {
|
|
1462
1595
|
chatId: approval.guardianChatId,
|
|
1463
|
-
text:
|
|
1596
|
+
text: composeApprovalMessage({ scenario: 'guardian_expired_guardian', toolName: approval.toolName, requesterIdentifier: approval.requesterExternalUserId }),
|
|
1597
|
+
assistantId: approval.assistantId,
|
|
1464
1598
|
}, bearerToken).catch((err) => {
|
|
1465
1599
|
log.error({ err, runId: approval.runId }, 'Failed to notify guardian of approval expiry');
|
|
1466
1600
|
});
|