@vellumai/assistant 0.4.4 → 0.4.6
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/ARCHITECTURE.md +4 -4
- package/README.md +6 -6
- package/bun.lock +6 -2
- package/docs/architecture/memory.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/actor-token-service.test.ts +5 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +1 -0
- package/src/__tests__/call-controller.test.ts +78 -0
- package/src/__tests__/call-domain.test.ts +148 -10
- package/src/__tests__/call-pointer-message-composer.test.ts +39 -49
- package/src/__tests__/call-pointer-messages.test.ts +105 -43
- package/src/__tests__/canonical-guardian-store.test.ts +44 -10
- package/src/__tests__/channel-approval-routes.test.ts +67 -65
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +2 -2
- package/src/__tests__/deterministic-verification-control-plane.test.ts +6 -6
- package/src/__tests__/guardian-actions-endpoint.test.ts +7 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +57 -12
- package/src/__tests__/guardian-grant-minting.test.ts +24 -24
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +205 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -25
- package/src/__tests__/guardian-routing-state.test.ts +4 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -2
- package/src/__tests__/inbound-invite-redemption.test.ts +8 -8
- package/src/__tests__/memory-retrieval.benchmark.test.ts +22 -47
- package/src/__tests__/no-is-trusted-guard.test.ts +77 -0
- package/src/__tests__/non-member-access-request.test.ts +50 -47
- package/src/__tests__/relay-server.test.ts +71 -0
- package/src/__tests__/send-endpoint-busy.test.ts +6 -0
- package/src/__tests__/session-tool-setup-tools-disabled.test.ts +155 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +66 -2
- package/src/__tests__/system-prompt.test.ts +1 -0
- package/src/__tests__/tool-approval-handler.test.ts +1 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +9 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +8 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +22 -22
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -4
- package/src/__tests__/trusted-contact-verification.test.ts +10 -10
- package/src/approvals/guardian-decision-primitive.ts +29 -25
- package/src/approvals/guardian-request-resolvers.ts +9 -5
- package/src/calls/call-pointer-message-composer.ts +27 -85
- package/src/calls/call-pointer-messages.ts +54 -21
- package/src/calls/guardian-dispatch.ts +30 -0
- package/src/calls/relay-server.ts +13 -13
- package/src/config/system-prompt.ts +10 -3
- package/src/config/templates/BOOTSTRAP.md +6 -5
- package/src/config/templates/USER.md +1 -0
- package/src/config/user-reference.ts +44 -0
- package/src/daemon/handlers/guardian-actions.ts +5 -2
- package/src/daemon/handlers/sessions.ts +8 -3
- package/src/daemon/lifecycle.ts +109 -3
- package/src/daemon/server.ts +32 -24
- package/src/daemon/session-agent-loop.ts +4 -3
- package/src/daemon/session-lifecycle.ts +1 -9
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-runtime-assembly.ts +2 -0
- package/src/daemon/session-tool-setup.ts +10 -0
- package/src/daemon/session.ts +1 -0
- package/src/memory/canonical-guardian-store.ts +40 -0
- package/src/memory/conversation-crud.ts +26 -0
- package/src/memory/conversation-store.ts +1 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/guardian-bindings.ts +4 -0
- package/src/memory/job-handlers/backfill.ts +2 -9
- package/src/memory/migrations/125-guardian-principal-id-columns.ts +19 -0
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +210 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/schema.ts +3 -0
- package/src/notifications/copy-composer.ts +2 -2
- package/src/runtime/access-request-helper.ts +43 -28
- package/src/runtime/actor-trust-resolver.ts +19 -14
- package/src/runtime/channel-guardian-service.ts +6 -0
- package/src/runtime/guardian-context-resolver.ts +6 -2
- package/src/runtime/guardian-reply-router.ts +33 -16
- package/src/runtime/guardian-vellum-migration.ts +29 -5
- package/src/runtime/http-types.ts +0 -13
- package/src/runtime/local-actor-identity.ts +19 -13
- package/src/runtime/middleware/actor-token.ts +2 -2
- package/src/runtime/routes/channel-delivery-routes.ts +5 -5
- package/src/runtime/routes/conversation-routes.ts +45 -35
- package/src/runtime/routes/guardian-action-routes.ts +7 -1
- package/src/runtime/routes/guardian-approval-interception.ts +52 -52
- package/src/runtime/routes/guardian-bootstrap-routes.ts +1 -0
- package/src/runtime/routes/inbound-conversation.ts +7 -7
- package/src/runtime/routes/inbound-message-handler.ts +105 -94
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/util/logger.ts +10 -0
- package/src/daemon/call-pointer-generators.ts +0 -59
|
@@ -51,9 +51,9 @@ export interface ApprovalInterceptionParams {
|
|
|
51
51
|
conversationId: string;
|
|
52
52
|
callbackData?: string;
|
|
53
53
|
content: string;
|
|
54
|
-
|
|
54
|
+
conversationExternalId: string;
|
|
55
55
|
sourceChannel: ChannelId;
|
|
56
|
-
|
|
56
|
+
actorExternalId?: string;
|
|
57
57
|
replyCallbackUrl: string;
|
|
58
58
|
bearerToken?: string;
|
|
59
59
|
guardianCtx: GuardianContext;
|
|
@@ -84,9 +84,9 @@ export async function handleApprovalInterception(
|
|
|
84
84
|
conversationId,
|
|
85
85
|
callbackData,
|
|
86
86
|
content,
|
|
87
|
-
|
|
87
|
+
conversationExternalId,
|
|
88
88
|
sourceChannel,
|
|
89
|
-
|
|
89
|
+
actorExternalId,
|
|
90
90
|
replyCallbackUrl,
|
|
91
91
|
bearerToken,
|
|
92
92
|
guardianCtx,
|
|
@@ -101,7 +101,7 @@ export async function handleApprovalInterception(
|
|
|
101
101
|
// of a non-guardian requester.
|
|
102
102
|
if (
|
|
103
103
|
guardianCtx.trustClass === 'guardian' &&
|
|
104
|
-
|
|
104
|
+
actorExternalId
|
|
105
105
|
) {
|
|
106
106
|
// Callback/button path: deterministic and takes priority.
|
|
107
107
|
let callbackDecision: ApprovalDecisionResult | null = null;
|
|
@@ -113,14 +113,14 @@ export async function handleApprovalInterception(
|
|
|
113
113
|
// the decision resolves to exactly the right approval even when
|
|
114
114
|
// multiple approvals target the same guardian chat.
|
|
115
115
|
let guardianApproval = callbackDecision?.requestId
|
|
116
|
-
? getPendingApprovalByRequestAndGuardianChat(callbackDecision.requestId, sourceChannel,
|
|
116
|
+
? getPendingApprovalByRequestAndGuardianChat(callbackDecision.requestId, sourceChannel, conversationExternalId, assistantId)
|
|
117
117
|
: null;
|
|
118
118
|
|
|
119
119
|
// When the scoped lookup didn't resolve an approval (either because
|
|
120
120
|
// there was no callback or the requestId pointed to a stale/expired request),
|
|
121
121
|
// fall back to checking all pending approvals for this guardian chat.
|
|
122
122
|
if (!guardianApproval && callbackDecision) {
|
|
123
|
-
const allPending = getAllPendingApprovalsByGuardianChat(sourceChannel,
|
|
123
|
+
const allPending = getAllPendingApprovalsByGuardianChat(sourceChannel, conversationExternalId, assistantId);
|
|
124
124
|
if (allPending.length === 1) {
|
|
125
125
|
guardianApproval = allPending[0];
|
|
126
126
|
} else if (allPending.length > 1) {
|
|
@@ -133,12 +133,12 @@ export async function handleApprovalInterception(
|
|
|
133
133
|
channel: sourceChannel,
|
|
134
134
|
}, {}, approvalCopyGenerator);
|
|
135
135
|
await deliverChannelReply(replyCallbackUrl, {
|
|
136
|
-
chatId:
|
|
136
|
+
chatId: conversationExternalId,
|
|
137
137
|
text: staleText,
|
|
138
138
|
assistantId,
|
|
139
139
|
}, bearerToken);
|
|
140
140
|
} catch (err) {
|
|
141
|
-
log.error({ err,
|
|
141
|
+
log.error({ err, conversationExternalId }, 'Failed to deliver stale callback disambiguation notice');
|
|
142
142
|
}
|
|
143
143
|
return { handled: true, type: 'stale_ignored' };
|
|
144
144
|
}
|
|
@@ -147,14 +147,14 @@ export async function handleApprovalInterception(
|
|
|
147
147
|
// For plain-text messages (no callback), check if there are any pending
|
|
148
148
|
// approvals for this guardian chat to route through the conversation engine.
|
|
149
149
|
if (!guardianApproval && !callbackDecision) {
|
|
150
|
-
const allPending = getAllPendingApprovalsByGuardianChat(sourceChannel,
|
|
150
|
+
const allPending = getAllPendingApprovalsByGuardianChat(sourceChannel, conversationExternalId, assistantId);
|
|
151
151
|
if (allPending.length === 1) {
|
|
152
152
|
guardianApproval = allPending[0];
|
|
153
153
|
} else if (allPending.length > 1) {
|
|
154
154
|
// Multiple pending — pick the first approval matching this sender as
|
|
155
155
|
// primary context. The conversation engine sees all matching approvals
|
|
156
156
|
// via pendingApprovals and can disambiguate.
|
|
157
|
-
guardianApproval = allPending.find(a => a.guardianExternalUserId ===
|
|
157
|
+
guardianApproval = allPending.find(a => a.guardianExternalUserId === actorExternalId) ?? allPending[0];
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -164,9 +164,9 @@ export async function handleApprovalInterception(
|
|
|
164
164
|
// trustClass check above already verifies the sender is a guardian,
|
|
165
165
|
// but this catches edge cases like binding rotation between request
|
|
166
166
|
// creation and decision.
|
|
167
|
-
if (
|
|
167
|
+
if (actorExternalId !== guardianApproval.guardianExternalUserId) {
|
|
168
168
|
log.warn(
|
|
169
|
-
{
|
|
169
|
+
{ conversationExternalId, actorExternalId, expectedGuardian: guardianApproval.guardianExternalUserId },
|
|
170
170
|
'Non-guardian sender attempted to act on guardian approval request',
|
|
171
171
|
);
|
|
172
172
|
try {
|
|
@@ -175,12 +175,12 @@ export async function handleApprovalInterception(
|
|
|
175
175
|
channel: sourceChannel,
|
|
176
176
|
}, {}, approvalCopyGenerator);
|
|
177
177
|
await deliverChannelReply(replyCallbackUrl, {
|
|
178
|
-
chatId:
|
|
178
|
+
chatId: conversationExternalId,
|
|
179
179
|
text: mismatchText,
|
|
180
180
|
assistantId,
|
|
181
181
|
}, bearerToken);
|
|
182
182
|
} catch (err) {
|
|
183
|
-
log.error({ err,
|
|
183
|
+
log.error({ err, conversationExternalId }, 'Failed to deliver guardian identity rejection notice');
|
|
184
184
|
}
|
|
185
185
|
return { handled: true, type: 'guardian_decision_applied' };
|
|
186
186
|
}
|
|
@@ -193,7 +193,7 @@ export async function handleApprovalInterception(
|
|
|
193
193
|
const accessResult = await handleAccessRequestApproval(
|
|
194
194
|
guardianApproval,
|
|
195
195
|
callbackDecision.action === 'reject' ? 'deny' : 'approve',
|
|
196
|
-
|
|
196
|
+
actorExternalId,
|
|
197
197
|
replyCallbackUrl,
|
|
198
198
|
assistantId,
|
|
199
199
|
bearerToken,
|
|
@@ -207,7 +207,7 @@ export async function handleApprovalInterception(
|
|
|
207
207
|
const result = applyGuardianDecision({
|
|
208
208
|
approval: guardianApproval,
|
|
209
209
|
decision: callbackDecision,
|
|
210
|
-
actorExternalUserId:
|
|
210
|
+
actorExternalUserId: actorExternalId,
|
|
211
211
|
actorChannel: sourceChannel,
|
|
212
212
|
});
|
|
213
213
|
|
|
@@ -242,11 +242,11 @@ export async function handleApprovalInterception(
|
|
|
242
242
|
// ── Conversational engine for guardian plain-text messages ──
|
|
243
243
|
// Gather all pending guardian approvals for this chat so the engine
|
|
244
244
|
// can handle disambiguation when multiple are pending.
|
|
245
|
-
const allGuardianPending = getAllPendingApprovalsByGuardianChat(sourceChannel,
|
|
245
|
+
const allGuardianPending = getAllPendingApprovalsByGuardianChat(sourceChannel, conversationExternalId, assistantId);
|
|
246
246
|
// Only present approvals that belong to this sender so the engine
|
|
247
247
|
// does not offer disambiguation for requests assigned to a rotated
|
|
248
248
|
// guardian the sender cannot act on.
|
|
249
|
-
const senderPending = allGuardianPending.filter(a => a.guardianExternalUserId ===
|
|
249
|
+
const senderPending = allGuardianPending.filter(a => a.guardianExternalUserId === actorExternalId);
|
|
250
250
|
const effectivePending = senderPending.length > 0 ? senderPending : allGuardianPending;
|
|
251
251
|
if (effectivePending.length > 0 && approvalConversationGenerator && content) {
|
|
252
252
|
const guardianAllowedActions = ['approve_once', 'reject'];
|
|
@@ -264,7 +264,7 @@ export async function handleApprovalInterception(
|
|
|
264
264
|
// Non-decision follow-up (clarification, disambiguation, etc.)
|
|
265
265
|
try {
|
|
266
266
|
await deliverChannelReply(replyCallbackUrl, {
|
|
267
|
-
chatId:
|
|
267
|
+
chatId: conversationExternalId,
|
|
268
268
|
text: engineResult.replyText,
|
|
269
269
|
assistantId,
|
|
270
270
|
}, bearerToken);
|
|
@@ -295,9 +295,9 @@ export async function handleApprovalInterception(
|
|
|
295
295
|
// that was assigned to a different guardian. Without this check a
|
|
296
296
|
// currently bound guardian could act on a request assigned to a
|
|
297
297
|
// previous guardian after a binding rotation.
|
|
298
|
-
if (
|
|
298
|
+
if (actorExternalId !== targetApproval.guardianExternalUserId) {
|
|
299
299
|
log.warn(
|
|
300
|
-
{
|
|
300
|
+
{ conversationExternalId, actorExternalId, expectedGuardian: targetApproval.guardianExternalUserId, targetRequestId: engineResult.targetRequestId },
|
|
301
301
|
'Guardian identity mismatch on engine-selected target approval',
|
|
302
302
|
);
|
|
303
303
|
try {
|
|
@@ -306,12 +306,12 @@ export async function handleApprovalInterception(
|
|
|
306
306
|
channel: sourceChannel,
|
|
307
307
|
}, {}, approvalCopyGenerator);
|
|
308
308
|
await deliverChannelReply(replyCallbackUrl, {
|
|
309
|
-
chatId:
|
|
309
|
+
chatId: conversationExternalId,
|
|
310
310
|
text: mismatchText,
|
|
311
311
|
assistantId,
|
|
312
312
|
}, bearerToken);
|
|
313
313
|
} catch (err) {
|
|
314
|
-
log.error({ err,
|
|
314
|
+
log.error({ err, conversationExternalId }, 'Failed to deliver guardian identity mismatch notice for engine target');
|
|
315
315
|
}
|
|
316
316
|
return { handled: true, type: 'guardian_decision_applied' };
|
|
317
317
|
}
|
|
@@ -321,7 +321,7 @@ export async function handleApprovalInterception(
|
|
|
321
321
|
const accessResult = await handleAccessRequestApproval(
|
|
322
322
|
targetApproval,
|
|
323
323
|
decisionAction === 'reject' ? 'deny' : 'approve',
|
|
324
|
-
|
|
324
|
+
actorExternalId,
|
|
325
325
|
replyCallbackUrl,
|
|
326
326
|
assistantId,
|
|
327
327
|
bearerToken,
|
|
@@ -339,7 +339,7 @@ export async function handleApprovalInterception(
|
|
|
339
339
|
const result = applyGuardianDecision({
|
|
340
340
|
approval: targetApproval,
|
|
341
341
|
decision: engineDecision,
|
|
342
|
-
actorExternalUserId:
|
|
342
|
+
actorExternalUserId: actorExternalId,
|
|
343
343
|
actorChannel: sourceChannel,
|
|
344
344
|
});
|
|
345
345
|
|
|
@@ -364,7 +364,7 @@ export async function handleApprovalInterception(
|
|
|
364
364
|
// Deliver the engine's reply to the guardian
|
|
365
365
|
try {
|
|
366
366
|
await deliverChannelReply(replyCallbackUrl, {
|
|
367
|
-
chatId:
|
|
367
|
+
chatId: conversationExternalId,
|
|
368
368
|
text: engineResult.replyText,
|
|
369
369
|
assistantId,
|
|
370
370
|
}, bearerToken);
|
|
@@ -383,7 +383,7 @@ export async function handleApprovalInterception(
|
|
|
383
383
|
channel: sourceChannel,
|
|
384
384
|
}, {}, approvalCopyGenerator);
|
|
385
385
|
await deliverChannelReply(replyCallbackUrl, {
|
|
386
|
-
chatId:
|
|
386
|
+
chatId: conversationExternalId,
|
|
387
387
|
text: staleText,
|
|
388
388
|
assistantId,
|
|
389
389
|
}, bearerToken);
|
|
@@ -414,7 +414,7 @@ export async function handleApprovalInterception(
|
|
|
414
414
|
const resolvedByRequest = getPendingApprovalByRequestAndGuardianChat(
|
|
415
415
|
legacyGuardianDecision.requestId,
|
|
416
416
|
sourceChannel,
|
|
417
|
-
|
|
417
|
+
conversationExternalId,
|
|
418
418
|
assistantId,
|
|
419
419
|
);
|
|
420
420
|
if (!resolvedByRequest) {
|
|
@@ -426,12 +426,12 @@ export async function handleApprovalInterception(
|
|
|
426
426
|
channel: sourceChannel,
|
|
427
427
|
}, {}, approvalCopyGenerator);
|
|
428
428
|
await deliverChannelReply(replyCallbackUrl, {
|
|
429
|
-
chatId:
|
|
429
|
+
chatId: conversationExternalId,
|
|
430
430
|
text: staleText,
|
|
431
431
|
assistantId,
|
|
432
432
|
}, bearerToken);
|
|
433
433
|
} catch (err) {
|
|
434
|
-
log.error({ err,
|
|
434
|
+
log.error({ err, conversationExternalId }, 'Failed to deliver stale approval notice (legacy path)');
|
|
435
435
|
}
|
|
436
436
|
return { handled: true, type: 'stale_ignored' };
|
|
437
437
|
}
|
|
@@ -441,9 +441,9 @@ export async function handleApprovalInterception(
|
|
|
441
441
|
// Re-validate guardian identity against the resolved target.
|
|
442
442
|
// The default guardianApproval was already checked, but a
|
|
443
443
|
// requestId-resolved approval may belong to a different guardian.
|
|
444
|
-
if (
|
|
444
|
+
if (actorExternalId !== targetLegacyApproval.guardianExternalUserId) {
|
|
445
445
|
log.warn(
|
|
446
|
-
{
|
|
446
|
+
{ conversationExternalId, actorExternalId, expectedGuardian: targetLegacyApproval.guardianExternalUserId, requestId: legacyGuardianDecision.requestId },
|
|
447
447
|
'Guardian identity mismatch on legacy ref-resolved target approval',
|
|
448
448
|
);
|
|
449
449
|
try {
|
|
@@ -452,12 +452,12 @@ export async function handleApprovalInterception(
|
|
|
452
452
|
channel: sourceChannel,
|
|
453
453
|
}, {}, approvalCopyGenerator);
|
|
454
454
|
await deliverChannelReply(replyCallbackUrl, {
|
|
455
|
-
chatId:
|
|
455
|
+
chatId: conversationExternalId,
|
|
456
456
|
text: mismatchText,
|
|
457
457
|
assistantId,
|
|
458
458
|
}, bearerToken);
|
|
459
459
|
} catch (err) {
|
|
460
|
-
log.error({ err,
|
|
460
|
+
log.error({ err, conversationExternalId }, 'Failed to deliver guardian identity mismatch notice (legacy path)');
|
|
461
461
|
}
|
|
462
462
|
return { handled: true, type: 'guardian_decision_applied' };
|
|
463
463
|
}
|
|
@@ -467,7 +467,7 @@ export async function handleApprovalInterception(
|
|
|
467
467
|
const accessResult = await handleAccessRequestApproval(
|
|
468
468
|
targetLegacyApproval,
|
|
469
469
|
legacyGuardianDecision.action === 'reject' ? 'deny' : 'approve',
|
|
470
|
-
|
|
470
|
+
actorExternalId,
|
|
471
471
|
replyCallbackUrl,
|
|
472
472
|
assistantId,
|
|
473
473
|
bearerToken,
|
|
@@ -479,7 +479,7 @@ export async function handleApprovalInterception(
|
|
|
479
479
|
const result = applyGuardianDecision({
|
|
480
480
|
approval: targetLegacyApproval,
|
|
481
481
|
decision: legacyGuardianDecision,
|
|
482
|
-
actorExternalUserId:
|
|
482
|
+
actorExternalUserId: actorExternalId,
|
|
483
483
|
actorChannel: sourceChannel,
|
|
484
484
|
});
|
|
485
485
|
|
|
@@ -511,7 +511,7 @@ export async function handleApprovalInterception(
|
|
|
511
511
|
channel: sourceChannel,
|
|
512
512
|
}, {}, approvalCopyGenerator);
|
|
513
513
|
await deliverChannelReply(replyCallbackUrl, {
|
|
514
|
-
chatId:
|
|
514
|
+
chatId: conversationExternalId,
|
|
515
515
|
text: staleText,
|
|
516
516
|
assistantId,
|
|
517
517
|
}, bearerToken);
|
|
@@ -529,7 +529,7 @@ export async function handleApprovalInterception(
|
|
|
529
529
|
channel: sourceChannel,
|
|
530
530
|
}, {}, approvalCopyGenerator);
|
|
531
531
|
await deliverChannelReply(replyCallbackUrl, {
|
|
532
|
-
chatId:
|
|
532
|
+
chatId: conversationExternalId,
|
|
533
533
|
text: reminderText,
|
|
534
534
|
assistantId,
|
|
535
535
|
}, bearerToken);
|
|
@@ -622,7 +622,7 @@ export async function handleApprovalInterception(
|
|
|
622
622
|
const cancelApplyResult = applyGuardianDecision({
|
|
623
623
|
approval: guardianApprovalForRequest,
|
|
624
624
|
decision: rejectDecision,
|
|
625
|
-
actorExternalUserId:
|
|
625
|
+
actorExternalUserId: actorExternalId,
|
|
626
626
|
actorChannel: sourceChannel,
|
|
627
627
|
});
|
|
628
628
|
if (cancelApplyResult.applied) {
|
|
@@ -634,7 +634,7 @@ export async function handleApprovalInterception(
|
|
|
634
634
|
}, {}, approvalCopyGenerator);
|
|
635
635
|
try {
|
|
636
636
|
await deliverChannelReply(replyCallbackUrl, {
|
|
637
|
-
chatId:
|
|
637
|
+
chatId: conversationExternalId,
|
|
638
638
|
text: replyText,
|
|
639
639
|
assistantId,
|
|
640
640
|
}, bearerToken);
|
|
@@ -669,7 +669,7 @@ export async function handleApprovalInterception(
|
|
|
669
669
|
channel: sourceChannel,
|
|
670
670
|
}, {}, approvalCopyGenerator);
|
|
671
671
|
await deliverChannelReply(replyCallbackUrl, {
|
|
672
|
-
chatId:
|
|
672
|
+
chatId: conversationExternalId,
|
|
673
673
|
text: staleText,
|
|
674
674
|
assistantId,
|
|
675
675
|
}, bearerToken);
|
|
@@ -682,7 +682,7 @@ export async function handleApprovalInterception(
|
|
|
682
682
|
if (requesterFollowupReplyText) {
|
|
683
683
|
try {
|
|
684
684
|
await deliverChannelReply(replyCallbackUrl, {
|
|
685
|
-
chatId:
|
|
685
|
+
chatId: conversationExternalId,
|
|
686
686
|
text: requesterFollowupReplyText,
|
|
687
687
|
assistantId,
|
|
688
688
|
}, bearerToken);
|
|
@@ -700,7 +700,7 @@ export async function handleApprovalInterception(
|
|
|
700
700
|
channel: sourceChannel,
|
|
701
701
|
}, {}, approvalCopyGenerator);
|
|
702
702
|
await deliverChannelReply(replyCallbackUrl, {
|
|
703
|
-
chatId:
|
|
703
|
+
chatId: conversationExternalId,
|
|
704
704
|
text: pendingText,
|
|
705
705
|
assistantId,
|
|
706
706
|
}, bearerToken);
|
|
@@ -732,7 +732,7 @@ export async function handleApprovalInterception(
|
|
|
732
732
|
channel: sourceChannel,
|
|
733
733
|
}, {}, approvalCopyGenerator);
|
|
734
734
|
await deliverChannelReply(replyCallbackUrl, {
|
|
735
|
-
chatId:
|
|
735
|
+
chatId: conversationExternalId,
|
|
736
736
|
text: expiredText,
|
|
737
737
|
assistantId,
|
|
738
738
|
}, bearerToken);
|
|
@@ -752,7 +752,7 @@ export async function handleApprovalInterception(
|
|
|
752
752
|
// pending request via handleChannelDecision.
|
|
753
753
|
if (guardianCtx.trustClass !== 'guardian' && guardianCtx.guardianExternalUserId) {
|
|
754
754
|
log.info(
|
|
755
|
-
{ conversationId,
|
|
755
|
+
{ conversationId, conversationExternalId, guardianExternalUserId: guardianCtx.guardianExternalUserId },
|
|
756
756
|
'Blocking non-guardian self-approval: pending confirmation exists but guardian approval row not yet created',
|
|
757
757
|
);
|
|
758
758
|
try {
|
|
@@ -761,7 +761,7 @@ export async function handleApprovalInterception(
|
|
|
761
761
|
channel: sourceChannel,
|
|
762
762
|
}, {}, approvalCopyGenerator);
|
|
763
763
|
await deliverChannelReply(replyCallbackUrl, {
|
|
764
|
-
chatId:
|
|
764
|
+
chatId: conversationExternalId,
|
|
765
765
|
text: pendingText,
|
|
766
766
|
assistantId,
|
|
767
767
|
}, bearerToken);
|
|
@@ -827,7 +827,7 @@ export async function handleApprovalInterception(
|
|
|
827
827
|
// Non-decision follow-up — deliver the engine's reply and keep the request pending
|
|
828
828
|
try {
|
|
829
829
|
await deliverChannelReply(replyCallbackUrl, {
|
|
830
|
-
chatId:
|
|
830
|
+
chatId: conversationExternalId,
|
|
831
831
|
text: engineResult.replyText,
|
|
832
832
|
assistantId,
|
|
833
833
|
}, bearerToken);
|
|
@@ -851,7 +851,7 @@ export async function handleApprovalInterception(
|
|
|
851
851
|
// Deliver the engine's reply text to the user
|
|
852
852
|
try {
|
|
853
853
|
await deliverChannelReply(replyCallbackUrl, {
|
|
854
|
-
chatId:
|
|
854
|
+
chatId: conversationExternalId,
|
|
855
855
|
text: engineResult.replyText,
|
|
856
856
|
assistantId,
|
|
857
857
|
}, bearerToken);
|
|
@@ -871,7 +871,7 @@ export async function handleApprovalInterception(
|
|
|
871
871
|
channel: sourceChannel,
|
|
872
872
|
}, {}, approvalCopyGenerator);
|
|
873
873
|
await deliverChannelReply(replyCallbackUrl, {
|
|
874
|
-
chatId:
|
|
874
|
+
chatId: conversationExternalId,
|
|
875
875
|
text: staleText,
|
|
876
876
|
assistantId,
|
|
877
877
|
}, bearerToken);
|
|
@@ -905,7 +905,7 @@ export async function handleApprovalInterception(
|
|
|
905
905
|
channel: sourceChannel,
|
|
906
906
|
}, {}, approvalCopyGenerator);
|
|
907
907
|
await deliverChannelReply(replyCallbackUrl, {
|
|
908
|
-
chatId:
|
|
908
|
+
chatId: conversationExternalId,
|
|
909
909
|
text: staleText,
|
|
910
910
|
assistantId,
|
|
911
911
|
}, bearerToken);
|
|
@@ -925,7 +925,7 @@ export async function handleApprovalInterception(
|
|
|
925
925
|
toolName: pending.length > 0 ? pending[0].toolName : undefined,
|
|
926
926
|
}, {}, approvalCopyGenerator);
|
|
927
927
|
await deliverChannelReply(replyCallbackUrl, {
|
|
928
|
-
chatId:
|
|
928
|
+
chatId: conversationExternalId,
|
|
929
929
|
text: statusText,
|
|
930
930
|
assistantId,
|
|
931
931
|
}, bearerToken);
|
|
@@ -56,6 +56,7 @@ function ensureGuardianPrincipal(assistantId: string): {
|
|
|
56
56
|
channel: 'vellum',
|
|
57
57
|
guardianExternalUserId: guardianPrincipalId,
|
|
58
58
|
guardianDeliveryChatId: 'local',
|
|
59
|
+
guardianPrincipalId,
|
|
59
60
|
verifiedVia: 'bootstrap',
|
|
60
61
|
metadataJson: JSON.stringify({ bootstrappedAt: Date.now() }),
|
|
61
62
|
});
|
|
@@ -9,23 +9,23 @@ import { httpError } from '../http-errors.js';
|
|
|
9
9
|
export async function handleDeleteConversation(req: Request, assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID): Promise<Response> {
|
|
10
10
|
const body = await req.json() as {
|
|
11
11
|
sourceChannel?: string;
|
|
12
|
-
|
|
12
|
+
conversationExternalId?: string;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const { sourceChannel,
|
|
15
|
+
const { sourceChannel, conversationExternalId } = body;
|
|
16
16
|
|
|
17
17
|
if (!sourceChannel || typeof sourceChannel !== 'string') {
|
|
18
18
|
return httpError('BAD_REQUEST', 'sourceChannel is required', 400);
|
|
19
19
|
}
|
|
20
|
-
if (!
|
|
21
|
-
return httpError('BAD_REQUEST', '
|
|
20
|
+
if (!conversationExternalId || typeof conversationExternalId !== 'string') {
|
|
21
|
+
return httpError('BAD_REQUEST', 'conversationExternalId is required', 400);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// Delete the assistant-scoped key unconditionally. The legacy key is
|
|
25
25
|
// canonical for the self assistant and must not be deleted from non-self
|
|
26
26
|
// routes, otherwise a non-self reset can accidentally reset self state.
|
|
27
|
-
const legacyKey = `${sourceChannel}:${
|
|
28
|
-
const scopedKey = `asst:${assistantId}:${sourceChannel}:${
|
|
27
|
+
const legacyKey = `${sourceChannel}:${conversationExternalId}`;
|
|
28
|
+
const scopedKey = `asst:${assistantId}:${sourceChannel}:${conversationExternalId}`;
|
|
29
29
|
deleteConversationKey(scopedKey);
|
|
30
30
|
if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
31
31
|
deleteConversationKey(legacyKey);
|
|
@@ -35,7 +35,7 @@ export async function handleDeleteConversation(req: Request, assistantId: string
|
|
|
35
35
|
// canonical self-assistant route so multi-assistant legacy routes do not
|
|
36
36
|
// clobber each other's bindings.
|
|
37
37
|
if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
38
|
-
externalConversationStore.deleteBindingByChannelChat(sourceChannel,
|
|
38
|
+
externalConversationStore.deleteBindingByChannelChat(sourceChannel, conversationExternalId);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return Response.json({ ok: true });
|