@vellumai/assistant 0.5.6 → 0.5.8
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 +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- 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/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- 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__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -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 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- 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__/gateway-client-managed-outbound.test.ts +79 -1
- 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__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- 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 +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- 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-memory.test.ts +17 -3
- 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__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- 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__/twilio-routes-twiml.test.ts +139 -1
- 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-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- 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 +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -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/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- 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 +234 -51
- package/src/daemon/message-types/conversations.ts +4 -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 +32 -95
- 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/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- 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/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -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/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- 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/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- 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/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- 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/twilio.ts +52 -10
- 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.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- 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/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -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 +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- 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/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- 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 +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- 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__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- 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/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- 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
|
@@ -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;
|
|
@@ -19,18 +19,25 @@ import { memorySegments, memorySummaries } from "../schema.js";
|
|
|
19
19
|
const log = getLogger("memory-jobs-worker");
|
|
20
20
|
|
|
21
21
|
const SUMMARY_LLM_TIMEOUT_MS = 20_000;
|
|
22
|
-
const SUMMARY_MAX_TOKENS =
|
|
22
|
+
const SUMMARY_MAX_TOKENS = 500;
|
|
23
23
|
|
|
24
24
|
const CONVERSATION_SUMMARY_SYSTEM_PROMPT = [
|
|
25
|
-
"You
|
|
25
|
+
"You compress conversation transcripts into compact summaries for semantic search and memory retrieval.",
|
|
26
|
+
"Focus on durable facts, not transient discussion.",
|
|
27
|
+
"Preserve: goals, decisions, constraints, preferences, names, technical details, actions taken.",
|
|
28
|
+
"Remove: filler, pleasantries, tool invocation details, transient status updates.",
|
|
26
29
|
"",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
30
|
+
"Return concise markdown:",
|
|
31
|
+
"## Topic",
|
|
32
|
+
"One-line description of what the conversation is about.",
|
|
33
|
+
"## Key Facts",
|
|
34
|
+
"Bullet points of concrete facts, names, decisions, preferences.",
|
|
35
|
+
"## Outcomes",
|
|
36
|
+
"What was decided, resolved, or accomplished.",
|
|
37
|
+
"## Open Items",
|
|
38
|
+
"Unresolved questions, pending tasks, or follow-ups (omit section if none).",
|
|
39
|
+
"",
|
|
40
|
+
"Target 200-400 tokens. Be dense.",
|
|
34
41
|
].join("\n");
|
|
35
42
|
|
|
36
43
|
export async function buildConversationSummaryJob(
|
|
@@ -62,9 +69,8 @@ export async function buildConversationSummaryJob(
|
|
|
62
69
|
|
|
63
70
|
// Build segment text for LLM input (chronological order)
|
|
64
71
|
const segmentTexts = rows
|
|
65
|
-
.slice(0, 30)
|
|
66
72
|
.reverse()
|
|
67
|
-
.map((row) => `[${row.role}] ${truncate(row.text,
|
|
73
|
+
.map((row) => `[${row.role}] ${truncate(row.text, 600)}`)
|
|
68
74
|
.join("\n\n");
|
|
69
75
|
|
|
70
76
|
const summaryText = await summarizeWithLLM(
|
|
@@ -208,14 +214,18 @@ async function summarizeWithLLM(
|
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
function buildFallbackSummary(
|
|
211
|
-
|
|
217
|
+
existingSummary: string | null,
|
|
212
218
|
newContent: string,
|
|
213
219
|
label: string,
|
|
214
220
|
): string {
|
|
215
221
|
const lines = newContent.split("\n").filter((l) => l.trim().length > 0);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
if (lines.length === 0) return existingSummary ?? `${label} (no content)`;
|
|
223
|
+
const head = lines.slice(0, 3).map((l) => `- ${truncate(l.trim(), 200)}`);
|
|
224
|
+
const tail =
|
|
225
|
+
lines.length > 6
|
|
226
|
+
? lines.slice(-3).map((l) => `- ${truncate(l.trim(), 200)}`)
|
|
227
|
+
: [];
|
|
228
|
+
const parts = [`${label} summary`, "", ...head];
|
|
229
|
+
if (tail.length > 0) parts.push("", "...", "", ...tail);
|
|
220
230
|
return parts.join("\n");
|
|
221
231
|
}
|
package/src/memory/jobs-store.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { and, asc, eq, inArray, lte, notInArray } from "drizzle-orm";
|
|
1
|
+
import { and, asc, eq, inArray, lte, notInArray, sql } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
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");
|
|
@@ -82,6 +86,38 @@ export function enqueueMemoryJob(
|
|
|
82
86
|
return id;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Upsert a debounced job: if a pending job of the same type and conversation
|
|
91
|
+
* already exists, push its `runAfter` forward instead of creating a duplicate.
|
|
92
|
+
* This prevents rapid message indexing from spawning redundant jobs.
|
|
93
|
+
*/
|
|
94
|
+
export function upsertDebouncedJob(
|
|
95
|
+
type: MemoryJobType,
|
|
96
|
+
payload: { conversationId: string },
|
|
97
|
+
runAfter: number,
|
|
98
|
+
): void {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
const existing = db
|
|
101
|
+
.select()
|
|
102
|
+
.from(memoryJobs)
|
|
103
|
+
.where(
|
|
104
|
+
and(
|
|
105
|
+
eq(memoryJobs.type, type),
|
|
106
|
+
eq(memoryJobs.status, "pending"),
|
|
107
|
+
sql`json_extract(${memoryJobs.payload}, '$.conversationId') = ${payload.conversationId}`,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
.get();
|
|
111
|
+
if (existing) {
|
|
112
|
+
db.update(memoryJobs)
|
|
113
|
+
.set({ runAfter, updatedAt: Date.now() })
|
|
114
|
+
.where(eq(memoryJobs.id, existing.id))
|
|
115
|
+
.run();
|
|
116
|
+
} else {
|
|
117
|
+
enqueueMemoryJob(type, payload, runAfter);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
85
121
|
export function enqueueCleanupStaleSupersededItemsJob(
|
|
86
122
|
retentionMs?: number,
|
|
87
123
|
): string {
|
|
@@ -201,14 +237,33 @@ export function claimMemoryJobs(limit: number): MemoryJob[] {
|
|
|
201
237
|
.all();
|
|
202
238
|
|
|
203
239
|
const remainingSlots = limit - nonEmbedCandidates.length;
|
|
240
|
+
|
|
241
|
+
// When the Qdrant circuit breaker is open, skip embed jobs entirely —
|
|
242
|
+
// they would just be claimed → fail → deferred, wasting CPU cycles.
|
|
243
|
+
// Exception: if the cooldown has elapsed (breaker ready for half-open probe),
|
|
244
|
+
// allow exactly 1 embed job through so the breaker can self-heal.
|
|
245
|
+
const breakerOpen = isQdrantBreakerOpen();
|
|
246
|
+
const probeAllowed = breakerOpen && shouldAllowQdrantProbe();
|
|
247
|
+
const skipEmbedJobs = breakerOpen && !probeAllowed;
|
|
248
|
+
const embedLimit = probeAllowed ? 1 : remainingSlots;
|
|
249
|
+
|
|
250
|
+
if (skipEmbedJobs && remainingSlots > 0) {
|
|
251
|
+
log.debug("Skipping embed job claims — Qdrant circuit breaker is open");
|
|
252
|
+
}
|
|
253
|
+
if (probeAllowed && remainingSlots > 0) {
|
|
254
|
+
log.debug(
|
|
255
|
+
"Allowing 1 embed probe job — Qdrant circuit breaker cooldown elapsed",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
204
259
|
const embedCandidates =
|
|
205
|
-
remainingSlots > 0
|
|
260
|
+
remainingSlots > 0 && !skipEmbedJobs
|
|
206
261
|
? db
|
|
207
262
|
.select()
|
|
208
263
|
.from(memoryJobs)
|
|
209
264
|
.where(and(pendingFilter, inArray(memoryJobs.type, EMBED_JOB_TYPES)))
|
|
210
265
|
.orderBy(asc(memoryJobs.runAfter), asc(memoryJobs.createdAt))
|
|
211
|
-
.limit(
|
|
266
|
+
.limit(embedLimit)
|
|
212
267
|
.all()
|
|
213
268
|
: [];
|
|
214
269
|
|
|
@@ -243,8 +298,8 @@ export function completeMemoryJob(id: string): void {
|
|
|
243
298
|
|
|
244
299
|
/** Max times a job can be deferred before it is marked as failed. */
|
|
245
300
|
const MAX_DEFERRALS = 50;
|
|
246
|
-
/**
|
|
247
|
-
const
|
|
301
|
+
/** Log warnings at these milestone counts to avoid flooding logs. */
|
|
302
|
+
const DEFERRAL_WARN_MILESTONES = [40, 45];
|
|
248
303
|
/** Base delay in ms for deferred jobs (grows with exponential backoff). */
|
|
249
304
|
const DEFER_BASE_DELAY_MS = 30_000;
|
|
250
305
|
/** Maximum delay cap for deferred jobs (5 minutes). */
|
|
@@ -286,7 +341,9 @@ export function deferMemoryJob(id: string): "deferred" | "failed" {
|
|
|
286
341
|
return "failed";
|
|
287
342
|
}
|
|
288
343
|
|
|
289
|
-
|
|
344
|
+
// Log at milestones only (40, 45) to avoid flooding logs.
|
|
345
|
+
// At 50, the job fails via the check above, so 40 and 45 are the warnings.
|
|
346
|
+
if (DEFERRAL_WARN_MILESTONES.includes(deferrals)) {
|
|
290
347
|
log.warn(
|
|
291
348
|
{ jobId: id, type: row.type, deferrals, max: MAX_DEFERRALS },
|
|
292
349
|
"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
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { and, eq } from "drizzle-orm";
|
|
5
|
+
import { v4 as uuid } from "uuid";
|
|
6
|
+
|
|
7
|
+
import { getLogger } from "../util/logger.js";
|
|
8
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
9
|
+
import { type DrizzleDb, getDb } from "./db.js";
|
|
10
|
+
import { computeMemoryFingerprint } from "./fingerprint.js";
|
|
11
|
+
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
12
|
+
import { memoryItems, memoryItemSources } from "./schema.js";
|
|
13
|
+
|
|
14
|
+
const log = getLogger("memory-journal");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Process a single journal `.md` file: read content, derive subject, compute
|
|
18
|
+
* fingerprint, upsert to DB, and enqueue an embed job.
|
|
19
|
+
*
|
|
20
|
+
* Returns `true` if a new memory item was inserted, `false` if it already
|
|
21
|
+
* existed (or was skipped).
|
|
22
|
+
*/
|
|
23
|
+
function upsertSingleJournalFile(
|
|
24
|
+
filepath: string,
|
|
25
|
+
filename: string,
|
|
26
|
+
messageCreatedAt: number,
|
|
27
|
+
scopeId: string,
|
|
28
|
+
messageId: string,
|
|
29
|
+
db: DrizzleDb,
|
|
30
|
+
): boolean {
|
|
31
|
+
const content = readFileSync(filepath, "utf-8");
|
|
32
|
+
|
|
33
|
+
// Derive subject from filename:
|
|
34
|
+
// strip .md extension, strip leading date prefix, replace hyphens with spaces, capitalize first letter
|
|
35
|
+
const basename = filename.replace(/\.md$/, "");
|
|
36
|
+
const withoutDate = basename.replace(/^\d{4}-\d{2}-\d{2}-?/, "");
|
|
37
|
+
const withSpaces = withoutDate.replace(/-/g, " ");
|
|
38
|
+
const subject =
|
|
39
|
+
withSpaces.length > 0
|
|
40
|
+
? withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1)
|
|
41
|
+
: basename;
|
|
42
|
+
|
|
43
|
+
const fingerprint = computeMemoryFingerprint(
|
|
44
|
+
scopeId,
|
|
45
|
+
"journal",
|
|
46
|
+
subject,
|
|
47
|
+
content,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const existing = db
|
|
51
|
+
.select()
|
|
52
|
+
.from(memoryItems)
|
|
53
|
+
.where(
|
|
54
|
+
and(
|
|
55
|
+
eq(memoryItems.fingerprint, fingerprint),
|
|
56
|
+
eq(memoryItems.scopeId, scopeId),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
.get();
|
|
60
|
+
|
|
61
|
+
let memoryItemId: string;
|
|
62
|
+
let inserted = false;
|
|
63
|
+
|
|
64
|
+
if (existing) {
|
|
65
|
+
memoryItemId = existing.id;
|
|
66
|
+
db.update(memoryItems)
|
|
67
|
+
.set({
|
|
68
|
+
lastSeenAt: messageCreatedAt,
|
|
69
|
+
status: "active",
|
|
70
|
+
})
|
|
71
|
+
.where(eq(memoryItems.id, existing.id))
|
|
72
|
+
.run();
|
|
73
|
+
} else {
|
|
74
|
+
memoryItemId = uuid();
|
|
75
|
+
db.insert(memoryItems)
|
|
76
|
+
.values({
|
|
77
|
+
id: memoryItemId,
|
|
78
|
+
kind: "journal",
|
|
79
|
+
subject,
|
|
80
|
+
statement: content,
|
|
81
|
+
status: "active",
|
|
82
|
+
confidence: 0.95,
|
|
83
|
+
importance: 0.8,
|
|
84
|
+
fingerprint,
|
|
85
|
+
sourceType: "extraction",
|
|
86
|
+
sourceMessageRole: "assistant",
|
|
87
|
+
verificationState: "assistant_inferred",
|
|
88
|
+
scopeId,
|
|
89
|
+
firstSeenAt: messageCreatedAt,
|
|
90
|
+
lastSeenAt: messageCreatedAt,
|
|
91
|
+
lastUsedAt: null,
|
|
92
|
+
supersedes: null,
|
|
93
|
+
overrideConfidence: null,
|
|
94
|
+
})
|
|
95
|
+
.run();
|
|
96
|
+
inserted = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
db.insert(memoryItemSources)
|
|
100
|
+
.values({
|
|
101
|
+
memoryItemId,
|
|
102
|
+
messageId,
|
|
103
|
+
evidence: content,
|
|
104
|
+
createdAt: Date.now(),
|
|
105
|
+
})
|
|
106
|
+
.onConflictDoNothing()
|
|
107
|
+
.run();
|
|
108
|
+
|
|
109
|
+
enqueueMemoryJob("embed_item", { itemId: memoryItemId });
|
|
110
|
+
|
|
111
|
+
return inserted;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Scan the journal directory for `.md` files created during (or after) the
|
|
116
|
+
* given message timestamp and upsert them as journal memory items with the
|
|
117
|
+
* raw, unedited file content as the `statement`.
|
|
118
|
+
*
|
|
119
|
+
* Also scans immediate subdirectories (e.g. per-user folders like
|
|
120
|
+
* `journal/sidd/`) so that user-scoped journal entries are indexed alongside
|
|
121
|
+
* root-level files.
|
|
122
|
+
*
|
|
123
|
+
* This bypasses the LLM extraction layer entirely — journal memories are
|
|
124
|
+
* stored verbatim so they are never summarised or rewritten.
|
|
125
|
+
*
|
|
126
|
+
* Returns the number of newly inserted items.
|
|
127
|
+
*/
|
|
128
|
+
export function upsertJournalMemoriesFromDisk(
|
|
129
|
+
messageCreatedAt: number,
|
|
130
|
+
scopeId: string,
|
|
131
|
+
messageId: string,
|
|
132
|
+
): number {
|
|
133
|
+
try {
|
|
134
|
+
const journalDir = join(getWorkspaceDir(), "journal");
|
|
135
|
+
|
|
136
|
+
let files: string[];
|
|
137
|
+
try {
|
|
138
|
+
files = readdirSync(journalDir);
|
|
139
|
+
} catch {
|
|
140
|
+
// Directory doesn't exist — no journal entries
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Filter for .md files, excluding readme.md (case-insensitive)
|
|
145
|
+
const mdFiles = files.filter(
|
|
146
|
+
(f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md",
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
let upserted = 0;
|
|
150
|
+
const db = getDb();
|
|
151
|
+
|
|
152
|
+
for (const filename of mdFiles) {
|
|
153
|
+
try {
|
|
154
|
+
const filepath = join(journalDir, filename);
|
|
155
|
+
const stat = statSync(filepath);
|
|
156
|
+
if (!stat.isFile()) continue;
|
|
157
|
+
|
|
158
|
+
// Only process files created during or after this message
|
|
159
|
+
if (stat.birthtimeMs < messageCreatedAt) continue;
|
|
160
|
+
|
|
161
|
+
if (upsertSingleJournalFile(filepath, filename, messageCreatedAt, scopeId, messageId, db)) {
|
|
162
|
+
upserted += 1;
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
log.warn(
|
|
166
|
+
{ filename, err: err instanceof Error ? err.message : String(err) },
|
|
167
|
+
"Failed to process journal file for memory — skipping",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Scan per-user journal subdirectories
|
|
173
|
+
for (const entry of files) {
|
|
174
|
+
try {
|
|
175
|
+
const subdirPath = join(journalDir, entry);
|
|
176
|
+
if (!statSync(subdirPath).isDirectory()) continue;
|
|
177
|
+
|
|
178
|
+
const subFiles = readdirSync(subdirPath).filter(
|
|
179
|
+
(f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md",
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
for (const filename of subFiles) {
|
|
183
|
+
try {
|
|
184
|
+
const filepath = join(subdirPath, filename);
|
|
185
|
+
const stat = statSync(filepath);
|
|
186
|
+
if (!stat.isFile()) continue;
|
|
187
|
+
|
|
188
|
+
// Only process files created during or after this message
|
|
189
|
+
if (stat.birthtimeMs < messageCreatedAt) continue;
|
|
190
|
+
|
|
191
|
+
if (upsertSingleJournalFile(filepath, filename, messageCreatedAt, scopeId, messageId, db)) {
|
|
192
|
+
upserted += 1;
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
log.warn(
|
|
196
|
+
{ filename, err: err instanceof Error ? err.message : String(err) },
|
|
197
|
+
"Failed to process journal file for memory — skipping",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
// Skip unreadable subdirectories
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return upserted;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
log.warn(
|
|
209
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
210
|
+
"Failed to scan journal directory for memories",
|
|
211
|
+
);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -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
|
+
}
|