@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/retriever.ts
CHANGED
|
@@ -373,7 +373,7 @@ export async function buildMemoryRecall(
|
|
|
373
373
|
// those messages are no longer in the conversation history and memory is
|
|
374
374
|
// the only way they can influence the response.
|
|
375
375
|
if (conversationId) {
|
|
376
|
-
const inContextMessageIds =
|
|
376
|
+
const inContextMessageIds = getEffectiveInContextMessageIds(conversationId);
|
|
377
377
|
if (inContextMessageIds) {
|
|
378
378
|
for (const [key, c] of candidateMap) {
|
|
379
379
|
if (c.type === "segment") {
|
|
@@ -392,6 +392,51 @@ export async function buildMemoryRecall(
|
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
// ── Item filtering: exclude items whose ALL sources are in-context ──
|
|
397
|
+
// Items distilled from messages the model can already see are redundant.
|
|
398
|
+
// However, items with ANY source outside the in-context set carry
|
|
399
|
+
// cross-conversation information and must be preserved.
|
|
400
|
+
const itemCandidateIds = [...candidateMap.values()]
|
|
401
|
+
.filter((c) => c.type === "item")
|
|
402
|
+
.map((c) => c.id);
|
|
403
|
+
|
|
404
|
+
if (itemCandidateIds.length > 0) {
|
|
405
|
+
try {
|
|
406
|
+
const db = getDb();
|
|
407
|
+
const allSources = db
|
|
408
|
+
.select({
|
|
409
|
+
memoryItemId: memoryItemSources.memoryItemId,
|
|
410
|
+
messageId: memoryItemSources.messageId,
|
|
411
|
+
})
|
|
412
|
+
.from(memoryItemSources)
|
|
413
|
+
.where(inArray(memoryItemSources.memoryItemId, itemCandidateIds))
|
|
414
|
+
.all();
|
|
415
|
+
|
|
416
|
+
// Build item ID → source message IDs map
|
|
417
|
+
const itemSourceMap = new Map<string, string[]>();
|
|
418
|
+
for (const s of allSources) {
|
|
419
|
+
const existing = itemSourceMap.get(s.memoryItemId);
|
|
420
|
+
if (existing) existing.push(s.messageId);
|
|
421
|
+
else itemSourceMap.set(s.memoryItemId, [s.messageId]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Filter items whose ALL sources are in-context
|
|
425
|
+
for (const [key, c] of candidateMap) {
|
|
426
|
+
if (c.type !== "item") continue;
|
|
427
|
+
const sourceMessageIds = itemSourceMap.get(c.id);
|
|
428
|
+
if (!sourceMessageIds || sourceMessageIds.length === 0) continue;
|
|
429
|
+
if (sourceMessageIds.every((mid) => inContextMessageIds.has(mid))) {
|
|
430
|
+
candidateMap.delete(key);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch (err) {
|
|
434
|
+
log.warn(
|
|
435
|
+
{ err },
|
|
436
|
+
"Failed to fetch item sources for in-context filtering; skipping",
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
395
440
|
}
|
|
396
441
|
}
|
|
397
442
|
|
|
@@ -574,14 +619,22 @@ export async function buildMemoryRecall(
|
|
|
574
619
|
}
|
|
575
620
|
|
|
576
621
|
/**
|
|
577
|
-
* Get the set of message IDs that are
|
|
578
|
-
* window
|
|
579
|
-
*
|
|
580
|
-
*
|
|
622
|
+
* Get the set of message IDs that are effectively in the conversation's
|
|
623
|
+
* context window. This includes:
|
|
624
|
+
* 1. Messages still visible (not compacted) in the conversation history.
|
|
625
|
+
* 2. Fork-source message IDs — when a conversation is forked, messages are
|
|
626
|
+
* copied with new IDs but their metadata stores the original parent
|
|
627
|
+
* message ID as `forkSourceMessageId`. Segments sourced from those parent
|
|
628
|
+
* messages are redundant because the fork already contains their content.
|
|
629
|
+
*
|
|
630
|
+
* Uses `contextCompactedMessageCount` to determine the compaction offset:
|
|
631
|
+
* messages ordered by createdAt after that count are still visible to the model.
|
|
581
632
|
*
|
|
582
633
|
* Returns `null` if the conversation is not found (deleted, or no DB row).
|
|
583
634
|
*/
|
|
584
|
-
function
|
|
635
|
+
function getEffectiveInContextMessageIds(
|
|
636
|
+
conversationId: string,
|
|
637
|
+
): Set<string> | null {
|
|
585
638
|
try {
|
|
586
639
|
const db = getDb();
|
|
587
640
|
|
|
@@ -599,9 +652,9 @@ function getInContextMessageIds(conversationId: string): Set<string> | null {
|
|
|
599
652
|
|
|
600
653
|
const offset = conv.contextCompactedMessageCount;
|
|
601
654
|
|
|
602
|
-
// Fetch message IDs ordered by creation time
|
|
655
|
+
// Fetch message IDs and metadata ordered by creation time
|
|
603
656
|
const rows = db
|
|
604
|
-
.select({ id: messages.id })
|
|
657
|
+
.select({ id: messages.id, metadata: messages.metadata })
|
|
605
658
|
.from(messages)
|
|
606
659
|
.where(eq(messages.conversationId, conversationId))
|
|
607
660
|
.orderBy(asc(messages.createdAt))
|
|
@@ -609,7 +662,30 @@ function getInContextMessageIds(conversationId: string): Set<string> | null {
|
|
|
609
662
|
|
|
610
663
|
// Messages up to `offset` have been compacted out of context
|
|
611
664
|
const inContextRows = rows.slice(offset);
|
|
612
|
-
|
|
665
|
+
const idSet = new Set(inContextRows.map((r) => r.id));
|
|
666
|
+
|
|
667
|
+
// Also include fork-source message IDs from in-context messages.
|
|
668
|
+
// When a conversation is forked, each copied message's metadata contains
|
|
669
|
+
// `forkSourceMessageId` pointing to the original (parent or grandparent)
|
|
670
|
+
// message ID. Segments sourced from those original messages are redundant.
|
|
671
|
+
for (const row of inContextRows) {
|
|
672
|
+
if (!row.metadata) continue;
|
|
673
|
+
try {
|
|
674
|
+
const parsed = JSON.parse(row.metadata);
|
|
675
|
+
if (
|
|
676
|
+
parsed &&
|
|
677
|
+
typeof parsed === "object" &&
|
|
678
|
+
!Array.isArray(parsed) &&
|
|
679
|
+
typeof parsed.forkSourceMessageId === "string"
|
|
680
|
+
) {
|
|
681
|
+
idSet.add(parsed.forkSourceMessageId);
|
|
682
|
+
}
|
|
683
|
+
} catch {
|
|
684
|
+
// Invalid metadata JSON — skip, don't break filtering.
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return idSet;
|
|
613
689
|
} catch (err) {
|
|
614
690
|
log.warn(
|
|
615
691
|
{ err },
|
|
@@ -26,12 +26,20 @@ export const conversations = sqliteTable(
|
|
|
26
26
|
memoryScopeId: text("memory_scope_id").notNull().default("default"),
|
|
27
27
|
originChannel: text("origin_channel"),
|
|
28
28
|
originInterface: text("origin_interface"),
|
|
29
|
+
forkParentConversationId: text("fork_parent_conversation_id"),
|
|
30
|
+
forkParentMessageId: text("fork_parent_message_id"),
|
|
29
31
|
isAutoTitle: integer("is_auto_title").notNull().default(1),
|
|
30
32
|
scheduleJobId: text("schedule_job_id"),
|
|
33
|
+
memoryReducedThroughMessageId: text("memory_reduced_through_message_id"),
|
|
34
|
+
memoryDirtyTailSinceMessageId: text("memory_dirty_tail_since_message_id"),
|
|
35
|
+
memoryLastReducedAt: integer("memory_last_reduced_at"),
|
|
31
36
|
},
|
|
32
37
|
(table) => [
|
|
33
38
|
index("idx_conversations_updated_at").on(table.updatedAt),
|
|
34
39
|
index("idx_conversations_conversation_type").on(table.conversationType),
|
|
40
|
+
index("idx_conversations_fork_parent_conversation_id").on(
|
|
41
|
+
table.forkParentConversationId,
|
|
42
|
+
),
|
|
35
43
|
],
|
|
36
44
|
);
|
|
37
45
|
|
|
@@ -88,6 +96,7 @@ export const attachments = sqliteTable("attachments", {
|
|
|
88
96
|
dataBase64: text("data_base64").notNull(),
|
|
89
97
|
contentHash: text("content_hash"),
|
|
90
98
|
thumbnailBase64: text("thumbnail_base64"),
|
|
99
|
+
filePath: text("file_path"),
|
|
91
100
|
createdAt: integer("created_at").notNull(),
|
|
92
101
|
});
|
|
93
102
|
|
|
@@ -3,6 +3,8 @@ export * from "./contacts.js";
|
|
|
3
3
|
export * from "./conversations.js";
|
|
4
4
|
export * from "./guardian.js";
|
|
5
5
|
export * from "./infrastructure.js";
|
|
6
|
+
export * from "./memory-archive.js";
|
|
7
|
+
export * from "./memory-brief.js";
|
|
6
8
|
export * from "./memory-core.js";
|
|
7
9
|
export * from "./notifications.js";
|
|
8
10
|
export * from "./oauth.js";
|
|
@@ -106,13 +106,19 @@ export const watcherEvents = sqliteTable("watcher_events", {
|
|
|
106
106
|
createdAt: integer("created_at").notNull(),
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
export const llmRequestLogs = sqliteTable(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
export const llmRequestLogs = sqliteTable(
|
|
110
|
+
"llm_request_logs",
|
|
111
|
+
{
|
|
112
|
+
id: text("id").primaryKey(),
|
|
113
|
+
conversationId: text("conversation_id").notNull(),
|
|
114
|
+
messageId: text("message_id"),
|
|
115
|
+
provider: text("provider"),
|
|
116
|
+
requestPayload: text("request_payload").notNull(),
|
|
117
|
+
responsePayload: text("response_payload").notNull(),
|
|
118
|
+
createdAt: integer("created_at").notNull(),
|
|
119
|
+
},
|
|
120
|
+
(table) => [index("idx_llm_request_logs_message_id").on(table.messageId)],
|
|
121
|
+
);
|
|
116
122
|
|
|
117
123
|
export const llmUsageEvents = sqliteTable(
|
|
118
124
|
"llm_usage_events",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
index,
|
|
3
|
+
integer,
|
|
4
|
+
sqliteTable,
|
|
5
|
+
text,
|
|
6
|
+
uniqueIndex,
|
|
7
|
+
} from "drizzle-orm/sqlite-core";
|
|
8
|
+
|
|
9
|
+
import { conversations, messages } from "./conversations.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Raw observation records captured from conversation turns. Each observation
|
|
13
|
+
* is a single factual statement extracted from user or assistant messages,
|
|
14
|
+
* annotated with modality and source metadata for downstream recall.
|
|
15
|
+
*/
|
|
16
|
+
export const memoryObservations = sqliteTable(
|
|
17
|
+
"memory_observations",
|
|
18
|
+
{
|
|
19
|
+
id: text("id").primaryKey(),
|
|
20
|
+
scopeId: text("scope_id").notNull().default("default"),
|
|
21
|
+
conversationId: text("conversation_id")
|
|
22
|
+
.notNull()
|
|
23
|
+
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
24
|
+
messageId: text("message_id").references(() => messages.id, {
|
|
25
|
+
onDelete: "set null",
|
|
26
|
+
}),
|
|
27
|
+
/** The role that produced the observation (e.g. "user", "assistant"). */
|
|
28
|
+
role: text("role").notNull(),
|
|
29
|
+
/** Free-text statement capturing the observed fact. */
|
|
30
|
+
content: text("content").notNull(),
|
|
31
|
+
/**
|
|
32
|
+
* Modality of the source material: "text", "voice", "image", etc.
|
|
33
|
+
* Enables downstream filters for recall relevance.
|
|
34
|
+
*/
|
|
35
|
+
modality: text("modality").notNull().default("text"),
|
|
36
|
+
/**
|
|
37
|
+
* Source channel or interface that produced the observation
|
|
38
|
+
* (e.g. "vellum", "telegram", "phone").
|
|
39
|
+
*/
|
|
40
|
+
source: text("source"),
|
|
41
|
+
createdAt: integer("created_at").notNull(),
|
|
42
|
+
},
|
|
43
|
+
(table) => [
|
|
44
|
+
index("idx_memory_observations_scope_id").on(table.scopeId),
|
|
45
|
+
index("idx_memory_observations_conversation_id").on(table.conversationId),
|
|
46
|
+
index("idx_memory_observations_created_at").on(table.createdAt),
|
|
47
|
+
],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Deduplicated content chunks derived from observations. Chunks are the unit
|
|
52
|
+
* of embedding and recall — each chunk carries a contentHash for idempotent
|
|
53
|
+
* dual-write safety so the same content is never stored twice.
|
|
54
|
+
*/
|
|
55
|
+
export const memoryChunks = sqliteTable(
|
|
56
|
+
"memory_chunks",
|
|
57
|
+
{
|
|
58
|
+
id: text("id").primaryKey(),
|
|
59
|
+
scopeId: text("scope_id").notNull().default("default"),
|
|
60
|
+
observationId: text("observation_id")
|
|
61
|
+
.notNull()
|
|
62
|
+
.references(() => memoryObservations.id, { onDelete: "cascade" }),
|
|
63
|
+
/** The chunk text used for embedding and recall. */
|
|
64
|
+
content: text("content").notNull(),
|
|
65
|
+
/** Token count estimate for context-window budgeting. */
|
|
66
|
+
tokenEstimate: integer("token_estimate").notNull(),
|
|
67
|
+
/**
|
|
68
|
+
* SHA-256 hash of the normalized content, used to skip duplicate inserts
|
|
69
|
+
* during dual-write windows.
|
|
70
|
+
*/
|
|
71
|
+
contentHash: text("content_hash").notNull(),
|
|
72
|
+
createdAt: integer("created_at").notNull(),
|
|
73
|
+
},
|
|
74
|
+
(table) => [
|
|
75
|
+
index("idx_memory_chunks_scope_id").on(table.scopeId),
|
|
76
|
+
index("idx_memory_chunks_observation_id").on(table.observationId),
|
|
77
|
+
uniqueIndex("idx_memory_chunks_content_hash").on(
|
|
78
|
+
table.scopeId,
|
|
79
|
+
table.contentHash,
|
|
80
|
+
),
|
|
81
|
+
index("idx_memory_chunks_created_at").on(table.createdAt),
|
|
82
|
+
],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Episode records that group related observations into coherent narrative
|
|
87
|
+
* units. An episode represents a meaningful interaction or topic span,
|
|
88
|
+
* with source-link metadata for provenance tracking.
|
|
89
|
+
*/
|
|
90
|
+
export const memoryEpisodes = sqliteTable(
|
|
91
|
+
"memory_episodes",
|
|
92
|
+
{
|
|
93
|
+
id: text("id").primaryKey(),
|
|
94
|
+
scopeId: text("scope_id").notNull().default("default"),
|
|
95
|
+
conversationId: text("conversation_id")
|
|
96
|
+
.notNull()
|
|
97
|
+
.references(() => conversations.id, { onDelete: "cascade" }),
|
|
98
|
+
/** Human-readable title summarizing the episode. */
|
|
99
|
+
title: text("title").notNull(),
|
|
100
|
+
/** Longer narrative summary of the episode content. */
|
|
101
|
+
summary: text("summary").notNull(),
|
|
102
|
+
/** Token count estimate for the summary. */
|
|
103
|
+
tokenEstimate: integer("token_estimate").notNull(),
|
|
104
|
+
/**
|
|
105
|
+
* Source channel or interface that produced the episode
|
|
106
|
+
* (mirrors observation.source for episode-level filtering).
|
|
107
|
+
*/
|
|
108
|
+
source: text("source"),
|
|
109
|
+
/** Epoch-ms timestamp of the earliest observation in the episode. */
|
|
110
|
+
startAt: integer("start_at").notNull(),
|
|
111
|
+
/** Epoch-ms timestamp of the latest observation in the episode. */
|
|
112
|
+
endAt: integer("end_at").notNull(),
|
|
113
|
+
createdAt: integer("created_at").notNull(),
|
|
114
|
+
updatedAt: integer("updated_at").notNull(),
|
|
115
|
+
},
|
|
116
|
+
(table) => [
|
|
117
|
+
index("idx_memory_episodes_scope_id").on(table.scopeId),
|
|
118
|
+
index("idx_memory_episodes_conversation_id").on(table.conversationId),
|
|
119
|
+
index("idx_memory_episodes_created_at").on(table.createdAt),
|
|
120
|
+
],
|
|
121
|
+
);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Time contexts represent bounded temporal windows that are relevant to the
|
|
5
|
+
* assistant's current awareness — e.g. "user is traveling next week",
|
|
6
|
+
* "quarterly planning period ends Friday". Each row captures one window
|
|
7
|
+
* with an activation range and a human-readable summary the brief can surface.
|
|
8
|
+
*/
|
|
9
|
+
export const timeContexts = sqliteTable(
|
|
10
|
+
"time_contexts",
|
|
11
|
+
{
|
|
12
|
+
id: text("id").primaryKey(),
|
|
13
|
+
scopeId: text("scope_id").notNull(),
|
|
14
|
+
summary: text("summary").notNull(),
|
|
15
|
+
source: text("source").notNull(), // e.g. 'conversation', 'schedule', 'manual'
|
|
16
|
+
activeFrom: integer("active_from").notNull(), // epoch ms — window start
|
|
17
|
+
activeUntil: integer("active_until").notNull(), // epoch ms — window end
|
|
18
|
+
createdAt: integer("created_at").notNull(),
|
|
19
|
+
updatedAt: integer("updated_at").notNull(),
|
|
20
|
+
},
|
|
21
|
+
(table) => [
|
|
22
|
+
index("idx_time_contexts_scope_active_until").on(
|
|
23
|
+
table.scopeId,
|
|
24
|
+
table.activeUntil,
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Open loops track unresolved items the assistant should follow up on —
|
|
31
|
+
* e.g. "waiting for Bob's reply", "need to file taxes before April 15".
|
|
32
|
+
* Each row carries a status and an optional due date so the brief can
|
|
33
|
+
* prioritise which loops to surface.
|
|
34
|
+
*/
|
|
35
|
+
export const openLoops = sqliteTable(
|
|
36
|
+
"open_loops",
|
|
37
|
+
{
|
|
38
|
+
id: text("id").primaryKey(),
|
|
39
|
+
scopeId: text("scope_id").notNull(),
|
|
40
|
+
summary: text("summary").notNull(),
|
|
41
|
+
status: text("status").notNull().default("open"), // 'open' | 'resolved' | 'expired'
|
|
42
|
+
source: text("source").notNull(), // e.g. 'conversation', 'followup', 'manual'
|
|
43
|
+
dueAt: integer("due_at"), // epoch ms — optional deadline
|
|
44
|
+
surfacedAt: integer("surfaced_at"), // epoch ms — last time shown in brief
|
|
45
|
+
createdAt: integer("created_at").notNull(),
|
|
46
|
+
updatedAt: integer("updated_at").notNull(),
|
|
47
|
+
},
|
|
48
|
+
(table) => [
|
|
49
|
+
index("idx_open_loops_scope_status_due").on(
|
|
50
|
+
table.scopeId,
|
|
51
|
+
table.status,
|
|
52
|
+
table.dueAt,
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
);
|
|
@@ -18,6 +18,12 @@ export const oauthProviders = sqliteTable("oauth_providers", {
|
|
|
18
18
|
extraParams: text("extra_params"),
|
|
19
19
|
callbackTransport: text("callback_transport"),
|
|
20
20
|
pingUrl: text("ping_url"),
|
|
21
|
+
managedServiceConfigKey: text("managed_service_config_key"),
|
|
22
|
+
displayName: text("display_name"),
|
|
23
|
+
description: text("description"),
|
|
24
|
+
dashboardUrl: text("dashboard_url"),
|
|
25
|
+
clientIdPlaceholder: text("client_id_placeholder"),
|
|
26
|
+
requiresClientSecret: integer("requires_client_secret").notNull().default(1),
|
|
21
27
|
createdAt: integer("created_at").notNull(),
|
|
22
28
|
updatedAt: integer("updated_at").notNull(),
|
|
23
29
|
});
|
|
@@ -61,6 +61,7 @@ export async function semanticSearch(
|
|
|
61
61
|
fetchLimit,
|
|
62
62
|
["item", "summary", "segment", "media"],
|
|
63
63
|
excludedMessageIds,
|
|
64
|
+
scopeIds,
|
|
64
65
|
),
|
|
65
66
|
);
|
|
66
67
|
}
|
|
@@ -277,13 +278,13 @@ export async function semanticSearch(
|
|
|
277
278
|
* Build a Qdrant filter for hybrid search. Mirrors the logic in
|
|
278
279
|
* `searchWithFilter` but as a standalone object for the query API.
|
|
279
280
|
*
|
|
280
|
-
* Scope filtering:
|
|
281
|
-
*
|
|
282
|
-
*
|
|
281
|
+
* Scope filtering: points with a `memory_scope_id` payload field are
|
|
282
|
+
* filtered at the Qdrant level. Legacy points without the field pass
|
|
283
|
+
* through and are caught by post-query DB filtering.
|
|
283
284
|
*/
|
|
284
285
|
function buildHybridFilter(
|
|
285
286
|
excludeMessageIds: string[],
|
|
286
|
-
|
|
287
|
+
scopeIds?: string[],
|
|
287
288
|
): Record<string, unknown> {
|
|
288
289
|
const mustConditions: Array<Record<string, unknown>> = [
|
|
289
290
|
{
|
|
@@ -310,6 +311,18 @@ function buildHybridFilter(
|
|
|
310
311
|
});
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
// Scope filtering: accept points whose memory_scope_id matches one of the
|
|
315
|
+
// allowed scopes, OR points that lack the field entirely (legacy data).
|
|
316
|
+
// Post-query DB filtering remains as defense-in-depth for legacy points.
|
|
317
|
+
if (scopeIds && scopeIds.length > 0) {
|
|
318
|
+
mustConditions.push({
|
|
319
|
+
should: [
|
|
320
|
+
{ key: "memory_scope_id", match: { any: scopeIds } },
|
|
321
|
+
{ is_empty: { key: "memory_scope_id" } },
|
|
322
|
+
],
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
313
326
|
const mustNotConditions: Array<Record<string, unknown>> = [
|
|
314
327
|
{ key: "_meta", match: { value: true } },
|
|
315
328
|
];
|
|
@@ -45,7 +45,9 @@ export function buildMultipartMime(options: MimeMessageOptions): string {
|
|
|
45
45
|
const sanitizedSubject = sanitizeHeaderValue(subject);
|
|
46
46
|
const sanitizedCc = cc ? sanitizeHeaderValue(cc) : undefined;
|
|
47
47
|
const sanitizedBcc = bcc ? sanitizeHeaderValue(bcc) : undefined;
|
|
48
|
-
const sanitizedInReplyTo = inReplyTo
|
|
48
|
+
const sanitizedInReplyTo = inReplyTo
|
|
49
|
+
? sanitizeHeaderValue(inReplyTo)
|
|
50
|
+
: undefined;
|
|
49
51
|
|
|
50
52
|
const headers = [
|
|
51
53
|
`To: ${sanitizedTo}`,
|
|
@@ -196,6 +196,14 @@ export function hasInviteFlowDirective(text: string | undefined): boolean {
|
|
|
196
196
|
* Build the deterministic access-request contract text from payload fields.
|
|
197
197
|
* This is the canonical baseline that enforcement can append when generated
|
|
198
198
|
* copy is missing required elements.
|
|
199
|
+
*
|
|
200
|
+
* Channel-agnostic by design: this function reads from the generic
|
|
201
|
+
* `contextPayload` and works identically regardless of which channel
|
|
202
|
+
* (Slack, Telegram, desktop, etc.) the notification is delivered to.
|
|
203
|
+
* When `guardianResolutionSource` is present and not `"source-channel-contact"`,
|
|
204
|
+
* the guardian was resolved via fallback (e.g. vellum anchor) rather than
|
|
205
|
+
* a verified same-channel contact — downstream copy or routing can use
|
|
206
|
+
* this to append verification CTAs like "Was this you?".
|
|
199
207
|
*/
|
|
200
208
|
export function buildAccessRequestContractText(
|
|
201
209
|
payload: Record<string, unknown>,
|
|
@@ -208,6 +216,15 @@ export function buildAccessRequestContractText(
|
|
|
208
216
|
? payload.previousMemberStatus
|
|
209
217
|
: undefined;
|
|
210
218
|
|
|
219
|
+
const guardianResolutionSource =
|
|
220
|
+
typeof payload.guardianResolutionSource === "string"
|
|
221
|
+
? payload.guardianResolutionSource
|
|
222
|
+
: undefined;
|
|
223
|
+
const sourceChannel =
|
|
224
|
+
typeof payload.sourceChannel === "string"
|
|
225
|
+
? payload.sourceChannel
|
|
226
|
+
: undefined;
|
|
227
|
+
|
|
211
228
|
const lines: string[] = [];
|
|
212
229
|
lines.push(buildAccessRequestIdentityLine(payload));
|
|
213
230
|
if (previousMemberStatus === "revoked") {
|
|
@@ -220,6 +237,15 @@ export function buildAccessRequestContractText(
|
|
|
220
237
|
);
|
|
221
238
|
}
|
|
222
239
|
lines.push(buildAccessRequestInviteDirective());
|
|
240
|
+
if (
|
|
241
|
+
(guardianResolutionSource === "vellum-anchor" ||
|
|
242
|
+
guardianResolutionSource === "none") &&
|
|
243
|
+
sourceChannel
|
|
244
|
+
) {
|
|
245
|
+
lines.push(
|
|
246
|
+
`Note: You haven't verified your identity on ${sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${sourceChannel}" to set up direct access.`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
223
249
|
return lines.join("\n");
|
|
224
250
|
}
|
|
225
251
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../providers/provider-send-message.js";
|
|
23
23
|
import type { ModelIntent, Provider } from "../providers/types.js";
|
|
24
24
|
import { getLogger } from "../util/logger.js";
|
|
25
|
+
import { truncate } from "../util/truncate.js";
|
|
25
26
|
import {
|
|
26
27
|
buildConversationCandidates,
|
|
27
28
|
type ConversationCandidateSet,
|
|
@@ -55,6 +56,15 @@ const log = getLogger("notification-decision-engine");
|
|
|
55
56
|
const DECISION_TIMEOUT_MS = 15_000;
|
|
56
57
|
const PROMPT_VERSION = "v4";
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Maximum character budget for identity context injected into the notification
|
|
61
|
+
* decision prompt. We truncate to prevent oversized prompts when SOUL.md /
|
|
62
|
+
* IDENTITY.md / USER.md are large — exceeding the provider context window
|
|
63
|
+
* would cause the LLM call to fail and silently degrade to deterministic
|
|
64
|
+
* fallback for all notifications.
|
|
65
|
+
*/
|
|
66
|
+
const MAX_IDENTITY_CONTEXT_CHARS = 2000;
|
|
67
|
+
|
|
58
68
|
// ── System prompt ──────────────────────────────────────────────────────
|
|
59
69
|
|
|
60
70
|
function buildSystemPrompt(
|
|
@@ -790,7 +800,10 @@ async function classifyWithLLM(
|
|
|
790
800
|
const candidateContext = candidateSet
|
|
791
801
|
? (serializeCandidatesForPrompt(candidateSet) ?? undefined)
|
|
792
802
|
: undefined;
|
|
793
|
-
const
|
|
803
|
+
const rawIdentityContext = buildCoreIdentityContext();
|
|
804
|
+
const identityContext = rawIdentityContext
|
|
805
|
+
? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
|
|
806
|
+
: undefined;
|
|
794
807
|
const systemPrompt = buildSystemPrompt(
|
|
795
808
|
availableChannels,
|
|
796
809
|
preferenceContext,
|
|
@@ -220,7 +220,7 @@ export async function emitNotificationSignal<TEventName extends string>(
|
|
|
220
220
|
sourceChannel: params.sourceChannel,
|
|
221
221
|
sourceContextId: params.sourceContextId,
|
|
222
222
|
attentionHints: params.attentionHints,
|
|
223
|
-
payload: params.contextPayload ?? {},
|
|
223
|
+
payload: (params.contextPayload ?? {}) as Record<string, unknown>,
|
|
224
224
|
dedupeKey: params.dedupeKey,
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -118,8 +118,44 @@ export interface AttentionHints {
|
|
|
118
118
|
|
|
119
119
|
export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
|
|
120
120
|
|
|
121
|
+
// ── Typed context payloads ──────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* How the guardian was resolved for an access request.
|
|
125
|
+
*
|
|
126
|
+
* - `"source-channel-contact"` — Guardian was found via the originating channel's
|
|
127
|
+
* contact store and their principalId matches the assistant's anchor.
|
|
128
|
+
* - `"vellum-anchor"` — No same-channel guardian matched; fell back to the
|
|
129
|
+
* assistant's vellum guardian principal.
|
|
130
|
+
* - `"none"` — No guardian binding could be resolved at all.
|
|
131
|
+
*
|
|
132
|
+
* Downstream consumers (notification copy, routing) use this to decide whether
|
|
133
|
+
* to append a "Was this you?" CTA or route notifications beyond the source channel.
|
|
134
|
+
* This is channel-agnostic by design — any channel's access request that
|
|
135
|
+
* resolves to a non-source-channel guardian gets the same treatment.
|
|
136
|
+
*/
|
|
137
|
+
export type GuardianResolutionSource =
|
|
138
|
+
| "source-channel-contact"
|
|
139
|
+
| "vellum-anchor"
|
|
140
|
+
| "none";
|
|
141
|
+
|
|
142
|
+
export interface AccessRequestContextPayload {
|
|
143
|
+
requestId: string;
|
|
144
|
+
requestCode: string;
|
|
145
|
+
sourceChannel: string;
|
|
146
|
+
conversationExternalId: string;
|
|
147
|
+
actorExternalId: string;
|
|
148
|
+
actorDisplayName: string | null;
|
|
149
|
+
actorUsername: string | null;
|
|
150
|
+
senderIdentifier: string;
|
|
151
|
+
guardianBindingChannel: string | null;
|
|
152
|
+
guardianResolutionSource: GuardianResolutionSource;
|
|
153
|
+
previousMemberStatus: string | null;
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
export interface NotificationEventContextPayloadMap {
|
|
122
157
|
"guardian.question": GuardianQuestionPayload;
|
|
158
|
+
"ingress.access_request": AccessRequestContextPayload;
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
export type NotificationContextPayload<TEventName extends string = string> =
|
|
@@ -236,8 +236,6 @@ function createConnection(service = "integration:google"): BYOOAuthConnection {
|
|
|
236
236
|
providerKey: service,
|
|
237
237
|
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
238
238
|
accountInfo: null,
|
|
239
|
-
grantedScopes: ["read", "write"],
|
|
240
|
-
credentialService: service,
|
|
241
239
|
});
|
|
242
240
|
}
|
|
243
241
|
|
|
@@ -500,12 +498,11 @@ describe("resolveOAuthConnection", () => {
|
|
|
500
498
|
|
|
501
499
|
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
502
500
|
expect(conn.providerKey).toBe("integration:google");
|
|
503
|
-
expect(conn.grantedScopes).toEqual(["read", "write"]);
|
|
504
501
|
});
|
|
505
502
|
|
|
506
503
|
test("throws when no credential metadata exists", async () => {
|
|
507
504
|
await expect(resolveOAuthConnection("integration:unknown")).rejects.toThrow(
|
|
508
|
-
/No
|
|
505
|
+
/No active OAuth connection found for "integration:unknown"/,
|
|
509
506
|
);
|
|
510
507
|
});
|
|
511
508
|
|
|
@@ -517,45 +514,4 @@ describe("resolveOAuthConnection", () => {
|
|
|
517
514
|
/No base URL configured for "integration:custom-service"/,
|
|
518
515
|
);
|
|
519
516
|
});
|
|
520
|
-
|
|
521
|
-
test("resolves base URL via app's canonical providerKey for custom credential_service", async () => {
|
|
522
|
-
// Set up a well-known provider with a baseUrl
|
|
523
|
-
mockProviders.set("github", {
|
|
524
|
-
key: "github",
|
|
525
|
-
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
526
|
-
baseUrl: "https://api.github.com",
|
|
527
|
-
});
|
|
528
|
-
// The custom credential service has no provider entry of its own
|
|
529
|
-
// (getProvider("integration:github-work") returns undefined)
|
|
530
|
-
|
|
531
|
-
// App points to the canonical "github" provider
|
|
532
|
-
const appId = "app-github-work";
|
|
533
|
-
mockApps.set(appId, {
|
|
534
|
-
id: appId,
|
|
535
|
-
providerKey: "github",
|
|
536
|
-
clientId: "test-client-id",
|
|
537
|
-
clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// Connection uses the custom credential service as its providerKey
|
|
541
|
-
const connId = "conn-github-work";
|
|
542
|
-
mockConnections.set("integration:github-work", {
|
|
543
|
-
id: connId,
|
|
544
|
-
providerKey: "integration:github-work",
|
|
545
|
-
oauthAppId: appId,
|
|
546
|
-
expiresAt: Date.now() + 3600 * 1000,
|
|
547
|
-
grantedScopes: JSON.stringify(["repo"]),
|
|
548
|
-
accountInfo: null,
|
|
549
|
-
});
|
|
550
|
-
await setSecureKeyAsync(
|
|
551
|
-
`oauth_connection/${connId}/access_token`,
|
|
552
|
-
"ghp-test-token",
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
const conn = await resolveOAuthConnection("integration:github-work");
|
|
556
|
-
|
|
557
|
-
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
558
|
-
expect(conn.providerKey).toBe("integration:github-work");
|
|
559
|
-
expect(conn.grantedScopes).toEqual(["repo"]);
|
|
560
|
-
});
|
|
561
517
|
});
|