@vellumai/assistant 0.5.16 → 0.6.1
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +69 -16
- package/Dockerfile +2 -5
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/knip.json +2 -1
- package/openapi.yaml +1198 -83
- package/package.json +5 -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 +217 -98
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -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 +63 -9
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/config-schema.test.ts +6 -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 +13 -6
- package/src/__tests__/conversation-agent-loop.test.ts +13 -51
- 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 +6 -1
- 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-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +13 -414
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- 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__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- 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__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +178 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +293 -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 +17 -28
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +103 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +74 -111
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +134 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- 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__/onboarding-template-contract.test.ts +62 -14
- 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__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -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__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +3 -3
- 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__/sandbox-host-parity.test.ts +5 -4
- 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__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +422 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +44 -9
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- 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 +23 -11
- package/src/__tests__/skill-memory.test.ts +2 -741
- 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__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- 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 +185 -241
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +7 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- 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 +118 -8
- 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-migration-028-recover-conversations-from-disk-view.test.ts +387 -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 +7 -15
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- 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/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +37 -84
- package/src/cli/commands/notifications.ts +7 -2
- 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 +25 -11
- 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/routes.ts +396 -0
- package/src/cli/commands/skills.ts +218 -36
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -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 +4 -12
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +17 -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/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +3 -7
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +78 -8
- package/src/config/feature-flag-registry.json +38 -125
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/security.ts +14 -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/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
- package/src/daemon/conversation-agent-loop.ts +236 -108
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +36 -9
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +305 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +59 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +85 -17
- package/src/daemon/handlers/skills.ts +416 -209
- package/src/daemon/lifecycle.ts +212 -131
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +29 -7
- package/src/daemon/message-types/messages.ts +12 -2
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +100 -11
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +50 -8
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +97 -7
- 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/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +42 -75
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +3 -1
- package/src/memory/conversation-crud.ts +211 -288
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +61 -13
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +194 -361
- 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 +521 -0
- package/src/memory/graph/capability-seed.ts +449 -0
- package/src/memory/graph/consolidation.ts +725 -0
- package/src/memory/graph/conversation-graph-memory.ts +659 -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 +74 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1297 -0
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +280 -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 +469 -0
- package/src/memory/graph/inspect.ts +543 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1111 -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 +1098 -0
- package/src/memory/graph/store.ts +838 -0
- package/src/memory/graph/tool-handlers.ts +301 -0
- package/src/memory/graph/tools.ts +97 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +295 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +32 -42
- 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 +21 -31
- package/src/memory/jobs-worker.ts +180 -129
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- 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 +55 -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/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +154 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +58 -61
- 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 +95 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +36 -4
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +10 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +62 -36
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +136 -220
- 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 +30 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +55 -5
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +22 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- 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/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- 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/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +170 -247
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +28 -11
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +52 -18
- 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 +25 -16
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +29 -18
- package/src/skills/skill-memory.ts +12 -229
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +7 -5
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +85 -1
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +17 -1
- package/src/tools/tool-manifest.ts +9 -3
- package/src/tools/types.ts +2 -0
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- 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/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -372
- package/src/__tests__/clipboard.test.ts +0 -88
- 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/cli/cli-memory.ts +0 -176
- 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
- package/src/util/clipboard.ts +0 -34
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
mock.module("../util/logger.js", () => ({
|
|
4
4
|
getLogger: () =>
|
|
@@ -15,22 +15,17 @@ import {
|
|
|
15
15
|
getMessages,
|
|
16
16
|
wipeConversation,
|
|
17
17
|
} from "../memory/conversation-crud.js";
|
|
18
|
-
import { getDb, initializeDb
|
|
18
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
19
19
|
import { enqueueMemoryJob } from "../memory/jobs-store.js";
|
|
20
20
|
|
|
21
21
|
// Initialize db once before all tests
|
|
22
22
|
initializeDb();
|
|
23
23
|
|
|
24
|
-
afterAll(() => {
|
|
25
|
-
resetDb();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
24
|
describe("wipeConversation", () => {
|
|
29
25
|
beforeEach(() => {
|
|
30
26
|
const db = getDb();
|
|
31
|
-
db.run(`DELETE FROM
|
|
27
|
+
db.run(`DELETE FROM memory_graph_nodes`);
|
|
32
28
|
db.run(`DELETE FROM memory_segments`);
|
|
33
|
-
db.run(`DELETE FROM memory_items`);
|
|
34
29
|
db.run(`DELETE FROM memory_summaries`);
|
|
35
30
|
db.run(`DELETE FROM memory_embeddings`);
|
|
36
31
|
db.run(`DELETE FROM memory_jobs`);
|
|
@@ -52,237 +47,6 @@ describe("wipeConversation", () => {
|
|
|
52
47
|
expect(getMessages(conv.id)).toEqual([]);
|
|
53
48
|
});
|
|
54
49
|
|
|
55
|
-
test("restores explicitly superseded memory items", async () => {
|
|
56
|
-
const convA = createConversation("conversation A");
|
|
57
|
-
const msgA = await addMessage(convA.id, "user", "I like blue");
|
|
58
|
-
|
|
59
|
-
const convB = createConversation("conversation B");
|
|
60
|
-
const msgB = await addMessage(convB.id, "user", "I like red");
|
|
61
|
-
|
|
62
|
-
const db = getDb();
|
|
63
|
-
const now = Date.now();
|
|
64
|
-
|
|
65
|
-
// Insert itemA: active preference about color
|
|
66
|
-
db.run(
|
|
67
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
68
|
-
VALUES ('itemA', 'active', 'preference', 'color', 'likes blue', 0.8, 'fp-a', 'default', ${now}, ${now})`,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Insert itemB: active preference about color, supersedes itemA
|
|
72
|
-
db.run(
|
|
73
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, supersedes, first_seen_at, last_seen_at)
|
|
74
|
-
VALUES ('itemB', 'active', 'preference', 'color', 'likes red', 0.9, 'fp-b', 'default', 'itemA', ${now}, ${now})`,
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
// Mark itemA as superseded by itemB
|
|
78
|
-
db.run(
|
|
79
|
-
`UPDATE memory_items SET status = 'superseded', superseded_by = 'itemB' WHERE id = 'itemA'`,
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// Link itemA to convA's message, itemB to convB's message
|
|
83
|
-
db.run(
|
|
84
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemA', '${msgA.id}', ${now})`,
|
|
85
|
-
);
|
|
86
|
-
db.run(
|
|
87
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemB', '${msgB.id}', ${now})`,
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const result = wipeConversation(convB.id);
|
|
91
|
-
|
|
92
|
-
// itemA should be restored to active with superseded_by cleared
|
|
93
|
-
const raw = (
|
|
94
|
-
getDb() as unknown as {
|
|
95
|
-
$client: import("bun:sqlite").Database;
|
|
96
|
-
}
|
|
97
|
-
).$client;
|
|
98
|
-
const itemARow = raw
|
|
99
|
-
.query(
|
|
100
|
-
"SELECT status, superseded_by FROM memory_items WHERE id = 'itemA'",
|
|
101
|
-
)
|
|
102
|
-
.get() as { status: string; superseded_by: string | null } | null;
|
|
103
|
-
expect(itemARow).not.toBeNull();
|
|
104
|
-
expect(itemARow!.status).toBe("active");
|
|
105
|
-
expect(itemARow!.superseded_by).toBeNull();
|
|
106
|
-
|
|
107
|
-
// itemB should no longer exist (orphaned and deleted by deleteConversation)
|
|
108
|
-
const itemBRow = (
|
|
109
|
-
getDb() as unknown as {
|
|
110
|
-
$client: import("bun:sqlite").Database;
|
|
111
|
-
}
|
|
112
|
-
).$client
|
|
113
|
-
.query("SELECT * FROM memory_items WHERE id = 'itemB'")
|
|
114
|
-
.get();
|
|
115
|
-
expect(itemBRow).toBeNull();
|
|
116
|
-
|
|
117
|
-
expect(result.unsupersededItemIds).toContain("itemA");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("does not restore superseded items when superseding item has other sources", async () => {
|
|
121
|
-
const convA = createConversation("conversation A");
|
|
122
|
-
const msgA = await addMessage(convA.id, "user", "I like red in A");
|
|
123
|
-
|
|
124
|
-
const convB = createConversation("conversation B");
|
|
125
|
-
const msgB = await addMessage(convB.id, "user", "I like red in B");
|
|
126
|
-
|
|
127
|
-
const db = getDb();
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
|
|
130
|
-
// Insert itemOld (will be superseded)
|
|
131
|
-
db.run(
|
|
132
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
133
|
-
VALUES ('itemOld', 'active', 'preference', 'color', 'likes blue', 0.8, 'fp-old', 'default', ${now}, ${now})`,
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
// Insert itemNew (supersedes itemOld)
|
|
137
|
-
db.run(
|
|
138
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, supersedes, first_seen_at, last_seen_at)
|
|
139
|
-
VALUES ('itemNew', 'active', 'preference', 'color', 'likes red', 0.9, 'fp-new', 'default', 'itemOld', ${now}, ${now})`,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// Mark itemOld as superseded
|
|
143
|
-
db.run(
|
|
144
|
-
`UPDATE memory_items SET status = 'superseded', superseded_by = 'itemNew' WHERE id = 'itemOld'`,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Link itemNew to BOTH conversations
|
|
148
|
-
db.run(
|
|
149
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemNew', '${msgA.id}', ${now})`,
|
|
150
|
-
);
|
|
151
|
-
db.run(
|
|
152
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemNew', '${msgB.id}', ${now})`,
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
wipeConversation(convA.id);
|
|
156
|
-
|
|
157
|
-
const raw = (
|
|
158
|
-
getDb() as unknown as {
|
|
159
|
-
$client: import("bun:sqlite").Database;
|
|
160
|
-
}
|
|
161
|
-
).$client;
|
|
162
|
-
|
|
163
|
-
// itemOld should still be superseded because itemNew has another source (convB)
|
|
164
|
-
const itemOldRow = raw
|
|
165
|
-
.query("SELECT status FROM memory_items WHERE id = 'itemOld'")
|
|
166
|
-
.get() as { status: string } | null;
|
|
167
|
-
expect(itemOldRow).not.toBeNull();
|
|
168
|
-
expect(itemOldRow!.status).toBe("superseded");
|
|
169
|
-
|
|
170
|
-
// itemNew should still exist (has source from convB)
|
|
171
|
-
const itemNewRow = raw
|
|
172
|
-
.query("SELECT * FROM memory_items WHERE id = 'itemNew'")
|
|
173
|
-
.get();
|
|
174
|
-
expect(itemNewRow).not.toBeNull();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("restores orphaned subject-match superseded items", async () => {
|
|
178
|
-
const convB = createConversation("conversation B");
|
|
179
|
-
const msgB = await addMessage(convB.id, "user", "I use vim");
|
|
180
|
-
|
|
181
|
-
const db = getDb();
|
|
182
|
-
const now = Date.now();
|
|
183
|
-
|
|
184
|
-
// Insert itemOld: superseded with no superseded_by link
|
|
185
|
-
db.run(
|
|
186
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
187
|
-
VALUES ('itemOld', 'superseded', 'preference', 'editor', 'uses emacs', 0.7, 'fp-old', 'default', ${now}, ${now})`,
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
// Insert itemNew: active, same kind/subject/scope_id
|
|
191
|
-
db.run(
|
|
192
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
193
|
-
VALUES ('itemNew', 'active', 'preference', 'editor', 'uses vim', 0.9, 'fp-new', 'default', ${now}, ${now})`,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Link itemNew to convB's message
|
|
197
|
-
db.run(
|
|
198
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemNew', '${msgB.id}', ${now})`,
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
wipeConversation(convB.id);
|
|
202
|
-
|
|
203
|
-
const raw = (
|
|
204
|
-
getDb() as unknown as {
|
|
205
|
-
$client: import("bun:sqlite").Database;
|
|
206
|
-
}
|
|
207
|
-
).$client;
|
|
208
|
-
|
|
209
|
-
// itemOld should now be active (restored as orphaned subject-match superseded item)
|
|
210
|
-
const itemOldRow = raw
|
|
211
|
-
.query("SELECT status FROM memory_items WHERE id = 'itemOld'")
|
|
212
|
-
.get() as { status: string } | null;
|
|
213
|
-
expect(itemOldRow).not.toBeNull();
|
|
214
|
-
expect(itemOldRow!.status).toBe("active");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("does not restore superseded items from unrelated conversations", async () => {
|
|
218
|
-
// convA has an item that superseded an older item — convA was previously
|
|
219
|
-
// deleted via regular deleteConversation, leaving the old item superseded
|
|
220
|
-
// with superseded_by = NULL. When we later wipe convB, Step F should NOT
|
|
221
|
-
// restore that unrelated item.
|
|
222
|
-
const convA = createConversation("conversation A");
|
|
223
|
-
const _msgA = await addMessage(convA.id, "user", "I use dark theme");
|
|
224
|
-
|
|
225
|
-
const convB = createConversation("conversation B");
|
|
226
|
-
const msgB = await addMessage(convB.id, "user", "I use vim");
|
|
227
|
-
|
|
228
|
-
const db = getDb();
|
|
229
|
-
const now = Date.now();
|
|
230
|
-
|
|
231
|
-
// unrelatedOld: superseded item from an old conversation (e.g. "uses light theme")
|
|
232
|
-
// Its superseder was deleted in a prior deleteConversation, leaving
|
|
233
|
-
// superseded_by = NULL.
|
|
234
|
-
db.run(
|
|
235
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
236
|
-
VALUES ('unrelatedOld', 'superseded', 'preference', 'theme', 'uses light theme', 0.7, 'fp-unrelated', 'default', ${now}, ${now})`,
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// convA's active item that superseded unrelatedOld — we simulate the
|
|
240
|
-
// case where convA was already deleted, leaving unrelatedOld with
|
|
241
|
-
// superseded_by = NULL and no active replacement.
|
|
242
|
-
// (We don't actually insert the superseder — just leave unrelatedOld
|
|
243
|
-
// as a superseded item with no superseded_by and no active match.)
|
|
244
|
-
|
|
245
|
-
// convB's items — itemOld is superseded by itemNew (subject: editor)
|
|
246
|
-
db.run(
|
|
247
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
248
|
-
VALUES ('editorOld', 'superseded', 'preference', 'editor', 'uses emacs', 0.7, 'fp-editor-old', 'default', ${now}, ${now})`,
|
|
249
|
-
);
|
|
250
|
-
db.run(
|
|
251
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
252
|
-
VALUES ('editorNew', 'active', 'preference', 'editor', 'uses vim', 0.9, 'fp-editor-new', 'default', ${now}, ${now})`,
|
|
253
|
-
);
|
|
254
|
-
db.run(
|
|
255
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('editorNew', '${msgB.id}', ${now})`,
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
const result = wipeConversation(convB.id);
|
|
259
|
-
|
|
260
|
-
const raw = (
|
|
261
|
-
getDb() as unknown as {
|
|
262
|
-
$client: import("bun:sqlite").Database;
|
|
263
|
-
}
|
|
264
|
-
).$client;
|
|
265
|
-
|
|
266
|
-
// editorOld SHOULD be restored (its kind+subject matches an orphaned item from convB)
|
|
267
|
-
const editorOldRow = raw
|
|
268
|
-
.query("SELECT status FROM memory_items WHERE id = 'editorOld'")
|
|
269
|
-
.get() as { status: string } | null;
|
|
270
|
-
expect(editorOldRow).not.toBeNull();
|
|
271
|
-
expect(editorOldRow!.status).toBe("active");
|
|
272
|
-
|
|
273
|
-
// unrelatedOld should NOT be restored — it was superseded by a different
|
|
274
|
-
// conversation's item (theme, not editor) and has nothing to do with convB
|
|
275
|
-
const unrelatedOldRow = raw
|
|
276
|
-
.query("SELECT status FROM memory_items WHERE id = 'unrelatedOld'")
|
|
277
|
-
.get() as { status: string } | null;
|
|
278
|
-
expect(unrelatedOldRow).not.toBeNull();
|
|
279
|
-
expect(unrelatedOldRow!.status).toBe("superseded");
|
|
280
|
-
|
|
281
|
-
// Only editorOld should be in the unsuperseded list, not unrelatedOld
|
|
282
|
-
expect(result.unsupersededItemIds).toContain("editorOld");
|
|
283
|
-
expect(result.unsupersededItemIds).not.toContain("unrelatedOld");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
50
|
test("deletes conversation summaries", async () => {
|
|
287
51
|
const conv = createConversation("test");
|
|
288
52
|
await addMessage(conv.id, "user", "hello");
|
|
@@ -330,7 +94,7 @@ describe("wipeConversation", () => {
|
|
|
330
94
|
|
|
331
95
|
test("cancels pending memory jobs", async () => {
|
|
332
96
|
const conv = createConversation("test");
|
|
333
|
-
|
|
97
|
+
await addMessage(conv.id, "user", "hello", undefined, {
|
|
334
98
|
skipIndexing: true,
|
|
335
99
|
});
|
|
336
100
|
|
|
@@ -338,7 +102,7 @@ describe("wipeConversation", () => {
|
|
|
338
102
|
const db = getDb();
|
|
339
103
|
db.run(`DELETE FROM memory_jobs`);
|
|
340
104
|
|
|
341
|
-
enqueueMemoryJob("
|
|
105
|
+
enqueueMemoryJob("graph_extract", { conversationId: conv.id });
|
|
342
106
|
enqueueMemoryJob("build_conversation_summary", {
|
|
343
107
|
conversationId: conv.id,
|
|
344
108
|
});
|
|
@@ -357,8 +121,6 @@ describe("wipeConversation", () => {
|
|
|
357
121
|
.all() as Array<{ status: string; last_error: string | null }>;
|
|
358
122
|
|
|
359
123
|
for (const job of jobs) {
|
|
360
|
-
// Skip embed_item jobs enqueued by wipeConversation's unsupersede logic
|
|
361
|
-
if (job.status === "pending") continue;
|
|
362
124
|
expect(job.status).toBe("failed");
|
|
363
125
|
expect(job.last_error).toContain("conversation_wiped");
|
|
364
126
|
}
|
|
@@ -373,57 +135,17 @@ describe("wipeConversation", () => {
|
|
|
373
135
|
|
|
374
136
|
expect(getConversation(conv.id)).toBeNull();
|
|
375
137
|
expect(result.segmentIds).toEqual([]);
|
|
376
|
-
expect(result.orphanedItemIds).toEqual([]);
|
|
377
|
-
expect(result.unsupersededItemIds).toEqual([]);
|
|
378
138
|
expect(result.deletedSummaryIds).toEqual([]);
|
|
379
139
|
expect(result.cancelledJobCount).toBe(0);
|
|
380
140
|
});
|
|
381
|
-
|
|
382
|
-
test("does not affect other conversations", async () => {
|
|
383
|
-
const convA = createConversation("conversation A");
|
|
384
|
-
await addMessage(convA.id, "user", "message in A");
|
|
385
|
-
|
|
386
|
-
const convB = createConversation("conversation B");
|
|
387
|
-
const msgB = await addMessage(convB.id, "user", "message in B");
|
|
388
|
-
|
|
389
|
-
const db = getDb();
|
|
390
|
-
const now = Date.now();
|
|
391
|
-
|
|
392
|
-
// Insert a memory item sourced from convB's message
|
|
393
|
-
db.run(
|
|
394
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
395
|
-
VALUES ('itemB', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-b', 'default', ${now}, ${now})`,
|
|
396
|
-
);
|
|
397
|
-
db.run(
|
|
398
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('itemB', '${msgB.id}', ${now})`,
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
wipeConversation(convA.id);
|
|
402
|
-
|
|
403
|
-
// convB should still exist
|
|
404
|
-
expect(getConversation(convB.id)).not.toBeNull();
|
|
405
|
-
expect(getMessages(convB.id)).toHaveLength(1);
|
|
406
|
-
|
|
407
|
-
// convB's memory item should still exist
|
|
408
|
-
const raw = (
|
|
409
|
-
getDb() as unknown as {
|
|
410
|
-
$client: import("bun:sqlite").Database;
|
|
411
|
-
}
|
|
412
|
-
).$client;
|
|
413
|
-
const itemBRow = raw
|
|
414
|
-
.query("SELECT * FROM memory_items WHERE id = 'itemB'")
|
|
415
|
-
.get();
|
|
416
|
-
expect(itemBRow).not.toBeNull();
|
|
417
|
-
});
|
|
418
141
|
});
|
|
419
142
|
|
|
420
143
|
describe("deleteConversation — private scope cleanup", () => {
|
|
421
144
|
beforeEach(() => {
|
|
422
145
|
const db = getDb();
|
|
423
146
|
db.run(`DELETE FROM conversation_starters`);
|
|
424
|
-
db.run(`DELETE FROM
|
|
147
|
+
db.run(`DELETE FROM memory_graph_nodes`);
|
|
425
148
|
db.run(`DELETE FROM memory_segments`);
|
|
426
|
-
db.run(`DELETE FROM memory_items`);
|
|
427
149
|
db.run(`DELETE FROM memory_summaries`);
|
|
428
150
|
db.run(`DELETE FROM memory_embeddings`);
|
|
429
151
|
db.run(`DELETE FROM memory_jobs`);
|
|
@@ -433,37 +155,6 @@ describe("deleteConversation — private scope cleanup", () => {
|
|
|
433
155
|
db.run(`DELETE FROM conversations`);
|
|
434
156
|
});
|
|
435
157
|
|
|
436
|
-
test("sourceless items cleaned up", () => {
|
|
437
|
-
const conv = createConversation({ conversationType: "private" });
|
|
438
|
-
const scopeId = conv.memoryScopeId;
|
|
439
|
-
const now = Date.now();
|
|
440
|
-
|
|
441
|
-
const raw = (
|
|
442
|
-
getDb() as unknown as {
|
|
443
|
-
$client: import("bun:sqlite").Database;
|
|
444
|
-
}
|
|
445
|
-
).$client;
|
|
446
|
-
|
|
447
|
-
// Insert a memory item with matching scopeId but no memory_item_sources
|
|
448
|
-
raw
|
|
449
|
-
.query(
|
|
450
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
451
|
-
VALUES ('priv-item-1', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-1', ?, ?, ?)`,
|
|
452
|
-
)
|
|
453
|
-
.run(scopeId, now, now);
|
|
454
|
-
|
|
455
|
-
const result = deleteConversation(conv.id);
|
|
456
|
-
|
|
457
|
-
// Item should be gone
|
|
458
|
-
const itemRow = raw
|
|
459
|
-
.query("SELECT * FROM memory_items WHERE id = 'priv-item-1'")
|
|
460
|
-
.get();
|
|
461
|
-
expect(itemRow).toBeNull();
|
|
462
|
-
|
|
463
|
-
// Its ID should be in orphanedItemIds
|
|
464
|
-
expect(result.orphanedItemIds).toContain("priv-item-1");
|
|
465
|
-
});
|
|
466
|
-
|
|
467
158
|
test("summaries cleaned up", () => {
|
|
468
159
|
const conv = createConversation({ conversationType: "private" });
|
|
469
160
|
const scopeId = conv.memoryScopeId;
|
|
@@ -496,71 +187,15 @@ describe("deleteConversation — private scope cleanup", () => {
|
|
|
496
187
|
});
|
|
497
188
|
|
|
498
189
|
test("standard conversations unaffected", async () => {
|
|
499
|
-
|
|
500
|
-
const
|
|
190
|
+
// Create a standard conversation and a private one
|
|
191
|
+
const standardConv = createConversation("standard test");
|
|
192
|
+
const privateConv = createConversation({ conversationType: "private" });
|
|
501
193
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
$client: import("bun:sqlite").Database;
|
|
505
|
-
}
|
|
506
|
-
).$client;
|
|
194
|
+
// Delete the private conversation
|
|
195
|
+
deleteConversation(privateConv.id);
|
|
507
196
|
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
.query(
|
|
511
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
512
|
-
VALUES ('default-item-1', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-default', 'default', ?, ?)`,
|
|
513
|
-
)
|
|
514
|
-
.run(now, now);
|
|
515
|
-
|
|
516
|
-
deleteConversation(conv.id);
|
|
517
|
-
|
|
518
|
-
// Default-scope items should still exist
|
|
519
|
-
const itemRow = raw
|
|
520
|
-
.query("SELECT * FROM memory_items WHERE id = 'default-item-1'")
|
|
521
|
-
.get();
|
|
522
|
-
expect(itemRow).not.toBeNull();
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
test("embeddings cleaned up", () => {
|
|
526
|
-
const conv = createConversation({ conversationType: "private" });
|
|
527
|
-
const scopeId = conv.memoryScopeId;
|
|
528
|
-
const now = Date.now();
|
|
529
|
-
|
|
530
|
-
const raw = (
|
|
531
|
-
getDb() as unknown as {
|
|
532
|
-
$client: import("bun:sqlite").Database;
|
|
533
|
-
}
|
|
534
|
-
).$client;
|
|
535
|
-
|
|
536
|
-
// Insert a memory item with matching scopeId
|
|
537
|
-
raw
|
|
538
|
-
.query(
|
|
539
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
540
|
-
VALUES ('priv-item-emb', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-emb', ?, ?, ?)`,
|
|
541
|
-
)
|
|
542
|
-
.run(scopeId, now, now);
|
|
543
|
-
|
|
544
|
-
// Insert a corresponding embedding
|
|
545
|
-
raw
|
|
546
|
-
.query(
|
|
547
|
-
`INSERT INTO memory_embeddings (id, target_type, target_id, provider, model, dimensions, created_at, updated_at)
|
|
548
|
-
VALUES ('emb-priv-item', 'item', 'priv-item-emb', 'test', 'test', 384, ?, ?)`,
|
|
549
|
-
)
|
|
550
|
-
.run(now, now);
|
|
551
|
-
|
|
552
|
-
deleteConversation(conv.id);
|
|
553
|
-
|
|
554
|
-
// Both item and embedding should be deleted
|
|
555
|
-
const itemRow = raw
|
|
556
|
-
.query("SELECT * FROM memory_items WHERE id = 'priv-item-emb'")
|
|
557
|
-
.get();
|
|
558
|
-
expect(itemRow).toBeNull();
|
|
559
|
-
|
|
560
|
-
const embeddingRow = raw
|
|
561
|
-
.query("SELECT * FROM memory_embeddings WHERE id = 'emb-priv-item'")
|
|
562
|
-
.get();
|
|
563
|
-
expect(embeddingRow).toBeNull();
|
|
197
|
+
// Standard conversation should still exist
|
|
198
|
+
expect(getConversation(standardConv.id)).not.toBeNull();
|
|
564
199
|
});
|
|
565
200
|
|
|
566
201
|
test("conversationStarters cleaned up", () => {
|
|
@@ -604,40 +239,4 @@ describe("deleteConversation — private scope cleanup", () => {
|
|
|
604
239
|
.get();
|
|
605
240
|
expect(defaultStarterRow).not.toBeNull();
|
|
606
241
|
});
|
|
607
|
-
|
|
608
|
-
test("no duplicate IDs", async () => {
|
|
609
|
-
const conv = createConversation({ conversationType: "private" });
|
|
610
|
-
const scopeId = conv.memoryScopeId;
|
|
611
|
-
const msg = await addMessage(conv.id, "user", "hello");
|
|
612
|
-
const now = Date.now();
|
|
613
|
-
|
|
614
|
-
const raw = (
|
|
615
|
-
getDb() as unknown as {
|
|
616
|
-
$client: import("bun:sqlite").Database;
|
|
617
|
-
}
|
|
618
|
-
).$client;
|
|
619
|
-
|
|
620
|
-
// Insert a memory item with the private scopeId AND a source linking to the message
|
|
621
|
-
raw
|
|
622
|
-
.query(
|
|
623
|
-
`INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
|
|
624
|
-
VALUES ('priv-item-dup', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-dup', ?, ?, ?)`,
|
|
625
|
-
)
|
|
626
|
-
.run(scopeId, now, now);
|
|
627
|
-
|
|
628
|
-
raw
|
|
629
|
-
.query(
|
|
630
|
-
`INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('priv-item-dup', ?, ?)`,
|
|
631
|
-
)
|
|
632
|
-
.run(msg.id, now);
|
|
633
|
-
|
|
634
|
-
const result = deleteConversation(conv.id);
|
|
635
|
-
|
|
636
|
-
// The item ID should appear exactly once in orphanedItemIds (caught by
|
|
637
|
-
// source-based cleanup, not double-counted by scope sweep).
|
|
638
|
-
const count = result.orphanedItemIds.filter(
|
|
639
|
-
(id) => id === "priv-item-dup",
|
|
640
|
-
).length;
|
|
641
|
-
expect(count).toBe(1);
|
|
642
|
-
});
|
|
643
242
|
});
|
|
@@ -91,7 +91,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
91
91
|
updateConversationUsage: () => {},
|
|
92
92
|
updateConversationTitle: () => {},
|
|
93
93
|
updateConversationContextWindow: () => {},
|
|
94
|
-
deleteMessageById: () => ({ segmentIds: [],
|
|
94
|
+
deleteMessageById: () => ({ segmentIds: [], deletedSummaryIds: [] }),
|
|
95
95
|
deleteLastExchange: () => 0,
|
|
96
96
|
}));
|
|
97
97
|
|
|
@@ -227,16 +227,13 @@ describe("Conversation workspace cache state", () => {
|
|
|
227
227
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
228
228
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
229
229
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
230
|
-
"<
|
|
230
|
+
"<workspace>",
|
|
231
231
|
);
|
|
232
232
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
233
|
-
"</
|
|
233
|
+
"</workspace>",
|
|
234
234
|
);
|
|
235
235
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
236
|
-
`Current conversation
|
|
237
|
-
);
|
|
238
|
-
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
239
|
-
`Attachment files: ${conversationAttachmentsPath}`,
|
|
236
|
+
`Current conversation attachments: ${conversationAttachmentsPath}`,
|
|
240
237
|
);
|
|
241
238
|
});
|
|
242
239
|
|
|
@@ -267,7 +264,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
267
264
|
|
|
268
265
|
expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
|
|
269
266
|
expect(conversation.getWorkspaceTopLevelContext()!).toContain(
|
|
270
|
-
"<
|
|
267
|
+
"<workspace>",
|
|
271
268
|
);
|
|
272
269
|
expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
|
|
273
270
|
});
|
|
@@ -286,10 +283,7 @@ describe("Conversation workspace cache state", () => {
|
|
|
286
283
|
tempConversation.refreshWorkspaceTopLevelContextIfNeeded();
|
|
287
284
|
|
|
288
285
|
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
289
|
-
`Current conversation
|
|
290
|
-
);
|
|
291
|
-
expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
|
|
292
|
-
`Attachment files: conversations/${legacyDirName}/attachments/`,
|
|
286
|
+
`Current conversation attachments: conversations/${legacyDirName}/attachments/`,
|
|
293
287
|
);
|
|
294
288
|
} finally {
|
|
295
289
|
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type { AgentEvent } from "../agent/loop.js";
|
|
4
|
-
import { getConversationDirName } from "../memory/conversation-disk-view.js";
|
|
5
4
|
import type { Message, ProviderResponse } from "../providers/types.js";
|
|
6
5
|
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
@@ -124,7 +123,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
124
123
|
updateConversationUsage: () => {},
|
|
125
124
|
updateConversationTitle: () => {},
|
|
126
125
|
updateConversationContextWindow: () => {},
|
|
127
|
-
deleteMessageById: () => ({ segmentIds: [],
|
|
126
|
+
deleteMessageById: () => ({ segmentIds: [], deletedSummaryIds: [] }),
|
|
128
127
|
deleteLastExchange: () => 0,
|
|
129
128
|
}));
|
|
130
129
|
|
|
@@ -274,13 +273,6 @@ function messageText(message: Message): string {
|
|
|
274
273
|
.join("\n");
|
|
275
274
|
}
|
|
276
275
|
|
|
277
|
-
const conversationDirName = getConversationDirName(
|
|
278
|
-
"conv-1",
|
|
279
|
-
Date.parse("2026-03-19T12:00:00.000Z"),
|
|
280
|
-
);
|
|
281
|
-
const conversationPath = `conversations/${conversationDirName}/`;
|
|
282
|
-
const conversationAttachmentsPath = `${conversationPath}attachments/`;
|
|
283
|
-
|
|
284
276
|
// ---------------------------------------------------------------------------
|
|
285
277
|
// Tests
|
|
286
278
|
// ---------------------------------------------------------------------------
|
|
@@ -302,8 +294,8 @@ describe("Conversation workspace injection", () => {
|
|
|
302
294
|
const runtimeUser = runCalls[0][runCalls[0].length - 1];
|
|
303
295
|
expect(runtimeUser.role).toBe("user");
|
|
304
296
|
const text = messageText(runtimeUser);
|
|
305
|
-
expect(text).toContain("<
|
|
306
|
-
expect(text).toContain("</
|
|
297
|
+
expect(text).toContain("<workspace>");
|
|
298
|
+
expect(text).toContain("</workspace>");
|
|
307
299
|
});
|
|
308
300
|
|
|
309
301
|
test("workspace context includes root path and directories", async () => {
|
|
@@ -316,8 +308,8 @@ describe("Conversation workspace injection", () => {
|
|
|
316
308
|
const runtimeUser = runCalls[0][runCalls[0].length - 1];
|
|
317
309
|
const text = messageText(runtimeUser);
|
|
318
310
|
expect(text).toContain("Root: /tmp");
|
|
319
|
-
expect(text).toContain(
|
|
320
|
-
expect(text).toContain(
|
|
311
|
+
expect(text).toContain("Directories: src, tests, docs");
|
|
312
|
+
expect(text).toContain("Files: README.md, package.json");
|
|
321
313
|
});
|
|
322
314
|
|
|
323
315
|
test("workspace context is prepended before user text", async () => {
|
|
@@ -331,33 +323,40 @@ describe("Conversation workspace injection", () => {
|
|
|
331
323
|
const firstBlock = runtimeUser.content[0];
|
|
332
324
|
expect(firstBlock.type).toBe("text");
|
|
333
325
|
const firstText = (firstBlock as { type: "text"; text: string }).text;
|
|
334
|
-
expect(firstText).toContain("<
|
|
326
|
+
expect(firstText).toContain("<workspace>");
|
|
335
327
|
});
|
|
336
328
|
|
|
337
|
-
test("workspace context
|
|
329
|
+
test("workspace context persists in history (not stripped between turns)", async () => {
|
|
338
330
|
const conversation = makeConversation();
|
|
339
331
|
await conversation.loadFromDb();
|
|
340
332
|
|
|
341
333
|
await conversation.processMessage("Hello", [], () => {});
|
|
342
334
|
|
|
335
|
+
// Workspace blocks use <workspace> tag which is intentionally NOT stripped.
|
|
343
336
|
const persistedMessages = conversation.getMessages();
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
337
|
+
const userMsg = persistedMessages.find((m) => m.role === "user");
|
|
338
|
+
expect(userMsg).toBeDefined();
|
|
339
|
+
const text = messageText(userMsg!);
|
|
340
|
+
expect(text).toContain("<workspace>");
|
|
348
341
|
});
|
|
349
342
|
|
|
350
|
-
test("
|
|
343
|
+
test("second message does NOT include workspace block", async () => {
|
|
351
344
|
const conversation = makeConversation();
|
|
352
345
|
await conversation.loadFromDb();
|
|
353
346
|
|
|
347
|
+
// First message — workspace is injected
|
|
354
348
|
await conversation.processMessage("Hello", [], () => {});
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
349
|
+
expect(runCalls).toHaveLength(1);
|
|
350
|
+
const firstCallUser = runCalls[0][runCalls[0].length - 1];
|
|
351
|
+
const firstText = messageText(firstCallUser);
|
|
352
|
+
expect(firstText).toContain("<workspace>");
|
|
353
|
+
|
|
354
|
+
// Second message — workspace is NOT injected (not first message, no compaction)
|
|
355
|
+
await conversation.processMessage("Follow up", [], () => {});
|
|
356
|
+
expect(runCalls).toHaveLength(2);
|
|
357
|
+
const secondCallUser = runCalls[1][runCalls[1].length - 1];
|
|
358
|
+
const secondText = messageText(secondCallUser);
|
|
359
|
+
expect(secondText).not.toContain("<workspace>");
|
|
361
360
|
});
|
|
362
361
|
});
|
|
363
362
|
|
|
@@ -120,7 +120,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
120
120
|
updateConversationUsage: () => {},
|
|
121
121
|
updateConversationTitle: () => {},
|
|
122
122
|
updateConversationContextWindow: () => {},
|
|
123
|
-
deleteMessageById: () => ({ segmentIds: [],
|
|
123
|
+
deleteMessageById: () => ({ segmentIds: [], deletedSummaryIds: [] }),
|
|
124
124
|
deleteLastExchange: () => 0,
|
|
125
125
|
}));
|
|
126
126
|
|