@vellumai/assistant 0.5.16 → 0.6.0
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 +1 -1
- package/Dockerfile +0 -3
- package/knip.json +2 -1
- package/openapi.yaml +660 -80
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +25 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/cli-memory.test.ts +74 -69
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +2 -6
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
- package/src/__tests__/skill-memory.test.ts +140 -98
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +173 -229
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/cli-memory.ts +67 -64
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +11 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +10 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +21 -124
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +96 -61
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +416 -202
- package/src/daemon/lifecycle.ts +40 -1
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +4 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +11 -2
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +30 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +24 -3
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +3 -29
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -14
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +152 -77
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -1
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- package/src/tools/memory/handlers.ts +0 -434
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for buildMemoryInjection — focused on echoes/serendipity
|
|
3
|
-
* rendering and budget enforcement.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, expect, test } from "bun:test";
|
|
6
|
-
|
|
7
|
-
import { buildMemoryInjection } from "./formatting.js";
|
|
8
|
-
import type { Candidate } from "./types.js";
|
|
9
|
-
|
|
10
|
-
type CandidateWithLabel = Candidate & { sourceLabel?: string };
|
|
11
|
-
|
|
12
|
-
function makeCandidate(
|
|
13
|
-
overrides: Partial<CandidateWithLabel> & { id: string },
|
|
14
|
-
): CandidateWithLabel {
|
|
15
|
-
return {
|
|
16
|
-
key: `item:${overrides.id}`,
|
|
17
|
-
type: "item",
|
|
18
|
-
source: "semantic",
|
|
19
|
-
text: overrides.text ?? `Statement for ${overrides.id}`,
|
|
20
|
-
kind: overrides.kind ?? "fact",
|
|
21
|
-
confidence: 1,
|
|
22
|
-
importance: overrides.importance ?? 0.5,
|
|
23
|
-
createdAt: overrides.createdAt ?? Date.now(),
|
|
24
|
-
semantic: 0.8,
|
|
25
|
-
recency: 0.5,
|
|
26
|
-
finalScore: overrides.finalScore ?? 0.6,
|
|
27
|
-
...overrides,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe("buildMemoryInjection — echoes section", () => {
|
|
32
|
-
test("renders <echoes> after <recalled> when serendipity items provided", () => {
|
|
33
|
-
const candidates = [makeCandidate({ id: "c1", finalScore: 0.8 })];
|
|
34
|
-
const serendipityItems = [
|
|
35
|
-
makeCandidate({ id: "s1", finalScore: 0, importance: 0.7 }),
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const result = buildMemoryInjection({
|
|
39
|
-
candidates,
|
|
40
|
-
serendipityItems,
|
|
41
|
-
totalBudgetTokens: 2000,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(result).toContain("<recalled>");
|
|
45
|
-
expect(result).toContain("</recalled>");
|
|
46
|
-
expect(result).toContain("<echoes>");
|
|
47
|
-
expect(result).toContain("</echoes>");
|
|
48
|
-
// <echoes> comes after </recalled>
|
|
49
|
-
const recalledEnd = result.indexOf("</recalled>");
|
|
50
|
-
const echoesStart = result.indexOf("<echoes>");
|
|
51
|
-
expect(echoesStart).toBeGreaterThan(recalledEnd);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("renders only <echoes> when no recalled candidates but serendipity items exist", () => {
|
|
55
|
-
const serendipityItems = [
|
|
56
|
-
makeCandidate({ id: "s1", finalScore: 0, importance: 0.6 }),
|
|
57
|
-
makeCandidate({ id: "s2", finalScore: 0, importance: 0.4 }),
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const result = buildMemoryInjection({
|
|
61
|
-
candidates: [],
|
|
62
|
-
serendipityItems,
|
|
63
|
-
totalBudgetTokens: 2000,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
expect(result).toContain("<memory_context");
|
|
67
|
-
expect(result).not.toContain("<recalled>");
|
|
68
|
-
expect(result).toContain("<echoes>");
|
|
69
|
-
expect(result).toContain("</echoes>");
|
|
70
|
-
expect(result).toContain("s1");
|
|
71
|
-
expect(result).toContain("s2");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("echoes section respects ~400 token cap", () => {
|
|
75
|
-
// Create serendipity items with very long text to test budget
|
|
76
|
-
const longText = "word ".repeat(200); // ~200 tokens
|
|
77
|
-
const serendipityItems = [
|
|
78
|
-
makeCandidate({ id: "s1", text: longText, finalScore: 0 }),
|
|
79
|
-
makeCandidate({ id: "s2", text: longText, finalScore: 0 }),
|
|
80
|
-
makeCandidate({ id: "s3", text: longText, finalScore: 0 }),
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
const result = buildMemoryInjection({
|
|
84
|
-
candidates: [],
|
|
85
|
-
serendipityItems,
|
|
86
|
-
totalBudgetTokens: 5000, // plenty of total budget
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// At ~200 tokens each, the 400-token echoes cap should allow at most 2
|
|
90
|
-
const itemMatches = result.match(/<item /g) ?? [];
|
|
91
|
-
expect(itemMatches.length).toBeLessThanOrEqual(2);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("no <echoes> section when serendipity array is empty", () => {
|
|
95
|
-
const candidates = [makeCandidate({ id: "c1", finalScore: 0.8 })];
|
|
96
|
-
|
|
97
|
-
const result = buildMemoryInjection({
|
|
98
|
-
candidates,
|
|
99
|
-
serendipityItems: [],
|
|
100
|
-
totalBudgetTokens: 2000,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(result).toContain("<recalled>");
|
|
104
|
-
expect(result).not.toContain("<echoes>");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("no <echoes> section when serendipity items omitted", () => {
|
|
108
|
-
const candidates = [makeCandidate({ id: "c1", finalScore: 0.8 })];
|
|
109
|
-
|
|
110
|
-
const result = buildMemoryInjection({
|
|
111
|
-
candidates,
|
|
112
|
-
totalBudgetTokens: 2000,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(result).toContain("<recalled>");
|
|
116
|
-
expect(result).not.toContain("<echoes>");
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("echoes items include importance and kind attributes", () => {
|
|
120
|
-
const serendipityItems = [
|
|
121
|
-
makeCandidate({
|
|
122
|
-
id: "echo1",
|
|
123
|
-
kind: "preference",
|
|
124
|
-
importance: 0.85,
|
|
125
|
-
text: "User likes dark mode",
|
|
126
|
-
finalScore: 0,
|
|
127
|
-
}),
|
|
128
|
-
];
|
|
129
|
-
|
|
130
|
-
const result = buildMemoryInjection({
|
|
131
|
-
candidates: [],
|
|
132
|
-
serendipityItems,
|
|
133
|
-
totalBudgetTokens: 2000,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
expect(result).toContain('kind="preference"');
|
|
137
|
-
expect(result).toContain('importance="0.85"');
|
|
138
|
-
expect(result).toContain("User likes dark mode");
|
|
139
|
-
});
|
|
140
|
-
});
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
|
-
|
|
3
|
-
import { estimateTextTokens } from "../../context/token-estimator.js";
|
|
4
|
-
import { getLogger } from "../../util/logger.js";
|
|
5
|
-
import { getDb } from "../db.js";
|
|
6
|
-
import { memoryItems } from "../schema.js";
|
|
7
|
-
import type { Candidate } from "./types.js";
|
|
8
|
-
|
|
9
|
-
const log = getLogger("memory-formatting");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Escape XML-like tag sequences in recalled text to prevent delimiter injection.
|
|
13
|
-
* Recalled content is interpolated verbatim inside `<memory>` wrapper tags,
|
|
14
|
-
* so any literal `</memory>` (or similar) in the text could break the wrapper
|
|
15
|
-
* and let recalled content masquerade as top-level prompt instructions.
|
|
16
|
-
*
|
|
17
|
-
* Strategy: replace `<` in any XML-tag-like pattern with the Unicode full-width
|
|
18
|
-
* less-than sign (U+FF1C) which is visually similar but won't be parsed as XML.
|
|
19
|
-
*/
|
|
20
|
-
export function escapeXmlTags(text: string): string {
|
|
21
|
-
// Match anything that looks like an XML tag: <word...> or </word...>
|
|
22
|
-
return text.replace(
|
|
23
|
-
/<\/?[a-zA-Z][a-zA-Z0-9_-]*[\s>\/]/g,
|
|
24
|
-
(match) => "\uFF1C" + match.slice(1),
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Convert an epoch-ms timestamp to a timezone-aware absolute time string.
|
|
30
|
-
* Format: "YYYY-MM-DD HH:mm TZ" (e.g. "2025-02-13 15:30 PST").
|
|
31
|
-
*/
|
|
32
|
-
export function formatAbsoluteTime(epochMs: number): string {
|
|
33
|
-
const date = new Date(epochMs);
|
|
34
|
-
const year = date.getFullYear();
|
|
35
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
36
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
37
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
38
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
39
|
-
|
|
40
|
-
// Extract short timezone abbreviation (e.g. "PST", "EST", "UTC")
|
|
41
|
-
const tz =
|
|
42
|
-
new Intl.DateTimeFormat("en-US", { timeZoneName: "short" })
|
|
43
|
-
.formatToParts(date)
|
|
44
|
-
.find((p) => p.type === "timeZoneName")?.value ?? "UTC";
|
|
45
|
-
|
|
46
|
-
return `${year}-${month}-${day} ${hours}:${minutes} ${tz}`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Convert an epoch-ms timestamp to a human-readable relative time string.
|
|
51
|
-
*/
|
|
52
|
-
export function formatRelativeTime(epochMs: number): string {
|
|
53
|
-
const elapsed = Math.max(0, Date.now() - epochMs);
|
|
54
|
-
const hours = elapsed / (1000 * 60 * 60);
|
|
55
|
-
if (hours < 1) return "just now";
|
|
56
|
-
if (hours < 24) {
|
|
57
|
-
const h = Math.floor(hours);
|
|
58
|
-
return `${h} hour${h === 1 ? "" : "s"} ago`;
|
|
59
|
-
}
|
|
60
|
-
const days = hours / 24;
|
|
61
|
-
if (days < 7) {
|
|
62
|
-
const d = Math.floor(days);
|
|
63
|
-
return `${d} day${d === 1 ? "" : "s"} ago`;
|
|
64
|
-
}
|
|
65
|
-
if (days < 30) {
|
|
66
|
-
const w = Math.floor(days / 7);
|
|
67
|
-
return `${w} week${w === 1 ? "" : "s"} ago`;
|
|
68
|
-
}
|
|
69
|
-
if (days < 365) {
|
|
70
|
-
const m = Math.floor(days / 30);
|
|
71
|
-
return `${m} month${m === 1 ? "" : "s"} ago`;
|
|
72
|
-
}
|
|
73
|
-
const y = Math.floor(days / 365);
|
|
74
|
-
return `${y} year${y === 1 ? "" : "s"} ago`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Unified injection format
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Build a unified `<memory_context>` XML injection block from scored candidates.
|
|
83
|
-
*
|
|
84
|
-
* All candidates are rendered in a single `<recalled>` section sorted by
|
|
85
|
-
* `finalScore` descending, with each candidate tagged by type:
|
|
86
|
-
* - items: `<item id="item:ID" kind="KIND" importance="N.NN" timestamp="..." from="...">`
|
|
87
|
-
* - segments: `<segment id="seg:ID" timestamp="..." from="...">`
|
|
88
|
-
* - summaries: `<summary id="sum:ID" timestamp="..." from="...">`
|
|
89
|
-
*
|
|
90
|
-
* An optional `<echoes>` section renders serendipity items — random
|
|
91
|
-
* importance-weighted memories for unexpected connections.
|
|
92
|
-
*
|
|
93
|
-
* Respects token budget: iterates candidates in score order, accumulates
|
|
94
|
-
* token estimates, and stops when the budget is exhausted.
|
|
95
|
-
*/
|
|
96
|
-
export function buildMemoryInjection(params: {
|
|
97
|
-
candidates: Array<Candidate & { sourceLabel?: string; staleness?: string; supersedes?: string }>;
|
|
98
|
-
serendipityItems?: Array<Candidate & { sourceLabel?: string }>;
|
|
99
|
-
totalBudgetTokens?: number;
|
|
100
|
-
}): string {
|
|
101
|
-
const { candidates, serendipityItems, totalBudgetTokens } = params;
|
|
102
|
-
|
|
103
|
-
if (candidates.length === 0 && (!serendipityItems || serendipityItems.length === 0)) {
|
|
104
|
-
return "";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Sort by finalScore descending
|
|
108
|
-
const sorted = [...candidates].sort((a, b) => b.finalScore - a.finalScore);
|
|
109
|
-
|
|
110
|
-
// Reserve tokens for structural overhead
|
|
111
|
-
const WRAPPER_OVERHEAD_TOKENS = estimateTextTokens(
|
|
112
|
-
"<memory_context __injected>\n<recalled>\n</recalled>\n</memory_context>",
|
|
113
|
-
);
|
|
114
|
-
let remainingTokens = totalBudgetTokens
|
|
115
|
-
? Math.max(1, totalBudgetTokens - WRAPPER_OVERHEAD_TOKENS)
|
|
116
|
-
: Infinity;
|
|
117
|
-
|
|
118
|
-
// Render candidates within budget
|
|
119
|
-
const lines: string[] = [];
|
|
120
|
-
for (const c of sorted) {
|
|
121
|
-
if (remainingTokens <= 0) break;
|
|
122
|
-
const line = renderCandidate(c);
|
|
123
|
-
const tokens = estimateTextTokens(line);
|
|
124
|
-
if (tokens > remainingTokens) continue;
|
|
125
|
-
lines.push(line);
|
|
126
|
-
remainingTokens -= tokens;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (lines.length === 0 && (!serendipityItems || serendipityItems.length === 0)) {
|
|
130
|
-
return "";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const sections: string[] = [];
|
|
134
|
-
|
|
135
|
-
if (lines.length > 0) {
|
|
136
|
-
sections.push(`<recalled>\n${lines.join("\n")}\n</recalled>`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Echoes section for serendipity items — capped at ~400 tokens of
|
|
140
|
-
// the remaining budget after <recalled> items are rendered.
|
|
141
|
-
if (serendipityItems && serendipityItems.length > 0) {
|
|
142
|
-
const ECHOES_MAX_TOKENS = 400;
|
|
143
|
-
let echoesBudget = Math.min(remainingTokens, ECHOES_MAX_TOKENS);
|
|
144
|
-
const echoLines: string[] = [];
|
|
145
|
-
for (const c of serendipityItems) {
|
|
146
|
-
if (echoesBudget <= 0) break;
|
|
147
|
-
const line = renderCandidate(c);
|
|
148
|
-
const tokens = estimateTextTokens(line);
|
|
149
|
-
if (tokens > echoesBudget) continue;
|
|
150
|
-
echoLines.push(line);
|
|
151
|
-
echoesBudget -= tokens;
|
|
152
|
-
remainingTokens -= tokens;
|
|
153
|
-
}
|
|
154
|
-
if (echoLines.length > 0) {
|
|
155
|
-
sections.push(`<echoes>\n${echoLines.join("\n")}\n</echoes>`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (sections.length === 0) return "";
|
|
160
|
-
|
|
161
|
-
return `<memory_context __injected>\n${sections.join("\n")}\n</memory_context>`;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Look up the supersession chain for a given superseded item ID.
|
|
166
|
-
*
|
|
167
|
-
* Returns the immediate predecessor's statement and timestamp, plus the
|
|
168
|
-
* total chain depth (how many items were superseded in sequence).
|
|
169
|
-
* Chain traversal is capped at 10 iterations to prevent infinite loops.
|
|
170
|
-
*/
|
|
171
|
-
export function lookupSupersessionChain(supersededId: string): {
|
|
172
|
-
previousStatement: string;
|
|
173
|
-
previousTimestamp: number;
|
|
174
|
-
chainDepth: number;
|
|
175
|
-
} | null {
|
|
176
|
-
try {
|
|
177
|
-
const db = getDb();
|
|
178
|
-
|
|
179
|
-
// Look up the immediate predecessor
|
|
180
|
-
const predecessor = db
|
|
181
|
-
.select({
|
|
182
|
-
statement: memoryItems.statement,
|
|
183
|
-
firstSeenAt: memoryItems.firstSeenAt,
|
|
184
|
-
supersedes: memoryItems.supersedes,
|
|
185
|
-
})
|
|
186
|
-
.from(memoryItems)
|
|
187
|
-
.where(eq(memoryItems.id, supersededId))
|
|
188
|
-
.get();
|
|
189
|
-
|
|
190
|
-
if (!predecessor) return null;
|
|
191
|
-
|
|
192
|
-
// Count chain depth by following supersedes links (cap at 10)
|
|
193
|
-
let chainDepth = 1;
|
|
194
|
-
let currentSupersedes = predecessor.supersedes;
|
|
195
|
-
const MAX_CHAIN_DEPTH = 10;
|
|
196
|
-
|
|
197
|
-
while (currentSupersedes && chainDepth < MAX_CHAIN_DEPTH) {
|
|
198
|
-
const ancestor = db
|
|
199
|
-
.select({ supersedes: memoryItems.supersedes })
|
|
200
|
-
.from(memoryItems)
|
|
201
|
-
.where(eq(memoryItems.id, currentSupersedes))
|
|
202
|
-
.get();
|
|
203
|
-
|
|
204
|
-
if (!ancestor) break;
|
|
205
|
-
chainDepth++;
|
|
206
|
-
currentSupersedes = ancestor.supersedes;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
previousStatement: predecessor.statement,
|
|
211
|
-
previousTimestamp: predecessor.firstSeenAt,
|
|
212
|
-
chainDepth,
|
|
213
|
-
};
|
|
214
|
-
} catch (err) {
|
|
215
|
-
log.warn({ err }, "Failed to look up supersession chain");
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Render a single candidate as an XML element based on its type.
|
|
222
|
-
*/
|
|
223
|
-
function renderCandidate(c: Candidate & { sourceLabel?: string; supersedes?: string }): string {
|
|
224
|
-
const text = escapeXmlTags(c.text);
|
|
225
|
-
const timestamp = formatAbsoluteTime(c.createdAt);
|
|
226
|
-
const fromAttr = c.sourceLabel
|
|
227
|
-
? ` from="${escapeXmlAttr(c.sourceLabel)}"`
|
|
228
|
-
: "";
|
|
229
|
-
const pathAttr = c.sourcePath
|
|
230
|
-
? ` path="${escapeXmlAttr(c.sourcePath)}"`
|
|
231
|
-
: "";
|
|
232
|
-
|
|
233
|
-
// Build inline supersession suffix for items
|
|
234
|
-
let supersessionSuffix = "";
|
|
235
|
-
if (c.type === "item" && c.supersedes) {
|
|
236
|
-
const chain = lookupSupersessionChain(c.supersedes);
|
|
237
|
-
if (chain) {
|
|
238
|
-
const prevTimestamp = formatAbsoluteTime(chain.previousTimestamp);
|
|
239
|
-
supersessionSuffix = `<supersedes count="${chain.chainDepth}">${escapeXmlTags(chain.previousStatement)} (${prevTimestamp})</supersedes>`;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
switch (c.type) {
|
|
244
|
-
case "item":
|
|
245
|
-
return `<item id="item:${escapeXmlAttr(c.id)}" kind="${escapeXmlAttr(c.kind)}" importance="${c.importance.toFixed(2)}" timestamp="${escapeXmlAttr(timestamp)}"${fromAttr}${pathAttr}>${text}${supersessionSuffix}</item>`;
|
|
246
|
-
case "segment":
|
|
247
|
-
return `<segment id="seg:${escapeXmlAttr(c.id)}" timestamp="${escapeXmlAttr(timestamp)}"${fromAttr}${pathAttr}>${text}</segment>`;
|
|
248
|
-
case "summary":
|
|
249
|
-
return `<summary id="sum:${escapeXmlAttr(c.id)}" timestamp="${escapeXmlAttr(timestamp)}"${fromAttr}${pathAttr}>${text}</summary>`;
|
|
250
|
-
default:
|
|
251
|
-
// media or unknown types — render as item
|
|
252
|
-
return `<item id="item:${escapeXmlAttr(c.id)}" kind="${escapeXmlAttr(c.kind)}" importance="${c.importance.toFixed(2)}" timestamp="${escapeXmlAttr(timestamp)}"${fromAttr}${pathAttr}>${text}${supersessionSuffix}</item>`;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function escapeXmlAttr(text: string): string {
|
|
257
|
-
return text
|
|
258
|
-
.replace(/&/g, "&")
|
|
259
|
-
.replace(/"/g, """)
|
|
260
|
-
.replace(/</g, "<")
|
|
261
|
-
.replace(/>/g, ">");
|
|
262
|
-
}
|
package/src/memory/search/mmr.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { generateSparseEmbedding } from "../embedding-backend.js";
|
|
2
|
-
import type { SparseEmbedding } from "../embedding-types.js";
|
|
3
|
-
import type { TieredCandidate } from "./tier-classifier.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Compute cosine similarity between two sparse vectors.
|
|
7
|
-
* Returns 0 if either vector has zero magnitude.
|
|
8
|
-
*/
|
|
9
|
-
function sparseCosine(a: SparseEmbedding, b: SparseEmbedding): number {
|
|
10
|
-
// Build index→value map for vector b
|
|
11
|
-
const bMap = new Map<number, number>();
|
|
12
|
-
for (let i = 0; i < b.indices.length; i++) {
|
|
13
|
-
bMap.set(b.indices[i]!, b.values[i]!);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Compute dot product over shared indices
|
|
17
|
-
let dotProduct = 0;
|
|
18
|
-
for (let i = 0; i < a.indices.length; i++) {
|
|
19
|
-
const bVal = bMap.get(a.indices[i]!);
|
|
20
|
-
if (bVal !== undefined) {
|
|
21
|
-
dotProduct += a.values[i]! * bVal;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Compute magnitudes
|
|
26
|
-
let magA = 0;
|
|
27
|
-
for (const v of a.values) magA += v * v;
|
|
28
|
-
magA = Math.sqrt(magA);
|
|
29
|
-
|
|
30
|
-
let magB = 0;
|
|
31
|
-
for (const v of b.values) magB += v * v;
|
|
32
|
-
magB = Math.sqrt(magB);
|
|
33
|
-
|
|
34
|
-
if (magA === 0 || magB === 0) return 0;
|
|
35
|
-
return dotProduct / (magA * magB);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Apply Maximal Marginal Relevance (MMR) diversity ranking to candidates.
|
|
40
|
-
*
|
|
41
|
-
* Items are re-ranked using a greedy selection loop that progressively
|
|
42
|
-
* penalizes candidates whose text is similar to already-selected ones.
|
|
43
|
-
* Non-item candidates (segments, summaries, media) pass through unpenalized
|
|
44
|
-
* since they represent different conversation windows.
|
|
45
|
-
*
|
|
46
|
-
* @param candidates - Scored candidates from upstream ranking
|
|
47
|
-
* @param penalty - Float 0..1. 0 = no diversity pressure, 1 = maximum
|
|
48
|
-
* @returns Re-ranked candidates with adjusted finalScores
|
|
49
|
-
*/
|
|
50
|
-
export function applyMMR(
|
|
51
|
-
candidates: TieredCandidate[],
|
|
52
|
-
penalty: number,
|
|
53
|
-
): TieredCandidate[] {
|
|
54
|
-
// Separate items from non-items
|
|
55
|
-
const items: { index: number; candidate: TieredCandidate }[] = [];
|
|
56
|
-
const nonItems: TieredCandidate[] = [];
|
|
57
|
-
|
|
58
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
59
|
-
const c = candidates[i]!;
|
|
60
|
-
if (c.type === "item") {
|
|
61
|
-
items.push({ index: i, candidate: c });
|
|
62
|
-
} else {
|
|
63
|
-
nonItems.push(c);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If no items or no penalty, pass through in original order
|
|
68
|
-
if (items.length === 0 || penalty === 0) {
|
|
69
|
-
return candidates;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Pre-compute sparse embeddings only for items (not segments, summaries, media)
|
|
73
|
-
const embeddings = new Map<number, SparseEmbedding>();
|
|
74
|
-
for (const item of items) {
|
|
75
|
-
embeddings.set(item.index, generateSparseEmbedding(item.candidate.text));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Greedy MMR selection loop
|
|
79
|
-
const selected: number[] = [];
|
|
80
|
-
const remaining = new Set<number>(items.map((_, i) => i));
|
|
81
|
-
const adjustedScores = new Map<number, number>();
|
|
82
|
-
|
|
83
|
-
// Select the item with the highest finalScore first
|
|
84
|
-
let bestIdx = -1;
|
|
85
|
-
let bestScore = -Infinity;
|
|
86
|
-
for (const idx of remaining) {
|
|
87
|
-
const score = items[idx]!.candidate.finalScore;
|
|
88
|
-
if (score > bestScore) {
|
|
89
|
-
bestScore = score;
|
|
90
|
-
bestIdx = idx;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
selected.push(bestIdx);
|
|
94
|
-
remaining.delete(bestIdx);
|
|
95
|
-
adjustedScores.set(bestIdx, items[bestIdx]!.candidate.finalScore);
|
|
96
|
-
|
|
97
|
-
// Iteratively select remaining items
|
|
98
|
-
while (remaining.size > 0) {
|
|
99
|
-
let nextBestIdx = -1;
|
|
100
|
-
let nextBestScore = -Infinity;
|
|
101
|
-
|
|
102
|
-
for (const idx of remaining) {
|
|
103
|
-
const itemEmbIdx = items[idx]!.index;
|
|
104
|
-
|
|
105
|
-
// Compute max similarity to any already-selected item
|
|
106
|
-
let maxSim = 0;
|
|
107
|
-
for (const selIdx of selected) {
|
|
108
|
-
const selEmbIdx = items[selIdx]!.index;
|
|
109
|
-
const sim = sparseCosine(
|
|
110
|
-
embeddings.get(itemEmbIdx)!,
|
|
111
|
-
embeddings.get(selEmbIdx)!,
|
|
112
|
-
);
|
|
113
|
-
if (sim > maxSim) maxSim = sim;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const adjustedScore =
|
|
117
|
-
items[idx]!.candidate.finalScore * (1 - maxSim * penalty);
|
|
118
|
-
if (adjustedScore > nextBestScore) {
|
|
119
|
-
nextBestScore = adjustedScore;
|
|
120
|
-
nextBestIdx = idx;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
selected.push(nextBestIdx);
|
|
125
|
-
remaining.delete(nextBestIdx);
|
|
126
|
-
adjustedScores.set(nextBestIdx, nextBestScore);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Rebuild output: non-items first (original order), then items in selected order
|
|
130
|
-
const result: TieredCandidate[] = [...nonItems];
|
|
131
|
-
for (const idx of selected) {
|
|
132
|
-
result.push({
|
|
133
|
-
...items[idx]!.candidate,
|
|
134
|
-
finalScore: adjustedScores.get(idx)!,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logarithmic recency decay (ACT-R inspired).
|
|
3
|
-
*
|
|
4
|
-
* Old formula `1/(1+ageDays)` decays far too aggressively:
|
|
5
|
-
* - 30 days -> 0.032, 1 year -> 0.003
|
|
6
|
-
*
|
|
7
|
-
* New formula `1/(1+log2(1+ageDays))` preserves long-term recall:
|
|
8
|
-
* - 1 day -> 0.50, 7 days -> 0.25, 30 days -> 0.17
|
|
9
|
-
* - 90 days -> 0.15, 1 year -> 0.12, 2 years -> 0.10
|
|
10
|
-
*/
|
|
11
|
-
export function computeRecencyScore(createdAt: number): number {
|
|
12
|
-
const ageMs = Math.max(0, Date.now() - createdAt);
|
|
13
|
-
const ageDays = ageMs / (24 * 60 * 60 * 1000);
|
|
14
|
-
return 1 / (1 + Math.log2(1 + ageDays));
|
|
15
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { StalenessLevel } from "./types.js";
|
|
2
|
-
|
|
3
|
-
const BASE_LIFETIME_MS: Record<string, number> = {
|
|
4
|
-
identity: 180 * 86_400_000, // 6 months
|
|
5
|
-
preference: 90 * 86_400_000, // 3 months
|
|
6
|
-
constraint: 30 * 86_400_000, // 1 month
|
|
7
|
-
project: 14 * 86_400_000, // 2 weeks
|
|
8
|
-
decision: 14 * 86_400_000, // 2 weeks
|
|
9
|
-
event: 3 * 86_400_000, // 3 days
|
|
10
|
-
// Journals are experiential reflections and forward-looking notes — more
|
|
11
|
-
// durable than ephemeral events or decisions, but not as permanent as
|
|
12
|
-
// identity. 90 days mirrors "preference" lifetime.
|
|
13
|
-
journal: 90 * 86_400_000, // 3 months
|
|
14
|
-
capability: Infinity,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const DEFAULT_LIFETIME_MS = 30 * 86_400_000;
|
|
18
|
-
|
|
19
|
-
export function computeStaleness(
|
|
20
|
-
item: {
|
|
21
|
-
kind: string;
|
|
22
|
-
firstSeenAt: number;
|
|
23
|
-
sourceConversationCount: number;
|
|
24
|
-
},
|
|
25
|
-
now: number,
|
|
26
|
-
): { level: StalenessLevel; ratio: number } {
|
|
27
|
-
const baseLifetime = BASE_LIFETIME_MS[item.kind] ?? DEFAULT_LIFETIME_MS;
|
|
28
|
-
const reinforcement = Math.max(
|
|
29
|
-
1,
|
|
30
|
-
1 + 0.3 * (item.sourceConversationCount - 1),
|
|
31
|
-
);
|
|
32
|
-
const effectiveLifetime = baseLifetime * reinforcement;
|
|
33
|
-
const age = now - item.firstSeenAt;
|
|
34
|
-
const ratio = age / effectiveLifetime;
|
|
35
|
-
|
|
36
|
-
if (ratio < 0.5) return { level: "fresh", ratio };
|
|
37
|
-
if (ratio <= 1) return { level: "aging", ratio };
|
|
38
|
-
if (ratio <= 2) return { level: "stale", ratio };
|
|
39
|
-
return { level: "very_stale", ratio };
|
|
40
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Candidate } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/** Backward-compatible alias — downstream files import this type. */
|
|
4
|
-
export type TieredCandidate = Candidate & {
|
|
5
|
-
/** Human-readable label for the source conversation/summary (e.g. conversation title). */
|
|
6
|
-
sourceLabel?: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const MIN_SCORE_THRESHOLD = 0.2;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Filter candidates to those exceeding the minimum relevance threshold.
|
|
13
|
-
* Replaces the former tier 1/tier 2 classification — all surviving candidates
|
|
14
|
-
* are treated equally and ranked by score.
|
|
15
|
-
*/
|
|
16
|
-
export function filterByMinScore(candidates: Candidate[]): TieredCandidate[] {
|
|
17
|
-
return candidates.filter((c) => c.finalScore > MIN_SCORE_THRESHOLD);
|
|
18
|
-
}
|