@vellumai/assistant 0.5.1 → 0.5.3
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/ARCHITECTURE.md +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/followups/followup-store.ts +47 -1
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +40 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +114 -21
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +99 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +160 -14
- package/src/permissions/defaults.ts +14 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +94 -5
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +140 -6
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +11 -1
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
package/src/memory/db-init.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
migrateAssistantContactMetadata,
|
|
38
38
|
migrateBackfillContactInteractionStats,
|
|
39
39
|
migrateBackfillGuardianPrincipalId,
|
|
40
|
+
migrateBackfillInlineAttachmentsToDisk,
|
|
40
41
|
migrateBackfillUsageCacheAccounting,
|
|
41
42
|
migrateCallSessionInviteMetadata,
|
|
42
43
|
migrateCallSessionMode,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
migrateContactsAssistantId,
|
|
51
52
|
migrateContactsNotesColumn,
|
|
52
53
|
migrateContactsRolePrincipal,
|
|
54
|
+
migrateConversationForkLineage,
|
|
53
55
|
migrateConversationsThreadTypeIndex,
|
|
54
56
|
migrateCreateThreadStartersTable,
|
|
55
57
|
migrateCreateTraceEventsTable,
|
|
@@ -78,11 +80,18 @@ import {
|
|
|
78
80
|
migrateGuardianVerificationSessions,
|
|
79
81
|
migrateInviteCodeHashColumn,
|
|
80
82
|
migrateInviteContactId,
|
|
83
|
+
migrateLlmRequestLogMessageId,
|
|
84
|
+
migrateLlmRequestLogProvider,
|
|
85
|
+
migrateMemoryArchiveTables,
|
|
86
|
+
migrateMemoryBriefState,
|
|
81
87
|
migrateMemoryItemSupersession,
|
|
88
|
+
migrateMemoryReducerCheckpoints,
|
|
82
89
|
migrateMessagesFtsBackfill,
|
|
83
90
|
migrateNormalizePhoneIdentities,
|
|
84
91
|
migrateNotificationDeliveryThreadDecision,
|
|
85
92
|
migrateOAuthAppsClientSecretPath,
|
|
93
|
+
migrateOAuthProvidersDisplayMetadata,
|
|
94
|
+
migrateOAuthProvidersManagedServiceConfigKey,
|
|
86
95
|
migrateOAuthProvidersPingUrl,
|
|
87
96
|
migrateReminderRoutingIntent,
|
|
88
97
|
migrateRemindersToSchedules,
|
|
@@ -96,6 +105,7 @@ import {
|
|
|
96
105
|
migrateRenameSequenceEnrollmentsThreadIdColumn,
|
|
97
106
|
migrateRenameSequenceStepsReplyKey,
|
|
98
107
|
migrateRenameSourceSessionIdColumn,
|
|
108
|
+
migrateRenameThreadStartersCheckpoints,
|
|
99
109
|
migrateRenameThreadStartersTable,
|
|
100
110
|
migrateRenameVerificationSessionIdColumn,
|
|
101
111
|
migrateRenameVerificationTable,
|
|
@@ -447,6 +457,9 @@ export function initializeDb(): void {
|
|
|
447
457
|
// 77. Rename thread_starters → conversation_starters table and indexes
|
|
448
458
|
migrateRenameThreadStartersTable(database);
|
|
449
459
|
|
|
460
|
+
// 77b. Rename checkpoint keys from thread_starters: → conversation_starters: prefix
|
|
461
|
+
migrateRenameThreadStartersCheckpoints(database);
|
|
462
|
+
|
|
450
463
|
// 78. Lifecycle events table for app_open / hatch telemetry
|
|
451
464
|
createLifecycleEventsTable(database);
|
|
452
465
|
|
|
@@ -456,6 +469,33 @@ export function initializeDb(): void {
|
|
|
456
469
|
// 80. Trace events table for persistent trace/activity storage across sessions
|
|
457
470
|
migrateCreateTraceEventsTable(database);
|
|
458
471
|
|
|
472
|
+
// 81. Add managed_service_config_key column to oauth_providers
|
|
473
|
+
migrateOAuthProvidersManagedServiceConfigKey(database);
|
|
474
|
+
|
|
475
|
+
// 81b. Add display metadata columns to oauth_providers (display_name, description, dashboard_url, etc.)
|
|
476
|
+
migrateOAuthProvidersDisplayMetadata(database);
|
|
477
|
+
|
|
478
|
+
// 82. Add message_id column to llm_request_logs for per-message LLM context lookup
|
|
479
|
+
migrateLlmRequestLogMessageId(database);
|
|
480
|
+
|
|
481
|
+
// 82b. Add provider column to llm_request_logs for runtime provider lookup
|
|
482
|
+
migrateLlmRequestLogProvider(database);
|
|
483
|
+
|
|
484
|
+
// 83. Backfill existing inline (base64-in-DB) attachments to on-disk storage
|
|
485
|
+
migrateBackfillInlineAttachmentsToDisk(database);
|
|
486
|
+
|
|
487
|
+
// 84. Add nullable conversation fork lineage columns and parent lookup index
|
|
488
|
+
migrateConversationForkLineage(database);
|
|
489
|
+
|
|
490
|
+
// 85. Memory brief state tables (time_contexts, open_loops) for simplified memory system
|
|
491
|
+
migrateMemoryBriefState(database);
|
|
492
|
+
|
|
493
|
+
// 86. Memory archive tables (observations, chunks, episodes) for simplified memory v1
|
|
494
|
+
migrateMemoryArchiveTables(database);
|
|
495
|
+
|
|
496
|
+
// 87. Add memory reducer checkpoint columns to conversations
|
|
497
|
+
migrateMemoryReducerCheckpoints(database);
|
|
498
|
+
|
|
459
499
|
validateMigrationState(database);
|
|
460
500
|
|
|
461
501
|
if (process.env.BUN_TEST === "1") {
|
|
@@ -203,7 +203,14 @@ function getCachedOrCreate<T extends EmbeddingBackend>(
|
|
|
203
203
|
return instance;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* Look up a previously cached backend instance. Returns undefined when no
|
|
208
|
+
* cached entry exists. Used as a fallback when a provider key lookup
|
|
209
|
+
* returns undefined — a transient credential-store outage should not
|
|
210
|
+
* disable a provider whose backend is already warmed in memory. Explicit
|
|
211
|
+
* key deletion triggers `clearEmbeddingBackendCache()` which empties the
|
|
212
|
+
* cache, so a stale backend is never returned after intentional removal.
|
|
213
|
+
*/
|
|
207
214
|
function getCached(
|
|
208
215
|
provider: string,
|
|
209
216
|
model: string,
|
|
@@ -259,9 +266,6 @@ export async function selectEmbeddingBackend(
|
|
|
259
266
|
};
|
|
260
267
|
}
|
|
261
268
|
if (requested === "ollama") {
|
|
262
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
263
|
-
const cached = getCached("ollama", config.memory.embeddings.ollamaModel);
|
|
264
|
-
if (cached) return { backend: cached, reason: null };
|
|
265
269
|
const ollamaKey = (await getProviderKeyAsync("ollama")) ?? undefined;
|
|
266
270
|
return {
|
|
267
271
|
backend: getCachedOrCreate(
|
|
@@ -298,14 +302,17 @@ export async function selectEmbeddingBackend(
|
|
|
298
302
|
reason: null,
|
|
299
303
|
};
|
|
300
304
|
case "openai": {
|
|
301
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
302
|
-
const cachedOpenai = getCached(
|
|
303
|
-
"openai",
|
|
304
|
-
config.memory.embeddings.openaiModel,
|
|
305
|
-
);
|
|
306
|
-
if (cachedOpenai) return { backend: cachedOpenai, reason: null };
|
|
307
305
|
const openaiKey = await getProviderKeyAsync("openai");
|
|
308
|
-
if (!openaiKey)
|
|
306
|
+
if (!openaiKey) {
|
|
307
|
+
// Preserve cached backend on transient credential-store failures.
|
|
308
|
+
// Explicit key deletion clears the cache via clearEmbeddingBackendCache().
|
|
309
|
+
const cached = getCached(
|
|
310
|
+
"openai",
|
|
311
|
+
config.memory.embeddings.openaiModel,
|
|
312
|
+
);
|
|
313
|
+
if (cached) return { backend: cached, reason: null };
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
309
316
|
return {
|
|
310
317
|
backend: getCachedOrCreate(
|
|
311
318
|
"openai",
|
|
@@ -320,15 +327,16 @@ export async function selectEmbeddingBackend(
|
|
|
320
327
|
};
|
|
321
328
|
}
|
|
322
329
|
case "gemini": {
|
|
323
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
324
|
-
const cachedGemini = getCached(
|
|
325
|
-
"gemini",
|
|
326
|
-
config.memory.embeddings.geminiModel,
|
|
327
|
-
geminiCacheExtras(config),
|
|
328
|
-
);
|
|
329
|
-
if (cachedGemini) return { backend: cachedGemini, reason: null };
|
|
330
330
|
const geminiKey = await getProviderKeyAsync("gemini");
|
|
331
|
-
if (!geminiKey)
|
|
331
|
+
if (!geminiKey) {
|
|
332
|
+
const cached = getCached(
|
|
333
|
+
"gemini",
|
|
334
|
+
config.memory.embeddings.geminiModel,
|
|
335
|
+
geminiCacheExtras(config),
|
|
336
|
+
);
|
|
337
|
+
if (cached) return { backend: cached, reason: null };
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
332
340
|
return {
|
|
333
341
|
backend: getCachedOrCreate(
|
|
334
342
|
"gemini",
|
|
@@ -348,12 +356,6 @@ export async function selectEmbeddingBackend(
|
|
|
348
356
|
};
|
|
349
357
|
}
|
|
350
358
|
case "ollama": {
|
|
351
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
352
|
-
const cachedOllama = getCached(
|
|
353
|
-
"ollama",
|
|
354
|
-
config.memory.embeddings.ollamaModel,
|
|
355
|
-
);
|
|
356
|
-
if (cachedOllama) return { backend: cachedOllama, reason: null };
|
|
357
359
|
if (!(await isOllamaConfigured(config))) continue;
|
|
358
360
|
const ollamaKey = (await getProviderKeyAsync("ollama")) ?? undefined;
|
|
359
361
|
return {
|
|
@@ -570,15 +572,6 @@ async function selectFallbackBackends(
|
|
|
570
572
|
if (provider === exclude) continue;
|
|
571
573
|
switch (provider) {
|
|
572
574
|
case "openai": {
|
|
573
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
574
|
-
const cachedOpenai = getCached(
|
|
575
|
-
"openai",
|
|
576
|
-
config.memory.embeddings.openaiModel,
|
|
577
|
-
);
|
|
578
|
-
if (cachedOpenai) {
|
|
579
|
-
backends.push(cachedOpenai);
|
|
580
|
-
break;
|
|
581
|
-
}
|
|
582
575
|
const openaiKey = await getProviderKeyAsync("openai");
|
|
583
576
|
if (openaiKey) {
|
|
584
577
|
backends.push(
|
|
@@ -592,20 +585,17 @@ async function selectFallbackBackends(
|
|
|
592
585
|
),
|
|
593
586
|
),
|
|
594
587
|
);
|
|
588
|
+
} else {
|
|
589
|
+
// Preserve cached backend on transient credential-store failures.
|
|
590
|
+
const cached = getCached(
|
|
591
|
+
"openai",
|
|
592
|
+
config.memory.embeddings.openaiModel,
|
|
593
|
+
);
|
|
594
|
+
if (cached) backends.push(cached);
|
|
595
595
|
}
|
|
596
596
|
break;
|
|
597
597
|
}
|
|
598
598
|
case "gemini": {
|
|
599
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
600
|
-
const cachedGemini = getCached(
|
|
601
|
-
"gemini",
|
|
602
|
-
config.memory.embeddings.geminiModel,
|
|
603
|
-
geminiCacheExtras(config),
|
|
604
|
-
);
|
|
605
|
-
if (cachedGemini) {
|
|
606
|
-
backends.push(cachedGemini);
|
|
607
|
-
break;
|
|
608
|
-
}
|
|
609
599
|
const geminiKey = await getProviderKeyAsync("gemini");
|
|
610
600
|
if (geminiKey) {
|
|
611
601
|
backends.push(
|
|
@@ -624,19 +614,18 @@ async function selectFallbackBackends(
|
|
|
624
614
|
geminiCacheExtras(config),
|
|
625
615
|
),
|
|
626
616
|
);
|
|
617
|
+
} else {
|
|
618
|
+
// Preserve cached backend on transient credential-store failures.
|
|
619
|
+
const cached = getCached(
|
|
620
|
+
"gemini",
|
|
621
|
+
config.memory.embeddings.geminiModel,
|
|
622
|
+
geminiCacheExtras(config),
|
|
623
|
+
);
|
|
624
|
+
if (cached) backends.push(cached);
|
|
627
625
|
}
|
|
628
626
|
break;
|
|
629
627
|
}
|
|
630
628
|
case "ollama": {
|
|
631
|
-
// Check cache first to avoid unnecessary async key fetch on cache hits
|
|
632
|
-
const cachedOllama = getCached(
|
|
633
|
-
"ollama",
|
|
634
|
-
config.memory.embeddings.ollamaModel,
|
|
635
|
-
);
|
|
636
|
-
if (cachedOllama) {
|
|
637
|
-
backends.push(cachedOllama);
|
|
638
|
-
break;
|
|
639
|
-
}
|
|
640
629
|
if (await isOllamaConfigured(config)) {
|
|
641
630
|
const ollamaKey = (await getProviderKeyAsync("ollama")) ?? undefined;
|
|
642
631
|
backends.push(
|
|
@@ -14,7 +14,9 @@ describe("GeminiEmbeddingBackend", () => {
|
|
|
14
14
|
let mockFetch: ReturnType<typeof mock>;
|
|
15
15
|
|
|
16
16
|
beforeEach(() => {
|
|
17
|
-
mockFetch = mock(() =>
|
|
17
|
+
mockFetch = mock(() =>
|
|
18
|
+
Promise.resolve(makeSuccessResponse([0.1, 0.2, 0.3])),
|
|
19
|
+
);
|
|
18
20
|
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
19
21
|
});
|
|
20
22
|
|
|
@@ -186,9 +188,7 @@ describe("GeminiEmbeddingBackend", () => {
|
|
|
186
188
|
describe("error handling", () => {
|
|
187
189
|
test("throws on non-OK response", async () => {
|
|
188
190
|
mockFetch = mock(() =>
|
|
189
|
-
Promise.resolve(
|
|
190
|
-
new Response("Internal Server Error", { status: 500 }),
|
|
191
|
-
),
|
|
191
|
+
Promise.resolve(new Response("Internal Server Error", { status: 500 })),
|
|
192
192
|
);
|
|
193
193
|
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
194
194
|
|
|
@@ -66,9 +66,7 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
66
66
|
const texts = inputs.map((i) => {
|
|
67
67
|
const n = normalizeEmbeddingInput(i);
|
|
68
68
|
if (n.type !== "text") {
|
|
69
|
-
throw new Error(
|
|
70
|
-
"Local embedding backend only supports text inputs",
|
|
71
|
-
);
|
|
69
|
+
throw new Error("Local embedding backend only supports text inputs");
|
|
72
70
|
}
|
|
73
71
|
return n.text;
|
|
74
72
|
});
|
|
@@ -34,9 +34,7 @@ export class OllamaEmbeddingBackend implements EmbeddingBackend {
|
|
|
34
34
|
const texts = inputs.map((i) => {
|
|
35
35
|
const n = normalizeEmbeddingInput(i);
|
|
36
36
|
if (n.type !== "text") {
|
|
37
|
-
throw new Error(
|
|
38
|
-
"Ollama embedding backend only supports text inputs",
|
|
39
|
-
);
|
|
37
|
+
throw new Error("Ollama embedding backend only supports text inputs");
|
|
40
38
|
}
|
|
41
39
|
return n.text;
|
|
42
40
|
});
|
|
@@ -26,9 +26,7 @@ export class OpenAIEmbeddingBackend implements EmbeddingBackend {
|
|
|
26
26
|
const texts = inputs.map((i) => {
|
|
27
27
|
const n = normalizeEmbeddingInput(i);
|
|
28
28
|
if (n.type !== "text") {
|
|
29
|
-
throw new Error(
|
|
30
|
-
"OpenAI embedding backend only supports text inputs",
|
|
31
|
-
);
|
|
29
|
+
throw new Error("OpenAI embedding backend only supports text inputs");
|
|
32
30
|
}
|
|
33
31
|
return n.text;
|
|
34
32
|
});
|
package/src/memory/indexer.ts
CHANGED
|
@@ -5,14 +5,15 @@ import { getConfig } from "../config/loader.js";
|
|
|
5
5
|
import type { MemoryConfig } from "../config/types.js";
|
|
6
6
|
import type { TrustClass } from "../runtime/actor-trust-resolver.js";
|
|
7
7
|
import { getLogger } from "../util/logger.js";
|
|
8
|
+
import { computeChunkContentHash } from "./archive-store.js";
|
|
8
9
|
import { getDb } from "./db.js";
|
|
9
10
|
import { selectedBackendSupportsMultimodal } from "./embedding-backend.js";
|
|
10
11
|
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
11
12
|
import {
|
|
12
|
-
|
|
13
|
+
extractMediaBlockMeta,
|
|
13
14
|
extractTextFromStoredMessageContent,
|
|
14
15
|
} from "./message-content.js";
|
|
15
|
-
import { memorySegments } from "./schema.js";
|
|
16
|
+
import { memoryChunks, memoryObservations, memorySegments } from "./schema.js";
|
|
16
17
|
import { segmentText } from "./segmenter.js";
|
|
17
18
|
|
|
18
19
|
const log = getLogger("memory-indexer");
|
|
@@ -53,7 +54,12 @@ export async function indexMessageNow(
|
|
|
53
54
|
input.provenanceTrustClass === undefined;
|
|
54
55
|
|
|
55
56
|
const text = extractTextFromStoredMessageContent(input.content);
|
|
56
|
-
|
|
57
|
+
const hasText = text.length > 0;
|
|
58
|
+
const candidateMediaMeta = extractMediaBlockMeta(input.content).filter(
|
|
59
|
+
(b) => b.type === "image",
|
|
60
|
+
);
|
|
61
|
+
const hasMedia = candidateMediaMeta.length > 0;
|
|
62
|
+
if (!hasText && !hasMedia) {
|
|
57
63
|
enqueueMemoryJob("build_conversation_summary", {
|
|
58
64
|
conversationId: input.conversationId,
|
|
59
65
|
});
|
|
@@ -62,31 +68,35 @@ export async function indexMessageNow(
|
|
|
62
68
|
|
|
63
69
|
const db = getDb();
|
|
64
70
|
const now = Date.now();
|
|
65
|
-
const segments =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
const segments = hasText
|
|
72
|
+
? segmentText(
|
|
73
|
+
text,
|
|
74
|
+
config.segmentation.targetTokens,
|
|
75
|
+
config.segmentation.overlapTokens,
|
|
76
|
+
)
|
|
77
|
+
: [];
|
|
70
78
|
const shouldExtract =
|
|
71
79
|
input.role === "user" ||
|
|
72
80
|
(input.role === "assistant" && config.extraction.extractFromAssistant);
|
|
73
81
|
// Check if the message has any image blocks before probing the backend.
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
82
|
+
// extractMediaBlockMeta is synchronous and lightweight — it detects image
|
|
83
|
+
// blocks without decoding base64 data into Buffers, avoiding CPU/memory
|
|
84
|
+
// overhead for messages on non-multimodal backends.
|
|
85
|
+
// selectedBackendSupportsMultimodal requires async key resolution, so we
|
|
86
|
+
// skip it entirely for text-only messages.
|
|
80
87
|
const mediaBlocks =
|
|
81
|
-
|
|
88
|
+
candidateMediaMeta.length > 0 &&
|
|
82
89
|
(await selectedBackendSupportsMultimodal(getConfig()))
|
|
83
|
-
?
|
|
90
|
+
? candidateMediaMeta
|
|
84
91
|
: [];
|
|
85
92
|
|
|
86
93
|
// Wrap all segment inserts and job enqueues in a single transaction so they
|
|
87
94
|
// either all succeed or all roll back, preventing partial/orphaned state.
|
|
88
95
|
let skippedEmbedJobs = 0;
|
|
96
|
+
let skippedChunkEmbedJobs = 0;
|
|
97
|
+
const scopeId = input.scopeId ?? "default";
|
|
89
98
|
db.transaction((tx) => {
|
|
99
|
+
// ── Legacy segment path (kept intact for parallel validation) ───
|
|
90
100
|
for (const segment of segments) {
|
|
91
101
|
const segmentId = buildSegmentId(input.messageId, segment.segmentIndex);
|
|
92
102
|
const hash = createHash("sha256").update(segment.text).digest("hex");
|
|
@@ -107,7 +117,7 @@ export async function indexMessageNow(
|
|
|
107
117
|
segmentIndex: segment.segmentIndex,
|
|
108
118
|
text: segment.text,
|
|
109
119
|
tokenEstimate: segment.tokenEstimate,
|
|
110
|
-
scopeId
|
|
120
|
+
scopeId,
|
|
111
121
|
contentHash: hash,
|
|
112
122
|
createdAt: input.createdAt,
|
|
113
123
|
updatedAt: now,
|
|
@@ -117,7 +127,7 @@ export async function indexMessageNow(
|
|
|
117
127
|
set: {
|
|
118
128
|
text: segment.text,
|
|
119
129
|
tokenEstimate: segment.tokenEstimate,
|
|
120
|
-
scopeId
|
|
130
|
+
scopeId,
|
|
121
131
|
contentHash: hash,
|
|
122
132
|
updatedAt: now,
|
|
123
133
|
},
|
|
@@ -131,6 +141,65 @@ export async function indexMessageNow(
|
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
|
|
144
|
+
// ── Archive chunk dual-write (mirrors segment boundaries) ──────
|
|
145
|
+
// Create a single observation per message, then create one chunk per
|
|
146
|
+
// segment using the same segmentation boundaries. Chunks are
|
|
147
|
+
// deduplicated by (scopeId, contentHash) via onConflictDoNothing so
|
|
148
|
+
// unchanged content does not enqueue duplicate embed_chunk jobs.
|
|
149
|
+
const observationId = buildObservationId(input.messageId);
|
|
150
|
+
tx.insert(memoryObservations)
|
|
151
|
+
.values({
|
|
152
|
+
id: observationId,
|
|
153
|
+
scopeId,
|
|
154
|
+
conversationId: input.conversationId,
|
|
155
|
+
messageId: input.messageId,
|
|
156
|
+
role: input.role,
|
|
157
|
+
content: hasText ? text : input.content,
|
|
158
|
+
modality: hasMedia ? "multimodal" : "text",
|
|
159
|
+
source: null,
|
|
160
|
+
createdAt: input.createdAt,
|
|
161
|
+
})
|
|
162
|
+
.onConflictDoNothing({ target: memoryObservations.id })
|
|
163
|
+
.run();
|
|
164
|
+
|
|
165
|
+
for (const segment of segments) {
|
|
166
|
+
const chunkId = buildChunkId(input.messageId, segment.segmentIndex);
|
|
167
|
+
const chunkHash = computeChunkContentHash(scopeId, segment.text);
|
|
168
|
+
|
|
169
|
+
// Check if this chunk already exists with the same content hash
|
|
170
|
+
const existingChunk = tx
|
|
171
|
+
.select({ contentHash: memoryChunks.contentHash })
|
|
172
|
+
.from(memoryChunks)
|
|
173
|
+
.where(eq(memoryChunks.id, chunkId))
|
|
174
|
+
.get();
|
|
175
|
+
|
|
176
|
+
tx.insert(memoryChunks)
|
|
177
|
+
.values({
|
|
178
|
+
id: chunkId,
|
|
179
|
+
scopeId,
|
|
180
|
+
observationId,
|
|
181
|
+
content: segment.text,
|
|
182
|
+
tokenEstimate: segment.tokenEstimate,
|
|
183
|
+
contentHash: chunkHash,
|
|
184
|
+
createdAt: input.createdAt,
|
|
185
|
+
})
|
|
186
|
+
.onConflictDoUpdate({
|
|
187
|
+
target: memoryChunks.id,
|
|
188
|
+
set: {
|
|
189
|
+
content: segment.text,
|
|
190
|
+
tokenEstimate: segment.tokenEstimate,
|
|
191
|
+
contentHash: chunkHash,
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
.run();
|
|
195
|
+
|
|
196
|
+
if (existingChunk?.contentHash === chunkHash) {
|
|
197
|
+
skippedChunkEmbedJobs++;
|
|
198
|
+
} else {
|
|
199
|
+
enqueueMemoryJob("embed_chunk", { chunkId, scopeId }, Date.now(), tx);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
134
203
|
// Enqueue embed_attachment jobs for image content blocks when the
|
|
135
204
|
// embedding provider supports multimodal (Gemini only).
|
|
136
205
|
for (const block of mediaBlocks) {
|
|
@@ -145,7 +214,7 @@ export async function indexMessageNow(
|
|
|
145
214
|
if (shouldExtract && isTrustedActor && !input.automated) {
|
|
146
215
|
enqueueMemoryJob(
|
|
147
216
|
"extract_items",
|
|
148
|
-
{ messageId: input.messageId, scopeId
|
|
217
|
+
{ messageId: input.messageId, scopeId },
|
|
149
218
|
Date.now(),
|
|
150
219
|
tx,
|
|
151
220
|
);
|
|
@@ -164,6 +233,12 @@ export async function indexMessageNow(
|
|
|
164
233
|
);
|
|
165
234
|
}
|
|
166
235
|
|
|
236
|
+
if (skippedChunkEmbedJobs > 0) {
|
|
237
|
+
log.debug(
|
|
238
|
+
`Skipped ${skippedChunkEmbedJobs}/${segments.length} embed_chunk jobs (content unchanged)`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
167
242
|
if (!isTrustedActor && shouldExtract) {
|
|
168
243
|
log.info(
|
|
169
244
|
`Skipping extraction jobs for untrusted actor (trustClass=${input.provenanceTrustClass})`,
|
|
@@ -175,9 +250,11 @@ export async function indexMessageNow(
|
|
|
175
250
|
}
|
|
176
251
|
|
|
177
252
|
const extractionGated = !isTrustedActor || !!input.automated;
|
|
253
|
+
const segmentEmbedJobs = segments.length - skippedEmbedJobs;
|
|
254
|
+
const chunkEmbedJobs = segments.length - skippedChunkEmbedJobs;
|
|
178
255
|
const enqueuedJobs =
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
segmentEmbedJobs +
|
|
257
|
+
chunkEmbedJobs +
|
|
181
258
|
mediaBlocks.length +
|
|
182
259
|
(shouldExtract && !extractionGated ? 2 : 1);
|
|
183
260
|
return {
|
|
@@ -211,3 +288,19 @@ export function getRecentSegmentsForConversation(
|
|
|
211
288
|
function buildSegmentId(messageId: string, segmentIndex: number): string {
|
|
212
289
|
return `${messageId}:${segmentIndex}`;
|
|
213
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Deterministic observation ID derived from the messageId so repeated
|
|
294
|
+
* indexer runs for the same message converge on the same observation row.
|
|
295
|
+
*/
|
|
296
|
+
function buildObservationId(messageId: string): string {
|
|
297
|
+
return `obs:${messageId}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Deterministic chunk ID derived from the messageId and segment index so
|
|
302
|
+
* the dual-write path mirrors the legacy segment identity scheme exactly.
|
|
303
|
+
*/
|
|
304
|
+
function buildChunkId(messageId: string, segmentIndex: number): string {
|
|
305
|
+
return `chunk:${messageId}:${segmentIndex}`;
|
|
306
|
+
}
|
|
@@ -135,6 +135,13 @@ function hasSemanticDensity(text: string): boolean {
|
|
|
135
135
|
|
|
136
136
|
// ── LLM-powered extraction ────────────────────────────────────────────
|
|
137
137
|
|
|
138
|
+
// Budget for the extraction system prompt (in characters). This is a
|
|
139
|
+
// conservative estimate that fits comfortably within even small model
|
|
140
|
+
// context windows (latency-optimized models like Haiku). The remaining
|
|
141
|
+
// context budget is consumed by the user message, tool schema, and response
|
|
142
|
+
// tokens. ~6000 tokens ≈ 24 000 chars is a safe ceiling.
|
|
143
|
+
const EXTRACTION_SYSTEM_PROMPT_CHAR_BUDGET = 24_000;
|
|
144
|
+
|
|
138
145
|
function buildExtractionSystemPrompt(
|
|
139
146
|
existingItems: Array<{
|
|
140
147
|
id: string;
|
|
@@ -144,16 +151,9 @@ function buildExtractionSystemPrompt(
|
|
|
144
151
|
}>,
|
|
145
152
|
messageRole: string,
|
|
146
153
|
): string {
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
let prompt = "";
|
|
152
|
-
if (identityContext) {
|
|
153
|
-
prompt += `# Identity Context\n\n${identityContext}\n\n---\n\n`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
prompt += `You are a memory extraction system. Given a message from a conversation, extract structured memory items that would be valuable to remember for future interactions.
|
|
154
|
+
// Build the fixed instruction body first so we can measure it and allocate
|
|
155
|
+
// the remaining budget to identity context.
|
|
156
|
+
let instructions = `You are a memory extraction system. Given a message from a conversation, extract structured memory items that would be valuable to remember for future interactions.
|
|
157
157
|
|
|
158
158
|
Extract items in these categories:
|
|
159
159
|
- identity: Personal info (name, role, location, timezone, background), notable facts, relationships between people/teams/systems
|
|
@@ -183,22 +183,51 @@ Rules:
|
|
|
183
183
|
- Only extract genuinely memorable information. Skip pleasantries, filler, and transient discussion.
|
|
184
184
|
- 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
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
|
+
- 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.
|
|
186
187
|
- Prefer fewer high-quality items over many low-quality ones.
|
|
187
188
|
- If the message contains no memorable information, return an empty array.`;
|
|
188
189
|
|
|
189
190
|
if (messageRole === "assistant") {
|
|
190
|
-
|
|
191
|
+
instructions += `
|
|
191
192
|
|
|
192
193
|
IMPORTANT: The message below is from the ASSISTANT, not the user. Do NOT attribute the assistant's own statements, feelings, self-descriptions, or introspection to the user. Only extract facts about the user, the world, or the project that the assistant is referencing or relaying — NOT the assistant's own identity, uncertainty, or behavior. If the assistant is simply talking about itself (e.g., introducing itself, expressing uncertainty about its own purpose), extract nothing.`;
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
if (existingItems.length > 0) {
|
|
196
|
-
|
|
197
|
+
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`;
|
|
197
198
|
for (const item of existingItems) {
|
|
198
|
-
|
|
199
|
+
instructions += `- [${item.id}] (${item.kind}) ${item.subject}: ${item.statement}\n`;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Inject identity context so extracted memories use real names instead of
|
|
204
|
+
// generic "User ..." labels. Budget is dynamically computed: whatever
|
|
205
|
+
// remains after the fixed instructions fits within the system prompt
|
|
206
|
+
// ceiling, preventing oversized prompts from exceeding the provider input
|
|
207
|
+
// window (which would cause sendMessage to error and fall back to
|
|
208
|
+
// lower-quality pattern-based extraction).
|
|
209
|
+
const rawIdentityContext = buildCoreIdentityContext();
|
|
210
|
+
|
|
211
|
+
let prompt = "";
|
|
212
|
+
if (rawIdentityContext) {
|
|
213
|
+
// Reserve space for the wrapping text: "# Identity Context\n\n" + "\n\n---\n\n"
|
|
214
|
+
const wrapperOverhead = "# Identity Context\n\n\n\n---\n\n".length;
|
|
215
|
+
const identityBudget =
|
|
216
|
+
EXTRACTION_SYSTEM_PROMPT_CHAR_BUDGET -
|
|
217
|
+
instructions.length -
|
|
218
|
+
wrapperOverhead;
|
|
219
|
+
|
|
220
|
+
if (identityBudget > 0) {
|
|
221
|
+
const identityContext = truncate(
|
|
222
|
+
rawIdentityContext,
|
|
223
|
+
identityBudget,
|
|
224
|
+
"\n...[identity context truncated]",
|
|
225
|
+
);
|
|
226
|
+
prompt += `# Identity Context\n\n${identityContext}\n\n---\n\n`;
|
|
199
227
|
}
|
|
200
228
|
}
|
|
201
229
|
|
|
230
|
+
prompt += instructions;
|
|
202
231
|
return prompt;
|
|
203
232
|
}
|
|
204
233
|
|
|
@@ -169,7 +169,12 @@ async function generateStarters(scopeId: string): Promise<GeneratedStarter[]> {
|
|
|
169
169
|
const now = new Date();
|
|
170
170
|
const timeContext = `Current time: ${now.toLocaleString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true })}`;
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
// Truncate identity context to prevent oversized prompts when SOUL.md /
|
|
173
|
+
// IDENTITY.md / USER.md are large.
|
|
174
|
+
const rawIdentityContext = buildCoreIdentityContext();
|
|
175
|
+
const identityContext = rawIdentityContext
|
|
176
|
+
? truncate(rawIdentityContext, 2000, "\n…[truncated]")
|
|
177
|
+
: null;
|
|
173
178
|
|
|
174
179
|
const systemPrompt = `You are generating 4 conversation starters for a personal assistant app. These appear as clickable chips on the empty conversation page — the first thing the user sees when they open the app.
|
|
175
180
|
|
|
@@ -142,10 +142,7 @@ describe("embedMediaJob", () => {
|
|
|
142
142
|
})
|
|
143
143
|
.run();
|
|
144
144
|
|
|
145
|
-
await embedMediaJob(
|
|
146
|
-
makeJob({ assetId: "asset-registered" }),
|
|
147
|
-
TEST_CONFIG,
|
|
148
|
-
);
|
|
145
|
+
await embedMediaJob(makeJob({ assetId: "asset-registered" }), TEST_CONFIG);
|
|
149
146
|
expect(embedAndUpsertCalls).toHaveLength(0);
|
|
150
147
|
});
|
|
151
148
|
|
|
@@ -186,6 +183,7 @@ describe("embedMediaJob", () => {
|
|
|
186
183
|
expect(call.extraPayload).toEqual({
|
|
187
184
|
created_at: now,
|
|
188
185
|
kind: "image",
|
|
186
|
+
memory_scope_id: "default",
|
|
189
187
|
subject: "My Screenshot",
|
|
190
188
|
});
|
|
191
189
|
});
|