@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
|
@@ -488,6 +488,24 @@
|
|
|
488
488
|
</div>
|
|
489
489
|
</section>
|
|
490
490
|
|
|
491
|
+
<!-- Brain Knowledge -->
|
|
492
|
+
<section class="section" id="home-base-brain-lane">
|
|
493
|
+
<div class="section-header anim anim-d6"><h2>Knowledge</h2></div>
|
|
494
|
+
|
|
495
|
+
<div class="card-stack">
|
|
496
|
+
<div class="card feature anim anim-d6">
|
|
497
|
+
<div class="card-icon">🧠</div>
|
|
498
|
+
<div class="card-body">
|
|
499
|
+
<div class="task">Knowledge Brain</div>
|
|
500
|
+
<div class="task-meta" id="home-base-brain-count">Loading...</div>
|
|
501
|
+
<div class="task-controls">
|
|
502
|
+
<a href="/v1/brain-graph-ui" class="task-button primary" id="home-base-brain-view">View →</a>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
</section>
|
|
508
|
+
|
|
491
509
|
<!-- Onboarding -->
|
|
492
510
|
<section class="section" id="home-base-onboarding-lane">
|
|
493
511
|
<div class="section-header anim anim-d6"><h2>Set up your assistant</h2></div>
|
|
@@ -656,6 +674,28 @@
|
|
|
656
674
|
});
|
|
657
675
|
});
|
|
658
676
|
}
|
|
677
|
+
|
|
678
|
+
// Fetch brain graph count for the Knowledge Brain card.
|
|
679
|
+
(function () {
|
|
680
|
+
var countEl = byId('home-base-brain-count');
|
|
681
|
+
if (!countEl) return;
|
|
682
|
+
var apiToken = document.querySelector('meta[name="api-token"]');
|
|
683
|
+
var headers = {};
|
|
684
|
+
if (apiToken && apiToken.content) headers['Authorization'] = 'Bearer ' + apiToken.content;
|
|
685
|
+
fetch('/v1/brain-graph', { headers: headers })
|
|
686
|
+
.then(function (res) {
|
|
687
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
688
|
+
return res.json();
|
|
689
|
+
})
|
|
690
|
+
.then(function (data) {
|
|
691
|
+
var entities = (data.entities || []).length;
|
|
692
|
+
var relations = (data.relations || []).length;
|
|
693
|
+
countEl.textContent = entities + ' nodes \u2022 ' + relations + ' relations';
|
|
694
|
+
})
|
|
695
|
+
.catch(function () {
|
|
696
|
+
countEl.textContent = '\u2014';
|
|
697
|
+
});
|
|
698
|
+
})();
|
|
659
699
|
})();
|
|
660
700
|
</script>
|
|
661
701
|
</body>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform callback route registration for containerized deployments.
|
|
3
|
+
*
|
|
4
|
+
* When the assistant daemon runs inside a container (IS_CONTAINERIZED=true)
|
|
5
|
+
* with a configured PLATFORM_BASE_URL and PLATFORM_ASSISTANT_ID, external
|
|
6
|
+
* service callbacks (Twilio webhooks, OAuth redirects, Telegram webhooks, etc.)
|
|
7
|
+
* must route through the platform's gateway proxy instead of hitting the
|
|
8
|
+
* assistant directly.
|
|
9
|
+
*
|
|
10
|
+
* This module registers callback routes with the platform's internal
|
|
11
|
+
* gateway endpoint so the platform knows how to forward inbound provider
|
|
12
|
+
* webhooks to the correct containerized assistant instance.
|
|
13
|
+
*
|
|
14
|
+
* The platform endpoint is:
|
|
15
|
+
* POST {PLATFORM_BASE_URL}/v1/internal/gateway/callback-routes/register/
|
|
16
|
+
*
|
|
17
|
+
* It accepts { assistant_id, callback_path, type } and returns a stable
|
|
18
|
+
* callback_url that external services should use.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getPlatformAssistantId, getPlatformBaseUrl, getPlatformInternalApiKey } from '../config/env.js';
|
|
22
|
+
import { getIsContainerized } from '../config/env-registry.js';
|
|
23
|
+
import { getLogger } from '../util/logger.js';
|
|
24
|
+
|
|
25
|
+
const log = getLogger('platform-callback-registration');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether the daemon should register callback routes with the platform.
|
|
29
|
+
* True when IS_CONTAINERIZED, PLATFORM_BASE_URL, and PLATFORM_ASSISTANT_ID
|
|
30
|
+
* are all set.
|
|
31
|
+
*/
|
|
32
|
+
export function shouldUsePlatformCallbacks(): boolean {
|
|
33
|
+
return getIsContainerized() && !!getPlatformBaseUrl() && !!getPlatformAssistantId();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RegisterCallbackRouteResponse {
|
|
37
|
+
callback_url: string;
|
|
38
|
+
callback_path: string;
|
|
39
|
+
type: string;
|
|
40
|
+
assistant_id: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register a callback route with the platform's internal gateway endpoint.
|
|
45
|
+
*
|
|
46
|
+
* @param callbackPath - The path portion after the ingress base URL
|
|
47
|
+
* (e.g. "webhooks/twilio/voice"). Leading/trailing slashes are stripped
|
|
48
|
+
* by the platform.
|
|
49
|
+
* @param type - The route type identifier (e.g. "twilio_voice", "twilio_sms",
|
|
50
|
+
* "twilio_status", "oauth", "telegram").
|
|
51
|
+
* @returns The platform-provided callback URL that external services should use.
|
|
52
|
+
* @throws If the platform request fails.
|
|
53
|
+
*/
|
|
54
|
+
export async function registerCallbackRoute(
|
|
55
|
+
callbackPath: string,
|
|
56
|
+
type: string,
|
|
57
|
+
): Promise<string> {
|
|
58
|
+
const platformBaseUrl = getPlatformBaseUrl().replace(/\/+$/, '');
|
|
59
|
+
const assistantId = getPlatformAssistantId();
|
|
60
|
+
const apiKey = getPlatformInternalApiKey();
|
|
61
|
+
|
|
62
|
+
const url = `${platformBaseUrl}/v1/internal/gateway/callback-routes/register/`;
|
|
63
|
+
|
|
64
|
+
const headers: Record<string, string> = {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (apiKey) {
|
|
69
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const body = JSON.stringify({
|
|
73
|
+
assistant_id: assistantId,
|
|
74
|
+
callback_path: callbackPath,
|
|
75
|
+
type,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
log.debug({ callbackPath, type }, 'Registering platform callback route');
|
|
79
|
+
|
|
80
|
+
const response = await fetch(url, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers,
|
|
83
|
+
body,
|
|
84
|
+
signal: AbortSignal.timeout(10_000),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const detail = await response.text().catch(() => '');
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Platform callback route registration failed (HTTP ${response.status}): ${detail}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const data = (await response.json()) as RegisterCallbackRouteResponse;
|
|
95
|
+
|
|
96
|
+
log.info(
|
|
97
|
+
{ callbackPath, type, callbackUrl: data.callback_url },
|
|
98
|
+
'Platform callback route registered',
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return data.callback_url;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a callback URL, registering with the platform when containerized.
|
|
106
|
+
*
|
|
107
|
+
* When platform callbacks are enabled, registers the route and returns the
|
|
108
|
+
* platform's stable callback URL (optionally with query parameters appended).
|
|
109
|
+
* Otherwise evaluates the lazy direct URL supplier and returns that value.
|
|
110
|
+
*
|
|
111
|
+
* The `directUrl` parameter is a **lazy supplier** (a function returning a
|
|
112
|
+
* string) rather than an eagerly-evaluated string. This is critical because
|
|
113
|
+
* the direct URL builders (e.g. `getTwilioVoiceWebhookUrl`) call
|
|
114
|
+
* `getPublicBaseUrl()` which throws when no public ingress URL is configured.
|
|
115
|
+
* In containerized environments that rely solely on platform callbacks, the
|
|
116
|
+
* direct URL is never needed — deferring evaluation avoids the throw.
|
|
117
|
+
*
|
|
118
|
+
* @param directUrl - Lazy supplier for the direct callback URL.
|
|
119
|
+
* @param callbackPath - The path to register (e.g. "webhooks/twilio/voice").
|
|
120
|
+
* @param type - The route type identifier.
|
|
121
|
+
* @param queryParams - Optional query parameters to append to the resolved URL.
|
|
122
|
+
* @returns The resolved callback URL.
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveCallbackUrl(
|
|
125
|
+
directUrl: () => string,
|
|
126
|
+
callbackPath: string,
|
|
127
|
+
type: string,
|
|
128
|
+
queryParams?: Record<string, string>,
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
if (!shouldUsePlatformCallbacks()) {
|
|
131
|
+
return directUrl();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
let url = await registerCallbackRoute(callbackPath, type);
|
|
136
|
+
if (queryParams && Object.keys(queryParams).length > 0) {
|
|
137
|
+
const params = new URLSearchParams(queryParams);
|
|
138
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
139
|
+
url = `${url}${separator}${params.toString()}`;
|
|
140
|
+
}
|
|
141
|
+
return url;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
log.warn(
|
|
144
|
+
{ err, callbackPath, type },
|
|
145
|
+
'Failed to register platform callback route, falling back to direct URL',
|
|
146
|
+
);
|
|
147
|
+
try {
|
|
148
|
+
return directUrl();
|
|
149
|
+
} catch (fallbackErr) {
|
|
150
|
+
log.error(
|
|
151
|
+
{ fallbackErr, callbackPath, type },
|
|
152
|
+
'Direct URL fallback also failed after platform registration failure',
|
|
153
|
+
);
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -292,7 +292,7 @@ export interface UpdateCanonicalGuardianRequestParams {
|
|
|
292
292
|
status?: CanonicalRequestStatus;
|
|
293
293
|
answerText?: string;
|
|
294
294
|
decidedByExternalUserId?: string;
|
|
295
|
-
followupState?: string;
|
|
295
|
+
followupState?: string | null;
|
|
296
296
|
expiresAt?: string;
|
|
297
297
|
}
|
|
298
298
|
|
|
@@ -7,6 +7,7 @@ import { parseChannelId, parseInterfaceId } from '../channels/types.js';
|
|
|
7
7
|
import { CHANNEL_IDS, INTERFACE_IDS, isChannelId } from '../channels/types.js';
|
|
8
8
|
import { getConfig } from '../config/loader.js';
|
|
9
9
|
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
10
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
10
11
|
import { getLogger } from '../util/logger.js';
|
|
11
12
|
import { createRowMapper } from '../util/row-mapper.js';
|
|
12
13
|
import { deleteOrphanAttachments } from './attachments-store.js';
|
|
@@ -299,7 +300,7 @@ export async function addMessage(conversationId: string, role: string, content:
|
|
|
299
300
|
try {
|
|
300
301
|
projectAssistantMessage({
|
|
301
302
|
conversationId,
|
|
302
|
-
assistantId:
|
|
303
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
303
304
|
messageId: message.id,
|
|
304
305
|
messageAt: message.createdAt,
|
|
305
306
|
});
|
|
@@ -286,7 +286,7 @@ function buildTitlePrompt(
|
|
|
286
286
|
assistantResponse?: string,
|
|
287
287
|
): string {
|
|
288
288
|
const parts: string[] = [
|
|
289
|
-
'Generate a very short title for this conversation. Rules: at most 5 words, at most 40 characters, no quotes.',
|
|
289
|
+
'Generate a very short title for this conversation. Rules: at most 5 words, at most 40 characters, no quotes, no markdown formatting.',
|
|
290
290
|
];
|
|
291
291
|
|
|
292
292
|
if (context) {
|
|
@@ -313,12 +313,26 @@ function buildTitlePrompt(
|
|
|
313
313
|
|
|
314
314
|
function normalizeTitle(raw: string): string {
|
|
315
315
|
let title = raw.trim().replace(/^["']|["']$/g, '');
|
|
316
|
+
title = stripMarkdown(title);
|
|
316
317
|
const words = title.split(/\s+/);
|
|
317
318
|
if (words.length > 5) title = words.slice(0, 5).join(' ');
|
|
318
319
|
if (title.length > 40) title = title.slice(0, 40).trimEnd();
|
|
319
320
|
return title;
|
|
320
321
|
}
|
|
321
322
|
|
|
323
|
+
/** Strip common markdown formatting so titles render as plain text. */
|
|
324
|
+
function stripMarkdown(text: string): string {
|
|
325
|
+
return text
|
|
326
|
+
.replace(/\*\*(.+?)\*\*/g, '$1') // **bold**
|
|
327
|
+
.replace(/__(.+?)__/g, '$1') // __bold__
|
|
328
|
+
.replace(/\*(.+?)\*/g, '$1') // *italic*
|
|
329
|
+
.replace(/(?<!\w)_(.+?)_(?!\w)/g, '$1') // _italic_ (word-boundary-aware to preserve snake_case)
|
|
330
|
+
.replace(/~~(.+?)~~/g, '$1') // ~~strikethrough~~
|
|
331
|
+
.replace(/`(.+?)`/g, '$1') // `code`
|
|
332
|
+
.replace(/\[(.+?)\]\(.+?\)/g, '$1') // [link](url)
|
|
333
|
+
.replace(/^#{1,6}\s+/gm, ''); // # headings
|
|
334
|
+
}
|
|
335
|
+
|
|
322
336
|
function deriveFallbackTitle(context?: TitleContext): string | null {
|
|
323
337
|
if (!context) return null;
|
|
324
338
|
if (context.systemHint) return truncate(context.systemHint, 40, '');
|
|
@@ -328,7 +342,7 @@ function deriveFallbackTitle(context?: TitleContext): string | null {
|
|
|
328
342
|
|
|
329
343
|
function buildRegenerationPrompt(recentMessages: MessageRow[]): string {
|
|
330
344
|
const parts: string[] = [
|
|
331
|
-
'Generate a very short title for this conversation based on the recent messages below. Rules: at most 5 words, at most 40 characters, no quotes.',
|
|
345
|
+
'Generate a very short title for this conversation based on the recent messages below. Rules: at most 5 words, at most 40 characters, no quotes, no markdown formatting.',
|
|
332
346
|
'',
|
|
333
347
|
'Recent messages:',
|
|
334
348
|
];
|
package/src/memory/db-init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDb } from './db-connection.js';
|
|
2
2
|
import {
|
|
3
3
|
addCoreColumns,
|
|
4
|
+
createActorTokenRecordsTable,
|
|
4
5
|
createAssistantInboxTables,
|
|
5
6
|
createCallSessionsTables,
|
|
6
7
|
createCanonicalGuardianTables,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
migrateReminderRoutingIntent,
|
|
38
39
|
migrateSchemaIndexesAndColumns,
|
|
39
40
|
migrateVoiceInviteColumns,
|
|
41
|
+
migrateVoiceInviteDisplayMetadata,
|
|
40
42
|
recoverCrashedMigrations,
|
|
41
43
|
runComplexMigrations,
|
|
42
44
|
runLateMigrations,
|
|
@@ -165,5 +167,11 @@ export function initializeDb(): void {
|
|
|
165
167
|
// 26. Voice invite columns on assistant_ingress_invites
|
|
166
168
|
migrateVoiceInviteColumns(database);
|
|
167
169
|
|
|
170
|
+
// 27. Voice invite display metadata (friend_name, guardian_name) for personalized prompts
|
|
171
|
+
migrateVoiceInviteDisplayMetadata(database);
|
|
172
|
+
|
|
173
|
+
// 28. Actor token records (hash-only actor token persistence)
|
|
174
|
+
createActorTokenRecordsTable(database);
|
|
175
|
+
|
|
168
176
|
validateMigrationState(database);
|
|
169
177
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { and, desc, eq, isNotNull } from 'drizzle-orm';
|
|
9
9
|
import { v4 as uuid } from 'uuid';
|
|
10
10
|
|
|
11
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
11
12
|
import { getConversationByKey, getOrCreateConversation, setConversationKeyIfAbsent } from './conversation-key-store.js';
|
|
12
13
|
import { getDb } from './db.js';
|
|
13
14
|
import { channelInboundEvents, conversations } from './schema.js';
|
|
@@ -73,7 +74,7 @@ export function recordInbound(
|
|
|
73
74
|
const scopedMapping = assistantId ? getConversationByKey(scopedKey) : null;
|
|
74
75
|
if (scopedMapping) {
|
|
75
76
|
mapping = { conversationId: scopedMapping.conversationId, created: false };
|
|
76
|
-
} else if (assistantId ===
|
|
77
|
+
} else if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
|
|
77
78
|
const legacyMapping = getConversationByKey(legacyKey);
|
|
78
79
|
if (legacyMapping) {
|
|
79
80
|
mapping = { conversationId: legacyMapping.conversationId, created: false };
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
|
|
13
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
13
14
|
import { getLogger } from '../util/logger.js';
|
|
14
15
|
import { getDb, rawChanges } from './db.js';
|
|
15
16
|
import {
|
|
@@ -160,7 +161,7 @@ export function createGuardianActionRequest(params: {
|
|
|
160
161
|
|
|
161
162
|
const row = {
|
|
162
163
|
id,
|
|
163
|
-
assistantId: params.assistantId ??
|
|
164
|
+
assistantId: params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
164
165
|
kind: params.kind,
|
|
165
166
|
sourceChannel: params.sourceChannel,
|
|
166
167
|
sourceConversationId: params.sourceConversationId,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { and, count, desc, eq, gt, lte } from 'drizzle-orm';
|
|
10
10
|
import { v4 as uuid } from 'uuid';
|
|
11
11
|
|
|
12
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
12
13
|
import { getDb } from './db.js';
|
|
13
14
|
import { channelGuardianApprovalRequests } from './schema.js';
|
|
14
15
|
|
|
@@ -100,7 +101,7 @@ export function createApprovalRequest(params: {
|
|
|
100
101
|
runId: params.runId,
|
|
101
102
|
requestId: params.requestId ?? null,
|
|
102
103
|
conversationId: params.conversationId,
|
|
103
|
-
assistantId: params.assistantId ??
|
|
104
|
+
assistantId: params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
104
105
|
channel: params.channel,
|
|
105
106
|
requesterExternalUserId: params.requesterExternalUserId,
|
|
106
107
|
requesterChatId: params.requesterChatId,
|
|
@@ -402,7 +403,7 @@ export function listPendingApprovalRequests(params: {
|
|
|
402
403
|
const db = getDb();
|
|
403
404
|
|
|
404
405
|
const conditions = [
|
|
405
|
-
eq(channelGuardianApprovalRequests.assistantId, params.assistantId ??
|
|
406
|
+
eq(channelGuardianApprovalRequests.assistantId, params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID),
|
|
406
407
|
];
|
|
407
408
|
if (params.channel) {
|
|
408
409
|
conditions.push(eq(channelGuardianApprovalRequests.channel, params.channel));
|
|
@@ -10,6 +10,7 @@ import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
|
10
10
|
|
|
11
11
|
import { and, desc, eq } from 'drizzle-orm';
|
|
12
12
|
|
|
13
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
13
14
|
import { getDb } from './db.js';
|
|
14
15
|
import { assistantIngressInvites, assistantIngressMembers } from './schema.js';
|
|
15
16
|
|
|
@@ -37,6 +38,9 @@ export interface IngressInvite {
|
|
|
37
38
|
expectedExternalUserId: string | null;
|
|
38
39
|
voiceCodeHash: string | null;
|
|
39
40
|
voiceCodeDigits: number | null;
|
|
41
|
+
// Display metadata for personalized voice prompts (null for non-voice invites)
|
|
42
|
+
friendName: string | null;
|
|
43
|
+
guardianName: string | null;
|
|
40
44
|
createdAt: number;
|
|
41
45
|
updatedAt: number;
|
|
42
46
|
}
|
|
@@ -97,6 +101,8 @@ function rowToInvite(row: typeof assistantIngressInvites.$inferSelect): IngressI
|
|
|
97
101
|
expectedExternalUserId: row.expectedExternalUserId,
|
|
98
102
|
voiceCodeHash: row.voiceCodeHash,
|
|
99
103
|
voiceCodeDigits: row.voiceCodeDigits,
|
|
104
|
+
friendName: row.friendName,
|
|
105
|
+
guardianName: row.guardianName,
|
|
100
106
|
createdAt: row.createdAt,
|
|
101
107
|
updatedAt: row.updatedAt,
|
|
102
108
|
};
|
|
@@ -138,6 +144,8 @@ export function createInvite(params: {
|
|
|
138
144
|
expectedExternalUserId?: string;
|
|
139
145
|
voiceCodeHash?: string;
|
|
140
146
|
voiceCodeDigits?: number;
|
|
147
|
+
friendName?: string;
|
|
148
|
+
guardianName?: string;
|
|
141
149
|
}): { invite: IngressInvite; rawToken: string } {
|
|
142
150
|
const db = getDb();
|
|
143
151
|
const now = Date.now();
|
|
@@ -147,7 +155,7 @@ export function createInvite(params: {
|
|
|
147
155
|
|
|
148
156
|
const row = {
|
|
149
157
|
id,
|
|
150
|
-
assistantId: params.assistantId ??
|
|
158
|
+
assistantId: params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
151
159
|
sourceChannel: params.sourceChannel,
|
|
152
160
|
tokenHash: tokenH,
|
|
153
161
|
createdBySessionId: params.createdBySessionId ?? null,
|
|
@@ -162,6 +170,8 @@ export function createInvite(params: {
|
|
|
162
170
|
expectedExternalUserId: params.expectedExternalUserId ?? null,
|
|
163
171
|
voiceCodeHash: params.voiceCodeHash ?? null,
|
|
164
172
|
voiceCodeDigits: params.voiceCodeDigits ?? null,
|
|
173
|
+
friendName: params.friendName ?? null,
|
|
174
|
+
guardianName: params.guardianName ?? null,
|
|
165
175
|
createdAt: now,
|
|
166
176
|
updatedAt: now,
|
|
167
177
|
};
|
|
@@ -183,7 +193,7 @@ export function listInvites(params: {
|
|
|
183
193
|
offset?: number;
|
|
184
194
|
}): IngressInvite[] {
|
|
185
195
|
const db = getDb();
|
|
186
|
-
const assistantId = params.assistantId ??
|
|
196
|
+
const assistantId = params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
187
197
|
|
|
188
198
|
const conditions = [eq(assistantIngressInvites.assistantId, assistantId)];
|
|
189
199
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { and, desc, eq, or } from 'drizzle-orm';
|
|
9
9
|
import { v4 as uuid } from 'uuid';
|
|
10
10
|
|
|
11
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
11
12
|
import { getDb } from './db.js';
|
|
12
13
|
import { assistantIngressMembers } from './schema.js';
|
|
13
14
|
|
|
@@ -78,7 +79,7 @@ export function upsertMember(params: {
|
|
|
78
79
|
createdBySessionId?: string;
|
|
79
80
|
assistantId?: string;
|
|
80
81
|
}): IngressMember {
|
|
81
|
-
const assistantId = params.assistantId ??
|
|
82
|
+
const assistantId = params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
82
83
|
|
|
83
84
|
if (!params.externalUserId && !params.externalChatId) {
|
|
84
85
|
throw new Error('At least one of externalUserId or externalChatId must be provided');
|
|
@@ -181,7 +182,7 @@ export function listMembers(params?: {
|
|
|
181
182
|
offset?: number;
|
|
182
183
|
}): IngressMember[] {
|
|
183
184
|
const db = getDb();
|
|
184
|
-
const assistantId = params?.assistantId ??
|
|
185
|
+
const assistantId = params?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
185
186
|
|
|
186
187
|
const conditions = [eq(assistantIngressMembers.assistantId, assistantId)];
|
|
187
188
|
if (params?.sourceChannel) {
|
|
@@ -304,7 +305,7 @@ export function findMember(params: {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
const db = getDb();
|
|
307
|
-
const assistantId = params.assistantId ??
|
|
308
|
+
const assistantId = params.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
308
309
|
|
|
309
310
|
// Prefer lookup by externalUserId when available, fall back to externalChatId
|
|
310
311
|
const matchConditions = [];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the actor_token_records table for hash-only actor token persistence.
|
|
5
|
+
*
|
|
6
|
+
* Stores the SHA-256 hash of each actor token alongside metadata for
|
|
7
|
+
* verification and revocation. The raw token plaintext is never stored.
|
|
8
|
+
*/
|
|
9
|
+
export function createActorTokenRecordsTable(database: DrizzleDb): void {
|
|
10
|
+
database.run(/*sql*/ `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS actor_token_records (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
token_hash TEXT NOT NULL,
|
|
14
|
+
assistant_id TEXT NOT NULL,
|
|
15
|
+
guardian_principal_id TEXT NOT NULL,
|
|
16
|
+
hashed_device_id TEXT NOT NULL,
|
|
17
|
+
platform TEXT NOT NULL,
|
|
18
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
19
|
+
issued_at INTEGER NOT NULL,
|
|
20
|
+
expires_at INTEGER,
|
|
21
|
+
created_at INTEGER NOT NULL,
|
|
22
|
+
updated_at INTEGER NOT NULL
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
// Unique active token per device binding
|
|
27
|
+
database.run(
|
|
28
|
+
/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_actor_tokens_active_device
|
|
29
|
+
ON actor_token_records(assistant_id, guardian_principal_id, hashed_device_id)
|
|
30
|
+
WHERE status = 'active'`,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Token hash lookup for verification
|
|
34
|
+
database.run(
|
|
35
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_actor_tokens_hash
|
|
36
|
+
ON actor_token_records(token_hash)
|
|
37
|
+
WHERE status = 'active'`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add display metadata columns to assistant_ingress_invites for personalized
|
|
5
|
+
* voice invite prompts. Both columns are nullable to keep existing invite
|
|
6
|
+
* rows compatible.
|
|
7
|
+
*
|
|
8
|
+
* - friend_name: the name of the person being invited (used in welcome prompt)
|
|
9
|
+
* - guardian_name: the name of the guardian who created the invite (used in prompts)
|
|
10
|
+
*/
|
|
11
|
+
export function migrateVoiceInviteDisplayMetadata(database: DrizzleDb): void {
|
|
12
|
+
try { database.run(/*sql*/ `ALTER TABLE assistant_ingress_invites ADD COLUMN friend_name TEXT`); } catch { /* already exists */ }
|
|
13
|
+
try { database.run(/*sql*/ `ALTER TABLE assistant_ingress_invites ADD COLUMN guardian_name TEXT`); } catch { /* already exists */ }
|
|
14
|
+
}
|
|
@@ -39,6 +39,7 @@ export { migrateGuardianActionToolMetadata } from './034-guardian-action-tool-me
|
|
|
39
39
|
export { migrateGuardianActionSupersession } from './035-guardian-action-supersession.js';
|
|
40
40
|
export { migrateNormalizePhoneIdentities } from './036-normalize-phone-identities.js';
|
|
41
41
|
export { migrateVoiceInviteColumns } from './037-voice-invite-columns.js';
|
|
42
|
+
export { createActorTokenRecordsTable } from './038-actor-token-records.js';
|
|
42
43
|
export { createCoreTables } from './100-core-tables.js';
|
|
43
44
|
export { createWatchersAndLogsTables } from './101-watchers-and-logs.js';
|
|
44
45
|
export { addCoreColumns } from './102-alter-table-columns.js';
|
|
@@ -63,6 +64,7 @@ export { migrateFkCascadeRebuilds } from './120-fk-cascade-rebuilds.js';
|
|
|
63
64
|
export { createCanonicalGuardianTables } from './121-canonical-guardian-requests.js';
|
|
64
65
|
export { migrateCanonicalGuardianRequesterChatId } from './122-canonical-guardian-requester-chat-id.js';
|
|
65
66
|
export { migrateCanonicalGuardianDeliveriesDestinationIndex } from './123-canonical-guardian-deliveries-destination-index.js';
|
|
67
|
+
export { migrateVoiceInviteDisplayMetadata } from './124-voice-invite-display-metadata.js';
|
|
66
68
|
export {
|
|
67
69
|
MIGRATION_REGISTRY,
|
|
68
70
|
type MigrationRegistryEntry,
|
package/src/memory/schema.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { blob, index,integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
2
|
|
|
3
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
|
|
4
|
+
|
|
3
5
|
export const conversations = sqliteTable('conversations', {
|
|
4
6
|
id: text('id').primaryKey(),
|
|
5
7
|
title: text('title'),
|
|
@@ -683,7 +685,7 @@ export const channelGuardianApprovalRequests = sqliteTable('channel_guardian_app
|
|
|
683
685
|
runId: text('run_id').notNull(),
|
|
684
686
|
requestId: text('request_id'),
|
|
685
687
|
conversationId: text('conversation_id').notNull(),
|
|
686
|
-
assistantId: text('assistant_id').notNull().default(
|
|
688
|
+
assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
687
689
|
channel: text('channel').notNull(),
|
|
688
690
|
requesterExternalUserId: text('requester_external_user_id').notNull(),
|
|
689
691
|
requesterChatId: text('requester_chat_id').notNull(),
|
|
@@ -819,7 +821,7 @@ export const mediaEventFeedback = sqliteTable('media_event_feedback', {
|
|
|
819
821
|
|
|
820
822
|
export const guardianActionRequests = sqliteTable('guardian_action_requests', {
|
|
821
823
|
id: text('id').primaryKey(),
|
|
822
|
-
assistantId: text('assistant_id').notNull().default(
|
|
824
|
+
assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
823
825
|
kind: text('kind').notNull(), // 'ask_guardian'
|
|
824
826
|
sourceChannel: text('source_channel').notNull(), // 'voice'
|
|
825
827
|
sourceConversationId: text('source_conversation_id').notNull(),
|
|
@@ -930,7 +932,7 @@ export const canonicalGuardianDeliveries = sqliteTable('canonical_guardian_deliv
|
|
|
930
932
|
|
|
931
933
|
export const assistantIngressInvites = sqliteTable('assistant_ingress_invites', {
|
|
932
934
|
id: text('id').primaryKey(),
|
|
933
|
-
assistantId: text('assistant_id').notNull().default(
|
|
935
|
+
assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
934
936
|
sourceChannel: text('source_channel').notNull(),
|
|
935
937
|
tokenHash: text('token_hash').notNull(),
|
|
936
938
|
createdBySessionId: text('created_by_session_id'),
|
|
@@ -946,13 +948,16 @@ export const assistantIngressInvites = sqliteTable('assistant_ingress_invites',
|
|
|
946
948
|
expectedExternalUserId: text('expected_external_user_id'),
|
|
947
949
|
voiceCodeHash: text('voice_code_hash'),
|
|
948
950
|
voiceCodeDigits: integer('voice_code_digits'),
|
|
951
|
+
// Display metadata for personalized voice prompts (nullable — non-voice invites leave these NULL)
|
|
952
|
+
friendName: text('friend_name'),
|
|
953
|
+
guardianName: text('guardian_name'),
|
|
949
954
|
createdAt: integer('created_at').notNull(),
|
|
950
955
|
updatedAt: integer('updated_at').notNull(),
|
|
951
956
|
});
|
|
952
957
|
|
|
953
958
|
export const assistantIngressMembers = sqliteTable('assistant_ingress_members', {
|
|
954
959
|
id: text('id').primaryKey(),
|
|
955
|
-
assistantId: text('assistant_id').notNull().default(
|
|
960
|
+
assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
956
961
|
sourceChannel: text('source_channel').notNull(),
|
|
957
962
|
externalUserId: text('external_user_id'),
|
|
958
963
|
externalChatId: text('external_chat_id'),
|
|
@@ -974,7 +979,7 @@ export const assistantInboxThreadState = sqliteTable('assistant_inbox_thread_sta
|
|
|
974
979
|
conversationId: text('conversation_id')
|
|
975
980
|
.primaryKey()
|
|
976
981
|
.references(() => conversations.id, { onDelete: 'cascade' }),
|
|
977
|
-
assistantId: text('assistant_id').notNull().default(
|
|
982
|
+
assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
|
|
978
983
|
sourceChannel: text('source_channel').notNull(),
|
|
979
984
|
externalChatId: text('external_chat_id').notNull(),
|
|
980
985
|
externalUserId: text('external_user_id'),
|
|
@@ -1138,6 +1143,22 @@ export const conversationAssistantAttentionState = sqliteTable('conversation_ass
|
|
|
1138
1143
|
index('idx_conv_attn_state_assistant_last_seen').on(table.assistantId, table.lastSeenAssistantMessageAt),
|
|
1139
1144
|
]);
|
|
1140
1145
|
|
|
1146
|
+
// ── Actor Token Records ──────────────────────────────────────────────
|
|
1147
|
+
|
|
1148
|
+
export const actorTokenRecords = sqliteTable('actor_token_records', {
|
|
1149
|
+
id: text('id').primaryKey(),
|
|
1150
|
+
tokenHash: text('token_hash').notNull(),
|
|
1151
|
+
assistantId: text('assistant_id').notNull(),
|
|
1152
|
+
guardianPrincipalId: text('guardian_principal_id').notNull(),
|
|
1153
|
+
hashedDeviceId: text('hashed_device_id').notNull(),
|
|
1154
|
+
platform: text('platform').notNull(),
|
|
1155
|
+
status: text('status').notNull().default('active'),
|
|
1156
|
+
issuedAt: integer('issued_at').notNull(),
|
|
1157
|
+
expiresAt: integer('expires_at'),
|
|
1158
|
+
createdAt: integer('created_at').notNull(),
|
|
1159
|
+
updatedAt: integer('updated_at').notNull(),
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1141
1162
|
// ── Scoped Approval Grants ──────────────────────────────────────────
|
|
1142
1163
|
|
|
1143
1164
|
export const scopedApprovalGrants = sqliteTable('scoped_approval_grants', {
|
|
@@ -81,3 +81,27 @@ export interface SendOptions {
|
|
|
81
81
|
/** Optional assistant scope for multi-assistant channels. */
|
|
82
82
|
assistantId?: string;
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/** Result from a sender digest scan — groups messages by sender for bulk cleanup. */
|
|
86
|
+
export interface SenderDigestEntry {
|
|
87
|
+
id: string;
|
|
88
|
+
displayName: string;
|
|
89
|
+
email: string;
|
|
90
|
+
messageCount: number;
|
|
91
|
+
hasUnsubscribe: boolean;
|
|
92
|
+
newestMessageId: string;
|
|
93
|
+
searchQuery: string;
|
|
94
|
+
messageIds: string[];
|
|
95
|
+
hasMore: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface SenderDigestResult {
|
|
99
|
+
senders: SenderDigestEntry[];
|
|
100
|
+
totalScanned: number;
|
|
101
|
+
queryUsed: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ArchiveResult {
|
|
105
|
+
archived: number;
|
|
106
|
+
truncated?: boolean;
|
|
107
|
+
}
|