@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
|
@@ -197,19 +197,26 @@ function findPendingCanonicalRequests(
|
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
200
|
+
// Actors without an externalUserId: scope by conversationId so the NL
|
|
201
|
+
// path can discover pending requests bound to this conversation.
|
|
202
|
+
// Include guardianPrincipalId filter when available so the guardian only
|
|
203
|
+
// sees requests they are authorized to act on.
|
|
202
204
|
if (conversationId) {
|
|
203
205
|
return listCanonicalGuardianRequests({
|
|
204
206
|
status: 'pending',
|
|
205
207
|
conversationId,
|
|
208
|
+
...(actor.guardianPrincipalId ? { guardianPrincipalId: actor.guardianPrincipalId } : {}),
|
|
206
209
|
});
|
|
207
210
|
}
|
|
208
211
|
|
|
209
|
-
//
|
|
210
|
-
// so desktop sessions can
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
// Actors with a guardianPrincipalId but no externalUserId or
|
|
213
|
+
// conversationId: query by principal so desktop sessions can still
|
|
214
|
+
// discover pending guardian work via their bound principal.
|
|
215
|
+
if (actor.guardianPrincipalId) {
|
|
216
|
+
return listCanonicalGuardianRequests({
|
|
217
|
+
status: 'pending',
|
|
218
|
+
guardianPrincipalId: actor.guardianPrincipalId,
|
|
219
|
+
});
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
return [];
|
|
@@ -301,22 +308,30 @@ export async function routeGuardianReply(
|
|
|
301
308
|
// silently defaulting to approve_once.
|
|
302
309
|
if (!codeResult.remainingText || codeResult.remainingText.trim().length === 0) {
|
|
303
310
|
// Identity check: only expose request details to the assigned guardian
|
|
304
|
-
//
|
|
305
|
-
// applyCanonicalGuardianDecision to prevent leaking request details
|
|
311
|
+
// principal. Strict principal equality prevents leaking request details
|
|
306
312
|
// (toolName, questionText) to unauthorized senders.
|
|
313
|
+
if (!actor.guardianPrincipalId) {
|
|
314
|
+
return {
|
|
315
|
+
decisionApplied: false,
|
|
316
|
+
consumed: true,
|
|
317
|
+
type: 'code_only_clarification',
|
|
318
|
+
requestId: request.id,
|
|
319
|
+
replyText: 'Request not found.',
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
307
323
|
if (
|
|
308
|
-
request.
|
|
309
|
-
|
|
310
|
-
actor.externalUserId !== request.guardianExternalUserId
|
|
324
|
+
request.guardianPrincipalId &&
|
|
325
|
+
actor.guardianPrincipalId !== request.guardianPrincipalId
|
|
311
326
|
) {
|
|
312
327
|
log.warn(
|
|
313
328
|
{
|
|
314
|
-
event: '
|
|
329
|
+
event: 'router_code_only_principal_mismatch',
|
|
315
330
|
requestId: request.id,
|
|
316
|
-
|
|
317
|
-
|
|
331
|
+
expectedPrincipal: request.guardianPrincipalId,
|
|
332
|
+
actualPrincipal: actor.guardianPrincipalId,
|
|
318
333
|
},
|
|
319
|
-
'Code-only clarification blocked: actor
|
|
334
|
+
'Code-only clarification blocked: actor principal does not match request principal',
|
|
320
335
|
);
|
|
321
336
|
return {
|
|
322
337
|
decisionApplied: false,
|
|
@@ -492,7 +507,9 @@ export async function routeGuardianReply(
|
|
|
492
507
|
// Attach the engine's reply text for stale/expired/identity-mismatch cases,
|
|
493
508
|
// but preserve resolver-authored replies (for example verification codes)
|
|
494
509
|
// and explicit resolver-failure text.
|
|
495
|
-
const hasResolverReplyText = Boolean(
|
|
510
|
+
const hasResolverReplyText = Boolean(
|
|
511
|
+
result.canonicalResult?.applied && result.canonicalResult.resolverReplyText,
|
|
512
|
+
);
|
|
496
513
|
if (engineResult.replyText && result.type !== 'canonical_resolver_failed' && !hasResolverReplyText) {
|
|
497
514
|
result.replyText = engineResult.replyText;
|
|
498
515
|
}
|
|
@@ -5,24 +5,30 @@
|
|
|
5
5
|
* 'vellum' channel with a guardianPrincipalId. This is required for
|
|
6
6
|
* the identity-bound hatch bootstrap flow.
|
|
7
7
|
*
|
|
8
|
-
* - If a vellum binding already exists, no-op.
|
|
8
|
+
* - If a vellum binding already exists with a guardianPrincipalId, no-op.
|
|
9
|
+
* - If a vellum binding exists but lacks guardianPrincipalId, backfill it
|
|
10
|
+
* from the binding's guardianExternalUserId.
|
|
9
11
|
* - If no vellum binding exists, creates one with a fresh principal.
|
|
10
12
|
* - Preserves existing guardian bindings for other channels unchanged.
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
import { eq } from 'drizzle-orm';
|
|
13
16
|
import { v4 as uuid } from 'uuid';
|
|
14
17
|
|
|
18
|
+
import { getDb } from '../memory/db.js';
|
|
15
19
|
import {
|
|
16
20
|
createBinding,
|
|
17
21
|
getActiveBinding,
|
|
18
22
|
} from '../memory/guardian-bindings.js';
|
|
23
|
+
import { channelGuardianBindings } from '../memory/schema.js';
|
|
19
24
|
import { getLogger } from '../util/logger.js';
|
|
20
25
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from './assistant-scope.js';
|
|
21
26
|
|
|
22
27
|
const log = getLogger('guardian-vellum-migration');
|
|
23
28
|
|
|
24
29
|
/**
|
|
25
|
-
* Ensure a vellum guardian binding exists for the given assistant
|
|
30
|
+
* Ensure a vellum guardian binding exists for the given assistant,
|
|
31
|
+
* with a populated guardianPrincipalId.
|
|
26
32
|
* Called during daemon startup to backfill existing installations.
|
|
27
33
|
*
|
|
28
34
|
* Returns the guardianPrincipalId (existing or newly created).
|
|
@@ -30,11 +36,28 @@ const log = getLogger('guardian-vellum-migration');
|
|
|
30
36
|
export function ensureVellumGuardianBinding(assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID): string {
|
|
31
37
|
const existing = getActiveBinding(assistantId, 'vellum');
|
|
32
38
|
if (existing) {
|
|
39
|
+
// If the binding exists but is missing guardianPrincipalId, backfill it
|
|
40
|
+
// from the binding's guardianExternalUserId (the canonical identity).
|
|
41
|
+
if (!existing.guardianPrincipalId) {
|
|
42
|
+
const principalId = existing.guardianExternalUserId;
|
|
43
|
+
const db = getDb();
|
|
44
|
+
db.update(channelGuardianBindings)
|
|
45
|
+
.set({ guardianPrincipalId: principalId, updatedAt: Date.now() })
|
|
46
|
+
.where(eq(channelGuardianBindings.id, existing.id))
|
|
47
|
+
.run();
|
|
48
|
+
|
|
49
|
+
log.info(
|
|
50
|
+
{ assistantId, guardianPrincipalId: principalId },
|
|
51
|
+
'Backfilled guardianPrincipalId on existing vellum binding',
|
|
52
|
+
);
|
|
53
|
+
return principalId;
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
log.debug(
|
|
34
|
-
{ assistantId, guardianPrincipalId: existing.
|
|
35
|
-
'Vellum guardian binding already exists',
|
|
57
|
+
{ assistantId, guardianPrincipalId: existing.guardianPrincipalId },
|
|
58
|
+
'Vellum guardian binding already exists with principal',
|
|
36
59
|
);
|
|
37
|
-
return existing.
|
|
60
|
+
return existing.guardianPrincipalId;
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
const guardianPrincipalId = `vellum-principal-${uuid()}`;
|
|
@@ -44,6 +67,7 @@ export function ensureVellumGuardianBinding(assistantId: string = DAEMON_INTERNA
|
|
|
44
67
|
channel: 'vellum',
|
|
45
68
|
guardianExternalUserId: guardianPrincipalId,
|
|
46
69
|
guardianDeliveryChatId: 'local',
|
|
70
|
+
guardianPrincipalId,
|
|
47
71
|
verifiedVia: 'startup-migration',
|
|
48
72
|
metadataJson: JSON.stringify({ migratedAt: Date.now() }),
|
|
49
73
|
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared types for the runtime HTTP server and its route handlers.
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
CallPointerMessageContext,
|
|
6
|
-
ComposeCallPointerMessageOptions,
|
|
7
|
-
} from '../calls/call-pointer-message-composer.js';
|
|
8
4
|
import type { ChannelId, InterfaceId } from '../channels/types.js';
|
|
9
5
|
import type { Session } from '../daemon/session.js';
|
|
10
6
|
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
@@ -59,15 +55,6 @@ export type ApprovalConversationGenerator = (
|
|
|
59
55
|
context: ApprovalConversationContext,
|
|
60
56
|
) => Promise<ApprovalConversationResult>;
|
|
61
57
|
|
|
62
|
-
/**
|
|
63
|
-
* Daemon-injected function that generates call pointer copy using a provider.
|
|
64
|
-
* Returns generated text or `null` on failure (caller falls back to deterministic text).
|
|
65
|
-
*/
|
|
66
|
-
export type PointerCopyGenerator = (
|
|
67
|
-
context: CallPointerMessageContext,
|
|
68
|
-
options?: ComposeCallPointerMessageOptions,
|
|
69
|
-
) => Promise<string | null>;
|
|
70
|
-
|
|
71
58
|
/**
|
|
72
59
|
* Daemon-injected function that generates guardian action copy using a provider.
|
|
73
60
|
* Returns generated text or `null` on failure (caller falls back to deterministic text).
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
resolveGuardianContext,
|
|
21
21
|
toGuardianRuntimeContext,
|
|
22
22
|
} from './guardian-context-resolver.js';
|
|
23
|
+
import { ensureVellumGuardianBinding } from './guardian-vellum-migration.js';
|
|
23
24
|
|
|
24
25
|
const log = getLogger('local-actor-identity');
|
|
25
26
|
|
|
@@ -42,17 +43,22 @@ export function resolveLocalIpcGuardianContext(
|
|
|
42
43
|
const binding = getActiveBinding(assistantId, 'vellum');
|
|
43
44
|
|
|
44
45
|
if (!binding) {
|
|
45
|
-
// No vellum binding yet (pre-bootstrap).
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
// No vellum binding yet (pre-bootstrap). Eagerly create one so
|
|
47
|
+
// downstream code that creates decisionable canonical requests
|
|
48
|
+
// (tool_approval, pending_question) always has a guardianPrincipalId
|
|
49
|
+
// available. Without this, createCanonicalGuardianRequest throws
|
|
50
|
+
// IntegrityError and the request is silently dropped.
|
|
51
|
+
log.debug('No vellum guardian binding found; bootstrapping binding for IPC');
|
|
52
|
+
const principalId = ensureVellumGuardianBinding(assistantId);
|
|
53
|
+
|
|
54
|
+
// Re-resolve through the shared pipeline now that the binding exists.
|
|
55
|
+
const guardianCtx = resolveGuardianContext({
|
|
56
|
+
assistantId,
|
|
57
|
+
sourceChannel: 'vellum',
|
|
58
|
+
conversationExternalId: 'local',
|
|
59
|
+
actorExternalId: principalId,
|
|
60
|
+
});
|
|
61
|
+
return toGuardianRuntimeContext(sourceChannel, guardianCtx);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
const guardianPrincipalId = binding.guardianExternalUserId;
|
|
@@ -66,8 +72,8 @@ export function resolveLocalIpcGuardianContext(
|
|
|
66
72
|
const guardianCtx = resolveGuardianContext({
|
|
67
73
|
assistantId,
|
|
68
74
|
sourceChannel: 'vellum',
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
conversationExternalId: 'local',
|
|
76
|
+
actorExternalId: guardianPrincipalId,
|
|
71
77
|
});
|
|
72
78
|
|
|
73
79
|
// Overlay the caller's actual sourceChannel onto the resolved context
|
|
@@ -116,8 +116,8 @@ export function verifyHttpActorToken(req: Request): ActorTokenVerification {
|
|
|
116
116
|
const guardianCtx = resolveGuardianContext({
|
|
117
117
|
assistantId,
|
|
118
118
|
sourceChannel: 'vellum',
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
conversationExternalId: 'local',
|
|
120
|
+
actorExternalId: claims.guardianPrincipalId,
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
const guardianContext = toGuardianRuntimeContext('vellum' as ChannelId, guardianCtx);
|
|
@@ -34,17 +34,17 @@ export async function handleReplayDeadLetters(req: Request): Promise<Response> {
|
|
|
34
34
|
export async function handleChannelDeliveryAck(req: Request): Promise<Response> {
|
|
35
35
|
const body = await req.json() as {
|
|
36
36
|
sourceChannel?: string;
|
|
37
|
-
|
|
37
|
+
conversationExternalId?: string;
|
|
38
38
|
externalMessageId?: string;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const { sourceChannel,
|
|
41
|
+
const { sourceChannel, conversationExternalId, externalMessageId } = body;
|
|
42
42
|
|
|
43
43
|
if (!sourceChannel || typeof sourceChannel !== 'string') {
|
|
44
44
|
return httpError('BAD_REQUEST', 'sourceChannel is required', 400);
|
|
45
45
|
}
|
|
46
|
-
if (!
|
|
47
|
-
return httpError('BAD_REQUEST', '
|
|
46
|
+
if (!conversationExternalId || typeof conversationExternalId !== 'string') {
|
|
47
|
+
return httpError('BAD_REQUEST', 'conversationExternalId is required', 400);
|
|
48
48
|
}
|
|
49
49
|
if (!externalMessageId || typeof externalMessageId !== 'string') {
|
|
50
50
|
return httpError('BAD_REQUEST', 'externalMessageId is required', 400);
|
|
@@ -52,7 +52,7 @@ export async function handleChannelDeliveryAck(req: Request): Promise<Response>
|
|
|
52
52
|
|
|
53
53
|
const acked = channelDeliveryStore.acknowledgeDelivery(
|
|
54
54
|
sourceChannel,
|
|
55
|
-
|
|
55
|
+
conversationExternalId,
|
|
56
56
|
externalMessageId,
|
|
57
57
|
);
|
|
58
58
|
|
|
@@ -86,6 +86,8 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
86
86
|
approvalConversationGenerator?: ApprovalConversationGenerator;
|
|
87
87
|
/** Verified actor identity from actor-token middleware. */
|
|
88
88
|
verifiedActorExternalUserId?: string;
|
|
89
|
+
/** Verified actor principal ID for principal-based authorization. */
|
|
90
|
+
verifiedActorPrincipalId?: string;
|
|
89
91
|
}): Promise<{ consumed: boolean; messageId?: string }> {
|
|
90
92
|
const {
|
|
91
93
|
conversationId,
|
|
@@ -97,6 +99,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
97
99
|
onEvent,
|
|
98
100
|
approvalConversationGenerator,
|
|
99
101
|
verifiedActorExternalUserId,
|
|
102
|
+
verifiedActorPrincipalId,
|
|
100
103
|
} = params;
|
|
101
104
|
const trimmedContent = content.trim();
|
|
102
105
|
|
|
@@ -113,12 +116,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
113
116
|
actor: {
|
|
114
117
|
externalUserId: verifiedActorExternalUserId,
|
|
115
118
|
channel: sourceChannel,
|
|
116
|
-
|
|
117
|
-
// that the identity-match checks in applyCanonicalGuardianDecision
|
|
118
|
-
// actually run. Only fall back to isTrusted when no verified identity
|
|
119
|
-
// was resolved (defensive — shouldn't happen for vellum since
|
|
120
|
-
// verification runs upstream).
|
|
121
|
-
isTrusted: !verifiedActorExternalUserId,
|
|
119
|
+
guardianPrincipalId: verifiedActorPrincipalId,
|
|
122
120
|
},
|
|
123
121
|
conversationId,
|
|
124
122
|
pendingRequestIds,
|
|
@@ -157,7 +155,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
|
|
|
157
155
|
assistantMessageChannel: sourceChannel,
|
|
158
156
|
userMessageInterface: sourceInterface,
|
|
159
157
|
assistantMessageInterface: sourceInterface,
|
|
160
|
-
|
|
158
|
+
provenanceTrustClass: 'guardian' as const,
|
|
161
159
|
};
|
|
162
160
|
|
|
163
161
|
const userMessage = createUserMessage(content, attachments);
|
|
@@ -345,33 +343,41 @@ function makeHubPublisher(
|
|
|
345
343
|
|
|
346
344
|
// Create a canonical guardian request so IPC/HTTP handlers can find it
|
|
347
345
|
// via applyCanonicalGuardianDecision.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
requesterExternalUserId: guardianContext?.requesterExternalUserId,
|
|
357
|
-
requesterChatId: guardianContext?.requesterChatId,
|
|
358
|
-
guardianExternalUserId: guardianContext?.guardianExternalUserId,
|
|
359
|
-
toolName: msg.toolName,
|
|
360
|
-
status: 'pending',
|
|
361
|
-
requestCode: generateCanonicalRequestCode(),
|
|
362
|
-
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// For trusted-contact sessions, bridge to guardian.question so the
|
|
366
|
-
// guardian gets notified and can approve via callback/request-code.
|
|
367
|
-
if (guardianContext) {
|
|
368
|
-
bridgeConfirmationRequestToGuardian({
|
|
369
|
-
canonicalRequest,
|
|
370
|
-
guardianContext,
|
|
346
|
+
try {
|
|
347
|
+
const guardianContext = session.guardianContext;
|
|
348
|
+
const sourceChannel = guardianContext?.sourceChannel ?? 'vellum';
|
|
349
|
+
const canonicalRequest = createCanonicalGuardianRequest({
|
|
350
|
+
id: msg.requestId,
|
|
351
|
+
kind: 'tool_approval',
|
|
352
|
+
sourceType: resolveCanonicalRequestSourceType(sourceChannel),
|
|
353
|
+
sourceChannel,
|
|
371
354
|
conversationId,
|
|
355
|
+
requesterExternalUserId: guardianContext?.requesterExternalUserId,
|
|
356
|
+
requesterChatId: guardianContext?.requesterChatId,
|
|
357
|
+
guardianExternalUserId: guardianContext?.guardianExternalUserId,
|
|
358
|
+
guardianPrincipalId: guardianContext?.guardianPrincipalId ?? undefined,
|
|
372
359
|
toolName: msg.toolName,
|
|
373
|
-
|
|
360
|
+
status: 'pending',
|
|
361
|
+
requestCode: generateCanonicalRequestCode(),
|
|
362
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
374
363
|
});
|
|
364
|
+
|
|
365
|
+
// For trusted-contact sessions, bridge to guardian.question so the
|
|
366
|
+
// guardian gets notified and can approve via callback/request-code.
|
|
367
|
+
if (guardianContext) {
|
|
368
|
+
bridgeConfirmationRequestToGuardian({
|
|
369
|
+
canonicalRequest,
|
|
370
|
+
guardianContext,
|
|
371
|
+
conversationId,
|
|
372
|
+
toolName: msg.toolName,
|
|
373
|
+
assistantId: session.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
} catch (err) {
|
|
377
|
+
log.debug(
|
|
378
|
+
{ err, requestId: msg.requestId, conversationId },
|
|
379
|
+
'Failed to create canonical request from hub publisher',
|
|
380
|
+
);
|
|
375
381
|
}
|
|
376
382
|
} else if (msg.type === 'secret_request') {
|
|
377
383
|
pendingInteractions.register(msg.requestId, {
|
|
@@ -505,12 +511,15 @@ export async function handleSendMessage(
|
|
|
505
511
|
? smDeps.resolveAttachments(attachmentIds)
|
|
506
512
|
: [];
|
|
507
513
|
|
|
508
|
-
// Resolve the verified actor's external user ID for inline
|
|
509
|
-
// routing. Uses the guardianExternalUserId
|
|
510
|
-
// (actor-token or local-fallback)
|
|
514
|
+
// Resolve the verified actor's external user ID and principal for inline
|
|
515
|
+
// approval routing. Uses the guardianExternalUserId and guardianPrincipalId
|
|
516
|
+
// from the verified context (actor-token or local-fallback).
|
|
511
517
|
const verifiedActorExternalUserId = actorVerification?.ok
|
|
512
518
|
? actorVerification.guardianContext.guardianExternalUserId
|
|
513
|
-
:
|
|
519
|
+
: session.guardianContext?.guardianExternalUserId;
|
|
520
|
+
const verifiedActorPrincipalId = actorVerification?.ok
|
|
521
|
+
? actorVerification.guardianContext.guardianPrincipalId ?? undefined
|
|
522
|
+
: session.guardianContext?.guardianPrincipalId ?? undefined;
|
|
514
523
|
|
|
515
524
|
// Try to consume the message as a canonical guardian approval/rejection reply.
|
|
516
525
|
// On failure, degrade to the existing queue/auto-deny path rather than
|
|
@@ -526,6 +535,7 @@ export async function handleSendMessage(
|
|
|
526
535
|
onEvent,
|
|
527
536
|
approvalConversationGenerator: deps.approvalConversationGenerator,
|
|
528
537
|
verifiedActorExternalUserId,
|
|
538
|
+
verifiedActorPrincipalId,
|
|
529
539
|
});
|
|
530
540
|
if (inlineReplyResult.consumed) {
|
|
531
541
|
return Response.json(
|
|
@@ -124,13 +124,19 @@ export async function handleGuardianActionDecision(req: Request, server: ServerW
|
|
|
124
124
|
? tokenResult.claims.guardianPrincipalId
|
|
125
125
|
: tokenResult.guardianContext.guardianExternalUserId;
|
|
126
126
|
|
|
127
|
+
// Resolve the actor's principal ID: from the token claims if present,
|
|
128
|
+
// otherwise from the vellum guardian binding (local fallback).
|
|
129
|
+
const actorPrincipalId = tokenResult.claims
|
|
130
|
+
? tokenResult.claims.guardianPrincipalId
|
|
131
|
+
: tokenResult.guardianContext.guardianPrincipalId ?? undefined;
|
|
132
|
+
|
|
127
133
|
const canonicalResult = await applyCanonicalGuardianDecision({
|
|
128
134
|
requestId,
|
|
129
135
|
action: action as ApprovalAction,
|
|
130
136
|
actorContext: {
|
|
131
137
|
externalUserId: actorExternalUserId,
|
|
132
138
|
channel: 'vellum',
|
|
133
|
-
|
|
139
|
+
guardianPrincipalId: actorPrincipalId,
|
|
134
140
|
},
|
|
135
141
|
userText: undefined,
|
|
136
142
|
});
|