@vellumai/assistant 0.8.10 → 0.8.11-staging.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/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -193,6 +193,19 @@ mock.module("../skills/version-hash.js", () => ({
|
|
|
193
193
|
},
|
|
194
194
|
}));
|
|
195
195
|
|
|
196
|
+
// Mock the skill_loaded telemetry dependencies of conversation-skill-tools so
|
|
197
|
+
// their heavy transitive imports (catalog-install → CLI program, sqlite) stay
|
|
198
|
+
// out of this import-light test.
|
|
199
|
+
mock.module("../skills/catalog-cache.js", () => ({
|
|
200
|
+
getCachedCatalogSync: () => [],
|
|
201
|
+
}));
|
|
202
|
+
mock.module("../skills/install-meta.js", () => ({
|
|
203
|
+
readInstallMeta: () => null,
|
|
204
|
+
}));
|
|
205
|
+
mock.module("../memory/skill-loaded-events-store.js", () => ({
|
|
206
|
+
recordSkillLoadedEvent: () => {},
|
|
207
|
+
}));
|
|
208
|
+
|
|
196
209
|
// ---------------------------------------------------------------------------
|
|
197
210
|
// Imports under test (after mocks)
|
|
198
211
|
// ---------------------------------------------------------------------------
|
|
@@ -63,6 +63,12 @@ import {
|
|
|
63
63
|
toolInvocations,
|
|
64
64
|
} from "../memory/schema.js";
|
|
65
65
|
import { hydrate as hydrateActivationState } from "../memory/v2/activation-store.js";
|
|
66
|
+
import {
|
|
67
|
+
getInjected as getV3Injected,
|
|
68
|
+
markPruned as markV3Pruned,
|
|
69
|
+
MEMORY_V3_INJECTED_BLOCK_METADATA_KEY,
|
|
70
|
+
recordInjected as recordV3Injected,
|
|
71
|
+
} from "../plugins/defaults/memory-v3-shadow/ever-injected-store.js";
|
|
66
72
|
|
|
67
73
|
initializeDb();
|
|
68
74
|
|
|
@@ -77,6 +83,7 @@ function resetTables(): void {
|
|
|
77
83
|
db.delete(llmRequestLogs).run();
|
|
78
84
|
db.delete(toolInvocations).run();
|
|
79
85
|
db.delete(memoryJobs).run();
|
|
86
|
+
db.run("DELETE FROM memory_v3_ever_injected");
|
|
80
87
|
db.run("DELETE FROM message_attachments");
|
|
81
88
|
db.run("DELETE FROM attachments");
|
|
82
89
|
db.run("DELETE FROM messages");
|
|
@@ -922,6 +929,258 @@ describe("forkConversation", () => {
|
|
|
922
929
|
expect(loadGraphMemoryState(fork.id)).toBeNull();
|
|
923
930
|
});
|
|
924
931
|
|
|
932
|
+
test("truncated fork seeds everInjected from inherited memory attachments", async () => {
|
|
933
|
+
const source = createConversation("Truncated seed thread");
|
|
934
|
+
await addMessage(source.id, "user", "first turn", {
|
|
935
|
+
metadata: {
|
|
936
|
+
memoryInjectedBlock:
|
|
937
|
+
"# memory/concepts/topics/page-a.md\nSummary A\n\n# memory/concepts/topics/page-b.md\nSummary B",
|
|
938
|
+
},
|
|
939
|
+
skipIndexing: true,
|
|
940
|
+
});
|
|
941
|
+
await addMessage(source.id, "assistant", "first reply", {
|
|
942
|
+
skipIndexing: true,
|
|
943
|
+
});
|
|
944
|
+
const boundaryMessage = await addMessage(source.id, "user", "second turn", {
|
|
945
|
+
metadata: {
|
|
946
|
+
memoryInjectedBlock: "# memory/concepts/topics/page-c.md\nSummary C",
|
|
947
|
+
},
|
|
948
|
+
skipIndexing: true,
|
|
949
|
+
});
|
|
950
|
+
await addMessage(source.id, "assistant", "second reply", {
|
|
951
|
+
skipIndexing: true,
|
|
952
|
+
});
|
|
953
|
+
// Past the fork boundary — its attachment must NOT be claimed.
|
|
954
|
+
await addMessage(source.id, "user", "third turn", {
|
|
955
|
+
metadata: {
|
|
956
|
+
memoryInjectedBlock: "# memory/concepts/topics/page-d.md\nSummary D",
|
|
957
|
+
},
|
|
958
|
+
skipIndexing: true,
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
const db = getDb();
|
|
962
|
+
db.insert(activationState)
|
|
963
|
+
.values({
|
|
964
|
+
conversationId: source.id,
|
|
965
|
+
messageId: "parent-msg",
|
|
966
|
+
stateJson: JSON.stringify({ "topics/page-d": 0.9 }),
|
|
967
|
+
everInjectedJson: JSON.stringify([
|
|
968
|
+
{ slug: "topics/page-a", turn: 1 },
|
|
969
|
+
{ slug: "topics/page-b", turn: 1 },
|
|
970
|
+
{ slug: "topics/page-c", turn: 2 },
|
|
971
|
+
{ slug: "topics/page-d", turn: 3 },
|
|
972
|
+
]),
|
|
973
|
+
currentTurn: 3,
|
|
974
|
+
updatedAt: 1_700_000_000_000,
|
|
975
|
+
})
|
|
976
|
+
.run();
|
|
977
|
+
|
|
978
|
+
const fork = forkConversation({
|
|
979
|
+
conversationId: source.id,
|
|
980
|
+
throughMessageId: boundaryMessage.id,
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
const childState = await hydrateActivationState(db, fork.id);
|
|
984
|
+
expect(childState).not.toBeNull();
|
|
985
|
+
// Exactly the slugs whose attachments live in the copied history —
|
|
986
|
+
// page-d (injected past the boundary) stays re-injectable.
|
|
987
|
+
expect(childState?.everInjected.map((e) => e.slug).sort()).toEqual([
|
|
988
|
+
"topics/page-a",
|
|
989
|
+
"topics/page-b",
|
|
990
|
+
"topics/page-c",
|
|
991
|
+
]);
|
|
992
|
+
expect(childState?.everInjected.every((e) => e.turn === 0)).toBe(true);
|
|
993
|
+
// Activation scores and the turn counter start fresh, matching the
|
|
994
|
+
// graph tracker (also not copied for truncated forks).
|
|
995
|
+
expect(childState?.state).toEqual({});
|
|
996
|
+
expect(childState?.currentTurn).toBe(0);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test("truncated fork ignores attachments behind an inherited compaction boundary", async () => {
|
|
1000
|
+
const source = createConversation("Compacted truncated thread");
|
|
1001
|
+
await addMessage(source.id, "user", "compacted turn", {
|
|
1002
|
+
metadata: {
|
|
1003
|
+
memoryInjectedBlock:
|
|
1004
|
+
"# memory/concepts/topics/page-compacted.md\nOld summary",
|
|
1005
|
+
},
|
|
1006
|
+
skipIndexing: true,
|
|
1007
|
+
});
|
|
1008
|
+
await addMessage(source.id, "assistant", "compacted reply", {
|
|
1009
|
+
skipIndexing: true,
|
|
1010
|
+
});
|
|
1011
|
+
const boundaryMessage = await addMessage(source.id, "user", "live turn", {
|
|
1012
|
+
metadata: {
|
|
1013
|
+
memoryInjectedBlock: "# memory/concepts/topics/page-live.md\nSummary",
|
|
1014
|
+
},
|
|
1015
|
+
skipIndexing: true,
|
|
1016
|
+
});
|
|
1017
|
+
await addMessage(source.id, "assistant", "live reply", {
|
|
1018
|
+
skipIndexing: true,
|
|
1019
|
+
});
|
|
1020
|
+
await addMessage(source.id, "user", "past boundary", {
|
|
1021
|
+
skipIndexing: true,
|
|
1022
|
+
});
|
|
1023
|
+
// First two messages sit behind the compaction boundary: their
|
|
1024
|
+
// attachments are not rendered, so the fork must not claim them.
|
|
1025
|
+
getDb()
|
|
1026
|
+
.update(conversations)
|
|
1027
|
+
.set({ contextCompactedMessageCount: 2 })
|
|
1028
|
+
.where(eq(conversations.id, source.id))
|
|
1029
|
+
.run();
|
|
1030
|
+
|
|
1031
|
+
const fork = forkConversation({
|
|
1032
|
+
conversationId: source.id,
|
|
1033
|
+
throughMessageId: boundaryMessage.id,
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
const childState = await hydrateActivationState(getDb(), fork.id);
|
|
1037
|
+
expect(childState?.everInjected.map((e) => e.slug)).toEqual([
|
|
1038
|
+
"topics/page-live",
|
|
1039
|
+
]);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test("copies the parent's memory-v3 everInjected record into a full fork", async () => {
|
|
1043
|
+
const source = createConversation("V3 carry thread");
|
|
1044
|
+
await addMessage(source.id, "user", "first turn", { skipIndexing: true });
|
|
1045
|
+
|
|
1046
|
+
recordV3Injected(
|
|
1047
|
+
source.id,
|
|
1048
|
+
[
|
|
1049
|
+
{ slug: "topics/page-a", bytes: 120 },
|
|
1050
|
+
{ slug: "topics/page-b", bytes: 340 },
|
|
1051
|
+
],
|
|
1052
|
+
1_700_000_000_000,
|
|
1053
|
+
);
|
|
1054
|
+
markV3Pruned(source.id, ["topics/page-b"], 1_700_000_001_000);
|
|
1055
|
+
|
|
1056
|
+
const fork = forkConversation({ conversationId: source.id });
|
|
1057
|
+
|
|
1058
|
+
// Full-row copy, pruned state included.
|
|
1059
|
+
expect(getV3Injected(fork.id)).toEqual(
|
|
1060
|
+
new Map([
|
|
1061
|
+
["topics/page-a", { bytes: 120, prunedAt: null }],
|
|
1062
|
+
["topics/page-b", { bytes: 340, prunedAt: 1_700_000_001_000 }],
|
|
1063
|
+
]),
|
|
1064
|
+
);
|
|
1065
|
+
// Parent record is untouched.
|
|
1066
|
+
expect(getV3Injected(source.id).size).toBe(2);
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
test("leaves the fork's memory-v3 record empty when the parent has none", async () => {
|
|
1070
|
+
const source = createConversation("V3 pristine thread");
|
|
1071
|
+
await addMessage(source.id, "user", "first message", {
|
|
1072
|
+
skipIndexing: true,
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
const fork = forkConversation({ conversationId: source.id });
|
|
1076
|
+
|
|
1077
|
+
expect(getV3Injected(fork.id).size).toBe(0);
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
test("truncated fork seeds the memory-v3 record from inherited v3 card blocks", async () => {
|
|
1081
|
+
const source = createConversation("V3 truncated seed thread");
|
|
1082
|
+
await addMessage(source.id, "user", "first turn", {
|
|
1083
|
+
metadata: {
|
|
1084
|
+
[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY]:
|
|
1085
|
+
"# memory/concepts/topics/page-a.md\nCard A\n\n# memory/concepts/topics/page-b.md\nCard B",
|
|
1086
|
+
// A v2 block on the same message must seed only the v2 record.
|
|
1087
|
+
memoryInjectedBlock: "# memory/concepts/topics/page-v2.md\nSummary",
|
|
1088
|
+
},
|
|
1089
|
+
skipIndexing: true,
|
|
1090
|
+
});
|
|
1091
|
+
await addMessage(source.id, "assistant", "first reply", {
|
|
1092
|
+
skipIndexing: true,
|
|
1093
|
+
});
|
|
1094
|
+
const boundaryMessage = await addMessage(source.id, "user", "second turn", {
|
|
1095
|
+
skipIndexing: true,
|
|
1096
|
+
});
|
|
1097
|
+
await addMessage(source.id, "assistant", "second reply", {
|
|
1098
|
+
skipIndexing: true,
|
|
1099
|
+
});
|
|
1100
|
+
// Past the fork boundary — its card must NOT be claimed.
|
|
1101
|
+
await addMessage(source.id, "user", "third turn", {
|
|
1102
|
+
metadata: {
|
|
1103
|
+
[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY]:
|
|
1104
|
+
"# memory/concepts/topics/page-c.md\nCard C",
|
|
1105
|
+
},
|
|
1106
|
+
skipIndexing: true,
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
recordV3Injected(
|
|
1110
|
+
source.id,
|
|
1111
|
+
[
|
|
1112
|
+
{ slug: "topics/page-a", bytes: 120 },
|
|
1113
|
+
{ slug: "topics/page-b", bytes: 340 },
|
|
1114
|
+
{ slug: "topics/page-c", bytes: 90 },
|
|
1115
|
+
],
|
|
1116
|
+
1_700_000_000_000,
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
const fork = forkConversation({
|
|
1120
|
+
conversationId: source.id,
|
|
1121
|
+
throughMessageId: boundaryMessage.id,
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
// Exactly the slugs whose card blocks live in the copied history,
|
|
1125
|
+
// dedup-only (`bytes = 0` — resident accounting restarts on the child).
|
|
1126
|
+
expect(getV3Injected(fork.id)).toEqual(
|
|
1127
|
+
new Map([
|
|
1128
|
+
["topics/page-a", { bytes: 0, prunedAt: null }],
|
|
1129
|
+
["topics/page-b", { bytes: 0, prunedAt: null }],
|
|
1130
|
+
]),
|
|
1131
|
+
);
|
|
1132
|
+
// The v2 seed picked up only the v2 block, not the v3 cards.
|
|
1133
|
+
const childState = await hydrateActivationState(getDb(), fork.id);
|
|
1134
|
+
expect(childState?.everInjected.map((e) => e.slug)).toEqual([
|
|
1135
|
+
"topics/page-v2",
|
|
1136
|
+
]);
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
test("truncated fork carries the parent's pruned tombstones for inherited v3 slugs", async () => {
|
|
1140
|
+
// Pruning never rewrites the persisted metadata block, so the fork scan
|
|
1141
|
+
// sees pruned cards' sections too — the seed must tombstone them, or the
|
|
1142
|
+
// child's rehydration would resurrect cards the parent's live view lost.
|
|
1143
|
+
const source = createConversation("V3 truncated pruned thread");
|
|
1144
|
+
await addMessage(source.id, "user", "first turn", {
|
|
1145
|
+
metadata: {
|
|
1146
|
+
[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY]:
|
|
1147
|
+
"# memory/concepts/topics/page-a.md\nCard A\n\n# memory/concepts/topics/page-b.md\nCard B",
|
|
1148
|
+
},
|
|
1149
|
+
skipIndexing: true,
|
|
1150
|
+
});
|
|
1151
|
+
const boundaryMessage = await addMessage(
|
|
1152
|
+
source.id,
|
|
1153
|
+
"assistant",
|
|
1154
|
+
"first reply",
|
|
1155
|
+
{ skipIndexing: true },
|
|
1156
|
+
);
|
|
1157
|
+
// Past the fork boundary — forces the truncated-seed path (a fork through
|
|
1158
|
+
// the final message takes the wholesale-copy path instead).
|
|
1159
|
+
await addMessage(source.id, "user", "second turn", { skipIndexing: true });
|
|
1160
|
+
|
|
1161
|
+
recordV3Injected(
|
|
1162
|
+
source.id,
|
|
1163
|
+
[
|
|
1164
|
+
{ slug: "topics/page-a", bytes: 120 },
|
|
1165
|
+
{ slug: "topics/page-b", bytes: 340 },
|
|
1166
|
+
],
|
|
1167
|
+
1_700_000_000_000,
|
|
1168
|
+
);
|
|
1169
|
+
markV3Pruned(source.id, ["topics/page-b"], 1_700_000_005_000);
|
|
1170
|
+
|
|
1171
|
+
const fork = forkConversation({
|
|
1172
|
+
conversationId: source.id,
|
|
1173
|
+
throughMessageId: boundaryMessage.id,
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
expect(getV3Injected(fork.id)).toEqual(
|
|
1177
|
+
new Map([
|
|
1178
|
+
["topics/page-a", { bytes: 0, prunedAt: null }],
|
|
1179
|
+
["topics/page-b", { bytes: 0, prunedAt: 1_700_000_005_000 }],
|
|
1180
|
+
]),
|
|
1181
|
+
);
|
|
1182
|
+
});
|
|
1183
|
+
|
|
925
1184
|
test("defaults conversationType to standard and inherits the parent's group", async () => {
|
|
926
1185
|
const source = createConversation("Default inheritance thread");
|
|
927
1186
|
await addMessage(source.id, "user", "first message", {
|
|
@@ -812,7 +812,7 @@ describe("web_search_tool_result structural guard", () => {
|
|
|
812
812
|
// Downgrades permanently-unsendable images nested in tool_result.contentBlocks
|
|
813
813
|
// (e.g. an oversized browser screenshot). web_search_tool_result has opaque
|
|
814
814
|
// content with no contentBlocks property, so it cannot hold such an image.
|
|
815
|
-
"
|
|
815
|
+
"plugins/defaults/image-recovery/recover.ts",
|
|
816
816
|
|
|
817
817
|
// Final orphan-pair safety pass in the Slack transcript renderer.
|
|
818
818
|
// Server-side block types (`server_tool_use`, `web_search_tool_result`)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
// Stub out heavy dependencies before importing Conversation
|
|
4
4
|
mock.module("../util/logger.js", () => ({
|
|
@@ -110,8 +110,38 @@ mock.module("../memory/conversation-queries.js", () => ({
|
|
|
110
110
|
listConversations: () => [],
|
|
111
111
|
}));
|
|
112
112
|
|
|
113
|
+
// `loadFromDb` reads the conversation's pruned v3 card slugs (prune valve)
|
|
114
|
+
// when a row carries a `memoryV3InjectedBlock`. Stub the store read so these
|
|
115
|
+
// tests never touch a real DB; the stub DELEGATES to the real implementation
|
|
116
|
+
// unless this file is actively running (`mock.module` is process-global and
|
|
117
|
+
// would otherwise leak into sibling files that use the real store).
|
|
118
|
+
const realEverInjectedStore = {
|
|
119
|
+
...(await import("../plugins/defaults/memory-v3-shadow/ever-injected-store.js")),
|
|
120
|
+
};
|
|
121
|
+
let lifecycleStoreMockActive = false;
|
|
122
|
+
let mockPrunedSlugs = new Set<string>();
|
|
123
|
+
mock.module(
|
|
124
|
+
"../plugins/defaults/memory-v3-shadow/ever-injected-store.js",
|
|
125
|
+
() => ({
|
|
126
|
+
...realEverInjectedStore,
|
|
127
|
+
getPrunedSlugs: (conversationId: string) =>
|
|
128
|
+
lifecycleStoreMockActive
|
|
129
|
+
? mockPrunedSlugs
|
|
130
|
+
: realEverInjectedStore.getPrunedSlugs(conversationId),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
113
134
|
import { Conversation } from "../daemon/conversation.js";
|
|
114
135
|
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
lifecycleStoreMockActive = true;
|
|
138
|
+
mockPrunedSlugs = new Set();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
afterAll(() => {
|
|
142
|
+
lifecycleStoreMockActive = false;
|
|
143
|
+
});
|
|
144
|
+
|
|
115
145
|
function makeConversation(): Conversation {
|
|
116
146
|
const provider = {
|
|
117
147
|
name: "mock",
|
|
@@ -346,6 +376,156 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
346
376
|
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
347
377
|
});
|
|
348
378
|
|
|
379
|
+
test("memoryV3InjectedBlock rehydrates wrapped on ALL rows (tail included)", async () => {
|
|
380
|
+
// The memory-v3 frozen card block persists UNWRAPPED under its own key and
|
|
381
|
+
// must come back wrapped on every row — including the tail, since the next
|
|
382
|
+
// turn injects only NET-NEW cards (deduped via the v3 store) and never
|
|
383
|
+
// re-renders this row's cards.
|
|
384
|
+
mockConversation = defaultConv();
|
|
385
|
+
mockDbMessages = [
|
|
386
|
+
{
|
|
387
|
+
id: "m1",
|
|
388
|
+
role: "user",
|
|
389
|
+
content: JSON.stringify([{ type: "text", text: "First turn" }]),
|
|
390
|
+
metadata: JSON.stringify({
|
|
391
|
+
memoryV3InjectedBlock:
|
|
392
|
+
"header line\n\n# memory/concepts/page-a.md\nhead a",
|
|
393
|
+
}),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "m2",
|
|
397
|
+
role: "assistant",
|
|
398
|
+
content: JSON.stringify([{ type: "text", text: "Reply" }]),
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "m3",
|
|
402
|
+
role: "user",
|
|
403
|
+
content: JSON.stringify([{ type: "text", text: "Tail turn" }]),
|
|
404
|
+
metadata: JSON.stringify({
|
|
405
|
+
memoryV3InjectedBlock: "# memory/concepts/page-b.md\nhead b",
|
|
406
|
+
}),
|
|
407
|
+
},
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
const conversation = makeConversation();
|
|
411
|
+
await conversation.loadFromDb();
|
|
412
|
+
const messages = conversation.getMessages();
|
|
413
|
+
|
|
414
|
+
expect(messages).toHaveLength(3);
|
|
415
|
+
expect(messages[0].content).toEqual([
|
|
416
|
+
{
|
|
417
|
+
type: "text",
|
|
418
|
+
text: "<memory>\nheader line\n\n# memory/concepts/page-a.md\nhead a\n</memory>",
|
|
419
|
+
},
|
|
420
|
+
{ type: "text", text: "First turn" },
|
|
421
|
+
]);
|
|
422
|
+
// Tail row rehydrates too (unlike turn_context / system_reminder).
|
|
423
|
+
expect(messages[2].content).toEqual([
|
|
424
|
+
{
|
|
425
|
+
type: "text",
|
|
426
|
+
text: "<memory>\n# memory/concepts/page-b.md\nhead b\n</memory>",
|
|
427
|
+
},
|
|
428
|
+
{ type: "text", text: "Tail turn" },
|
|
429
|
+
]);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("pruned slugs' card sections are skipped at v3 rehydration (prune valve persistence)", async () => {
|
|
433
|
+
// The prune valve marks cards pruned in the everInjected store instead of
|
|
434
|
+
// rewriting the persisted metadata; the rehydration splice re-filters on
|
|
435
|
+
// every load, which is what makes a prune survive restarts.
|
|
436
|
+
mockConversation = defaultConv();
|
|
437
|
+
mockPrunedSlugs = new Set(["page-a"]);
|
|
438
|
+
mockDbMessages = [
|
|
439
|
+
{
|
|
440
|
+
id: "m1",
|
|
441
|
+
role: "user",
|
|
442
|
+
content: JSON.stringify([{ type: "text", text: "First turn" }]),
|
|
443
|
+
metadata: JSON.stringify({
|
|
444
|
+
memoryV3InjectedBlock:
|
|
445
|
+
"header line\n\n# memory/concepts/page-a.md\nhead a\n\n# memory/concepts/page-b.md\nhead b",
|
|
446
|
+
}),
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: "m2",
|
|
450
|
+
role: "assistant",
|
|
451
|
+
content: JSON.stringify([{ type: "text", text: "Reply" }]),
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
const conversation = makeConversation();
|
|
456
|
+
await conversation.loadFromDb();
|
|
457
|
+
const messages = conversation.getMessages();
|
|
458
|
+
|
|
459
|
+
expect(messages[0].content).toEqual([
|
|
460
|
+
{
|
|
461
|
+
type: "text",
|
|
462
|
+
text: "<memory>\nheader line\n\n# memory/concepts/page-b.md\nhead b\n</memory>",
|
|
463
|
+
},
|
|
464
|
+
{ type: "text", text: "First turn" },
|
|
465
|
+
]);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("a fully-pruned memoryV3InjectedBlock is skipped entirely at rehydration", async () => {
|
|
469
|
+
mockConversation = defaultConv();
|
|
470
|
+
mockPrunedSlugs = new Set(["page-a"]);
|
|
471
|
+
mockDbMessages = [
|
|
472
|
+
{
|
|
473
|
+
id: "m1",
|
|
474
|
+
role: "user",
|
|
475
|
+
content: JSON.stringify([{ type: "text", text: "First turn" }]),
|
|
476
|
+
metadata: JSON.stringify({
|
|
477
|
+
memoryV3InjectedBlock:
|
|
478
|
+
"header line\n\n# memory/concepts/page-a.md\nhead a",
|
|
479
|
+
}),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "m2",
|
|
483
|
+
role: "assistant",
|
|
484
|
+
content: JSON.stringify([{ type: "text", text: "Reply" }]),
|
|
485
|
+
},
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
const conversation = makeConversation();
|
|
489
|
+
await conversation.loadFromDb();
|
|
490
|
+
const messages = conversation.getMessages();
|
|
491
|
+
|
|
492
|
+
// No memory block at all — an instruction header with zero cards carries
|
|
493
|
+
// no content (matches the live strip, which removes the emptied block).
|
|
494
|
+
expect(messages[0].content).toEqual([{ type: "text", text: "First turn" }]);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("defensively-wrapped memoryV3InjectedBlock rehydrates singly-wrapped", async () => {
|
|
498
|
+
mockConversation = defaultConv();
|
|
499
|
+
mockDbMessages = [
|
|
500
|
+
{
|
|
501
|
+
id: "m1",
|
|
502
|
+
role: "user",
|
|
503
|
+
content: JSON.stringify([{ type: "text", text: "Hi" }]),
|
|
504
|
+
metadata: JSON.stringify({
|
|
505
|
+
memoryV3InjectedBlock:
|
|
506
|
+
"<memory>\n# memory/concepts/page-a.md\nhead a\n</memory>",
|
|
507
|
+
}),
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "m2",
|
|
511
|
+
role: "assistant",
|
|
512
|
+
content: JSON.stringify([{ type: "text", text: "Hello" }]),
|
|
513
|
+
},
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
const conversation = makeConversation();
|
|
517
|
+
await conversation.loadFromDb();
|
|
518
|
+
const messages = conversation.getMessages();
|
|
519
|
+
|
|
520
|
+
const firstBlock = messages[0].content[0];
|
|
521
|
+
expect(firstBlock).toEqual({
|
|
522
|
+
type: "text",
|
|
523
|
+
text: "<memory>\n# memory/concepts/page-a.md\nhead a\n</memory>",
|
|
524
|
+
});
|
|
525
|
+
if (firstBlock.type !== "text") throw new Error("unexpected block type");
|
|
526
|
+
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
349
529
|
test("malformed metadata is tolerated: load does not throw, content unchanged", async () => {
|
|
350
530
|
mockConversation = defaultConv();
|
|
351
531
|
mockDbMessages = [
|
|
@@ -606,6 +786,82 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
606
786
|
]);
|
|
607
787
|
});
|
|
608
788
|
|
|
789
|
+
test("rehydration order matches live assembly when memoryV2StaticBlock and memoryV3InjectedBlock co-occur", async () => {
|
|
790
|
+
// First-turn and first-post-compaction rows of a memory-v3-live
|
|
791
|
+
// conversation persist BOTH `memoryV2StaticBlock` and the v3 card block.
|
|
792
|
+
// Live assembly applies after-memory-prefix splices in ascending
|
|
793
|
+
// injector order (pkb-context 30, pkb-reminder 35, memory-v2-static 38,
|
|
794
|
+
// now-md 40, memory-v3-shadow 1000); each splice lands at the
|
|
795
|
+
// memory-prefix boundary (`countMemoryPrefixBlocks` counts `<memory>`
|
|
796
|
+
// AND `<info>` blocks), so the v3 block — spliced last and itself
|
|
797
|
+
// `<memory>`-wrapped — lands AFTER the `<info>` static block but before
|
|
798
|
+
// now-md's earlier splice:
|
|
799
|
+
// [<workspace>, <turn_context>, <memory>dynamic</memory>,
|
|
800
|
+
// <info>v2static</info>, <memory>v3cards</memory>, <NOW.md>,
|
|
801
|
+
// <system_reminder>, <knowledge_base>, ...original]
|
|
802
|
+
// Rehydration must reproduce this byte-for-byte or every daemon restart
|
|
803
|
+
// busts the provider prefix cache for the whole conversation history.
|
|
804
|
+
// (`memoryInjectedBlock` and the v3 key are mutually exclusive on real
|
|
805
|
+
// rows; both are included here to pin the relative order of all splices.)
|
|
806
|
+
mockConversation = defaultConv();
|
|
807
|
+
mockDbMessages = [
|
|
808
|
+
{
|
|
809
|
+
id: "m1",
|
|
810
|
+
role: "user",
|
|
811
|
+
content: JSON.stringify([{ type: "text", text: "First turn" }]),
|
|
812
|
+
metadata: JSON.stringify({
|
|
813
|
+
workspaceBlock: "<workspace>\nworkspace body\n</workspace>",
|
|
814
|
+
turnContextBlock: "<turn_context>\nctx payload\n</turn_context>",
|
|
815
|
+
memoryInjectedBlock: "mem payload",
|
|
816
|
+
memoryV2StaticBlock:
|
|
817
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
818
|
+
memoryV3InjectedBlock:
|
|
819
|
+
"header line\n\n# memory/concepts/page-a.md\nhead a",
|
|
820
|
+
nowScratchpadBlock: "<NOW.md>\nnow body\n</NOW.md>",
|
|
821
|
+
pkbSystemReminderBlock:
|
|
822
|
+
"<system_reminder>\npkb reminder body\n</system_reminder>",
|
|
823
|
+
pkbContextBlock: "<knowledge_base>\nkb body\n</knowledge_base>",
|
|
824
|
+
}),
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
id: "m2",
|
|
828
|
+
role: "assistant",
|
|
829
|
+
content: JSON.stringify([{ type: "text", text: "Reply" }]),
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
id: "m3",
|
|
833
|
+
role: "user",
|
|
834
|
+
content: JSON.stringify([{ type: "text", text: "Tail" }]),
|
|
835
|
+
},
|
|
836
|
+
];
|
|
837
|
+
|
|
838
|
+
const conversation = makeConversation();
|
|
839
|
+
await conversation.loadFromDb();
|
|
840
|
+
const messages = conversation.getMessages();
|
|
841
|
+
|
|
842
|
+
expect(messages).toHaveLength(3);
|
|
843
|
+
expect(messages[0].content).toEqual([
|
|
844
|
+
{ type: "text", text: "<workspace>\nworkspace body\n</workspace>" },
|
|
845
|
+
{ type: "text", text: "<turn_context>\nctx payload\n</turn_context>" },
|
|
846
|
+
{ type: "text", text: "<memory>\nmem payload\n</memory>" },
|
|
847
|
+
{
|
|
848
|
+
type: "text",
|
|
849
|
+
text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
type: "text",
|
|
853
|
+
text: "<memory>\nheader line\n\n# memory/concepts/page-a.md\nhead a\n</memory>",
|
|
854
|
+
},
|
|
855
|
+
{ type: "text", text: "<NOW.md>\nnow body\n</NOW.md>" },
|
|
856
|
+
{
|
|
857
|
+
type: "text",
|
|
858
|
+
text: "<system_reminder>\npkb reminder body\n</system_reminder>",
|
|
859
|
+
},
|
|
860
|
+
{ type: "text", text: "<knowledge_base>\nkb body\n</knowledge_base>" },
|
|
861
|
+
{ type: "text", text: "First turn" },
|
|
862
|
+
]);
|
|
863
|
+
});
|
|
864
|
+
|
|
609
865
|
test("untrusted-actor view does not rehydrate memoryV2StaticBlock", async () => {
|
|
610
866
|
mockConversation = defaultConv();
|
|
611
867
|
// Rows with `trusted_contact` / `unknown` provenance survive the
|