@vellumai/assistant 0.4.2 → 0.4.4
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/.env.example +3 -0
- package/ARCHITECTURE.md +124 -10
- package/README.md +43 -35
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +1 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/actor-token-service.test.ts +1099 -0
- package/src/__tests__/agent-loop.test.ts +51 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
- package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
- package/src/__tests__/call-controller.test.ts +49 -0
- package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
- package/src/__tests__/call-pointer-messages.test.ts +93 -3
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/callback-handoff-copy.test.ts +186 -0
- package/src/__tests__/channel-approval-routes.test.ts +133 -12
- package/src/__tests__/channel-guardian.test.ts +0 -86
- package/src/__tests__/channel-readiness-service.test.ts +10 -16
- package/src/__tests__/checker.test.ts +33 -12
- package/src/__tests__/config-schema.test.ts +6 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
- package/src/__tests__/conversation-routes.test.ts +12 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/daemon-server-session-init.test.ts +4 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -5
- package/src/__tests__/guardian-question-mode.test.ts +200 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
- package/src/__tests__/guardian-routing-state.test.ts +525 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
- package/src/__tests__/handlers-telegram-config.test.ts +0 -83
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
- package/src/__tests__/headless-browser-navigate.test.ts +2 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +159 -9
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +106 -2
- package/src/__tests__/notification-guardian-path.test.ts +3 -0
- package/src/__tests__/recording-intent-handler.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +1475 -33
- package/src/__tests__/send-endpoint-busy.test.ts +5 -0
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-confirmation-signals.test.ts +523 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +21 -2
- package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -13
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +12 -3
- package/src/approvals/guardian-request-resolvers.ts +169 -11
- package/src/calls/call-constants.ts +29 -0
- package/src/calls/call-controller.ts +11 -3
- package/src/calls/call-domain.ts +33 -11
- package/src/calls/call-pointer-message-composer.ts +154 -0
- package/src/calls/call-pointer-messages.ts +106 -27
- package/src/calls/guardian-dispatch.ts +4 -2
- package/src/calls/relay-server.ts +921 -112
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +4 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/cli.ts +5 -4
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
- package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
- package/src/config/bundled-skills/messaging/SKILL.md +61 -12
- package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
- package/src/config/bundled-skills/twitter/SKILL.md +3 -3
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
- package/src/config/calls-schema.ts +36 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +2 -2
- package/src/config/skills.ts +11 -0
- package/src/config/system-prompt.ts +11 -1
- package/src/config/templates/SOUL.md +2 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
- package/src/daemon/call-pointer-generators.ts +59 -0
- package/src/daemon/computer-use-session.ts +2 -5
- package/src/daemon/handlers/apps.ts +76 -20
- package/src/daemon/handlers/config-channels.ts +9 -61
- package/src/daemon/handlers/config-inbox.ts +11 -3
- package/src/daemon/handlers/config-ingress.ts +28 -3
- package/src/daemon/handlers/config-telegram.ts +12 -0
- package/src/daemon/handlers/config.ts +2 -6
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/pairing.ts +2 -0
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +59 -5
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +1 -97
- package/src/daemon/ipc-contract/messages.ts +47 -1
- package/src/daemon/ipc-contract/notifications.ts +11 -0
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +17 -0
- package/src/daemon/server.ts +16 -2
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +24 -12
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +6 -1
- package/src/daemon/session-surfaces.ts +32 -3
- package/src/daemon/session.ts +88 -1
- package/src/daemon/tool-side-effects.ts +22 -0
- package/src/home-base/prebuilt/brain-graph.html +1483 -0
- package/src/home-base/prebuilt/index.html +40 -0
- package/src/inbound/platform-callback-registration.ts +157 -0
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +8 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema.ts +26 -5
- package/src/messaging/provider-types.ts +24 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/adapter.ts +127 -0
- package/src/messaging/providers/sms/adapter.ts +40 -37
- package/src/notifications/adapters/macos.ts +45 -2
- package/src/notifications/broadcaster.ts +16 -0
- package/src/notifications/copy-composer.ts +50 -2
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +18 -9
- package/src/notifications/guardian-question-mode.ts +419 -0
- package/src/notifications/signal.ts +14 -3
- package/src/permissions/checker.ts +13 -1
- package/src/permissions/prompter.ts +14 -0
- package/src/providers/anthropic/client.ts +20 -0
- package/src/providers/provider-send-message.ts +15 -3
- package/src/runtime/access-request-helper.ts +82 -4
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -0
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/channel-approvals.ts +5 -3
- package/src/runtime/channel-readiness-service.ts +23 -64
- package/src/runtime/channel-readiness-types.ts +3 -4
- package/src/runtime/channel-retry-sweep.ts +4 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-context-resolver.ts +82 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -7
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +75 -31
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +10 -1
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +142 -53
- package/src/runtime/routes/events-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +45 -3
- package/src/runtime/routes/guardian-approval-interception.ts +29 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +147 -5
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/runtime/routes/integration-routes.ts +7 -15
- package/src/runtime/routes/pairing-routes.ts +163 -0
- package/src/runtime/routes/twilio-routes.ts +934 -0
- package/src/runtime/tool-grant-request-helper.ts +3 -1
- package/src/security/oauth2.ts +27 -2
- package/src/security/token-manager.ts +46 -10
- package/src/tools/browser/browser-execution.ts +4 -3
- package/src/tools/browser/browser-handoff.ts +10 -18
- package/src/tools/browser/browser-manager.ts +80 -25
- package/src/tools/browser/browser-screencast.ts +35 -119
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/permission-checker.ts +15 -4
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +244 -19
- package/src/workspace/git-service.ts +19 -0
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- package/src/daemon/handlers/config-twilio.ts +0 -1082
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* GET /v1/events?conversationKey=...
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Bearer auth is enforced by RuntimeHttpServer before this handler is called.
|
|
7
|
+
* Actor-token identity verification (with local CLI fallback) is performed
|
|
8
|
+
* within this handler to bind the SSE stream to a verified actor identity.
|
|
7
9
|
* Subscribers receive all assistant events scoped to the given conversation.
|
|
8
10
|
*/
|
|
9
11
|
|
|
@@ -11,7 +13,9 @@ import { getOrCreateConversation } from '../../memory/conversation-key-store.js'
|
|
|
11
13
|
import { formatSseFrame, formatSseHeartbeat } from '../assistant-event.js';
|
|
12
14
|
import type { AssistantEventSubscription } from '../assistant-event-hub.js';
|
|
13
15
|
import { AssistantEventHub,assistantEventHub } from '../assistant-event-hub.js';
|
|
16
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
|
|
14
17
|
import { httpError } from '../http-errors.js';
|
|
18
|
+
import { type ServerWithRequestIP, verifyHttpActorTokenWithLocalFallback } from '../middleware/actor-token.js';
|
|
15
19
|
|
|
16
20
|
/** Keep-alive comment sent to idle clients every 30 s by default. */
|
|
17
21
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
@@ -29,11 +33,23 @@ const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
|
29
33
|
export function handleSubscribeAssistantEvents(
|
|
30
34
|
req: Request,
|
|
31
35
|
url: URL,
|
|
32
|
-
options?:
|
|
33
|
-
hub?: AssistantEventHub;
|
|
34
|
-
heartbeatIntervalMs?: number;
|
|
35
|
-
},
|
|
36
|
+
options?:
|
|
37
|
+
| { hub?: AssistantEventHub; heartbeatIntervalMs?: number; skipActorVerification?: false; server: ServerWithRequestIP }
|
|
38
|
+
| { hub?: AssistantEventHub; heartbeatIntervalMs?: number; skipActorVerification: true },
|
|
36
39
|
): Response {
|
|
40
|
+
// Verify actor-token identity for vellum channel requests, with local
|
|
41
|
+
// CLI fallback for bearer-authenticated clients without X-Actor-Token.
|
|
42
|
+
if (options && !options.skipActorVerification) {
|
|
43
|
+
const actorVerification = verifyHttpActorTokenWithLocalFallback(req, options.server);
|
|
44
|
+
if (!actorVerification.ok) {
|
|
45
|
+
return httpError(
|
|
46
|
+
actorVerification.status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
47
|
+
actorVerification.message,
|
|
48
|
+
actorVerification.status,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
const conversationKey = url.searchParams.get('conversationKey');
|
|
38
54
|
if (!conversationKey) {
|
|
39
55
|
return httpError('BAD_REQUEST', 'conversationKey is required', 400);
|
|
@@ -50,8 +66,6 @@ export function handleSubscribeAssistantEvents(
|
|
|
50
66
|
// closures are in place before events can arrive. `controllerRef` is set
|
|
51
67
|
// synchronously inside ReadableStream's start(), so it is non-null by the
|
|
52
68
|
// time any event or eviction fires.
|
|
53
|
-
// 'self' is the assistantId used by buildAssistantEvent('self', ...) for
|
|
54
|
-
// all HTTP and voice session events.
|
|
55
69
|
let controllerRef: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
56
70
|
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
57
71
|
let sub!: AssistantEventSubscription;
|
|
@@ -63,7 +77,7 @@ export function handleSubscribeAssistantEvents(
|
|
|
63
77
|
|
|
64
78
|
try {
|
|
65
79
|
sub = hub.subscribe(
|
|
66
|
-
{ assistantId:
|
|
80
|
+
{ assistantId: DAEMON_INTERNAL_ASSISTANT_ID, sessionId: mapping.conversationId },
|
|
67
81
|
(event) => {
|
|
68
82
|
const controller = controllerRef;
|
|
69
83
|
if (!controller) return;
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These endpoints let desktop clients fetch pending guardian prompts and
|
|
5
5
|
* submit button decisions without relying on text parsing.
|
|
6
|
+
*
|
|
7
|
+
* All guardian action endpoints require a valid actor token via the
|
|
8
|
+
* X-Actor-Token header (with local CLI fallback). Guardian decisions
|
|
9
|
+
* additionally verify the actor is the bound guardian.
|
|
6
10
|
*/
|
|
7
11
|
import {
|
|
8
12
|
applyCanonicalGuardianDecision,
|
|
@@ -16,6 +20,12 @@ import type { ApprovalAction } from '../channel-approval-types.js';
|
|
|
16
20
|
import type { GuardianDecisionPrompt } from '../guardian-decision-types.js';
|
|
17
21
|
import { buildDecisionActions } from '../guardian-decision-types.js';
|
|
18
22
|
import { httpError } from '../http-errors.js';
|
|
23
|
+
import {
|
|
24
|
+
isActorBoundGuardian,
|
|
25
|
+
isLocalFallbackBoundGuardian,
|
|
26
|
+
type ServerWithRequestIP,
|
|
27
|
+
verifyHttpActorTokenWithLocalFallback,
|
|
28
|
+
} from '../middleware/actor-token.js';
|
|
19
29
|
|
|
20
30
|
// ---------------------------------------------------------------------------
|
|
21
31
|
// GET /v1/guardian-actions/pending?conversationId=...
|
|
@@ -23,12 +33,22 @@ import { httpError } from '../http-errors.js';
|
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
35
|
* List pending guardian decision prompts for a conversation.
|
|
36
|
+
* Requires a valid actor token.
|
|
26
37
|
*
|
|
27
38
|
* Returns guardian approval requests (from the channel guardian store) that
|
|
28
39
|
* are still pending, mapped to the GuardianDecisionPrompt shape so clients
|
|
29
40
|
* can render structured button UIs.
|
|
30
41
|
*/
|
|
31
|
-
export function handleGuardianActionsPending(req: Request): Response {
|
|
42
|
+
export function handleGuardianActionsPending(req: Request, server: ServerWithRequestIP): Response {
|
|
43
|
+
const tokenResult = verifyHttpActorTokenWithLocalFallback(req, server);
|
|
44
|
+
if (!tokenResult.ok) {
|
|
45
|
+
return httpError(
|
|
46
|
+
tokenResult.status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
47
|
+
tokenResult.message,
|
|
48
|
+
tokenResult.status,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
const url = new URL(req.url);
|
|
33
53
|
const conversationId = url.searchParams.get('conversationId');
|
|
34
54
|
|
|
@@ -46,12 +66,28 @@ export function handleGuardianActionsPending(req: Request): Response {
|
|
|
46
66
|
|
|
47
67
|
/**
|
|
48
68
|
* Submit a guardian action decision.
|
|
69
|
+
* Requires a valid actor token for a bound guardian.
|
|
49
70
|
*
|
|
50
71
|
* Routes all decisions through the unified canonical guardian decision
|
|
51
72
|
* primitive which handles CAS resolution, resolver dispatch, and grant
|
|
52
73
|
* minting.
|
|
53
74
|
*/
|
|
54
|
-
export async function handleGuardianActionDecision(req: Request): Promise<Response> {
|
|
75
|
+
export async function handleGuardianActionDecision(req: Request, server: ServerWithRequestIP): Promise<Response> {
|
|
76
|
+
const tokenResult = verifyHttpActorTokenWithLocalFallback(req, server);
|
|
77
|
+
if (!tokenResult.ok) {
|
|
78
|
+
return httpError(
|
|
79
|
+
tokenResult.status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
80
|
+
tokenResult.message,
|
|
81
|
+
tokenResult.status,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const isBoundGuardian = tokenResult.claims
|
|
85
|
+
? isActorBoundGuardian(tokenResult.claims)
|
|
86
|
+
: isLocalFallbackBoundGuardian();
|
|
87
|
+
if (!isBoundGuardian) {
|
|
88
|
+
return httpError('FORBIDDEN', 'Actor is not the bound guardian for this channel', 403);
|
|
89
|
+
}
|
|
90
|
+
|
|
55
91
|
const body = await req.json() as {
|
|
56
92
|
requestId?: string;
|
|
57
93
|
action?: string;
|
|
@@ -82,11 +118,17 @@ export async function handleGuardianActionDecision(req: Request): Promise<Respon
|
|
|
82
118
|
}
|
|
83
119
|
}
|
|
84
120
|
|
|
121
|
+
// Resolve the actor's external user ID: from the token claims if present,
|
|
122
|
+
// otherwise from the vellum guardian binding (local fallback).
|
|
123
|
+
const actorExternalUserId = tokenResult.claims
|
|
124
|
+
? tokenResult.claims.guardianPrincipalId
|
|
125
|
+
: tokenResult.guardianContext.guardianExternalUserId;
|
|
126
|
+
|
|
85
127
|
const canonicalResult = await applyCanonicalGuardianDecision({
|
|
86
128
|
requestId,
|
|
87
129
|
action: action as ApprovalAction,
|
|
88
130
|
actorContext: {
|
|
89
|
-
externalUserId:
|
|
131
|
+
externalUserId: actorExternalUserId,
|
|
90
132
|
channel: 'vellum',
|
|
91
133
|
isTrusted: true,
|
|
92
134
|
},
|
|
@@ -741,6 +741,35 @@ export async function handleApprovalInterception(
|
|
|
741
741
|
}
|
|
742
742
|
return { handled: true, type: 'decision_applied' };
|
|
743
743
|
}
|
|
744
|
+
|
|
745
|
+
// Guard: non-guardian actors with a guardian binding must not self-approve
|
|
746
|
+
// even when no guardian approval row exists yet. The guardian approval
|
|
747
|
+
// row is created asynchronously when the approval prompt is delivered
|
|
748
|
+
// to the guardian. In the window between the pending confirmation being
|
|
749
|
+
// created (isInteractive=true) and the guardian approval row being
|
|
750
|
+
// persisted, any non-guardian actor could otherwise fall through to the
|
|
751
|
+
// standard conversational engine / legacy parser and resolve their own
|
|
752
|
+
// pending request via handleChannelDecision.
|
|
753
|
+
if (guardianCtx.trustClass !== 'guardian' && guardianCtx.guardianExternalUserId) {
|
|
754
|
+
log.info(
|
|
755
|
+
{ conversationId, externalChatId, guardianExternalUserId: guardianCtx.guardianExternalUserId },
|
|
756
|
+
'Blocking non-guardian self-approval: pending confirmation exists but guardian approval row not yet created',
|
|
757
|
+
);
|
|
758
|
+
try {
|
|
759
|
+
const pendingText = await composeApprovalMessageGenerative({
|
|
760
|
+
scenario: 'request_pending_guardian',
|
|
761
|
+
channel: sourceChannel,
|
|
762
|
+
}, {}, approvalCopyGenerator);
|
|
763
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
764
|
+
chatId: externalChatId,
|
|
765
|
+
text: pendingText,
|
|
766
|
+
assistantId,
|
|
767
|
+
}, bearerToken);
|
|
768
|
+
} catch (err) {
|
|
769
|
+
log.error({ err, conversationId }, 'Failed to deliver guardian-pending notice to non-guardian actor (pre-row guard)');
|
|
770
|
+
}
|
|
771
|
+
return { handled: true, type: 'assistant_turn' };
|
|
772
|
+
}
|
|
744
773
|
}
|
|
745
774
|
}
|
|
746
775
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /v1/integrations/guardian/vellum/bootstrap
|
|
3
|
+
*
|
|
4
|
+
* Idempotent bootstrap endpoint for the vellum guardian channel.
|
|
5
|
+
* Creates or confirms a guardianPrincipalId and channel='vellum'
|
|
6
|
+
* guardian binding, then mints and returns an actor token bound
|
|
7
|
+
* to (assistantId, guardianPrincipalId, deviceId).
|
|
8
|
+
*
|
|
9
|
+
* Only the hashed token is persisted.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
import { v4 as uuid } from 'uuid';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createBinding,
|
|
18
|
+
getActiveBinding,
|
|
19
|
+
} from '../../memory/guardian-bindings.js';
|
|
20
|
+
import { getLogger } from '../../util/logger.js';
|
|
21
|
+
import { mintActorToken } from '../actor-token-service.js';
|
|
22
|
+
import {
|
|
23
|
+
createActorTokenRecord,
|
|
24
|
+
revokeByDeviceBinding,
|
|
25
|
+
} from '../actor-token-store.js';
|
|
26
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
|
|
27
|
+
import { httpError } from '../http-errors.js';
|
|
28
|
+
import type { ServerWithRequestIP } from '../middleware/actor-token.js';
|
|
29
|
+
|
|
30
|
+
const log = getLogger('guardian-bootstrap');
|
|
31
|
+
|
|
32
|
+
/** Hash a device ID for storage (same pattern as approved-devices-store). */
|
|
33
|
+
function hashDeviceId(deviceId: string): string {
|
|
34
|
+
return createHash('sha256').update(deviceId).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Ensure a guardianPrincipalId exists for the vellum channel.
|
|
39
|
+
* If a binding already exists, returns the existing guardianExternalUserId
|
|
40
|
+
* as the principal. Otherwise creates a new binding with a fresh principal.
|
|
41
|
+
*/
|
|
42
|
+
function ensureGuardianPrincipal(assistantId: string): {
|
|
43
|
+
guardianPrincipalId: string;
|
|
44
|
+
isNew: boolean;
|
|
45
|
+
} {
|
|
46
|
+
const existing = getActiveBinding(assistantId, 'vellum');
|
|
47
|
+
if (existing) {
|
|
48
|
+
return { guardianPrincipalId: existing.guardianExternalUserId, isNew: false };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Mint a new principal ID for the vellum channel
|
|
52
|
+
const guardianPrincipalId = `vellum-principal-${uuid()}`;
|
|
53
|
+
|
|
54
|
+
createBinding({
|
|
55
|
+
assistantId,
|
|
56
|
+
channel: 'vellum',
|
|
57
|
+
guardianExternalUserId: guardianPrincipalId,
|
|
58
|
+
guardianDeliveryChatId: 'local',
|
|
59
|
+
verifiedVia: 'bootstrap',
|
|
60
|
+
metadataJson: JSON.stringify({ bootstrappedAt: Date.now() }),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
log.info({ assistantId, guardianPrincipalId }, 'Created vellum guardian principal via bootstrap');
|
|
64
|
+
return { guardianPrincipalId, isNew: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Loopback addresses — used to gate the bootstrap endpoint to local-only. */
|
|
68
|
+
const LOOPBACK_ADDRESSES = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle POST /v1/integrations/guardian/vellum/bootstrap
|
|
72
|
+
*
|
|
73
|
+
* Body: { platform: 'macos', deviceId: string }
|
|
74
|
+
* Returns: { guardianPrincipalId, actorToken, isNew }
|
|
75
|
+
*
|
|
76
|
+
* This endpoint is loopback-only (macOS local use only). iOS devices
|
|
77
|
+
* obtain actor tokens exclusively through the QR pairing flow.
|
|
78
|
+
*/
|
|
79
|
+
export async function handleGuardianBootstrap(req: Request, server: ServerWithRequestIP): Promise<Response> {
|
|
80
|
+
// Reject proxied requests — bootstrap is local-only
|
|
81
|
+
if (req.headers.get('x-forwarded-for')) {
|
|
82
|
+
return httpError('FORBIDDEN', 'Bootstrap endpoint is local-only', 403);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Reject non-loopback peers
|
|
86
|
+
const peerIp = server.requestIP(req)?.address;
|
|
87
|
+
if (!peerIp || !LOOPBACK_ADDRESSES.has(peerIp)) {
|
|
88
|
+
return httpError('FORBIDDEN', 'Bootstrap endpoint is local-only', 403);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const body = await req.json() as Record<string, unknown>;
|
|
93
|
+
const platform = typeof body.platform === 'string' ? body.platform.trim() : '';
|
|
94
|
+
const deviceId = typeof body.deviceId === 'string' ? body.deviceId.trim() : '';
|
|
95
|
+
|
|
96
|
+
if (!platform || !deviceId) {
|
|
97
|
+
return httpError('BAD_REQUEST', 'Missing required fields: platform, deviceId', 400);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (platform !== 'macos') {
|
|
101
|
+
return httpError('BAD_REQUEST', 'Invalid platform. Bootstrap is macOS-only; iOS uses QR pairing.', 400);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
105
|
+
const { guardianPrincipalId, isNew } = ensureGuardianPrincipal(assistantId);
|
|
106
|
+
const hashedDeviceId = hashDeviceId(deviceId);
|
|
107
|
+
|
|
108
|
+
// Revoke any existing active tokens for this device binding
|
|
109
|
+
// so we maintain one-active-token-per-device
|
|
110
|
+
revokeByDeviceBinding(assistantId, guardianPrincipalId, hashedDeviceId);
|
|
111
|
+
|
|
112
|
+
// Mint a new actor token
|
|
113
|
+
const { token, tokenHash, claims } = mintActorToken({
|
|
114
|
+
assistantId,
|
|
115
|
+
platform,
|
|
116
|
+
deviceId,
|
|
117
|
+
guardianPrincipalId,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Store only the hash
|
|
121
|
+
createActorTokenRecord({
|
|
122
|
+
tokenHash,
|
|
123
|
+
assistantId,
|
|
124
|
+
guardianPrincipalId,
|
|
125
|
+
hashedDeviceId,
|
|
126
|
+
platform,
|
|
127
|
+
issuedAt: claims.iat,
|
|
128
|
+
expiresAt: claims.exp,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
log.info(
|
|
132
|
+
{ assistantId, platform, guardianPrincipalId, isNew },
|
|
133
|
+
'Guardian bootstrap completed',
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return Response.json({
|
|
137
|
+
guardianPrincipalId,
|
|
138
|
+
actorToken: token,
|
|
139
|
+
isNew,
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
log.error({ err }, 'Guardian bootstrap failed');
|
|
143
|
+
return httpError('INTERNAL_ERROR', 'Internal server error', 500);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { deleteConversationKey } from '../../memory/conversation-key-store.js';
|
|
5
5
|
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
6
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
|
|
6
7
|
import { httpError } from '../http-errors.js';
|
|
7
8
|
|
|
8
|
-
export async function handleDeleteConversation(req: Request, assistantId: string =
|
|
9
|
+
export async function handleDeleteConversation(req: Request, assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID): Promise<Response> {
|
|
9
10
|
const body = await req.json() as {
|
|
10
11
|
sourceChannel?: string;
|
|
11
12
|
externalChatId?: string;
|
|
@@ -26,14 +27,14 @@ export async function handleDeleteConversation(req: Request, assistantId: string
|
|
|
26
27
|
const legacyKey = `${sourceChannel}:${externalChatId}`;
|
|
27
28
|
const scopedKey = `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
|
|
28
29
|
deleteConversationKey(scopedKey);
|
|
29
|
-
if (assistantId ===
|
|
30
|
+
if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
30
31
|
deleteConversationKey(legacyKey);
|
|
31
32
|
}
|
|
32
33
|
// external_conversation_bindings is currently assistant-agnostic
|
|
33
34
|
// (unique by sourceChannel + externalChatId). Restrict mutations to the
|
|
34
35
|
// canonical self-assistant route so multi-assistant legacy routes do not
|
|
35
36
|
// clobber each other's bindings.
|
|
36
|
-
if (assistantId ===
|
|
37
|
+
if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
37
38
|
externalConversationStore.deleteBindingByChannelChat(sourceChannel, externalChatId);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -28,6 +28,7 @@ import { IngressBlockedError } from '../../util/errors.js';
|
|
|
28
28
|
import { getLogger } from '../../util/logger.js';
|
|
29
29
|
import { readHttpToken } from '../../util/platform.js';
|
|
30
30
|
import { notifyGuardianOfAccessRequest } from '../access-request-helper.js';
|
|
31
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
|
|
31
32
|
import {
|
|
32
33
|
buildApprovalUIMetadata,
|
|
33
34
|
getApprovalInfoByConversation,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
} from '../channel-guardian-service.js';
|
|
47
48
|
import { getTransport } from '../channel-invite-transport.js';
|
|
48
49
|
import { deliverChannelReply } from '../gateway-client.js';
|
|
49
|
-
import { resolveGuardianContext } from '../guardian-context-resolver.js';
|
|
50
|
+
import { resolveGuardianContext, resolveRoutingState } from '../guardian-context-resolver.js';
|
|
50
51
|
import { routeGuardianReply } from '../guardian-reply-router.js';
|
|
51
52
|
import {
|
|
52
53
|
composeChannelVerifyReply,
|
|
@@ -97,7 +98,7 @@ export async function handleChannelInbound(
|
|
|
97
98
|
req: Request,
|
|
98
99
|
processMessage?: MessageProcessor,
|
|
99
100
|
bearerToken?: string,
|
|
100
|
-
assistantId: string =
|
|
101
|
+
assistantId: string = DAEMON_INTERNAL_ASSISTANT_ID,
|
|
101
102
|
gatewayOriginSecret?: string,
|
|
102
103
|
approvalCopyGenerator?: ApprovalCopyGenerator,
|
|
103
104
|
approvalConversationGenerator?: ApprovalConversationGenerator,
|
|
@@ -414,6 +415,7 @@ export async function handleChannelInbound(
|
|
|
414
415
|
senderExternalUserId: canonicalSenderId ?? rawSenderId,
|
|
415
416
|
senderName: body.senderName,
|
|
416
417
|
senderUsername: body.senderUsername,
|
|
418
|
+
previousMemberStatus: resolvedMember.status,
|
|
417
419
|
});
|
|
418
420
|
guardianNotified = accessResult.notified;
|
|
419
421
|
} catch (err) {
|
|
@@ -580,7 +582,7 @@ export async function handleChannelInbound(
|
|
|
580
582
|
// external_conversation_bindings is assistant-agnostic. Restrict writes to
|
|
581
583
|
// self so assistant-scoped legacy routes do not overwrite each other's
|
|
582
584
|
// channel binding metadata for the same chat.
|
|
583
|
-
if (canonicalAssistantId ===
|
|
585
|
+
if (canonicalAssistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
584
586
|
externalConversationStore.upsertBinding({
|
|
585
587
|
conversationId: result.conversationId,
|
|
586
588
|
sourceChannel,
|
|
@@ -1346,6 +1348,13 @@ interface BackgroundProcessingParams {
|
|
|
1346
1348
|
const TELEGRAM_TYPING_INTERVAL_MS = 4_000;
|
|
1347
1349
|
const PENDING_APPROVAL_POLL_INTERVAL_MS = 300;
|
|
1348
1350
|
|
|
1351
|
+
// Module-level map tracking which approval requestIds have already been
|
|
1352
|
+
// notified to trusted contacts. Maps requestId -> conversationId so that
|
|
1353
|
+
// cleanup can be scoped to the owning conversation's poller, preventing
|
|
1354
|
+
// concurrent pollers from different conversations from evicting each
|
|
1355
|
+
// other's entries.
|
|
1356
|
+
const globalNotifiedApprovalRequestIds = new Map<string, string>();
|
|
1357
|
+
|
|
1349
1358
|
function delay(ms: number): Promise<void> {
|
|
1350
1359
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1351
1360
|
}
|
|
@@ -1447,7 +1456,7 @@ function startPendingApprovalPromptWatcher(params: {
|
|
|
1447
1456
|
replyCallbackUrl,
|
|
1448
1457
|
chatId: externalChatId,
|
|
1449
1458
|
sourceChannel,
|
|
1450
|
-
assistantId: assistantId ??
|
|
1459
|
+
assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1451
1460
|
bearerToken,
|
|
1452
1461
|
prompt,
|
|
1453
1462
|
uiMetadata: buildApprovalUIMetadata(prompt, info),
|
|
@@ -1477,6 +1486,126 @@ function startPendingApprovalPromptWatcher(params: {
|
|
|
1477
1486
|
};
|
|
1478
1487
|
}
|
|
1479
1488
|
|
|
1489
|
+
/**
|
|
1490
|
+
* Resolve a human-readable guardian name from the guardian binding metadata.
|
|
1491
|
+
* Returns the display name, username (prefixed with @), or undefined if
|
|
1492
|
+
* no name is available.
|
|
1493
|
+
*/
|
|
1494
|
+
function resolveGuardianDisplayName(
|
|
1495
|
+
assistantId: string,
|
|
1496
|
+
sourceChannel: ChannelId,
|
|
1497
|
+
): string | undefined {
|
|
1498
|
+
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
1499
|
+
if (!binding?.metadataJson) return undefined;
|
|
1500
|
+
try {
|
|
1501
|
+
const parsed = JSON.parse(binding.metadataJson) as Record<string, unknown>;
|
|
1502
|
+
if (typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0) {
|
|
1503
|
+
return parsed.displayName.trim();
|
|
1504
|
+
}
|
|
1505
|
+
if (typeof parsed.username === 'string' && parsed.username.trim().length > 0) {
|
|
1506
|
+
return `@${parsed.username.trim()}`;
|
|
1507
|
+
}
|
|
1508
|
+
} catch {
|
|
1509
|
+
// ignore malformed metadata
|
|
1510
|
+
}
|
|
1511
|
+
return undefined;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Start a poller that sends a one-shot "waiting for guardian approval" message
|
|
1516
|
+
* to the trusted contact when a confirmation_request enters guardian approval
|
|
1517
|
+
* wait. Deduplicates by requestId so each request only produces one message.
|
|
1518
|
+
*
|
|
1519
|
+
* Only activates for trusted-contact actors with a resolvable guardian route.
|
|
1520
|
+
*/
|
|
1521
|
+
function startTrustedContactApprovalNotifier(params: {
|
|
1522
|
+
conversationId: string;
|
|
1523
|
+
sourceChannel: ChannelId;
|
|
1524
|
+
externalChatId: string;
|
|
1525
|
+
guardianTrustClass: GuardianContext['trustClass'];
|
|
1526
|
+
guardianExternalUserId?: string;
|
|
1527
|
+
replyCallbackUrl: string;
|
|
1528
|
+
bearerToken?: string;
|
|
1529
|
+
assistantId?: string;
|
|
1530
|
+
}): () => void {
|
|
1531
|
+
const {
|
|
1532
|
+
conversationId,
|
|
1533
|
+
sourceChannel,
|
|
1534
|
+
externalChatId,
|
|
1535
|
+
guardianTrustClass,
|
|
1536
|
+
guardianExternalUserId,
|
|
1537
|
+
replyCallbackUrl,
|
|
1538
|
+
bearerToken,
|
|
1539
|
+
assistantId,
|
|
1540
|
+
} = params;
|
|
1541
|
+
|
|
1542
|
+
// Only notify trusted contacts who have a resolvable guardian route.
|
|
1543
|
+
if (guardianTrustClass !== 'trusted_contact' || !guardianExternalUserId) {
|
|
1544
|
+
return () => {};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
let active = true;
|
|
1548
|
+
|
|
1549
|
+
const poll = async (): Promise<void> => {
|
|
1550
|
+
while (active) {
|
|
1551
|
+
try {
|
|
1552
|
+
const pending = getApprovalInfoByConversation(conversationId);
|
|
1553
|
+
const info = pending[0];
|
|
1554
|
+
|
|
1555
|
+
// Clean up resolved requests from the module-level dedupe map.
|
|
1556
|
+
// Only remove entries that belong to THIS conversation — other
|
|
1557
|
+
// conversations' pollers own their own entries. Without this
|
|
1558
|
+
// scoping, concurrent pollers would evict each other's request
|
|
1559
|
+
// IDs and cause duplicate notifications.
|
|
1560
|
+
const currentPendingIds = new Set(pending.map(p => p.requestId));
|
|
1561
|
+
for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
|
|
1562
|
+
if (cid === conversationId && !currentPendingIds.has(rid)) {
|
|
1563
|
+
globalNotifiedApprovalRequestIds.delete(rid);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
|
|
1568
|
+
globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
|
|
1569
|
+
const guardianName = resolveGuardianDisplayName(
|
|
1570
|
+
assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1571
|
+
sourceChannel,
|
|
1572
|
+
);
|
|
1573
|
+
const waitingText = guardianName
|
|
1574
|
+
? `Waiting for ${guardianName}'s approval...`
|
|
1575
|
+
: 'Waiting for your guardian\'s approval...';
|
|
1576
|
+
try {
|
|
1577
|
+
await deliverChannelReply(replyCallbackUrl, {
|
|
1578
|
+
chatId: externalChatId,
|
|
1579
|
+
text: waitingText,
|
|
1580
|
+
assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1581
|
+
}, bearerToken);
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
log.warn({ err, conversationId }, 'Failed to deliver trusted-contact pending-approval notification');
|
|
1584
|
+
// Remove from notified set so delivery is retried on next poll
|
|
1585
|
+
globalNotifiedApprovalRequestIds.delete(info.requestId);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
log.warn({ err, conversationId }, 'Trusted-contact approval notifier poll failed');
|
|
1590
|
+
}
|
|
1591
|
+
await delay(PENDING_APPROVAL_POLL_INTERVAL_MS);
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
void poll();
|
|
1596
|
+
return () => {
|
|
1597
|
+
active = false;
|
|
1598
|
+
|
|
1599
|
+
// Evict all dedupe entries owned by this conversation so the
|
|
1600
|
+
// module-level map doesn't grow unboundedly after the poller stops.
|
|
1601
|
+
for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
|
|
1602
|
+
if (cid === conversationId) {
|
|
1603
|
+
globalNotifiedApprovalRequestIds.delete(rid);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1480
1609
|
function processChannelMessageInBackground(params: BackgroundProcessingParams): void {
|
|
1481
1610
|
const {
|
|
1482
1611
|
processMessage,
|
|
@@ -1519,6 +1648,18 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
1519
1648
|
approvalCopyGenerator,
|
|
1520
1649
|
})
|
|
1521
1650
|
: undefined;
|
|
1651
|
+
const stopTcApprovalNotifier = replyCallbackUrl
|
|
1652
|
+
? startTrustedContactApprovalNotifier({
|
|
1653
|
+
conversationId,
|
|
1654
|
+
sourceChannel,
|
|
1655
|
+
externalChatId,
|
|
1656
|
+
guardianTrustClass: guardianCtx.trustClass,
|
|
1657
|
+
guardianExternalUserId: guardianCtx.guardianExternalUserId,
|
|
1658
|
+
replyCallbackUrl,
|
|
1659
|
+
bearerToken,
|
|
1660
|
+
assistantId,
|
|
1661
|
+
})
|
|
1662
|
+
: undefined;
|
|
1522
1663
|
|
|
1523
1664
|
try {
|
|
1524
1665
|
const cmdIntent = commandIntent && typeof commandIntent.type === 'string'
|
|
@@ -1536,7 +1677,7 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
1536
1677
|
},
|
|
1537
1678
|
assistantId,
|
|
1538
1679
|
guardianContext: toGuardianRuntimeContext(sourceChannel, guardianCtx),
|
|
1539
|
-
isInteractive: guardianCtx.
|
|
1680
|
+
isInteractive: resolveRoutingState(guardianCtx).promptWaitingAllowed,
|
|
1540
1681
|
...(cmdIntent ? { commandIntent: cmdIntent } : {}),
|
|
1541
1682
|
},
|
|
1542
1683
|
sourceChannel,
|
|
@@ -1564,6 +1705,7 @@ function processChannelMessageInBackground(params: BackgroundProcessingParams):
|
|
|
1564
1705
|
} finally {
|
|
1565
1706
|
stopTypingHeartbeat?.();
|
|
1566
1707
|
stopApprovalWatcher?.();
|
|
1708
|
+
stopTcApprovalNotifier?.();
|
|
1567
1709
|
}
|
|
1568
1710
|
})();
|
|
1569
1711
|
}
|
|
@@ -147,6 +147,8 @@ export async function handleCreateInvite(req: Request): Promise<Response> {
|
|
|
147
147
|
expiresInMs: body.expiresInMs as number | undefined,
|
|
148
148
|
expectedExternalUserId: body.expectedExternalUserId as string | undefined,
|
|
149
149
|
voiceCodeDigits: body.voiceCodeDigits as number | undefined,
|
|
150
|
+
friendName: body.friendName as string | undefined,
|
|
151
|
+
guardianName: body.guardianName as string | undefined,
|
|
150
152
|
});
|
|
151
153
|
|
|
152
154
|
if (!result.ok) {
|