@vellumai/assistant 0.5.6 → 0.5.7
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 +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +1 -1
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +8 -14
- package/src/config/feature-flag-registry.json +48 -8
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +227 -51
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +12 -0
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/jobs-store.ts +30 -5
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +27 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.ts +24 -6
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +3 -50
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Platform callback route registration for containerized deployments.
|
|
3
3
|
*
|
|
4
4
|
* When the assistant daemon runs inside a container (IS_CONTAINERIZED=true)
|
|
5
|
-
* with a configured
|
|
5
|
+
* with a configured VELLUM_PLATFORM_URL and PLATFORM_ASSISTANT_ID, external
|
|
6
6
|
* service callbacks (Twilio webhooks, OAuth redirects, Telegram webhooks, etc.)
|
|
7
7
|
* must route through the platform's gateway proxy instead of hitting the
|
|
8
8
|
* assistant directly.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* webhooks to the correct containerized assistant instance.
|
|
13
13
|
*
|
|
14
14
|
* The platform endpoint is:
|
|
15
|
-
* POST {
|
|
15
|
+
* POST {VELLUM_PLATFORM_URL}/v1/internal/gateway/callback-routes/register/
|
|
16
16
|
*
|
|
17
17
|
* It accepts { assistant_id, callback_path, type } and returns a stable
|
|
18
18
|
* callback_url that external services should use.
|
|
@@ -30,7 +30,7 @@ const log = getLogger("platform-callback-registration");
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Whether the daemon should register callback routes with the platform.
|
|
33
|
-
* True when IS_CONTAINERIZED,
|
|
33
|
+
* True when IS_CONTAINERIZED, VELLUM_PLATFORM_URL, and PLATFORM_ASSISTANT_ID
|
|
34
34
|
* are all set. Intentionally does **not** require the managed proxy API key
|
|
35
35
|
* so that callback-only flows (OAuth transport, Telegram/Twilio callback
|
|
36
36
|
* registration) work during partial bootstrap before the key is injected.
|
package/src/instrument.ts
CHANGED
|
@@ -39,14 +39,17 @@ function redactObject(obj: unknown): unknown {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Call after dotenv has loaded so
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Call after dotenv has loaded so SENTRY_DSN_ASSISTANT is available.
|
|
43
|
+
* Initializes Sentry when the DSN is set; no-ops when empty/unset so
|
|
44
|
+
* local dev builds don't send crash reports. If the user later opts out
|
|
45
|
+
* via the sendDiagnostics config key (or VELLUM_DEV=1), call closeSentry()
|
|
46
|
+
* after config is loaded to stop future event capturing.
|
|
46
47
|
*/
|
|
47
48
|
export function initSentry(): void {
|
|
49
|
+
const dsn = getSentryDsn();
|
|
50
|
+
if (!dsn) return;
|
|
48
51
|
Sentry.init({
|
|
49
|
-
dsn
|
|
52
|
+
dsn,
|
|
50
53
|
release: `vellum-assistant@${APP_VERSION}`,
|
|
51
54
|
dist: COMMIT_SHA,
|
|
52
55
|
environment: APP_VERSION === "0.0.0-dev" ? "development" : "production",
|
|
@@ -358,12 +358,61 @@ function deriveFallbackTitle(context?: TitleContext): string | null {
|
|
|
358
358
|
return null;
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Extract only human-authored text from stored message content for title
|
|
363
|
+
* generation. Unlike extractTextFromStoredMessageContent (which includes
|
|
364
|
+
* tool metadata like "Tool use (...): {...}"), this only extracts:
|
|
365
|
+
* - `text` blocks (the actual conversation content)
|
|
366
|
+
* - `tool_result` string content (topical signal from tool responses)
|
|
367
|
+
* — web_search_tool_result is skipped (structured search data, not topical)
|
|
368
|
+
*
|
|
369
|
+
* Returns empty string for content-block arrays with no extractable text,
|
|
370
|
+
* preventing raw JSON from polluting the title prompt.
|
|
371
|
+
*/
|
|
372
|
+
function extractTextForTitle(raw: string): string {
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(raw);
|
|
375
|
+
if (typeof parsed === "string") return parsed;
|
|
376
|
+
if (!Array.isArray(parsed)) return raw;
|
|
377
|
+
const texts: string[] = [];
|
|
378
|
+
for (const block of parsed) {
|
|
379
|
+
if (!block || typeof block !== "object") continue;
|
|
380
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
381
|
+
texts.push(block.text);
|
|
382
|
+
// guard:allow-tool-result-only — web_search_tool_result has structured
|
|
383
|
+
// search result arrays, not useful for title generation; only plain
|
|
384
|
+
// tool_result string content carries topical signal.
|
|
385
|
+
} else if (block.type === "tool_result") {
|
|
386
|
+
if (typeof block.content === "string") {
|
|
387
|
+
texts.push(block.content);
|
|
388
|
+
} else if (Array.isArray(block.content)) {
|
|
389
|
+
for (const nested of block.content) {
|
|
390
|
+
if (
|
|
391
|
+
nested &&
|
|
392
|
+
typeof nested === "object" &&
|
|
393
|
+
nested.type === "text" &&
|
|
394
|
+
typeof nested.text === "string"
|
|
395
|
+
) {
|
|
396
|
+
texts.push(nested.text);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return texts.join("\n");
|
|
403
|
+
} catch {
|
|
404
|
+
return raw;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
361
408
|
function buildRegenerationPrompt(recentMessages: MessageRow[]): string {
|
|
362
409
|
const parts: string[] = ["Recent messages:"];
|
|
363
410
|
|
|
364
411
|
for (const msg of recentMessages) {
|
|
412
|
+
const text = extractTextForTitle(msg.content);
|
|
413
|
+
if (!text) continue;
|
|
365
414
|
const role = msg.role === "user" ? "User" : "Assistant";
|
|
366
|
-
parts.push(`${role}: ${truncate(
|
|
415
|
+
parts.push(`${role}: ${truncate(text, 200, "")}`);
|
|
367
416
|
}
|
|
368
417
|
|
|
369
418
|
return parts.join("\n");
|
package/src/memory/db-init.ts
CHANGED
|
@@ -35,12 +35,14 @@ import {
|
|
|
35
35
|
createTasksAndWorkItemsTables,
|
|
36
36
|
createWatchersAndLogsTables,
|
|
37
37
|
migrateAssistantContactMetadata,
|
|
38
|
+
migrateBackfillAudioAttachmentMimeTypes,
|
|
38
39
|
migrateBackfillContactInteractionStats,
|
|
39
40
|
migrateBackfillGuardianPrincipalId,
|
|
40
41
|
migrateBackfillInlineAttachmentsToDisk,
|
|
41
42
|
migrateBackfillUsageCacheAccounting,
|
|
42
43
|
migrateCallSessionInviteMetadata,
|
|
43
44
|
migrateCallSessionMode,
|
|
45
|
+
migrateCallSessionSkipDisclosure,
|
|
44
46
|
migrateCanonicalGuardianDeliveriesDestinationIndex,
|
|
45
47
|
migrateCanonicalGuardianRequesterChatId,
|
|
46
48
|
migrateCapabilityCardColumns,
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
migrateContactsAssistantId,
|
|
52
54
|
migrateContactsNotesColumn,
|
|
53
55
|
migrateContactsRolePrincipal,
|
|
56
|
+
migrateContactsUserFileColumn,
|
|
54
57
|
migrateConversationForkLineage,
|
|
55
58
|
migrateConversationsThreadTypeIndex,
|
|
56
59
|
migrateCreateThreadStartersTable,
|
|
@@ -492,6 +495,15 @@ export function initializeDb(): void {
|
|
|
492
495
|
// 86. Drop simplified-memory tables and reducer checkpoint columns
|
|
493
496
|
migrateDropSimplifiedMemory(database);
|
|
494
497
|
|
|
498
|
+
// 87. Add skip_disclosure column to call_sessions for per-call disclosure control
|
|
499
|
+
migrateCallSessionSkipDisclosure(database);
|
|
500
|
+
|
|
501
|
+
// 88. Backfill correct MIME types for audio attachments stored as application/octet-stream
|
|
502
|
+
migrateBackfillAudioAttachmentMimeTypes(database);
|
|
503
|
+
|
|
504
|
+
// 89. Add user_file column to contacts for per-user persona file mapping
|
|
505
|
+
migrateContactsUserFileColumn(database);
|
|
506
|
+
|
|
495
507
|
validateMigrationState(database);
|
|
496
508
|
|
|
497
509
|
if (process.env.BUN_TEST === "1") {
|
|
@@ -3,6 +3,7 @@ import { v4 as uuid } from "uuid";
|
|
|
3
3
|
|
|
4
4
|
import { getConfig } from "../config/loader.js";
|
|
5
5
|
import type { MemoryExtractionConfig } from "../config/types.js";
|
|
6
|
+
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
6
7
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
7
8
|
import {
|
|
8
9
|
createTimeout,
|
|
@@ -150,6 +151,7 @@ function buildExtractionSystemPrompt(
|
|
|
150
151
|
statement: string;
|
|
151
152
|
}>,
|
|
152
153
|
messageRole: string,
|
|
154
|
+
userPersona?: string | null,
|
|
153
155
|
): string {
|
|
154
156
|
// Build the fixed instruction body first so we can measure it and allocate
|
|
155
157
|
// the remaining budget to identity context.
|
|
@@ -206,7 +208,9 @@ IMPORTANT: The message below is from the ASSISTANT, not the user. Do NOT attribu
|
|
|
206
208
|
// ceiling, preventing oversized prompts from exceeding the provider input
|
|
207
209
|
// window (which would cause sendMessage to error and fall back to
|
|
208
210
|
// lower-quality pattern-based extraction).
|
|
209
|
-
const rawIdentityContext = buildCoreIdentityContext(
|
|
211
|
+
const rawIdentityContext = buildCoreIdentityContext(
|
|
212
|
+
userPersona ? { userPersona } : undefined,
|
|
213
|
+
);
|
|
210
214
|
|
|
211
215
|
let prompt = "";
|
|
212
216
|
if (rawIdentityContext) {
|
|
@@ -316,6 +320,7 @@ async function extractItemsWithLLM(
|
|
|
316
320
|
extractionConfig: MemoryExtractionConfig,
|
|
317
321
|
scopeId: string,
|
|
318
322
|
messageRole: string,
|
|
323
|
+
userPersona?: string | null,
|
|
319
324
|
): Promise<ExtractedItem[]> {
|
|
320
325
|
const provider = await getConfiguredProvider();
|
|
321
326
|
if (!provider) {
|
|
@@ -334,6 +339,7 @@ async function extractItemsWithLLM(
|
|
|
334
339
|
const systemPrompt = buildExtractionSystemPrompt(
|
|
335
340
|
existingItems,
|
|
336
341
|
messageRole,
|
|
342
|
+
userPersona,
|
|
337
343
|
);
|
|
338
344
|
|
|
339
345
|
const messagePrefix =
|
|
@@ -532,12 +538,20 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
532
538
|
const config = getConfig();
|
|
533
539
|
const extractionConfig = config.memory.extraction;
|
|
534
540
|
const effectiveScopeId = scopeId ?? "default";
|
|
541
|
+
|
|
542
|
+
// Resolve the guardian's persona to provide personality-aware extraction
|
|
543
|
+
// context. Currently uses the guardian persona for all conversations —
|
|
544
|
+
// non-guardian conversations are rare and the guardian's persona provides
|
|
545
|
+
// better extraction context than none.
|
|
546
|
+
const userPersona = resolveGuardianPersona();
|
|
547
|
+
|
|
535
548
|
const extracted = extractionConfig.useLLM
|
|
536
549
|
? await extractItemsWithLLM(
|
|
537
550
|
text,
|
|
538
551
|
extractionConfig,
|
|
539
552
|
effectiveScopeId,
|
|
540
553
|
message.role,
|
|
554
|
+
userPersona,
|
|
541
555
|
)
|
|
542
556
|
: extractItemsPatternBased(text, effectiveScopeId);
|
|
543
557
|
|
|
@@ -9,6 +9,7 @@ import { and, desc, eq } from "drizzle-orm";
|
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
10
|
|
|
11
11
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
12
|
+
import { resolveGuardianPersona } from "../../prompts/persona-resolver.js";
|
|
12
13
|
import { buildCoreIdentityContext } from "../../prompts/system-prompt.js";
|
|
13
14
|
import {
|
|
14
15
|
createTimeout,
|
|
@@ -171,7 +172,9 @@ async function generateStarters(scopeId: string): Promise<GeneratedStarter[]> {
|
|
|
171
172
|
|
|
172
173
|
// Truncate identity context to prevent oversized prompts when SOUL.md /
|
|
173
174
|
// IDENTITY.md / USER.md are large.
|
|
174
|
-
const rawIdentityContext = buildCoreIdentityContext(
|
|
175
|
+
const rawIdentityContext = buildCoreIdentityContext({
|
|
176
|
+
userPersona: resolveGuardianPersona(),
|
|
177
|
+
});
|
|
175
178
|
const identityContext = rawIdentityContext
|
|
176
179
|
? truncate(rawIdentityContext, 2000, "\n…[truncated]")
|
|
177
180
|
: null;
|
package/src/memory/jobs-store.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { v4 as uuid } from "uuid";
|
|
|
4
4
|
import { getLogger } from "../util/logger.js";
|
|
5
5
|
import { truncate } from "../util/truncate.js";
|
|
6
6
|
import { getDb, rawAll, rawChanges } from "./db.js";
|
|
7
|
+
import {
|
|
8
|
+
isQdrantBreakerOpen,
|
|
9
|
+
shouldAllowQdrantProbe,
|
|
10
|
+
} from "./qdrant-circuit-breaker.js";
|
|
7
11
|
import { memoryJobs } from "./schema.js";
|
|
8
12
|
|
|
9
13
|
const log = getLogger("memory-jobs-store");
|
|
@@ -201,14 +205,33 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
|
|
|
201
205
|
.all();
|
|
202
206
|
|
|
203
207
|
const remainingSlots = limit - nonEmbedCandidates.length;
|
|
208
|
+
|
|
209
|
+
// When the Qdrant circuit breaker is open, skip embed jobs entirely —
|
|
210
|
+
// they would just be claimed → fail → deferred, wasting CPU cycles.
|
|
211
|
+
// Exception: if the cooldown has elapsed (breaker ready for half-open probe),
|
|
212
|
+
// allow exactly 1 embed job through so the breaker can self-heal.
|
|
213
|
+
const breakerOpen = isQdrantBreakerOpen();
|
|
214
|
+
const probeAllowed = breakerOpen && shouldAllowQdrantProbe();
|
|
215
|
+
const skipEmbedJobs = breakerOpen && !probeAllowed;
|
|
216
|
+
const embedLimit = probeAllowed ? 1 : remainingSlots;
|
|
217
|
+
|
|
218
|
+
if (skipEmbedJobs && remainingSlots > 0) {
|
|
219
|
+
log.debug("Skipping embed job claims — Qdrant circuit breaker is open");
|
|
220
|
+
}
|
|
221
|
+
if (probeAllowed && remainingSlots > 0) {
|
|
222
|
+
log.debug(
|
|
223
|
+
"Allowing 1 embed probe job — Qdrant circuit breaker cooldown elapsed",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
204
227
|
const embedCandidates =
|
|
205
|
-
remainingSlots > 0
|
|
228
|
+
remainingSlots > 0 && !skipEmbedJobs
|
|
206
229
|
? db
|
|
207
230
|
.select()
|
|
208
231
|
.from(memoryJobs)
|
|
209
232
|
.where(and(pendingFilter, inArray(memoryJobs.type, EMBED_JOB_TYPES)))
|
|
210
233
|
.orderBy(asc(memoryJobs.runAfter), asc(memoryJobs.createdAt))
|
|
211
|
-
.limit(
|
|
234
|
+
.limit(embedLimit)
|
|
212
235
|
.all()
|
|
213
236
|
: [];
|
|
214
237
|
|
|
@@ -243,8 +266,8 @@ export function completeMemoryJob(id: string): void {
|
|
|
243
266
|
|
|
244
267
|
/** Max times a job can be deferred before it is marked as failed. */
|
|
245
268
|
const MAX_DEFERRALS = 50;
|
|
246
|
-
/**
|
|
247
|
-
const
|
|
269
|
+
/** Log warnings at these milestone counts to avoid flooding logs. */
|
|
270
|
+
const DEFERRAL_WARN_MILESTONES = [40, 45];
|
|
248
271
|
/** Base delay in ms for deferred jobs (grows with exponential backoff). */
|
|
249
272
|
const DEFER_BASE_DELAY_MS = 30_000;
|
|
250
273
|
/** Maximum delay cap for deferred jobs (5 minutes). */
|
|
@@ -286,7 +309,9 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
|
|
|
286
309
|
return "failed";
|
|
287
310
|
}
|
|
288
311
|
|
|
289
|
-
|
|
312
|
+
// Log at milestones only (40, 45) to avoid flooding logs.
|
|
313
|
+
// At 50, the job fails via the check above, so 40 and 45 are the warnings.
|
|
314
|
+
if (DEFERRAL_WARN_MILESTONES.includes(deferrals)) {
|
|
290
315
|
log.warn(
|
|
291
316
|
{ jobId: id, type: row.type, deferrals, max: MAX_DEFERRALS },
|
|
292
317
|
"Job approaching max deferral limit",
|
|
@@ -44,6 +44,9 @@ import { QdrantCircuitOpenError } from "./qdrant-circuit-breaker.js";
|
|
|
44
44
|
|
|
45
45
|
const log = getLogger("memory-jobs-worker");
|
|
46
46
|
|
|
47
|
+
export const POLL_INTERVAL_MIN_MS = 1_500;
|
|
48
|
+
export const POLL_INTERVAL_MAX_MS = 30_000;
|
|
49
|
+
|
|
47
50
|
export interface MemoryJobsWorker {
|
|
48
51
|
runOnce(): Promise<number>;
|
|
49
52
|
stop(): void;
|
|
@@ -57,24 +60,45 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
|
|
|
57
60
|
|
|
58
61
|
let stopped = false;
|
|
59
62
|
let tickRunning = false;
|
|
63
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
64
|
+
let currentIntervalMs = POLL_INTERVAL_MIN_MS;
|
|
60
65
|
|
|
61
66
|
const tick = async () => {
|
|
62
67
|
if (stopped || tickRunning) return;
|
|
63
68
|
tickRunning = true;
|
|
64
69
|
try {
|
|
65
|
-
await runMemoryJobsOnce({
|
|
70
|
+
const processed = await runMemoryJobsOnce({
|
|
71
|
+
enableScheduledCleanup: true,
|
|
72
|
+
});
|
|
73
|
+
if (processed > 0) {
|
|
74
|
+
currentIntervalMs = POLL_INTERVAL_MIN_MS;
|
|
75
|
+
} else {
|
|
76
|
+
currentIntervalMs = Math.min(
|
|
77
|
+
currentIntervalMs * 2,
|
|
78
|
+
POLL_INTERVAL_MAX_MS,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
66
81
|
} catch (err) {
|
|
67
82
|
log.error({ err }, "Memory worker tick failed");
|
|
83
|
+
currentIntervalMs = Math.min(currentIntervalMs * 2, POLL_INTERVAL_MAX_MS);
|
|
68
84
|
} finally {
|
|
69
85
|
tickRunning = false;
|
|
70
86
|
}
|
|
71
87
|
};
|
|
72
88
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
const scheduleTick = () => {
|
|
90
|
+
if (stopped) return;
|
|
91
|
+
timer = setTimeout(() => {
|
|
92
|
+
void tick().then(() => {
|
|
93
|
+
if (!stopped) scheduleTick();
|
|
94
|
+
});
|
|
95
|
+
}, currentIntervalMs);
|
|
96
|
+
(timer as NodeJS.Timeout).unref?.();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
void tick().then(() => {
|
|
100
|
+
if (!stopped) scheduleTick();
|
|
101
|
+
});
|
|
78
102
|
|
|
79
103
|
return {
|
|
80
104
|
async runOnce(): Promise<number> {
|
|
@@ -82,7 +106,7 @@ export function startMemoryJobsWorker(): MemoryJobsWorker {
|
|
|
82
106
|
},
|
|
83
107
|
stop(): void {
|
|
84
108
|
stopped = true;
|
|
85
|
-
|
|
109
|
+
clearTimeout(timer);
|
|
86
110
|
},
|
|
87
111
|
};
|
|
88
112
|
}
|
|
@@ -49,3 +49,22 @@ export function migrateJobDeferrals(database: DrizzleDb): void {
|
|
|
49
49
|
throw e;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reverse the deferral reconciliation by moving `deferrals` back into `attempts`
|
|
55
|
+
* for pending embed jobs. Best-effort: jobs that accumulated real deferral counts
|
|
56
|
+
* after the forward migration ran cannot be distinguished from migrated ones.
|
|
57
|
+
*/
|
|
58
|
+
export function downJobDeferrals(database: DrizzleDb): void {
|
|
59
|
+
const raw = getSqliteFrom(database);
|
|
60
|
+
raw.exec(/*sql*/ `
|
|
61
|
+
UPDATE memory_jobs
|
|
62
|
+
SET attempts = deferrals,
|
|
63
|
+
deferrals = 0,
|
|
64
|
+
updated_at = ${Date.now()}
|
|
65
|
+
WHERE status = 'pending'
|
|
66
|
+
AND deferrals > 0
|
|
67
|
+
AND attempts = 0
|
|
68
|
+
AND type IN ('embed_segment', 'embed_item', 'embed_summary')
|
|
69
|
+
`);
|
|
70
|
+
}
|
|
@@ -91,3 +91,13 @@ export function migrateMemoryEntityRelationDedup(database: DrizzleDb): void {
|
|
|
91
91
|
throw e;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* No-op down: deduplication is a lossy operation — deleted duplicate rows
|
|
97
|
+
* cannot be restored. The forward migration merged rows by keeping the most
|
|
98
|
+
* recent evidence per (source, target, relation) triple; the discarded rows
|
|
99
|
+
* are permanently lost.
|
|
100
|
+
*/
|
|
101
|
+
export function downMemoryEntityRelationDedup(_database: DrizzleDb): void {
|
|
102
|
+
// Intentionally empty — irreversible lossy migration.
|
|
103
|
+
}
|
|
@@ -93,3 +93,79 @@ export function migrateMemoryItemsFingerprintScopeUnique(
|
|
|
93
93
|
raw.exec("PRAGMA foreign_keys = ON");
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reverse the compound (fingerprint, scope_id) unique index change by rebuilding
|
|
99
|
+
* memory_items with a column-level UNIQUE on fingerprint.
|
|
100
|
+
*
|
|
101
|
+
* WARNING: This is dangerous if data now relies on the compound constraint
|
|
102
|
+
* (i.e., the same fingerprint exists in multiple scopes). In that case, the
|
|
103
|
+
* rebuild will fail with a UNIQUE constraint violation. This is intentional —
|
|
104
|
+
* it prevents silent data loss on rollback.
|
|
105
|
+
*/
|
|
106
|
+
export function downMemoryItemsFingerprintScopeUnique(
|
|
107
|
+
database: DrizzleDb,
|
|
108
|
+
): void {
|
|
109
|
+
const raw = getSqliteFrom(database);
|
|
110
|
+
|
|
111
|
+
// Check if the column-level UNIQUE already exists — if so, nothing to do.
|
|
112
|
+
const tableDdl = raw
|
|
113
|
+
.query(
|
|
114
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memory_items'`,
|
|
115
|
+
)
|
|
116
|
+
.get() as { sql: string } | null;
|
|
117
|
+
if (
|
|
118
|
+
!tableDdl ||
|
|
119
|
+
tableDdl.sql.match(/fingerprint\s+TEXT\s+NOT\s+NULL\s+UNIQUE/i)
|
|
120
|
+
) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
raw.exec("PRAGMA foreign_keys = OFF");
|
|
125
|
+
try {
|
|
126
|
+
raw.exec("BEGIN");
|
|
127
|
+
|
|
128
|
+
raw.exec(/*sql*/ `
|
|
129
|
+
CREATE TABLE memory_items_new (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
kind TEXT NOT NULL,
|
|
132
|
+
subject TEXT NOT NULL,
|
|
133
|
+
statement TEXT NOT NULL,
|
|
134
|
+
status TEXT NOT NULL,
|
|
135
|
+
confidence REAL NOT NULL,
|
|
136
|
+
fingerprint TEXT NOT NULL UNIQUE,
|
|
137
|
+
first_seen_at INTEGER NOT NULL,
|
|
138
|
+
last_seen_at INTEGER NOT NULL,
|
|
139
|
+
last_used_at INTEGER,
|
|
140
|
+
importance REAL,
|
|
141
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
142
|
+
valid_from INTEGER,
|
|
143
|
+
invalid_at INTEGER,
|
|
144
|
+
verification_state TEXT NOT NULL DEFAULT 'assistant_inferred',
|
|
145
|
+
scope_id TEXT NOT NULL DEFAULT 'default'
|
|
146
|
+
)
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
raw.exec(/*sql*/ `
|
|
150
|
+
INSERT INTO memory_items_new
|
|
151
|
+
SELECT id, kind, subject, statement, status, confidence, fingerprint,
|
|
152
|
+
first_seen_at, last_seen_at, last_used_at, importance, access_count,
|
|
153
|
+
valid_from, invalid_at, verification_state, scope_id
|
|
154
|
+
FROM memory_items
|
|
155
|
+
`);
|
|
156
|
+
|
|
157
|
+
raw.exec(/*sql*/ `DROP TABLE memory_items`);
|
|
158
|
+
raw.exec(/*sql*/ `ALTER TABLE memory_items_new RENAME TO memory_items`);
|
|
159
|
+
|
|
160
|
+
raw.exec("COMMIT");
|
|
161
|
+
} catch (e) {
|
|
162
|
+
try {
|
|
163
|
+
raw.exec("ROLLBACK");
|
|
164
|
+
} catch {
|
|
165
|
+
/* no active transaction */
|
|
166
|
+
}
|
|
167
|
+
throw e;
|
|
168
|
+
} finally {
|
|
169
|
+
raw.exec("PRAGMA foreign_keys = ON");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
|
|
2
4
|
import { computeMemoryFingerprint } from "../fingerprint.js";
|
|
3
5
|
|
|
@@ -75,3 +77,51 @@ export function migrateMemoryItemsScopeSaltedFingerprints(
|
|
|
75
77
|
throw e;
|
|
76
78
|
}
|
|
77
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reverse the scope-salted fingerprint migration by recomputing fingerprints
|
|
83
|
+
* WITHOUT the scope_id prefix.
|
|
84
|
+
*
|
|
85
|
+
* Old format: sha256(`${kind}|${subject.toLowerCase()}|${statement.toLowerCase()}`)
|
|
86
|
+
*/
|
|
87
|
+
export function downMemoryItemsScopeSaltedFingerprints(
|
|
88
|
+
database: DrizzleDb,
|
|
89
|
+
): void {
|
|
90
|
+
const raw = getSqliteFrom(database);
|
|
91
|
+
|
|
92
|
+
interface ItemRow {
|
|
93
|
+
id: string;
|
|
94
|
+
kind: string;
|
|
95
|
+
subject: string;
|
|
96
|
+
statement: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const items = raw
|
|
100
|
+
.query(`SELECT id, kind, subject, statement FROM memory_items`)
|
|
101
|
+
.all() as ItemRow[];
|
|
102
|
+
|
|
103
|
+
if (items.length === 0) return;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
raw.exec("BEGIN");
|
|
107
|
+
|
|
108
|
+
const updateStmt = raw.prepare(
|
|
109
|
+
`UPDATE memory_items SET fingerprint = ? WHERE id = ?`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
const normalized = `${item.kind}|${item.subject.toLowerCase()}|${item.statement.toLowerCase()}`;
|
|
114
|
+
const fingerprint = createHash("sha256").update(normalized).digest("hex");
|
|
115
|
+
updateStmt.run(fingerprint, item.id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
raw.exec("COMMIT");
|
|
119
|
+
} catch (e) {
|
|
120
|
+
try {
|
|
121
|
+
raw.exec("ROLLBACK");
|
|
122
|
+
} catch {
|
|
123
|
+
/* no active transaction */
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -265,3 +265,13 @@ export function migrateAssistantIdToSelf(database: DrizzleDb): void {
|
|
|
265
265
|
throw e;
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* No-op down: the original assistant_id values are not recoverable. The forward
|
|
271
|
+
* migration normalized all assistant_id values to "self" and merged/deduplicated
|
|
272
|
+
* rows where the same logical entity existed under both the real assistantId and
|
|
273
|
+
* "self". The original per-assistant IDs are permanently lost.
|
|
274
|
+
*/
|
|
275
|
+
export function downAssistantIdToSelf(_database: DrizzleDb): void {
|
|
276
|
+
// Intentionally empty — original assistant_id values cannot be restored.
|
|
277
|
+
}
|
|
@@ -228,3 +228,37 @@ export function migrateRemoveAssistantIdColumns(database: DrizzleDb): void {
|
|
|
228
228
|
raw.exec("PRAGMA foreign_keys = ON");
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Add the assistant_id column back to the 4 tables that had it removed.
|
|
234
|
+
*
|
|
235
|
+
* NOTE: The data previously stored in assistant_id is lost — all rows will
|
|
236
|
+
* have assistant_id = 'self' after this down migration. This only restores
|
|
237
|
+
* the column structure so that older code expecting the column can function.
|
|
238
|
+
*/
|
|
239
|
+
export function downRemoveAssistantIdColumns(database: DrizzleDb): void {
|
|
240
|
+
const raw = getSqliteFrom(database);
|
|
241
|
+
|
|
242
|
+
const tables = [
|
|
243
|
+
"conversation_keys",
|
|
244
|
+
"attachments",
|
|
245
|
+
"channel_inbound_events",
|
|
246
|
+
"message_runs",
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
for (const table of tables) {
|
|
250
|
+
// Check if the table exists and lacks assistant_id
|
|
251
|
+
const ddl = raw
|
|
252
|
+
.query(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
253
|
+
.get(table) as { sql: string } | null;
|
|
254
|
+
if (!ddl || ddl.sql.includes("assistant_id")) continue;
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
raw.exec(
|
|
258
|
+
/*sql*/ `ALTER TABLE ${table} ADD COLUMN assistant_id TEXT NOT NULL DEFAULT 'self'`,
|
|
259
|
+
);
|
|
260
|
+
} catch {
|
|
261
|
+
/* column already exists */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -95,3 +95,29 @@ export function migrateLlmUsageEventsDropAssistantId(
|
|
|
95
95
|
raw.exec("PRAGMA foreign_keys = ON");
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Add the assistant_id column back to llm_usage_events.
|
|
101
|
+
*
|
|
102
|
+
* NOTE: The data previously stored in assistant_id is lost — all rows will
|
|
103
|
+
* have assistant_id = NULL after this down migration. This only restores
|
|
104
|
+
* the column structure so that older code expecting the column can function.
|
|
105
|
+
*/
|
|
106
|
+
export function downLlmUsageEventsDropAssistantId(database: DrizzleDb): void {
|
|
107
|
+
const raw = getSqliteFrom(database);
|
|
108
|
+
|
|
109
|
+
const ddl = raw
|
|
110
|
+
.query(
|
|
111
|
+
`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'llm_usage_events'`,
|
|
112
|
+
)
|
|
113
|
+
.get() as { sql: string } | null;
|
|
114
|
+
if (!ddl || ddl.sql.includes("assistant_id")) return;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
raw.exec(
|
|
118
|
+
/*sql*/ `ALTER TABLE llm_usage_events ADD COLUMN assistant_id TEXT`,
|
|
119
|
+
);
|
|
120
|
+
} catch {
|
|
121
|
+
/* column already exists */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -88,3 +88,13 @@ export function migrateBackfillInboxThreadStateFromBindings(
|
|
|
88
88
|
throw e;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* No-op down: the seeded inbox thread state rows are expected to remain.
|
|
94
|
+
* The forward migration used INSERT OR IGNORE, so existing rows were never
|
|
95
|
+
* modified. Removing the seeded rows could leave the inbox empty for
|
|
96
|
+
* pre-existing conversations, which is worse than keeping them.
|
|
97
|
+
*/
|
|
98
|
+
export function downBackfillInboxThreadState(_database: DrizzleDb): void {
|
|
99
|
+
// Intentionally empty — seeded data is expected to remain.
|
|
100
|
+
}
|