@vellumai/assistant 0.4.3 → 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 +40 -3
- package/README.md +43 -35
- 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__/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 +125 -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__/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 -87
- 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 +4 -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__/guardian-actions-endpoint.test.ts +19 -14
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -4
- 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__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +131 -8
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +62 -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 +841 -39
- 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 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -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__/twilio-config.test.ts +2 -13
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +10 -2
- package/src/approvals/guardian-request-resolvers.ts +128 -9
- package/src/calls/call-constants.ts +21 -0
- package/src/calls/call-controller.ts +9 -2
- package/src/calls/call-domain.ts +28 -7
- 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 +424 -12
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +1 -1
- package/src/calls/types.ts +3 -1
- 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 +146 -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 +1 -0
- package/src/config/calls-schema.ts +24 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -0
- 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 +10 -9
- 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 +5 -55
- package/src/daemon/handlers/config-inbox.ts +9 -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/pairing.ts +2 -0
- package/src/daemon/handlers/sessions.ts +48 -3
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/integrations.ts +1 -99
- 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 +14 -1
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +22 -11
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +3 -0
- package/src/daemon/session-surfaces.ts +3 -2
- 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/db-init.ts +4 -0
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema.ts +16 -0
- 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 +39 -1
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +16 -8
- 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 +71 -1
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -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 +0 -3
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +65 -12
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/invite-redemption-service.ts +8 -0
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/conversation-routes.ts +140 -52
- package/src/runtime/routes/events-routes.ts +20 -5
- 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-message-handler.ts +143 -2
- 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/permission-checker.ts +15 -4
- package/src/tools/tool-approval-handler.ts +242 -18
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- package/src/daemon/handlers/config-twilio.ts +0 -1082
|
@@ -3,20 +3,77 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These endpoints resolve pending confirmations, secrets, and trust rules
|
|
5
5
|
* by requestId — orthogonal to message sending.
|
|
6
|
+
*
|
|
7
|
+
* All approval endpoints require a valid actor token via the X-Actor-Token
|
|
8
|
+
* header (with local CLI fallback). Guardian decisions additionally verify
|
|
9
|
+
* that the actor is the bound guardian.
|
|
6
10
|
*/
|
|
7
11
|
import { getConversationByKey } from '../../memory/conversation-key-store.js';
|
|
8
12
|
import { addRule } from '../../permissions/trust-store.js';
|
|
9
13
|
import { getTool } from '../../tools/registry.js';
|
|
10
14
|
import { getLogger } from '../../util/logger.js';
|
|
11
15
|
import { httpError } from '../http-errors.js';
|
|
16
|
+
import {
|
|
17
|
+
isActorBoundGuardian,
|
|
18
|
+
isLocalFallbackBoundGuardian,
|
|
19
|
+
type ServerWithRequestIP,
|
|
20
|
+
verifyHttpActorTokenWithLocalFallback,
|
|
21
|
+
} from '../middleware/actor-token.js';
|
|
12
22
|
import * as pendingInteractions from '../pending-interactions.js';
|
|
13
23
|
|
|
14
24
|
const log = getLogger('approval-routes');
|
|
15
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Verify the actor token from the request with local fallback.
|
|
28
|
+
* Returns an error Response if verification fails, or null if
|
|
29
|
+
* the actor is authenticated (via actor token or local identity).
|
|
30
|
+
*/
|
|
31
|
+
function requireActorToken(req: Request, server: ServerWithRequestIP): Response | null {
|
|
32
|
+
const result = verifyHttpActorTokenWithLocalFallback(req, server);
|
|
33
|
+
if (!result.ok) {
|
|
34
|
+
return httpError(
|
|
35
|
+
result.status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
36
|
+
result.message,
|
|
37
|
+
result.status,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Verify the actor token and confirm the actor is the bound guardian.
|
|
45
|
+
* When no actor token is present (bearer-authenticated local client),
|
|
46
|
+
* falls back to local IPC identity resolution and checks the local
|
|
47
|
+
* identity is the bound guardian.
|
|
48
|
+
*/
|
|
49
|
+
function requireBoundGuardian(req: Request, server: ServerWithRequestIP): Response | null {
|
|
50
|
+
const result = verifyHttpActorTokenWithLocalFallback(req, server);
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
return httpError(
|
|
53
|
+
result.status === 401 ? 'UNAUTHORIZED' : 'FORBIDDEN',
|
|
54
|
+
result.message,
|
|
55
|
+
result.status,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
// For actor-token-authenticated requests, check the token's identity.
|
|
59
|
+
// For local fallback (bearer-auth only), check the local identity.
|
|
60
|
+
const isBoundGuardian = result.claims
|
|
61
|
+
? isActorBoundGuardian(result.claims)
|
|
62
|
+
: isLocalFallbackBoundGuardian();
|
|
63
|
+
if (!isBoundGuardian) {
|
|
64
|
+
return httpError('FORBIDDEN', 'Actor is not the bound guardian for this channel', 403);
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
16
69
|
/**
|
|
17
70
|
* POST /v1/confirm — resolve a pending confirmation by requestId.
|
|
71
|
+
* Requires a valid actor token (guardian-bound).
|
|
18
72
|
*/
|
|
19
|
-
export async function handleConfirm(req: Request): Promise<Response> {
|
|
73
|
+
export async function handleConfirm(req: Request, server: ServerWithRequestIP): Promise<Response> {
|
|
74
|
+
const authError = requireBoundGuardian(req, server);
|
|
75
|
+
if (authError) return authError;
|
|
76
|
+
|
|
20
77
|
const body = await req.json() as {
|
|
21
78
|
requestId?: string;
|
|
22
79
|
decision?: string;
|
|
@@ -37,14 +94,20 @@ export async function handleConfirm(req: Request): Promise<Response> {
|
|
|
37
94
|
return httpError('NOT_FOUND', 'No pending interaction found for this requestId', 404);
|
|
38
95
|
}
|
|
39
96
|
|
|
40
|
-
interaction.session.handleConfirmationResponse(requestId, decision
|
|
97
|
+
interaction.session.handleConfirmationResponse(requestId, decision, undefined, undefined, undefined, {
|
|
98
|
+
source: 'button',
|
|
99
|
+
});
|
|
41
100
|
return Response.json({ accepted: true });
|
|
42
101
|
}
|
|
43
102
|
|
|
44
103
|
/**
|
|
45
104
|
* POST /v1/secret — resolve a pending secret request by requestId.
|
|
105
|
+
* Requires a valid actor token (guardian-bound).
|
|
46
106
|
*/
|
|
47
|
-
export async function handleSecret(req: Request): Promise<Response> {
|
|
107
|
+
export async function handleSecret(req: Request, server: ServerWithRequestIP): Promise<Response> {
|
|
108
|
+
const authError = requireBoundGuardian(req, server);
|
|
109
|
+
if (authError) return authError;
|
|
110
|
+
|
|
48
111
|
const body = await req.json() as {
|
|
49
112
|
requestId?: string;
|
|
50
113
|
value?: string;
|
|
@@ -76,13 +139,17 @@ export async function handleSecret(req: Request): Promise<Response> {
|
|
|
76
139
|
|
|
77
140
|
/**
|
|
78
141
|
* POST /v1/trust-rules — add a trust rule for a pending confirmation.
|
|
142
|
+
* Requires a valid actor token (guardian-bound).
|
|
79
143
|
*
|
|
80
144
|
* Does NOT resolve the confirmation itself (the client still needs to
|
|
81
145
|
* POST /v1/confirm to approve/deny). Validates the pattern and scope
|
|
82
146
|
* against the server-provided allowlist options from the original
|
|
83
147
|
* confirmation_request.
|
|
84
148
|
*/
|
|
85
|
-
export async function handleTrustRule(req: Request): Promise<Response> {
|
|
149
|
+
export async function handleTrustRule(req: Request, server: ServerWithRequestIP): Promise<Response> {
|
|
150
|
+
const authError = requireBoundGuardian(req, server);
|
|
151
|
+
if (authError) return authError;
|
|
152
|
+
|
|
86
153
|
const body = await req.json() as {
|
|
87
154
|
requestId?: string;
|
|
88
155
|
pattern?: string;
|
|
@@ -130,9 +197,14 @@ export async function handleTrustRule(req: Request): Promise<Response> {
|
|
|
130
197
|
return httpError('FORBIDDEN', 'pattern does not match any server-provided allowlist option', 403);
|
|
131
198
|
}
|
|
132
199
|
|
|
133
|
-
// Validate scope against server-provided scope options
|
|
200
|
+
// Validate scope against server-provided scope options.
|
|
201
|
+
// Non-scoped tools have empty scopeOptions — only "everywhere" is valid for them.
|
|
134
202
|
const validScopes = (confirmation.scopeOptions ?? []).map((o) => o.scope);
|
|
135
|
-
if (
|
|
203
|
+
if (validScopes.length === 0) {
|
|
204
|
+
if (scope !== 'everywhere') {
|
|
205
|
+
return httpError('FORBIDDEN', 'non-scoped tools only accept scope "everywhere"', 403);
|
|
206
|
+
}
|
|
207
|
+
} else if (!validScopes.includes(scope)) {
|
|
136
208
|
return httpError('FORBIDDEN', 'scope does not match any server-provided scope option', 403);
|
|
137
209
|
}
|
|
138
210
|
|
|
@@ -155,12 +227,15 @@ export async function handleTrustRule(req: Request): Promise<Response> {
|
|
|
155
227
|
|
|
156
228
|
/**
|
|
157
229
|
* GET /v1/pending-interactions?conversationKey=...
|
|
230
|
+
* Requires a valid actor token.
|
|
158
231
|
*
|
|
159
232
|
* Returns pending confirmations and secrets for a conversation, allowing
|
|
160
233
|
* polling-based clients (like the CLI) to discover approval requests
|
|
161
234
|
* without SSE.
|
|
162
235
|
*/
|
|
163
|
-
export function handleListPendingInteractions(url: URL): Response {
|
|
236
|
+
export function handleListPendingInteractions(url: URL, req: Request, server: ServerWithRequestIP): Response {
|
|
237
|
+
const authError = requireActorToken(req, server);
|
|
238
|
+
if (authError) return authError;
|
|
164
239
|
const conversationKey = url.searchParams.get('conversationKey');
|
|
165
240
|
const conversationId = url.searchParams.get('conversationId');
|
|
166
241
|
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for the brain graph visualization endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Queries the memory database to return a knowledge graph shaped for brain-lobe
|
|
5
|
+
* visualization, with entities mapped to brain regions based on their type.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { count } from 'drizzle-orm';
|
|
12
|
+
|
|
13
|
+
import { getDb } from '../../memory/db.js';
|
|
14
|
+
import { memoryEntities, memoryEntityRelations, memoryItems } from '../../memory/schema.js';
|
|
15
|
+
import { resolveBundledDir } from '../../util/bundled-asset.js';
|
|
16
|
+
|
|
17
|
+
function getLobeRegion(entityType: string): string {
|
|
18
|
+
switch (entityType) {
|
|
19
|
+
case 'person':
|
|
20
|
+
case 'organization':
|
|
21
|
+
return 'right-social';
|
|
22
|
+
case 'project':
|
|
23
|
+
case 'company':
|
|
24
|
+
return 'left-planning';
|
|
25
|
+
case 'tool':
|
|
26
|
+
return 'left-technical';
|
|
27
|
+
case 'concept':
|
|
28
|
+
return 'right-creative';
|
|
29
|
+
case 'location':
|
|
30
|
+
return 'right-spatial';
|
|
31
|
+
default:
|
|
32
|
+
return 'center';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getEntityColor(entityType: string): string {
|
|
37
|
+
switch (entityType) {
|
|
38
|
+
case 'person':
|
|
39
|
+
return '#22c55e';
|
|
40
|
+
case 'project':
|
|
41
|
+
return '#f97316';
|
|
42
|
+
case 'tool':
|
|
43
|
+
return '#06b6d4';
|
|
44
|
+
case 'company':
|
|
45
|
+
return '#a855f7';
|
|
46
|
+
case 'organization':
|
|
47
|
+
return '#a855f7';
|
|
48
|
+
case 'concept':
|
|
49
|
+
return '#eab308';
|
|
50
|
+
case 'location':
|
|
51
|
+
return '#14b8a6';
|
|
52
|
+
default:
|
|
53
|
+
return '#94a3b8';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getMemoryKindColor(kind: string): string {
|
|
58
|
+
switch (kind) {
|
|
59
|
+
case 'profile':
|
|
60
|
+
return '#8b5cf6';
|
|
61
|
+
case 'preference':
|
|
62
|
+
return '#3b82f6';
|
|
63
|
+
case 'constraint':
|
|
64
|
+
return '#ef4444';
|
|
65
|
+
case 'instruction':
|
|
66
|
+
return '#f59e0b';
|
|
67
|
+
case 'style':
|
|
68
|
+
return '#ec4899';
|
|
69
|
+
default:
|
|
70
|
+
return '#94a3b8';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function handleGetBrainGraph(): Response {
|
|
75
|
+
try {
|
|
76
|
+
const db = getDb();
|
|
77
|
+
|
|
78
|
+
const entityRows = db
|
|
79
|
+
.select({
|
|
80
|
+
id: memoryEntities.id,
|
|
81
|
+
name: memoryEntities.name,
|
|
82
|
+
type: memoryEntities.type,
|
|
83
|
+
mentionCount: memoryEntities.mentionCount,
|
|
84
|
+
firstSeenAt: memoryEntities.firstSeenAt,
|
|
85
|
+
lastSeenAt: memoryEntities.lastSeenAt,
|
|
86
|
+
})
|
|
87
|
+
.from(memoryEntities)
|
|
88
|
+
.all();
|
|
89
|
+
|
|
90
|
+
const relationRows = db
|
|
91
|
+
.select({
|
|
92
|
+
sourceEntityId: memoryEntityRelations.sourceEntityId,
|
|
93
|
+
targetEntityId: memoryEntityRelations.targetEntityId,
|
|
94
|
+
relation: memoryEntityRelations.relation,
|
|
95
|
+
})
|
|
96
|
+
.from(memoryEntityRelations)
|
|
97
|
+
.all();
|
|
98
|
+
|
|
99
|
+
const kindCountRows = db
|
|
100
|
+
.select({
|
|
101
|
+
kind: memoryItems.kind,
|
|
102
|
+
count: count(),
|
|
103
|
+
})
|
|
104
|
+
.from(memoryItems)
|
|
105
|
+
.groupBy(memoryItems.kind)
|
|
106
|
+
.all();
|
|
107
|
+
|
|
108
|
+
const entities = entityRows.map((entity) => ({
|
|
109
|
+
id: entity.id,
|
|
110
|
+
name: entity.name,
|
|
111
|
+
type: entity.type,
|
|
112
|
+
lobeRegion: getLobeRegion(entity.type),
|
|
113
|
+
color: getEntityColor(entity.type),
|
|
114
|
+
mentionCount: entity.mentionCount,
|
|
115
|
+
firstSeenAt: entity.firstSeenAt,
|
|
116
|
+
lastSeenAt: entity.lastSeenAt,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
const relations = relationRows.map((rel) => ({
|
|
120
|
+
sourceId: rel.sourceEntityId,
|
|
121
|
+
targetId: rel.targetEntityId,
|
|
122
|
+
relation: rel.relation,
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
const memorySummary = kindCountRows.map((row) => ({
|
|
126
|
+
kind: row.kind,
|
|
127
|
+
count: row.count,
|
|
128
|
+
color: getMemoryKindColor(row.kind),
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const totalKnowledgeCount = memorySummary.reduce((sum, entry) => sum + entry.count, 0);
|
|
132
|
+
|
|
133
|
+
return Response.json({
|
|
134
|
+
entities,
|
|
135
|
+
relations,
|
|
136
|
+
memorySummary,
|
|
137
|
+
totalKnowledgeCount,
|
|
138
|
+
generatedAt: new Date().toISOString(),
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
return Response.json(
|
|
142
|
+
{ error: 'Failed to generate brain graph', detail: err instanceof Error ? err.message : String(err) },
|
|
143
|
+
{ status: 500 },
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function handleServeHomeBaseUI(bearerToken?: string): Response {
|
|
149
|
+
try {
|
|
150
|
+
const prebuiltDir = resolveBundledDir(
|
|
151
|
+
import.meta.dirname ?? __dirname,
|
|
152
|
+
'../../home-base/prebuilt',
|
|
153
|
+
'prebuilt',
|
|
154
|
+
);
|
|
155
|
+
let html = readFileSync(join(prebuiltDir, 'index.html'), 'utf-8');
|
|
156
|
+
if (bearerToken) {
|
|
157
|
+
const escapedToken = bearerToken
|
|
158
|
+
.replace(/&/g, '&')
|
|
159
|
+
.replace(/"/g, '"')
|
|
160
|
+
.replace(/</g, '<')
|
|
161
|
+
.replace(/>/g, '>');
|
|
162
|
+
html = html.replace(
|
|
163
|
+
'</head>',
|
|
164
|
+
` <meta name="api-token" content="${escapedToken}">\n</head>`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return new Response(html, {
|
|
168
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return Response.json(
|
|
172
|
+
{ error: 'Home Base UI not available', detail: err instanceof Error ? err.message : String(err) },
|
|
173
|
+
{ status: 500 },
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function handleServeBrainGraphUI(bearerToken?: string): Response {
|
|
179
|
+
try {
|
|
180
|
+
const prebuiltDir = resolveBundledDir(
|
|
181
|
+
import.meta.dirname ?? __dirname,
|
|
182
|
+
'../../home-base/prebuilt',
|
|
183
|
+
'prebuilt',
|
|
184
|
+
);
|
|
185
|
+
let html = readFileSync(join(prebuiltDir, 'brain-graph.html'), 'utf-8');
|
|
186
|
+
if (bearerToken) {
|
|
187
|
+
// Inject token as a meta tag for client-side fetch authentication.
|
|
188
|
+
// HTML-escape the token value to guard against injection if the token
|
|
189
|
+
// comes from an environment variable with special characters.
|
|
190
|
+
const escapedToken = bearerToken
|
|
191
|
+
.replace(/&/g, '&')
|
|
192
|
+
.replace(/"/g, '"')
|
|
193
|
+
.replace(/</g, '<')
|
|
194
|
+
.replace(/>/g, '>');
|
|
195
|
+
html = html.replace(
|
|
196
|
+
'</head>',
|
|
197
|
+
` <meta name="api-token" content="${escapedToken}">\n</head>`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
// CSP permits the CDN sources required by D3.js and Three.js.
|
|
201
|
+
// 'unsafe-eval' is needed by Three.js's shader compilation path.
|
|
202
|
+
const csp = [
|
|
203
|
+
"default-src 'self'",
|
|
204
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://d3js.org",
|
|
205
|
+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
206
|
+
"font-src 'self' https://fonts.gstatic.com",
|
|
207
|
+
"connect-src 'self'",
|
|
208
|
+
"img-src 'self' data:",
|
|
209
|
+
].join('; ');
|
|
210
|
+
return new Response(html, {
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
213
|
+
'Content-Security-Policy': csp,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return Response.json(
|
|
218
|
+
{ error: 'Brain graph UI not available', detail: err instanceof Error ? err.message : String(err) },
|
|
219
|
+
{ status: 500 },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for channel readiness endpoints.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/channels/readiness — get channel readiness snapshots
|
|
5
|
+
* POST /v1/channels/readiness/refresh — invalidate cache and refresh readiness
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ChannelId } from '../../channels/types.js';
|
|
9
|
+
import { getReadinessService } from '../../daemon/handlers/config-channels.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* GET /v1/channels/readiness
|
|
13
|
+
*
|
|
14
|
+
* Query params: channel? (optional ChannelId), includeRemote? (optional boolean)
|
|
15
|
+
*/
|
|
16
|
+
export async function handleGetChannelReadiness(url: URL): Promise<Response> {
|
|
17
|
+
const channel = (url.searchParams.get('channel') as ChannelId | null) ?? undefined;
|
|
18
|
+
const includeRemote = url.searchParams.get('includeRemote') === 'true';
|
|
19
|
+
|
|
20
|
+
const service = getReadinessService();
|
|
21
|
+
const snapshots = await service.getReadiness(channel, includeRemote);
|
|
22
|
+
|
|
23
|
+
return Response.json({
|
|
24
|
+
success: true,
|
|
25
|
+
snapshots: snapshots.map((s) => ({
|
|
26
|
+
channel: s.channel,
|
|
27
|
+
ready: s.ready,
|
|
28
|
+
checkedAt: s.checkedAt,
|
|
29
|
+
stale: s.stale,
|
|
30
|
+
reasons: s.reasons,
|
|
31
|
+
localChecks: s.localChecks,
|
|
32
|
+
remoteChecks: s.remoteChecks,
|
|
33
|
+
})),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* POST /v1/channels/readiness/refresh
|
|
39
|
+
*
|
|
40
|
+
* Body: { channel?: ChannelId, includeRemote?: boolean }
|
|
41
|
+
*/
|
|
42
|
+
export async function handleRefreshChannelReadiness(req: Request): Promise<Response> {
|
|
43
|
+
const body = (await req.json().catch(() => ({}))) as {
|
|
44
|
+
channel?: ChannelId;
|
|
45
|
+
includeRemote?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const service = getReadinessService();
|
|
49
|
+
|
|
50
|
+
// Invalidate cache before fetching
|
|
51
|
+
if (body.channel) {
|
|
52
|
+
service.invalidateChannel(body.channel);
|
|
53
|
+
} else {
|
|
54
|
+
service.invalidateAll();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const snapshots = await service.getReadiness(body.channel, body.includeRemote);
|
|
58
|
+
|
|
59
|
+
return Response.json({
|
|
60
|
+
success: true,
|
|
61
|
+
snapshots: snapshots.map((s) => ({
|
|
62
|
+
channel: s.channel,
|
|
63
|
+
ready: s.ready,
|
|
64
|
+
checkedAt: s.checkedAt,
|
|
65
|
+
stale: s.stale,
|
|
66
|
+
reasons: s.reasons,
|
|
67
|
+
localChecks: s.localChecks,
|
|
68
|
+
remoteChecks: s.remoteChecks,
|
|
69
|
+
})),
|
|
70
|
+
});
|
|
71
|
+
}
|