@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
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { and, eq, like, sql } from "drizzle-orm";
|
|
1
|
+
import { and, desc, eq, like, sql } from "drizzle-orm";
|
|
2
2
|
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 { getAssistantName } from "../daemon/identity-helpers.js";
|
|
7
|
+
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
6
8
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
7
9
|
import {
|
|
8
|
-
createTimeout,
|
|
9
10
|
extractToolUse,
|
|
10
11
|
getConfiguredProvider,
|
|
11
12
|
userMessage,
|
|
12
13
|
} from "../providers/provider-send-message.js";
|
|
14
|
+
import { BackendUnavailableError } from "../util/errors.js";
|
|
13
15
|
import { getLogger } from "../util/logger.js";
|
|
14
16
|
import { truncate } from "../util/truncate.js";
|
|
15
17
|
import { maybeEnqueueConversationStartersJob } from "./conversation-starters-cadence.js";
|
|
16
18
|
import { getDb } from "./db.js";
|
|
17
19
|
import { computeMemoryFingerprint } from "./fingerprint.js";
|
|
18
20
|
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
21
|
+
import { upsertJournalMemoriesFromDisk } from "./journal-memory.js";
|
|
19
22
|
import { extractTextFromStoredMessageContent } from "./message-content.js";
|
|
20
23
|
import { withQdrantBreaker } from "./qdrant-circuit-breaker.js";
|
|
21
24
|
import { getQdrantClient } from "./qdrant-client.js";
|
|
@@ -31,7 +34,8 @@ export type MemoryItemKind =
|
|
|
31
34
|
| "project"
|
|
32
35
|
| "decision"
|
|
33
36
|
| "constraint"
|
|
34
|
-
| "event"
|
|
37
|
+
| "event"
|
|
38
|
+
| "journal";
|
|
35
39
|
|
|
36
40
|
export type OverrideConfidence = "explicit" | "tentative" | "inferred";
|
|
37
41
|
|
|
@@ -55,8 +59,16 @@ const VALID_KINDS = new Set<string>([
|
|
|
55
59
|
"decision",
|
|
56
60
|
"constraint",
|
|
57
61
|
"event",
|
|
62
|
+
"journal",
|
|
58
63
|
]);
|
|
59
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Kinds the LLM is allowed to produce during extraction. Excludes "journal"
|
|
67
|
+
* because journal memories are created directly from disk files — any
|
|
68
|
+
* LLM-produced journal items would be silently dropped, wasting tokens.
|
|
69
|
+
*/
|
|
70
|
+
const EXTRACTION_KINDS = [...VALID_KINDS].filter((k) => k !== "journal");
|
|
71
|
+
|
|
60
72
|
/** Maps old kind names to their new equivalents for graceful migration. */
|
|
61
73
|
const KIND_MIGRATION_MAP: Record<string, MemoryItemKind> = {
|
|
62
74
|
profile: "identity",
|
|
@@ -150,6 +162,7 @@ function buildExtractionSystemPrompt(
|
|
|
150
162
|
statement: string;
|
|
151
163
|
}>,
|
|
152
164
|
messageRole: string,
|
|
165
|
+
userPersona?: string | null,
|
|
153
166
|
): string {
|
|
154
167
|
// Build the fixed instruction body first so we can measure it and allocate
|
|
155
168
|
// the remaining budget to identity context.
|
|
@@ -182,17 +195,57 @@ For each item, provide:
|
|
|
182
195
|
Rules:
|
|
183
196
|
- Only extract genuinely memorable information. Skip pleasantries, filler, and transient discussion.
|
|
184
197
|
- Do NOT extract information about what tools the assistant used or what files it read — only extract substantive facts about the user, their projects, and their preferences.
|
|
185
|
-
- Do NOT extract claims about actions the assistant performed, outcomes it achieved, or progress it reported (e.g., "I booked an appointment", "I sent the email"). Only extract facts stated by the user or from external sources — the assistant's self-reports are not reliable memory material.
|
|
186
198
|
- Do NOT extract raw code snippets, JSON fragments, YAML, configuration values, log output, or data structures. Only extract the human-readable meaning or intent behind such content, not the literal syntax.
|
|
187
199
|
- Prefer fewer high-quality items over many low-quality ones.
|
|
188
|
-
- If the message contains no memorable information, return an empty array
|
|
200
|
+
- If the message contains no memorable information, return an empty array.
|
|
201
|
+
- The preceding conversation context (if provided) is for disambiguation only. Extract items ONLY from the final message after the --- separator, not from the context messages.`;
|
|
202
|
+
|
|
203
|
+
// Try to extract user name from persona text
|
|
204
|
+
let userName = "the user";
|
|
205
|
+
if (userPersona) {
|
|
206
|
+
const nameMatch = userPersona.match(/\*\*Name:\*\*\s*(.+)/);
|
|
207
|
+
if (nameMatch) {
|
|
208
|
+
userName = nameMatch[1].trim();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
189
211
|
|
|
190
212
|
if (messageRole === "assistant") {
|
|
191
213
|
instructions += `
|
|
192
214
|
|
|
193
|
-
IMPORTANT: The message below is from the ASSISTANT
|
|
215
|
+
IMPORTANT: The message below is from the ASSISTANT. You may extract facts about actions taken, decisions made, and outcomes achieved. However, do NOT attribute the assistant's own identity, personality, or self-descriptions to the user. If the assistant is just introducing itself or expressing uncertainty about its own nature, extract nothing.`;
|
|
194
216
|
}
|
|
195
217
|
|
|
218
|
+
instructions += `
|
|
219
|
+
|
|
220
|
+
## Examples
|
|
221
|
+
|
|
222
|
+
Good extractions from user messages:
|
|
223
|
+
- "I'm a backend engineer at Acme Corp, mostly working with Go and PostgreSQL"
|
|
224
|
+
→ kind: identity, subject: "Role at Acme Corp", statement: "${userName} is a backend engineer at Acme Corp, works primarily with Go and PostgreSQL"
|
|
225
|
+
|
|
226
|
+
- "Always use semantic commits in this repo. I hate squash merges."
|
|
227
|
+
→ kind: constraint, subject: "Git conventions", statement: "${userName} requires semantic commit messages. Strongly dislikes squash merges."
|
|
228
|
+
|
|
229
|
+
- "We decided to go with Redis for the cache layer because DynamoDB was too expensive at our read volume"
|
|
230
|
+
→ kind: decision, subject: "Cache layer choice", statement: "${userName} chose Redis over DynamoDB for caching due to cost at high read volumes"
|
|
231
|
+
|
|
232
|
+
Good extractions from assistant messages:
|
|
233
|
+
- "Based on your earlier mention, I see you're using Next.js 14 with the app router for the dashboard project."
|
|
234
|
+
→ kind: project, subject: "Dashboard tech stack", statement: "${userName}'s dashboard project uses Next.js 14 with the app router"
|
|
235
|
+
|
|
236
|
+
- "Since you mentioned your team follows trunk-based development, I'll keep the changes in a single commit."
|
|
237
|
+
→ kind: constraint, subject: "Team branching strategy", statement: "${userName}'s team follows trunk-based development"
|
|
238
|
+
|
|
239
|
+
- "I've refactored the auth middleware to use JWT validation and added rate limiting to the login endpoint."
|
|
240
|
+
→ kind: project, subject: "Auth middleware changes", statement: "Auth middleware was refactored to use JWT validation with rate limiting on the login endpoint"
|
|
241
|
+
|
|
242
|
+
Do NOT extract:
|
|
243
|
+
- "I'll check that file for you" → assistant operational statement with no lasting information
|
|
244
|
+
- "I think the best approach would be to refactor this" → speculative, no action taken yet
|
|
245
|
+
- "The tests passed" → transient status
|
|
246
|
+
- "Sure, sounds good" → filler
|
|
247
|
+
- "\`\`\`json {"key": "val"} \`\`\`" → raw code/data, extract meaning not syntax`;
|
|
248
|
+
|
|
196
249
|
if (existingItems.length > 0) {
|
|
197
250
|
instructions += `\n\nExisting memory items (use these to identify supersession targets — set \`supersedes\` to the item ID if the new information replaces one of these):\n`;
|
|
198
251
|
for (const item of existingItems) {
|
|
@@ -204,9 +257,10 @@ IMPORTANT: The message below is from the ASSISTANT, not the user. Do NOT attribu
|
|
|
204
257
|
// generic "User ..." labels. Budget is dynamically computed: whatever
|
|
205
258
|
// remains after the fixed instructions fits within the system prompt
|
|
206
259
|
// ceiling, preventing oversized prompts from exceeding the provider input
|
|
207
|
-
// window (which would cause sendMessage to error
|
|
208
|
-
|
|
209
|
-
|
|
260
|
+
// window (which would cause sendMessage to error).
|
|
261
|
+
const rawIdentityContext = buildCoreIdentityContext(
|
|
262
|
+
userPersona ? { userPersona } : undefined,
|
|
263
|
+
);
|
|
210
264
|
|
|
211
265
|
let prompt = "";
|
|
212
266
|
if (rawIdentityContext) {
|
|
@@ -316,186 +370,200 @@ async function extractItemsWithLLM(
|
|
|
316
370
|
extractionConfig: MemoryExtractionConfig,
|
|
317
371
|
scopeId: string,
|
|
318
372
|
messageRole: string,
|
|
373
|
+
precedingMessages: Array<{ role: string; content: string }>,
|
|
374
|
+
userPersona?: string | null,
|
|
319
375
|
): Promise<ExtractedItem[]> {
|
|
320
376
|
const provider = await getConfiguredProvider();
|
|
321
377
|
if (!provider) {
|
|
322
|
-
|
|
323
|
-
"
|
|
378
|
+
throw new BackendUnavailableError(
|
|
379
|
+
"Provider unavailable for memory extraction",
|
|
324
380
|
);
|
|
325
|
-
return extractItemsPatternBased(text, scopeId);
|
|
326
381
|
}
|
|
327
382
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
383
|
+
// Query existing items to give the LLM supersession context
|
|
384
|
+
const existingItems = queryExistingItemsForContext(scopeId, text);
|
|
385
|
+
const systemPrompt = buildExtractionSystemPrompt(
|
|
386
|
+
existingItems,
|
|
387
|
+
messageRole,
|
|
388
|
+
userPersona,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const assistantName = getAssistantName() ?? "the assistant";
|
|
392
|
+
const messagePrefix =
|
|
393
|
+
messageRole === "assistant"
|
|
394
|
+
? `[This message is from ${assistantName}]\n\n`
|
|
395
|
+
: `[This message is from the user]\n\n`;
|
|
396
|
+
|
|
397
|
+
// Build user content with optional preceding conversation context
|
|
398
|
+
const contextParts: string[] = [];
|
|
399
|
+
for (const msg of precedingMessages) {
|
|
400
|
+
const msgText = extractTextFromStoredMessageContent(msg.content);
|
|
401
|
+
if (msgText.length === 0) continue;
|
|
402
|
+
const roleLabel =
|
|
403
|
+
msg.role === "assistant"
|
|
404
|
+
? (getAssistantName() ?? "assistant")
|
|
405
|
+
: "user";
|
|
406
|
+
contextParts.push(`[${roleLabel}]: ${msgText}`);
|
|
407
|
+
}
|
|
408
|
+
let userContent = `${messagePrefix}${text}`;
|
|
409
|
+
if (contextParts.length > 0) {
|
|
410
|
+
userContent = `Preceding conversation context:\n${contextParts.join("\n\n")}\n\n---\n\nMessage to extract from:\n${messagePrefix}${text}`;
|
|
411
|
+
}
|
|
338
412
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
},
|
|
387
|
-
overrideConfidence: {
|
|
388
|
-
type: "string",
|
|
389
|
-
enum: ["explicit", "tentative", "inferred"],
|
|
390
|
-
description:
|
|
391
|
-
"How confident you are that this overrides an existing item: explicit (clear override), tentative (ambiguous), inferred (weak signal)",
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
required: [
|
|
395
|
-
"kind",
|
|
396
|
-
"subject",
|
|
397
|
-
"statement",
|
|
398
|
-
"confidence",
|
|
399
|
-
"importance",
|
|
400
|
-
"supersedes",
|
|
401
|
-
"overrideConfidence",
|
|
402
|
-
],
|
|
413
|
+
const response = await provider.sendMessage(
|
|
414
|
+
[userMessage(userContent)],
|
|
415
|
+
[
|
|
416
|
+
{
|
|
417
|
+
name: "store_memory_items",
|
|
418
|
+
description: "Store extracted memory items from the message",
|
|
419
|
+
input_schema: {
|
|
420
|
+
type: "object" as const,
|
|
421
|
+
properties: {
|
|
422
|
+
items: {
|
|
423
|
+
type: "array",
|
|
424
|
+
items: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
kind: {
|
|
428
|
+
type: "string",
|
|
429
|
+
enum: EXTRACTION_KINDS,
|
|
430
|
+
description: "Category of memory item",
|
|
431
|
+
},
|
|
432
|
+
subject: {
|
|
433
|
+
type: "string",
|
|
434
|
+
description:
|
|
435
|
+
"Short label (2-8 words) for what this is about",
|
|
436
|
+
},
|
|
437
|
+
statement: {
|
|
438
|
+
type: "string",
|
|
439
|
+
description:
|
|
440
|
+
"Relationship-rich factual statement to remember (1-2 sentences). Include relational context.",
|
|
441
|
+
},
|
|
442
|
+
confidence: {
|
|
443
|
+
type: "number",
|
|
444
|
+
description: "Confidence that this is accurate (0.0-1.0)",
|
|
445
|
+
},
|
|
446
|
+
importance: {
|
|
447
|
+
type: "number",
|
|
448
|
+
description: "How valuable this is to remember (0.0-1.0)",
|
|
449
|
+
},
|
|
450
|
+
supersedes: {
|
|
451
|
+
type: ["string", "null"],
|
|
452
|
+
description:
|
|
453
|
+
"ID of the existing memory item this replaces, or null if not replacing anything",
|
|
454
|
+
},
|
|
455
|
+
overrideConfidence: {
|
|
456
|
+
type: "string",
|
|
457
|
+
enum: ["explicit", "tentative", "inferred"],
|
|
458
|
+
description:
|
|
459
|
+
"How confident you are that this overrides an existing item: explicit (clear override), tentative (ambiguous), inferred (weak signal)",
|
|
403
460
|
},
|
|
404
461
|
},
|
|
462
|
+
required: [
|
|
463
|
+
"kind",
|
|
464
|
+
"subject",
|
|
465
|
+
"statement",
|
|
466
|
+
"confidence",
|
|
467
|
+
"importance",
|
|
468
|
+
"supersedes",
|
|
469
|
+
"overrideConfidence",
|
|
470
|
+
],
|
|
405
471
|
},
|
|
406
|
-
required: ["items"],
|
|
407
472
|
},
|
|
408
473
|
},
|
|
409
|
-
|
|
410
|
-
systemPrompt,
|
|
411
|
-
{
|
|
412
|
-
config: {
|
|
413
|
-
modelIntent: extractionConfig.modelIntent,
|
|
414
|
-
max_tokens: 1024,
|
|
415
|
-
tool_choice: { type: "tool" as const, name: "store_memory_items" },
|
|
416
|
-
},
|
|
417
|
-
signal,
|
|
474
|
+
required: ["items"],
|
|
418
475
|
},
|
|
419
|
-
|
|
420
|
-
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
systemPrompt,
|
|
479
|
+
{
|
|
480
|
+
config: {
|
|
481
|
+
modelIntent: extractionConfig.modelIntent,
|
|
482
|
+
tool_choice: { type: "tool" as const, name: "store_memory_items" },
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
);
|
|
421
486
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
);
|
|
427
|
-
return extractItemsPatternBased(text, scopeId);
|
|
428
|
-
}
|
|
487
|
+
const toolBlock = extractToolUse(response);
|
|
488
|
+
if (!toolBlock) {
|
|
489
|
+
throw new Error("No tool_use block in LLM extraction response");
|
|
490
|
+
}
|
|
429
491
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
);
|
|
435
|
-
return extractItemsPatternBased(text, scopeId);
|
|
436
|
-
}
|
|
492
|
+
const input = toolBlock.input as { items?: LLMExtractedItem[] };
|
|
493
|
+
if (!Array.isArray(input.items)) {
|
|
494
|
+
throw new Error("Invalid items structure in LLM extraction response");
|
|
495
|
+
}
|
|
437
496
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const items: ExtractedItem[] = [];
|
|
442
|
-
for (const raw of input.items) {
|
|
443
|
-
// Apply kind migration map for old kind names, then validate
|
|
444
|
-
const resolvedKind = KIND_MIGRATION_MAP[raw.kind] ?? raw.kind;
|
|
445
|
-
if (!VALID_KINDS.has(resolvedKind)) continue;
|
|
446
|
-
if (!raw.subject || !raw.statement) continue;
|
|
447
|
-
const subject = truncate(String(raw.subject), 80, "");
|
|
448
|
-
const statement = truncate(String(raw.statement), 500, "");
|
|
449
|
-
const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
|
|
450
|
-
const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
|
|
451
|
-
const fingerprint = computeMemoryFingerprint(
|
|
452
|
-
scopeId,
|
|
453
|
-
resolvedKind,
|
|
454
|
-
subject,
|
|
455
|
-
statement,
|
|
456
|
-
);
|
|
497
|
+
// Build set of known existing item IDs for supersession validation
|
|
498
|
+
const existingItemIds = new Set(existingItems.map((e) => e.id));
|
|
457
499
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
items.push({
|
|
476
|
-
kind: resolvedKind as MemoryItemKind,
|
|
477
|
-
subject,
|
|
478
|
-
statement,
|
|
479
|
-
confidence,
|
|
480
|
-
importance,
|
|
481
|
-
fingerprint,
|
|
482
|
-
supersedes,
|
|
483
|
-
overrideConfidence,
|
|
484
|
-
supersedesRejected,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
500
|
+
const items: ExtractedItem[] = [];
|
|
501
|
+
for (const raw of input.items) {
|
|
502
|
+
// Apply kind migration map for old kind names, then validate
|
|
503
|
+
const resolvedKind = KIND_MIGRATION_MAP[raw.kind] ?? raw.kind;
|
|
504
|
+
if (resolvedKind === "journal") continue; // journal memories created directly from disk
|
|
505
|
+
if (!VALID_KINDS.has(resolvedKind)) continue;
|
|
506
|
+
if (!raw.subject || !raw.statement) continue;
|
|
507
|
+
const subject = String(raw.subject).trim();
|
|
508
|
+
const statement = String(raw.statement).trim();
|
|
509
|
+
const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
|
|
510
|
+
const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
|
|
511
|
+
const fingerprint = computeMemoryFingerprint(
|
|
512
|
+
scopeId,
|
|
513
|
+
resolvedKind,
|
|
514
|
+
subject,
|
|
515
|
+
statement,
|
|
516
|
+
);
|
|
487
517
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
518
|
+
// Validate supersedes: must reference a known existing item ID.
|
|
519
|
+
// Reject hallucinated IDs that don't match any item we showed the LLM.
|
|
520
|
+
const rawSupersedes =
|
|
521
|
+
typeof raw.supersedes === "string" && raw.supersedes.length > 0
|
|
522
|
+
? raw.supersedes
|
|
523
|
+
: null;
|
|
524
|
+
const supersedes =
|
|
525
|
+
rawSupersedes && existingItemIds.has(rawSupersedes)
|
|
526
|
+
? rawSupersedes
|
|
527
|
+
: null;
|
|
528
|
+
const supersedesRejected = !!rawSupersedes && !supersedes;
|
|
529
|
+
const overrideConfidence = VALID_OVERRIDE_CONFIDENCES.has(
|
|
530
|
+
raw.overrideConfidence,
|
|
531
|
+
)
|
|
532
|
+
? (raw.overrideConfidence as OverrideConfidence)
|
|
533
|
+
: "inferred";
|
|
534
|
+
|
|
535
|
+
items.push({
|
|
536
|
+
kind: resolvedKind as MemoryItemKind,
|
|
537
|
+
subject,
|
|
538
|
+
statement,
|
|
539
|
+
confidence,
|
|
540
|
+
importance,
|
|
541
|
+
fingerprint,
|
|
542
|
+
supersedes,
|
|
543
|
+
overrideConfidence,
|
|
544
|
+
supersedesRejected,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return deduplicateItems(items);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Fire conversation starters generation when journal memories were created.
|
|
553
|
+
* Wrapped in try/catch so failures never propagate to the caller.
|
|
554
|
+
*/
|
|
555
|
+
function triggerConversationStartersIfNeeded(
|
|
556
|
+
count: number,
|
|
557
|
+
scopeId: string,
|
|
558
|
+
): void {
|
|
559
|
+
if (count <= 0) return;
|
|
560
|
+
try {
|
|
561
|
+
maybeEnqueueConversationStartersJob(scopeId);
|
|
492
562
|
} catch (err) {
|
|
493
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
494
563
|
log.warn(
|
|
495
|
-
{ err: message },
|
|
496
|
-
"
|
|
564
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
565
|
+
"Failed to check conversation starters cadence",
|
|
497
566
|
);
|
|
498
|
-
return extractItemsPatternBased(text, scopeId);
|
|
499
567
|
}
|
|
500
568
|
}
|
|
501
569
|
|
|
@@ -513,6 +581,7 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
513
581
|
role: messages.role,
|
|
514
582
|
content: messages.content,
|
|
515
583
|
createdAt: messages.createdAt,
|
|
584
|
+
conversationId: messages.conversationId,
|
|
516
585
|
})
|
|
517
586
|
.from(messages)
|
|
518
587
|
.where(eq(messages.id, messageId))
|
|
@@ -520,28 +589,77 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
520
589
|
|
|
521
590
|
if (!message) return 0;
|
|
522
591
|
|
|
592
|
+
// Fetch up to 6 preceding messages from the same conversation for
|
|
593
|
+
// disambiguation context (e.g. resolving "that framework" or "yes, do it").
|
|
594
|
+
const effectiveConversationId = conversationId ?? message.conversationId;
|
|
595
|
+
const precedingMessages = effectiveConversationId
|
|
596
|
+
? db
|
|
597
|
+
.select({ role: messages.role, content: messages.content })
|
|
598
|
+
.from(messages)
|
|
599
|
+
.where(
|
|
600
|
+
and(
|
|
601
|
+
eq(messages.conversationId, effectiveConversationId),
|
|
602
|
+
sql`${messages.createdAt} < ${message.createdAt}`,
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
.orderBy(desc(messages.createdAt))
|
|
606
|
+
.limit(6)
|
|
607
|
+
.all()
|
|
608
|
+
.reverse()
|
|
609
|
+
: [];
|
|
610
|
+
|
|
611
|
+
const effectiveScopeId = scopeId ?? "default";
|
|
612
|
+
|
|
613
|
+
// Directly create journal memories from any journal files written during
|
|
614
|
+
// this message, bypassing LLM extraction (which would summarize/rewrite them).
|
|
615
|
+
// This must run before the extraction guards (semantic density, useLLM, etc.)
|
|
616
|
+
// because journal disk scanning is independent of LLM extraction.
|
|
617
|
+
let journalUpserted = 0;
|
|
618
|
+
if (message.role === "assistant") {
|
|
619
|
+
journalUpserted = upsertJournalMemoriesFromDisk(
|
|
620
|
+
message.createdAt,
|
|
621
|
+
effectiveScopeId,
|
|
622
|
+
messageId,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
523
626
|
const text = extractTextFromStoredMessageContent(message.content);
|
|
524
627
|
if (!hasSemanticDensity(text)) {
|
|
525
628
|
log.debug(
|
|
526
629
|
{ messageId },
|
|
527
630
|
"Skipping extraction — message lacks semantic density",
|
|
528
631
|
);
|
|
529
|
-
|
|
632
|
+
triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
|
|
633
|
+
return journalUpserted;
|
|
530
634
|
}
|
|
531
635
|
|
|
532
636
|
const config = getConfig();
|
|
533
637
|
const extractionConfig = config.memory.extraction;
|
|
534
|
-
const effectiveScopeId = scopeId ?? "default";
|
|
535
|
-
const extracted = extractionConfig.useLLM
|
|
536
|
-
? await extractItemsWithLLM(
|
|
537
|
-
text,
|
|
538
|
-
extractionConfig,
|
|
539
|
-
effectiveScopeId,
|
|
540
|
-
message.role,
|
|
541
|
-
)
|
|
542
|
-
: extractItemsPatternBased(text, effectiveScopeId);
|
|
543
638
|
|
|
544
|
-
|
|
639
|
+
// Resolve the guardian's persona to provide personality-aware extraction
|
|
640
|
+
// context. Currently uses the guardian persona for all conversations —
|
|
641
|
+
// non-guardian conversations are rare and the guardian's persona provides
|
|
642
|
+
// better extraction context than none.
|
|
643
|
+
const userPersona = resolveGuardianPersona();
|
|
644
|
+
|
|
645
|
+
if (!extractionConfig.useLLM) {
|
|
646
|
+
triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
|
|
647
|
+
return journalUpserted;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const extracted = await extractItemsWithLLM(
|
|
651
|
+
text,
|
|
652
|
+
extractionConfig,
|
|
653
|
+
effectiveScopeId,
|
|
654
|
+
message.role,
|
|
655
|
+
precedingMessages,
|
|
656
|
+
userPersona,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
if (extracted.length === 0) {
|
|
660
|
+
triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
|
|
661
|
+
return journalUpserted;
|
|
662
|
+
}
|
|
545
663
|
|
|
546
664
|
// Guard: re-check after the async LLM call. The event loop yields during
|
|
547
665
|
// extractItemsWithLLM, so another task could have marked the conversation
|
|
@@ -551,13 +669,10 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
551
669
|
{ messageId, conversationId },
|
|
552
670
|
"Skipping upsert — conversation marked failed during extraction",
|
|
553
671
|
);
|
|
554
|
-
|
|
672
|
+
triggerConversationStartersIfNeeded(journalUpserted, effectiveScopeId);
|
|
673
|
+
return journalUpserted;
|
|
555
674
|
}
|
|
556
675
|
|
|
557
|
-
// Determine verification state from message role
|
|
558
|
-
const verificationState =
|
|
559
|
-
message.role === "user" ? "user_reported" : "assistant_inferred";
|
|
560
|
-
|
|
561
676
|
let upserted = 0;
|
|
562
677
|
for (const item of extracted) {
|
|
563
678
|
const now = Date.now();
|
|
@@ -577,13 +692,21 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
577
692
|
let effectiveStatus: string = "active";
|
|
578
693
|
if (existing) {
|
|
579
694
|
memoryItemId = existing.id;
|
|
580
|
-
// Promote verification state if re-seen from a more trusted source
|
|
581
|
-
const promotedState =
|
|
582
|
-
existing.verificationState === "assistant_inferred" &&
|
|
583
|
-
verificationState === "user_reported"
|
|
584
|
-
? "user_reported"
|
|
585
|
-
: existing.verificationState;
|
|
586
695
|
effectiveStatus = "active";
|
|
696
|
+
// Preserve sourceType for tool-sourced items — extraction should not
|
|
697
|
+
// demote items the user explicitly saved.
|
|
698
|
+
const effectiveSourceType =
|
|
699
|
+
existing.sourceType === "tool" ? "tool" : "extraction";
|
|
700
|
+
|
|
701
|
+
// Dual-write verificationState alongside sourceType for client compat.
|
|
702
|
+
// Promote from assistant_inferred → user_reported when re-seen from user.
|
|
703
|
+
const effectiveVerificationState =
|
|
704
|
+
message.role === "user" || existing.verificationState === "user_reported"
|
|
705
|
+
? "user_reported"
|
|
706
|
+
: existing.verificationState === "user_confirmed"
|
|
707
|
+
? "user_confirmed"
|
|
708
|
+
: "assistant_inferred";
|
|
709
|
+
|
|
587
710
|
db.update(memoryItems)
|
|
588
711
|
.set({
|
|
589
712
|
status: effectiveStatus,
|
|
@@ -594,7 +717,9 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
594
717
|
Math.max(existing.importance ?? 0, item.importance),
|
|
595
718
|
),
|
|
596
719
|
lastSeenAt: Math.max(existing.lastSeenAt, seenAt),
|
|
597
|
-
|
|
720
|
+
sourceType: effectiveSourceType,
|
|
721
|
+
sourceMessageRole: message.role,
|
|
722
|
+
verificationState: effectiveVerificationState,
|
|
598
723
|
})
|
|
599
724
|
.where(eq(memoryItems.id, existing.id))
|
|
600
725
|
.run();
|
|
@@ -610,7 +735,11 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
610
735
|
confidence: item.confidence,
|
|
611
736
|
importance: item.importance,
|
|
612
737
|
fingerprint: item.fingerprint,
|
|
613
|
-
|
|
738
|
+
sourceType: "extraction",
|
|
739
|
+
sourceMessageRole: message.role,
|
|
740
|
+
// Dual-write verificationState for client compat
|
|
741
|
+
verificationState:
|
|
742
|
+
message.role === "user" ? "user_reported" : "assistant_inferred",
|
|
614
743
|
scopeId: effectiveScopeId,
|
|
615
744
|
firstSeenAt: message.createdAt,
|
|
616
745
|
lastSeenAt: seenAt,
|
|
@@ -701,11 +830,9 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
701
830
|
}
|
|
702
831
|
|
|
703
832
|
// Fallback subject-match supersession: only when the LLM did not
|
|
704
|
-
// explicitly handle supersession for this item.
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
// rejected (hallucinated) — they should coexist, not trigger
|
|
708
|
-
// subject-based replacement.
|
|
833
|
+
// explicitly handle supersession for this item. Skip items whose
|
|
834
|
+
// supersedes ID was rejected (hallucinated) — they should coexist,
|
|
835
|
+
// not trigger subject-based replacement.
|
|
709
836
|
if (
|
|
710
837
|
!item.supersedes &&
|
|
711
838
|
!item.supersedesRejected &&
|
|
@@ -730,7 +857,7 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
730
857
|
.values({
|
|
731
858
|
memoryItemId,
|
|
732
859
|
messageId,
|
|
733
|
-
evidence:
|
|
860
|
+
evidence: item.statement,
|
|
734
861
|
createdAt: now,
|
|
735
862
|
})
|
|
736
863
|
.onConflictDoNothing()
|
|
@@ -739,139 +866,19 @@ export async function extractAndUpsertMemoryItemsForMessage(
|
|
|
739
866
|
enqueueMemoryJob("embed_item", { itemId: memoryItemId });
|
|
740
867
|
}
|
|
741
868
|
|
|
869
|
+
upserted += journalUpserted;
|
|
870
|
+
|
|
742
871
|
log.debug(
|
|
743
872
|
{ messageId, extracted: extracted.length, upserted },
|
|
744
873
|
"Extracted memory items from message",
|
|
745
874
|
);
|
|
746
875
|
|
|
747
876
|
// Trigger conversation starters generation when new items are upserted
|
|
748
|
-
|
|
749
|
-
try {
|
|
750
|
-
maybeEnqueueConversationStartersJob(effectiveScopeId);
|
|
751
|
-
} catch (err) {
|
|
752
|
-
log.warn(
|
|
753
|
-
{ err: err instanceof Error ? err.message : String(err) },
|
|
754
|
-
"Failed to check conversation starters cadence",
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
877
|
+
triggerConversationStartersIfNeeded(upserted, effectiveScopeId);
|
|
758
878
|
|
|
759
879
|
return upserted;
|
|
760
880
|
}
|
|
761
881
|
|
|
762
|
-
// ── Pattern-based extraction (fallback) ────────────────────────────────
|
|
763
|
-
|
|
764
|
-
function extractItemsPatternBased(
|
|
765
|
-
text: string,
|
|
766
|
-
scopeId: string = "default",
|
|
767
|
-
): ExtractedItem[] {
|
|
768
|
-
const sentences = text
|
|
769
|
-
.split(/[\n\r]+|(?<=[.!?])\s+/)
|
|
770
|
-
.map((s) => s.trim())
|
|
771
|
-
.filter((s) => s.length >= 20 && s.length <= 500);
|
|
772
|
-
|
|
773
|
-
const items: ExtractedItem[] = [];
|
|
774
|
-
for (const sentence of sentences) {
|
|
775
|
-
const lower = sentence.toLowerCase();
|
|
776
|
-
const classification = classifySentence(lower);
|
|
777
|
-
if (!classification) continue;
|
|
778
|
-
const subject = inferSubject(sentence, classification.kind);
|
|
779
|
-
const statement = sentence.replace(/\s+/g, " ").trim();
|
|
780
|
-
const fingerprint = computeMemoryFingerprint(
|
|
781
|
-
scopeId,
|
|
782
|
-
classification.kind,
|
|
783
|
-
subject,
|
|
784
|
-
statement,
|
|
785
|
-
);
|
|
786
|
-
items.push({
|
|
787
|
-
kind: classification.kind,
|
|
788
|
-
subject,
|
|
789
|
-
statement,
|
|
790
|
-
confidence: classification.confidence,
|
|
791
|
-
importance: classification.importance,
|
|
792
|
-
fingerprint,
|
|
793
|
-
supersedes: null,
|
|
794
|
-
overrideConfidence: "inferred" as OverrideConfidence,
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
return deduplicateItems(items);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function classifySentence(
|
|
802
|
-
lower: string,
|
|
803
|
-
): { kind: MemoryItemKind; confidence: number; importance: number } | null {
|
|
804
|
-
if (
|
|
805
|
-
includesAny(lower, [
|
|
806
|
-
"i prefer",
|
|
807
|
-
"prefer to",
|
|
808
|
-
"favorite",
|
|
809
|
-
"i like",
|
|
810
|
-
"i dislike",
|
|
811
|
-
])
|
|
812
|
-
) {
|
|
813
|
-
return { kind: "preference", confidence: 0.78, importance: 0.7 };
|
|
814
|
-
}
|
|
815
|
-
if (
|
|
816
|
-
includesAny(lower, [
|
|
817
|
-
"my name is",
|
|
818
|
-
"i am ",
|
|
819
|
-
"i work as",
|
|
820
|
-
"i live in",
|
|
821
|
-
"timezone",
|
|
822
|
-
])
|
|
823
|
-
) {
|
|
824
|
-
return { kind: "identity", confidence: 0.72, importance: 0.8 };
|
|
825
|
-
}
|
|
826
|
-
if (includesAny(lower, ["project", "repository", "repo", "codebase"])) {
|
|
827
|
-
return { kind: "project", confidence: 0.68, importance: 0.6 };
|
|
828
|
-
}
|
|
829
|
-
if (
|
|
830
|
-
includesAny(lower, ["we decided", "decision", "chosen approach", "we will"])
|
|
831
|
-
) {
|
|
832
|
-
return { kind: "decision", confidence: 0.75, importance: 0.7 };
|
|
833
|
-
}
|
|
834
|
-
if (
|
|
835
|
-
includesAny(lower, ["todo", "to do", "next step", "follow up", "need to"])
|
|
836
|
-
) {
|
|
837
|
-
return { kind: "project", confidence: 0.74, importance: 0.6 };
|
|
838
|
-
}
|
|
839
|
-
if (
|
|
840
|
-
includesAny(lower, [
|
|
841
|
-
"must",
|
|
842
|
-
"cannot",
|
|
843
|
-
"should not",
|
|
844
|
-
"constraint",
|
|
845
|
-
"requirement",
|
|
846
|
-
])
|
|
847
|
-
) {
|
|
848
|
-
return { kind: "constraint", confidence: 0.7, importance: 0.7 };
|
|
849
|
-
}
|
|
850
|
-
if (includesAny(lower, ["remember", "important", "fact", "noted"])) {
|
|
851
|
-
return { kind: "identity", confidence: 0.62, importance: 0.5 };
|
|
852
|
-
}
|
|
853
|
-
return null;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
function inferSubject(sentence: string, kind: MemoryItemKind): string {
|
|
857
|
-
const trimmed = sentence.trim();
|
|
858
|
-
if (kind === "project") {
|
|
859
|
-
const match = trimmed.match(
|
|
860
|
-
/(?:project|repo(?:sitory)?)\s+([A-Za-z0-9._/-]{2,80})/i,
|
|
861
|
-
);
|
|
862
|
-
if (match) return match[1];
|
|
863
|
-
}
|
|
864
|
-
const words = trimmed.split(/\s+/).slice(0, 6).join(" ");
|
|
865
|
-
return truncate(words, 80, "");
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
function includesAny(text: string, needles: string[]): boolean {
|
|
869
|
-
for (const needle of needles) {
|
|
870
|
-
if (text.includes(needle)) return true;
|
|
871
|
-
}
|
|
872
|
-
return false;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
882
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
876
883
|
|
|
877
884
|
function deduplicateItems(items: ExtractedItem[]): ExtractedItem[] {
|