@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
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Memory Graph — Context assembly and injection tracking
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { optimizeImageForTransport } from "../../agent/image-optimize.js";
|
|
6
|
+
import { getLogger } from "../../util/logger.js";
|
|
7
|
+
import { loadImageRefData } from "./image-ref-utils.js";
|
|
8
|
+
import type { MemoryNode, ScoredNode } from "./types.js";
|
|
9
|
+
import { isCapabilityNode } from "./types.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Image injection budgets
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export const MAX_CONTEXT_LOAD_IMAGES = 3;
|
|
16
|
+
export const MAX_PER_TURN_IMAGES = 1;
|
|
17
|
+
export const MAX_REFRESH_IMAGES = 2;
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface ResolvedImage {
|
|
24
|
+
base64Data: string;
|
|
25
|
+
mediaType: string;
|
|
26
|
+
description: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// InContextTracker — tracks which node IDs are visible to the LLM
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
interface InjectionLogEntry {
|
|
34
|
+
nodeId: string;
|
|
35
|
+
turn: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Tracks which memory graph nodes are currently in the LLM's context.
|
|
40
|
+
* Handles:
|
|
41
|
+
* - Deduplication: never re-inject a node already visible
|
|
42
|
+
* - Compaction eviction: when context compaction removes turns,
|
|
43
|
+
* evict those nodes so they can be re-injected if relevant later
|
|
44
|
+
*/
|
|
45
|
+
export class InContextTracker {
|
|
46
|
+
private inContext = new Set<string>();
|
|
47
|
+
private log: InjectionLogEntry[] = [];
|
|
48
|
+
private currentTurn = 0;
|
|
49
|
+
|
|
50
|
+
/** Mark nodes as loaded into context. */
|
|
51
|
+
add(nodeIds: string[]): void {
|
|
52
|
+
for (const id of nodeIds) {
|
|
53
|
+
this.inContext.add(id);
|
|
54
|
+
this.log.push({ nodeId: id, turn: this.currentTurn });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Check if a node is already in context. */
|
|
59
|
+
isInContext(nodeId: string): boolean {
|
|
60
|
+
return this.inContext.has(nodeId);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Filter candidates to only those not already in context. */
|
|
64
|
+
filterNew(candidates: ScoredNode[]): ScoredNode[] {
|
|
65
|
+
return candidates.filter((c) => !this.inContext.has(c.node.id));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Advance the turn counter. Called before each retrieval step. */
|
|
69
|
+
advanceTurn(): void {
|
|
70
|
+
this.currentTurn++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Evict nodes that were injected in compacted turns.
|
|
75
|
+
* Called when context compaction removes message history.
|
|
76
|
+
*/
|
|
77
|
+
evictCompactedTurns(upToTurn: number): void {
|
|
78
|
+
const evicted: string[] = [];
|
|
79
|
+
this.log = this.log.filter((entry) => {
|
|
80
|
+
if (entry.turn <= upToTurn) {
|
|
81
|
+
evicted.push(entry.nodeId);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Only evict if the node isn't also loaded in a later turn
|
|
88
|
+
const stillPresent = new Set(this.log.map((e) => e.nodeId));
|
|
89
|
+
for (const id of evicted) {
|
|
90
|
+
if (!stillPresent.has(id)) {
|
|
91
|
+
this.inContext.delete(id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Get all node IDs currently in context. Useful for extraction. */
|
|
97
|
+
getActiveNodeIds(): string[] {
|
|
98
|
+
return [...this.inContext];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Get the injection log. Useful for debugging. */
|
|
102
|
+
getLog(): InjectionLogEntry[] {
|
|
103
|
+
return [...this.log];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Current turn number. */
|
|
107
|
+
getTurn(): number {
|
|
108
|
+
return this.currentTurn;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Context assembly — programmatic, not LLM
|
|
114
|
+
//
|
|
115
|
+
// Each node's full prose lives in node.content. The context block gets a
|
|
116
|
+
// compressed version: 1-2 sentences + light metadata (type, age).
|
|
117
|
+
// Full detail available via the recall tool.
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
// No assembly options needed — the retriever's node count (30-40) is the only limit.
|
|
121
|
+
// The context block includes full node content.
|
|
122
|
+
|
|
123
|
+
/** Format relative time from epoch ms. */
|
|
124
|
+
function relativeAge(createdMs: number): string {
|
|
125
|
+
const diffMs = Date.now() - createdMs;
|
|
126
|
+
const mins = Math.floor(diffMs / 60_000);
|
|
127
|
+
if (mins < 60) return `${mins}m ago`;
|
|
128
|
+
const hours = Math.floor(mins / 60);
|
|
129
|
+
if (hours < 24) return `${hours}h ago`;
|
|
130
|
+
const days = Math.floor(hours / 24);
|
|
131
|
+
if (days < 90) return `${days}d ago`;
|
|
132
|
+
const months = Math.floor(days / 30);
|
|
133
|
+
return `${months}mo ago`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Format an event date as a human-readable string with relative countdown.
|
|
138
|
+
* Examples:
|
|
139
|
+
* "Tue Apr 8, 6:00 PM (in 3d)"
|
|
140
|
+
* "Thu Apr 10 (in 5d)"
|
|
141
|
+
* "Mon Apr 7, 9:00 AM (tomorrow)"
|
|
142
|
+
* "Wed Apr 1 (today)"
|
|
143
|
+
*/
|
|
144
|
+
export function formatEventDate(epochMs: number): string {
|
|
145
|
+
const date = new Date(epochMs);
|
|
146
|
+
const now = new Date();
|
|
147
|
+
|
|
148
|
+
// Day names and month names
|
|
149
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
150
|
+
const monthNames = [
|
|
151
|
+
"Jan",
|
|
152
|
+
"Feb",
|
|
153
|
+
"Mar",
|
|
154
|
+
"Apr",
|
|
155
|
+
"May",
|
|
156
|
+
"Jun",
|
|
157
|
+
"Jul",
|
|
158
|
+
"Aug",
|
|
159
|
+
"Sep",
|
|
160
|
+
"Oct",
|
|
161
|
+
"Nov",
|
|
162
|
+
"Dec",
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const dayName = dayNames[date.getDay()];
|
|
166
|
+
const monthName = monthNames[date.getMonth()];
|
|
167
|
+
const dayOfMonth = date.getDate();
|
|
168
|
+
|
|
169
|
+
// Include time only when hours/minutes are non-zero (midnight = date-only event)
|
|
170
|
+
// Use UTC methods: date-only events are stored as midnight UTC, so local timezone
|
|
171
|
+
// methods would show non-zero hours in west-of-UTC timezones, defeating the check.
|
|
172
|
+
const hasTime = date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0;
|
|
173
|
+
let datePart = `${dayName} ${monthName} ${dayOfMonth}`;
|
|
174
|
+
if (hasTime) {
|
|
175
|
+
const hours = date.getHours();
|
|
176
|
+
const minutes = date.getMinutes();
|
|
177
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
178
|
+
const displayHour = hours % 12 || 12;
|
|
179
|
+
const timePart =
|
|
180
|
+
minutes === 0
|
|
181
|
+
? `${displayHour}:00 ${ampm}`
|
|
182
|
+
: `${displayHour}:${String(minutes).padStart(2, "0")} ${ampm}`;
|
|
183
|
+
datePart += `, ${timePart}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Calculate relative countdown using calendar days
|
|
187
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
188
|
+
const eventStart = new Date(
|
|
189
|
+
date.getFullYear(),
|
|
190
|
+
date.getMonth(),
|
|
191
|
+
date.getDate(),
|
|
192
|
+
);
|
|
193
|
+
const diffDays = Math.round(
|
|
194
|
+
(eventStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
let relative: string;
|
|
198
|
+
if (diffDays === 0) {
|
|
199
|
+
relative = "today";
|
|
200
|
+
} else if (diffDays === -1) {
|
|
201
|
+
// For date-only inputs (no time component), -1 day can be UTC midnight drift — treat as "today".
|
|
202
|
+
// For timed events, -1 day is genuinely yesterday.
|
|
203
|
+
relative = hasTime ? "yesterday" : "today";
|
|
204
|
+
} else if (diffDays < -1) {
|
|
205
|
+
// Past dates
|
|
206
|
+
const absDays = -diffDays;
|
|
207
|
+
if (absDays < 14) {
|
|
208
|
+
relative = `${absDays}d ago`;
|
|
209
|
+
} else if (absDays < 60) {
|
|
210
|
+
relative = `${Math.floor(absDays / 7)}w ago`;
|
|
211
|
+
} else {
|
|
212
|
+
relative = `${Math.floor(absDays / 30)}mo ago`;
|
|
213
|
+
}
|
|
214
|
+
} else if (diffDays === 1) {
|
|
215
|
+
relative = "tomorrow";
|
|
216
|
+
} else if (diffDays < 14) {
|
|
217
|
+
relative = `in ${diffDays}d`;
|
|
218
|
+
} else if (diffDays < 60) {
|
|
219
|
+
relative = `in ${Math.floor(diffDays / 7)}w`;
|
|
220
|
+
} else {
|
|
221
|
+
relative = `in ${Math.floor(diffDays / 30)}mo`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return `${datePart} (${relative})`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Format an upcoming entry — uses event date when available, falls back to standard format. */
|
|
228
|
+
function formatUpcomingEntry(scored: ScoredNode): string {
|
|
229
|
+
const node = scored.node;
|
|
230
|
+
if (node.eventDate != null) {
|
|
231
|
+
return `- ${formatEventDate(node.eventDate)} — ${node.content}`;
|
|
232
|
+
}
|
|
233
|
+
return formatNodeEntry(scored);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Format a single node for the context block. */
|
|
237
|
+
function formatNodeEntry(scored: ScoredNode): string {
|
|
238
|
+
const node = scored.node;
|
|
239
|
+
const age = relativeAge(node.created);
|
|
240
|
+
let entry = `- (${age}) ${node.content}`;
|
|
241
|
+
if (node.imageRefs && node.imageRefs.length > 0) {
|
|
242
|
+
const desc = node.imageRefs[0]!.description;
|
|
243
|
+
entry += ` [image: ${desc}]`;
|
|
244
|
+
}
|
|
245
|
+
return entry;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Assemble a context block from scored memory nodes.
|
|
250
|
+
*
|
|
251
|
+
* Structure:
|
|
252
|
+
* - Right Now: present-tense state (most recent emotional + very recent episodic)
|
|
253
|
+
* - Active Threads: prospective nodes (commitments, tasks, plans)
|
|
254
|
+
* - What Today Means: date-triggered nodes (anniversaries, milestones)
|
|
255
|
+
* - On My Mind: everything else, ordered by score — no sub-categories
|
|
256
|
+
* - Serendipity: the random mid-tier wildcard(s)
|
|
257
|
+
*/
|
|
258
|
+
export function assembleContextBlock(
|
|
259
|
+
nodes: ScoredNode[],
|
|
260
|
+
options?: { serendipityNodes?: ScoredNode[] },
|
|
261
|
+
): string {
|
|
262
|
+
// Partition nodes into sections
|
|
263
|
+
const rightNow: ScoredNode[] = [];
|
|
264
|
+
const threads: ScoredNode[] = [];
|
|
265
|
+
const triggered: ScoredNode[] = [];
|
|
266
|
+
const upcoming: ScoredNode[] = [];
|
|
267
|
+
const capabilities: ScoredNode[] = [];
|
|
268
|
+
const onMyMind: ScoredNode[] = [];
|
|
269
|
+
|
|
270
|
+
for (const scored of nodes) {
|
|
271
|
+
const node = scored.node;
|
|
272
|
+
|
|
273
|
+
if (scored.scoreBreakdown.triggerBoost > 0) {
|
|
274
|
+
triggered.push(scored);
|
|
275
|
+
} else if (node.eventDate != null && node.eventDate > Date.now()) {
|
|
276
|
+
// Future-dated events without an active trigger go to Upcoming
|
|
277
|
+
upcoming.push(scored);
|
|
278
|
+
} else if (node.type === "prospective") {
|
|
279
|
+
threads.push(scored);
|
|
280
|
+
} else if (isCapabilityNode(node)) {
|
|
281
|
+
capabilities.push(scored);
|
|
282
|
+
} else if (node.type === "emotional" && isRecent(node)) {
|
|
283
|
+
// Recent emotional nodes go in "Right Now" — present-tense state
|
|
284
|
+
rightNow.push(scored);
|
|
285
|
+
} else if (isVeryRecent(node)) {
|
|
286
|
+
// Very recent nodes (last few hours) are "right now" context
|
|
287
|
+
rightNow.push(scored);
|
|
288
|
+
} else {
|
|
289
|
+
onMyMind.push(scored);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Sort upcoming by eventDate ascending (soonest first)
|
|
294
|
+
upcoming.sort((a, b) => (a.node.eventDate ?? 0) - (b.node.eventDate ?? 0));
|
|
295
|
+
|
|
296
|
+
const parts: string[] = [];
|
|
297
|
+
|
|
298
|
+
// --- Right Now ---
|
|
299
|
+
if (rightNow.length > 0) {
|
|
300
|
+
const entries = buildSection(rightNow, 3);
|
|
301
|
+
if (entries.length > 0) {
|
|
302
|
+
parts.push(`### Right Now\n${entries.join("\n")}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// --- Active Threads ---
|
|
307
|
+
if (threads.length > 0) {
|
|
308
|
+
const entries = buildSection(threads, 5);
|
|
309
|
+
if (entries.length > 0) {
|
|
310
|
+
parts.push(`### Active Threads\n${entries.join("\n")}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// --- Skills You Can Use ---
|
|
315
|
+
if (capabilities.length > 0) {
|
|
316
|
+
const entries = capabilities.slice(0, 5).map((scored) => {
|
|
317
|
+
const content = scored.node.content.replace(/^(?:skill|cli):\S+\n/, "");
|
|
318
|
+
return `- ${content} → use skill_load to activate`;
|
|
319
|
+
});
|
|
320
|
+
parts.push(`### Skills You Can Use\n${entries.join("\n")}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// --- Upcoming ---
|
|
324
|
+
if (upcoming.length > 0) {
|
|
325
|
+
const entries = upcoming.slice(0, 5).map(formatUpcomingEntry);
|
|
326
|
+
if (entries.length > 0) {
|
|
327
|
+
parts.push(`### Upcoming\n${entries.join("\n")}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- What Today Means ---
|
|
332
|
+
if (triggered.length > 0) {
|
|
333
|
+
const entries = buildSection(triggered, 3);
|
|
334
|
+
if (entries.length > 0) {
|
|
335
|
+
parts.push(`### What Today Means\n${entries.join("\n")}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// --- On My Mind ---
|
|
340
|
+
if (onMyMind.length > 0) {
|
|
341
|
+
const entries = buildSection(onMyMind, onMyMind.length);
|
|
342
|
+
if (entries.length > 0) {
|
|
343
|
+
parts.push(`### On My Mind\n${entries.join("\n")}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// --- Serendipity ---
|
|
348
|
+
const serendipity = options?.serendipityNodes ?? [];
|
|
349
|
+
if (serendipity.length > 0) {
|
|
350
|
+
const entries = buildSection(serendipity, 2);
|
|
351
|
+
if (entries.length > 0) {
|
|
352
|
+
parts.push(`### Serendipity\n${entries.join("\n")}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (parts.length === 0) return "";
|
|
357
|
+
return `## What I Remember Right Now\n\n${parts.join("\n\n")}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function buildSection(nodes: ScoredNode[], maxItems: number): string[] {
|
|
361
|
+
return nodes.slice(0, maxItems).map(formatNodeEntry);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Assemble an injection block for mid-conversation memory flashes.
|
|
366
|
+
* Uses the same per-node format as context-load (age + full content).
|
|
367
|
+
*/
|
|
368
|
+
export function assembleInjectionBlock(nodes: ScoredNode[]): string {
|
|
369
|
+
if (nodes.length === 0) return "";
|
|
370
|
+
return nodes
|
|
371
|
+
.map((scored) => {
|
|
372
|
+
if (isCapabilityNode(scored.node)) {
|
|
373
|
+
const content = scored.node.content.replace(/^(?:skill|cli):\S+\n/, "");
|
|
374
|
+
return `- [skill] ${content} → use skill_load to activate`;
|
|
375
|
+
}
|
|
376
|
+
if (scored.node.eventDate != null) {
|
|
377
|
+
return formatUpcomingEntry(scored);
|
|
378
|
+
}
|
|
379
|
+
return formatNodeEntry(scored);
|
|
380
|
+
})
|
|
381
|
+
.join("\n");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
// Image resolution
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Load and optimize images for memory injection.
|
|
390
|
+
* Iterates scored nodes (already sorted by score), resolves the first
|
|
391
|
+
* image ref for each image-bearing node, and returns up to maxImages.
|
|
392
|
+
*/
|
|
393
|
+
export async function resolveInjectionImages(
|
|
394
|
+
nodes: ScoredNode[],
|
|
395
|
+
maxImages: number,
|
|
396
|
+
): Promise<Map<string, ResolvedImage>> {
|
|
397
|
+
const log = getLogger("memory-graph");
|
|
398
|
+
const result = new Map<string, ResolvedImage>();
|
|
399
|
+
for (const scored of nodes) {
|
|
400
|
+
if (result.size >= maxImages) break;
|
|
401
|
+
const refs = scored.node.imageRefs;
|
|
402
|
+
if (!refs || refs.length === 0) continue;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const data = await loadImageRefData(refs[0]!);
|
|
406
|
+
if (!data) continue;
|
|
407
|
+
|
|
408
|
+
const optimized = optimizeImageForTransport(
|
|
409
|
+
data.data.toString("base64"),
|
|
410
|
+
data.mimeType,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
result.set(scored.node.id, {
|
|
414
|
+
base64Data: optimized.data,
|
|
415
|
+
mediaType: optimized.mediaType,
|
|
416
|
+
description: refs[0]!.description,
|
|
417
|
+
});
|
|
418
|
+
} catch (err) {
|
|
419
|
+
log.warn(
|
|
420
|
+
`Skipping image for node ${scored.node.id}: ${err instanceof Error ? err.message : String(err)}`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// Helpers
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
function isRecent(node: MemoryNode): boolean {
|
|
432
|
+
const dayMs = 1000 * 60 * 60 * 24;
|
|
433
|
+
return Date.now() - node.created < 2 * dayMs;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function isVeryRecent(node: MemoryNode): boolean {
|
|
437
|
+
const hourMs = 1000 * 60 * 60;
|
|
438
|
+
return Date.now() - node.created < 4 * hourMs;
|
|
439
|
+
}
|