@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { injectedConceptHeader } from "../../../memory/v2/injected-block-slugs.js";
|
|
2
|
+
import {
|
|
3
|
+
FRONTMATTER_REGEX,
|
|
4
|
+
parseFrontmatterFields,
|
|
5
|
+
} from "../../../skills/frontmatter.js";
|
|
6
|
+
import type { Slug } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compact card renderer for memory-v3: a page's head section (the `# Title`
|
|
10
|
+
* line plus lead paragraphs, everything before the first `## ` heading) plus a
|
|
11
|
+
* one-line section TOC. Cards are the compact injection unit — they carry
|
|
12
|
+
* enough signal to act on (or `file_read` the full page) at a fraction of the
|
|
13
|
+
* full-page byte cost.
|
|
14
|
+
*
|
|
15
|
+
* The `# memory/concepts/<slug>.md` header (shared builder:
|
|
16
|
+
* `injectedConceptHeader` in `memory/v2/injected-block-slugs.ts`) matches the
|
|
17
|
+
* v2 memory-block page convention, so the existing `file_read` affordance
|
|
18
|
+
* instruction applies to cards unchanged.
|
|
19
|
+
*
|
|
20
|
+
* Pure text → text: no I/O, no LLM calls. Callers load the raw page text via
|
|
21
|
+
* the existing page-store helpers (see `page-content.ts`).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Max characters of a `links:` entry note carried into the `[linked: …]`
|
|
25
|
+
* line. Notes longer than this are truncated with an ellipsis. */
|
|
26
|
+
const LINK_NOTE_MAX_CHARS = 80;
|
|
27
|
+
|
|
28
|
+
/** Matches a `## ` section heading line; capture group is the heading text. */
|
|
29
|
+
const SECTION_HEADING_REGEX = /^## (.*)$/m;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Render one `links:` frontmatter entry (`"<target-slug> — <note>"`) for the
|
|
33
|
+
* `[linked: …]` TOC line, truncating the note at {@link LINK_NOTE_MAX_CHARS}.
|
|
34
|
+
* Entries with no ` — ` separator are bare target slugs and pass through.
|
|
35
|
+
*/
|
|
36
|
+
function renderLinkEntry(entry: string): string {
|
|
37
|
+
const sep = entry.indexOf(" — ");
|
|
38
|
+
if (sep === -1) return entry.trim();
|
|
39
|
+
const target = entry.slice(0, sep).trim();
|
|
40
|
+
let note = entry.slice(sep + " — ".length).trim();
|
|
41
|
+
if (note.length > LINK_NOTE_MAX_CHARS) {
|
|
42
|
+
note = `${note.slice(0, LINK_NOTE_MAX_CHARS).trimEnd()}…`;
|
|
43
|
+
}
|
|
44
|
+
return note.length > 0 ? `${target} — ${note}` : target;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build the one-line TOC for a card.
|
|
49
|
+
*
|
|
50
|
+
* Index pages (`kind: index` frontmatter) render their `links:` map instead of
|
|
51
|
+
* section names — index sections have generic names ("Pages", "See also");
|
|
52
|
+
* the link map is the real signal. All other pages (including an index with no
|
|
53
|
+
* usable `links:`) render their `## ` heading names. Returns `null` when there
|
|
54
|
+
* is nothing to list (the TOC line is omitted).
|
|
55
|
+
*/
|
|
56
|
+
function renderTocLine(
|
|
57
|
+
body: string,
|
|
58
|
+
fields: Record<string, unknown>,
|
|
59
|
+
): string | null {
|
|
60
|
+
if (fields.kind === "index" && Array.isArray(fields.links)) {
|
|
61
|
+
const entries = fields.links
|
|
62
|
+
.filter((entry): entry is string => typeof entry === "string")
|
|
63
|
+
.map(renderLinkEntry)
|
|
64
|
+
.filter((entry) => entry.length > 0);
|
|
65
|
+
if (entries.length > 0) return `[linked: ${entries.join(" · ")}]`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const headings = [...body.matchAll(new RegExp(SECTION_HEADING_REGEX, "gm"))]
|
|
69
|
+
.map((match) => match[1]!.trim())
|
|
70
|
+
.filter((heading) => heading.length > 0);
|
|
71
|
+
if (headings.length === 0) return null;
|
|
72
|
+
return `[sections: ${headings.map((h) => `§${h}`).join(" · ")}]`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render a page's compact card: header marker, head section (uncapped — the
|
|
77
|
+
* corpus's rare long leads inject whole; a length cap is a deliberate
|
|
78
|
+
* non-feature), and one-line TOC.
|
|
79
|
+
*
|
|
80
|
+
* ```
|
|
81
|
+
* # memory/concepts/<slug>.md
|
|
82
|
+
* <head section verbatim>
|
|
83
|
+
*
|
|
84
|
+
* [sections: §… · §…]
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* The TOC line is omitted when the page has no `## ` sections and no usable
|
|
88
|
+
* `links:`. Deterministic for a given (slug, rawPageText).
|
|
89
|
+
*/
|
|
90
|
+
export function renderCard(slug: Slug, rawPageText: string): string {
|
|
91
|
+
const parsed = parseFrontmatterFields(rawPageText);
|
|
92
|
+
// `parseFrontmatterFields` returns null both for "no frontmatter block" and
|
|
93
|
+
// "block present but YAML failed to parse" — strip the block either way so a
|
|
94
|
+
// malformed page never leaks raw frontmatter into the card head.
|
|
95
|
+
const body = parsed
|
|
96
|
+
? parsed.body
|
|
97
|
+
: rawPageText.replace(FRONTMATTER_REGEX, "");
|
|
98
|
+
const fields = parsed?.fields ?? {};
|
|
99
|
+
|
|
100
|
+
const firstHeading = SECTION_HEADING_REGEX.exec(body);
|
|
101
|
+
const head = (firstHeading ? body.slice(0, firstHeading.index) : body).trim();
|
|
102
|
+
|
|
103
|
+
let card = injectedConceptHeader(slug);
|
|
104
|
+
if (head.length > 0) card += `\n${head}`;
|
|
105
|
+
|
|
106
|
+
const toc = renderTocLine(body, fields);
|
|
107
|
+
if (toc !== null) card += `\n\n${toc}`;
|
|
108
|
+
|
|
109
|
+
return card;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** UTF-8 byte length of a rendered card (prune-valve and footprint
|
|
113
|
+
* accounting both budget in bytes, not characters). */
|
|
114
|
+
export function cardBytes(card: string): number {
|
|
115
|
+
return Buffer.byteLength(card, "utf8");
|
|
116
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import { loadCoreSet } from "./core-set.js";
|
|
7
|
+
|
|
8
|
+
let workspaceDir: string;
|
|
9
|
+
|
|
10
|
+
function writeCorePages(content: string): void {
|
|
11
|
+
mkdirSync(join(workspaceDir, "memory"), { recursive: true });
|
|
12
|
+
writeFileSync(join(workspaceDir, "memory", "core-pages.md"), content);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "core-set-test-"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("loadCoreSet", () => {
|
|
24
|
+
test("missing file yields []", () => {
|
|
25
|
+
expect(loadCoreSet(workspaceDir)).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("accepts both wikilink and bare-slug list forms", () => {
|
|
29
|
+
writeCorePages(["- [[page-a]]", "- page-b"].join("\n"));
|
|
30
|
+
expect(loadCoreSet(workspaceDir)).toEqual(["page-a", "page-b"]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("accepts inline annotations after the slug", () => {
|
|
34
|
+
writeCorePages(
|
|
35
|
+
[
|
|
36
|
+
"- [[page-a]] — register frame, never matches lexically",
|
|
37
|
+
"- [[page-b]] keep while the project is live",
|
|
38
|
+
"- page-c — calibration rules",
|
|
39
|
+
"- page-d – en-dash annotation",
|
|
40
|
+
"- page-e - hyphen annotation",
|
|
41
|
+
].join("\n"),
|
|
42
|
+
);
|
|
43
|
+
expect(loadCoreSet(workspaceDir)).toEqual([
|
|
44
|
+
"page-a",
|
|
45
|
+
"page-b",
|
|
46
|
+
"page-c",
|
|
47
|
+
"page-d",
|
|
48
|
+
"page-e",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("ignores headings, blank lines, and prose annotations", () => {
|
|
53
|
+
writeCorePages(
|
|
54
|
+
[
|
|
55
|
+
"# Core pages",
|
|
56
|
+
"",
|
|
57
|
+
"Curated during consolidation; keep this list short.",
|
|
58
|
+
"- [[page-a]]",
|
|
59
|
+
"",
|
|
60
|
+
"## Identity frames",
|
|
61
|
+
"These never match lexically but should always be candidates.",
|
|
62
|
+
"- page-b",
|
|
63
|
+
].join("\n"),
|
|
64
|
+
);
|
|
65
|
+
expect(loadCoreSet(workspaceDir)).toEqual(["page-a", "page-b"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("dedupes while preserving first-seen order", () => {
|
|
69
|
+
writeCorePages(
|
|
70
|
+
["- page-b", "- [[page-a]]", "- [[page-b]]", "- page-a", "- page-c"].join(
|
|
71
|
+
"\n",
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
expect(loadCoreSet(workspaceDir)).toEqual(["page-b", "page-a", "page-c"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("skips entries outside the slug-safe charset", () => {
|
|
78
|
+
writeCorePages(
|
|
79
|
+
[
|
|
80
|
+
"- [[Page A]]",
|
|
81
|
+
"- page_b",
|
|
82
|
+
"- [[topics/page-a]]",
|
|
83
|
+
"- UPPER-CASE",
|
|
84
|
+
"- page-b",
|
|
85
|
+
"- [[]]",
|
|
86
|
+
].join("\n"),
|
|
87
|
+
);
|
|
88
|
+
expect(loadCoreSet(workspaceDir)).toEqual(["topics/page-a", "page-b"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("malformed list lines are skipped, not fatal", () => {
|
|
92
|
+
writeCorePages(
|
|
93
|
+
[
|
|
94
|
+
"-",
|
|
95
|
+
"-no-space",
|
|
96
|
+
"- [[unclosed",
|
|
97
|
+
"- two words here",
|
|
98
|
+
"- prose that mentions — a dash mid-sentence",
|
|
99
|
+
"- page-a",
|
|
100
|
+
].join("\n"),
|
|
101
|
+
);
|
|
102
|
+
expect(loadCoreSet(workspaceDir)).toEqual(["page-a"]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { Slug } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Workspace-relative path of the maintainer-curated core-set file. Edited
|
|
8
|
+
* during memory consolidation — the loader never creates or modifies it.
|
|
9
|
+
*/
|
|
10
|
+
const CORE_PAGES_RELATIVE_PATH = join("memory", "core-pages.md");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A list line is `- <item>`; the item may be a bare slug or a `[[slug]]`
|
|
14
|
+
* wikilink, optionally followed by an inline annotation. After a wikilink the
|
|
15
|
+
* annotation is free-form (`- [[some-slug]] — why this matters`); after a
|
|
16
|
+
* bare slug it must be introduced by a dash (`- some-slug — why this
|
|
17
|
+
* matters`), so a multi-word prose line (`- two words here`) is still read as
|
|
18
|
+
* the maintainer's notes, not a slug. Anything else on the line (headings,
|
|
19
|
+
* prose) is ignored.
|
|
20
|
+
*/
|
|
21
|
+
const LIST_LINE = /^-\s+(?:\[\[([^\]]+)\]\](?:\s+.*)?|(\S+)(?:\s+[—–-].*)?)$/;
|
|
22
|
+
|
|
23
|
+
/** Slug-safe charset; existence against the page store is checked at lane init. */
|
|
24
|
+
const SLUG_SHAPE = /^[a-z0-9-/]+$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load the maintainer-curated core set from `<workspaceDir>/memory/core-pages.md`.
|
|
28
|
+
*
|
|
29
|
+
* The core lane answers the associative-texture gap (pages with no
|
|
30
|
+
* lexical/semantic match to the message), so its membership is curated, not
|
|
31
|
+
* computed. Parsing is tolerant: list lines in `- [[some-slug]]` or
|
|
32
|
+
* `- some-slug` form are accepted, with optional inline annotations (see
|
|
33
|
+
* {@link LIST_LINE}); headings, blank lines, and prose are skipped so the
|
|
34
|
+
* maintainer can annotate the file; malformed or non-slug-shaped entries are
|
|
35
|
+
* dropped, never fatal.
|
|
36
|
+
*
|
|
37
|
+
* Returns slugs deduped in first-seen file order — that order is the stable
|
|
38
|
+
* sort the selector uses for the core prefix. A missing file yields `[]`.
|
|
39
|
+
*/
|
|
40
|
+
export function loadCoreSet(workspaceDir: string): Slug[] {
|
|
41
|
+
let raw: string;
|
|
42
|
+
try {
|
|
43
|
+
raw = readFileSync(join(workspaceDir, CORE_PAGES_RELATIVE_PATH), "utf8");
|
|
44
|
+
} catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const seen = new Set<Slug>();
|
|
49
|
+
const slugs: Slug[] = [];
|
|
50
|
+
for (const line of raw.split("\n")) {
|
|
51
|
+
const match = LIST_LINE.exec(line.trim());
|
|
52
|
+
if (!match) continue;
|
|
53
|
+
const slug = (match[1] ?? match[2] ?? "").trim();
|
|
54
|
+
if (!SLUG_SHAPE.test(slug) || seen.has(slug)) continue;
|
|
55
|
+
seen.add(slug);
|
|
56
|
+
slugs.push(slug);
|
|
57
|
+
}
|
|
58
|
+
return slugs;
|
|
59
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `ever-injected-store.ts` — memory-v3's per-conversation
|
|
3
|
+
* everInjected record:
|
|
4
|
+
* - record/get/active-set round-trip and re-record clearing `pruned_at`;
|
|
5
|
+
* - `markPruned` excluding rows from the active set and `residentBytes`;
|
|
6
|
+
* - `clearConversation` (compaction reset);
|
|
7
|
+
* - fork hooks: full-row copy (pruned state included) and truncated-fork
|
|
8
|
+
* seeding (`bytes = 0`, dedup-only);
|
|
9
|
+
* - migration idempotence (run twice).
|
|
10
|
+
*
|
|
11
|
+
* `mock.module` is process-global and leaks into sibling files in a directory
|
|
12
|
+
* run, so the db-connection stub DELEGATES to the real implementation unless
|
|
13
|
+
* this test is actively running (`storeMockActive`, toggled in
|
|
14
|
+
* beforeEach/afterAll). Mirrors `__tests__/selection-log-store.test.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Database } from "bun:sqlite";
|
|
18
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
21
|
+
|
|
22
|
+
import { migrateAddMemoryV3EverInjected } from "../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
23
|
+
import * as schema from "../../../memory/schema.js";
|
|
24
|
+
|
|
25
|
+
const realDb = { ...(await import("../../../memory/db-connection.js")) };
|
|
26
|
+
|
|
27
|
+
let storeMockActive = false;
|
|
28
|
+
|
|
29
|
+
let testSqlite: Database;
|
|
30
|
+
let testDb = makeDb();
|
|
31
|
+
function makeDb() {
|
|
32
|
+
testSqlite = new Database(":memory:");
|
|
33
|
+
const db = drizzle(testSqlite, { schema });
|
|
34
|
+
migrateAddMemoryV3EverInjected(db);
|
|
35
|
+
return db;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
mock.module("../../../memory/db-connection.js", () => ({
|
|
39
|
+
...realDb,
|
|
40
|
+
getDb: () => (storeMockActive ? testDb : realDb.getDb()),
|
|
41
|
+
getSqliteFrom: (db: unknown) =>
|
|
42
|
+
storeMockActive
|
|
43
|
+
? testSqlite
|
|
44
|
+
: realDb.getSqliteFrom(db as Parameters<typeof realDb.getSqliteFrom>[0]),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
clearConversation,
|
|
49
|
+
forkEverInjected,
|
|
50
|
+
getActiveSlugs,
|
|
51
|
+
getInjected,
|
|
52
|
+
getPrunedSlugs,
|
|
53
|
+
markPruned,
|
|
54
|
+
MEMORY_V3_INJECTED_BLOCK_METADATA_KEY,
|
|
55
|
+
recordInjected,
|
|
56
|
+
residentBytes,
|
|
57
|
+
seedEverInjectedFromSlugs,
|
|
58
|
+
} = await import("./ever-injected-store.js");
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
storeMockActive = true;
|
|
62
|
+
testDb = makeDb();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
storeMockActive = false;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("metadata key constant", () => {
|
|
70
|
+
test("exports the v3 injected-block metadata key", () => {
|
|
71
|
+
expect(MEMORY_V3_INJECTED_BLOCK_METADATA_KEY).toBe("memoryV3InjectedBlock");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("recordInjected / getInjected / getActiveSlugs", () => {
|
|
76
|
+
test("round-trips the recorded entries", () => {
|
|
77
|
+
recordInjected(
|
|
78
|
+
"conv-1",
|
|
79
|
+
[
|
|
80
|
+
{ slug: "topics/page-a", bytes: 100 },
|
|
81
|
+
{ slug: "topics/page-b", bytes: 250 },
|
|
82
|
+
],
|
|
83
|
+
1_000,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(getInjected("conv-1")).toEqual(
|
|
87
|
+
new Map([
|
|
88
|
+
["topics/page-a", { bytes: 100, prunedAt: null }],
|
|
89
|
+
["topics/page-b", { bytes: 250, prunedAt: null }],
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
expect(getActiveSlugs("conv-1")).toEqual(
|
|
93
|
+
new Set(["topics/page-a", "topics/page-b"]),
|
|
94
|
+
);
|
|
95
|
+
// Other conversations see nothing.
|
|
96
|
+
expect(getInjected("conv-other").size).toBe(0);
|
|
97
|
+
expect(getActiveSlugs("conv-other").size).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("re-recording a pruned slug clears pruned_at and refreshes bytes", () => {
|
|
101
|
+
recordInjected("conv-1", [{ slug: "topics/page-a", bytes: 100 }], 1_000);
|
|
102
|
+
markPruned("conv-1", ["topics/page-a"], 2_000);
|
|
103
|
+
expect(getInjected("conv-1").get("topics/page-a")).toEqual({
|
|
104
|
+
bytes: 100,
|
|
105
|
+
prunedAt: 2_000,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
recordInjected("conv-1", [{ slug: "topics/page-a", bytes: 140 }], 3_000);
|
|
109
|
+
|
|
110
|
+
expect(getInjected("conv-1").get("topics/page-a")).toEqual({
|
|
111
|
+
bytes: 140,
|
|
112
|
+
prunedAt: null,
|
|
113
|
+
});
|
|
114
|
+
expect(getActiveSlugs("conv-1")).toEqual(new Set(["topics/page-a"]));
|
|
115
|
+
const row = testSqlite
|
|
116
|
+
.query(
|
|
117
|
+
"SELECT injected_at FROM memory_v3_ever_injected WHERE conversation_id = ? AND slug = ?",
|
|
118
|
+
)
|
|
119
|
+
.get("conv-1", "topics/page-a") as { injected_at: number };
|
|
120
|
+
expect(row.injected_at).toBe(3_000);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("empty entries are a no-op", () => {
|
|
124
|
+
recordInjected("conv-1", []);
|
|
125
|
+
expect(getInjected("conv-1").size).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("markPruned / residentBytes", () => {
|
|
130
|
+
test("pruned rows leave the active set and resident bytes but stay on record", () => {
|
|
131
|
+
recordInjected(
|
|
132
|
+
"conv-1",
|
|
133
|
+
[
|
|
134
|
+
{ slug: "topics/page-a", bytes: 100 },
|
|
135
|
+
{ slug: "topics/page-b", bytes: 250 },
|
|
136
|
+
{ slug: "topics/page-c", bytes: 50 },
|
|
137
|
+
],
|
|
138
|
+
1_000,
|
|
139
|
+
);
|
|
140
|
+
expect(residentBytes("conv-1")).toBe(400);
|
|
141
|
+
|
|
142
|
+
markPruned("conv-1", ["topics/page-a", "topics/page-c"], 2_000);
|
|
143
|
+
|
|
144
|
+
expect(getActiveSlugs("conv-1")).toEqual(new Set(["topics/page-b"]));
|
|
145
|
+
expect(residentBytes("conv-1")).toBe(250);
|
|
146
|
+
// Rows are never deleted — the record stays auditable.
|
|
147
|
+
expect([...getInjected("conv-1").keys()].sort()).toEqual([
|
|
148
|
+
"topics/page-a",
|
|
149
|
+
"topics/page-b",
|
|
150
|
+
"topics/page-c",
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("empty slug list is a no-op and residentBytes is 0 for unknown conversations", () => {
|
|
155
|
+
recordInjected("conv-1", [{ slug: "topics/page-a", bytes: 100 }], 1_000);
|
|
156
|
+
markPruned("conv-1", [], 2_000);
|
|
157
|
+
expect(residentBytes("conv-1")).toBe(100);
|
|
158
|
+
expect(residentBytes("conv-unknown")).toBe(0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("clearConversation", () => {
|
|
163
|
+
test("deletes all rows for the conversation only", () => {
|
|
164
|
+
recordInjected("conv-1", [{ slug: "topics/page-a", bytes: 100 }], 1_000);
|
|
165
|
+
recordInjected("conv-2", [{ slug: "topics/page-b", bytes: 200 }], 1_000);
|
|
166
|
+
|
|
167
|
+
clearConversation("conv-1");
|
|
168
|
+
|
|
169
|
+
expect(getInjected("conv-1").size).toBe(0);
|
|
170
|
+
expect(getInjected("conv-2").size).toBe(1);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("forkEverInjected", () => {
|
|
175
|
+
test("copies the parent's full record, pruned state included", () => {
|
|
176
|
+
recordInjected(
|
|
177
|
+
"conv-parent",
|
|
178
|
+
[
|
|
179
|
+
{ slug: "topics/page-a", bytes: 100 },
|
|
180
|
+
{ slug: "topics/page-b", bytes: 250 },
|
|
181
|
+
],
|
|
182
|
+
1_000,
|
|
183
|
+
);
|
|
184
|
+
markPruned("conv-parent", ["topics/page-b"], 2_000);
|
|
185
|
+
|
|
186
|
+
forkEverInjected(testDb, "conv-parent", "conv-child");
|
|
187
|
+
|
|
188
|
+
expect(getInjected("conv-child")).toEqual(
|
|
189
|
+
new Map([
|
|
190
|
+
["topics/page-a", { bytes: 100, prunedAt: null }],
|
|
191
|
+
["topics/page-b", { bytes: 250, prunedAt: 2_000 }],
|
|
192
|
+
]),
|
|
193
|
+
);
|
|
194
|
+
expect(residentBytes("conv-child")).toBe(100);
|
|
195
|
+
// Parent record is untouched.
|
|
196
|
+
expect(getInjected("conv-parent").size).toBe(2);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("is a no-op when the parent has no rows", () => {
|
|
200
|
+
forkEverInjected(testDb, "conv-empty", "conv-child");
|
|
201
|
+
expect(getInjected("conv-child").size).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("seedEverInjectedFromSlugs", () => {
|
|
206
|
+
test("seeds dedup-only rows with bytes = 0 stamped at the given time", () => {
|
|
207
|
+
seedEverInjectedFromSlugs(
|
|
208
|
+
testDb,
|
|
209
|
+
"conv-parent",
|
|
210
|
+
"conv-child",
|
|
211
|
+
["topics/page-a", "topics/page-b"],
|
|
212
|
+
5_000,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(getInjected("conv-child")).toEqual(
|
|
216
|
+
new Map([
|
|
217
|
+
["topics/page-a", { bytes: 0, prunedAt: null }],
|
|
218
|
+
["topics/page-b", { bytes: 0, prunedAt: null }],
|
|
219
|
+
]),
|
|
220
|
+
);
|
|
221
|
+
expect(getActiveSlugs("conv-child")).toEqual(
|
|
222
|
+
new Set(["topics/page-a", "topics/page-b"]),
|
|
223
|
+
);
|
|
224
|
+
// Inherited cards carry no byte accounting — resident accounting
|
|
225
|
+
// restarts from the fork's own injections.
|
|
226
|
+
expect(residentBytes("conv-child")).toBe(0);
|
|
227
|
+
const row = testSqlite
|
|
228
|
+
.query(
|
|
229
|
+
"SELECT injected_at FROM memory_v3_ever_injected WHERE conversation_id = ? AND slug = ?",
|
|
230
|
+
)
|
|
231
|
+
.get("conv-child", "topics/page-a") as { injected_at: number };
|
|
232
|
+
expect(row.injected_at).toBe(5_000);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("carries the parent's pruned_at tombstones for inherited slugs", () => {
|
|
236
|
+
// Parent injected both pages, then pruned page-a: the metadata block the
|
|
237
|
+
// child inherits still contains page-a's section, so the fork scan seeds
|
|
238
|
+
// both slugs — but page-a must arrive tombstoned, not active, or the
|
|
239
|
+
// child's rehydration would resurrect a card the parent's live view lost.
|
|
240
|
+
recordInjected(
|
|
241
|
+
"conv-parent",
|
|
242
|
+
[
|
|
243
|
+
{ slug: "topics/page-a", bytes: 100 },
|
|
244
|
+
{ slug: "topics/page-b", bytes: 200 },
|
|
245
|
+
],
|
|
246
|
+
1_000,
|
|
247
|
+
);
|
|
248
|
+
markPruned("conv-parent", ["topics/page-a"], 2_000);
|
|
249
|
+
|
|
250
|
+
seedEverInjectedFromSlugs(
|
|
251
|
+
testDb,
|
|
252
|
+
"conv-parent",
|
|
253
|
+
"conv-child",
|
|
254
|
+
["topics/page-a", "topics/page-b"],
|
|
255
|
+
5_000,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
expect(getInjected("conv-child")).toEqual(
|
|
259
|
+
new Map([
|
|
260
|
+
["topics/page-a", { bytes: 0, prunedAt: 2_000 }],
|
|
261
|
+
["topics/page-b", { bytes: 0, prunedAt: null }],
|
|
262
|
+
]),
|
|
263
|
+
);
|
|
264
|
+
expect(getActiveSlugs("conv-child")).toEqual(new Set(["topics/page-b"]));
|
|
265
|
+
expect(getPrunedSlugs("conv-child")).toEqual(new Set(["topics/page-a"]));
|
|
266
|
+
|
|
267
|
+
// Re-selection clears the inherited tombstone, same as in the parent.
|
|
268
|
+
recordInjected("conv-child", [{ slug: "topics/page-a", bytes: 50 }], 6_000);
|
|
269
|
+
expect(getActiveSlugs("conv-child")).toEqual(
|
|
270
|
+
new Set(["topics/page-a", "topics/page-b"]),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("is a no-op for an empty slug list and never overwrites existing rows", () => {
|
|
275
|
+
seedEverInjectedFromSlugs(testDb, "conv-parent", "conv-child", [], 5_000);
|
|
276
|
+
expect(getInjected("conv-child").size).toBe(0);
|
|
277
|
+
|
|
278
|
+
recordInjected(
|
|
279
|
+
"conv-child",
|
|
280
|
+
[{ slug: "topics/page-a", bytes: 100 }],
|
|
281
|
+
1_000,
|
|
282
|
+
);
|
|
283
|
+
seedEverInjectedFromSlugs(
|
|
284
|
+
testDb,
|
|
285
|
+
"conv-parent",
|
|
286
|
+
"conv-child",
|
|
287
|
+
["topics/page-a"],
|
|
288
|
+
5_000,
|
|
289
|
+
);
|
|
290
|
+
expect(getInjected("conv-child").get("topics/page-a")).toEqual({
|
|
291
|
+
bytes: 100,
|
|
292
|
+
prunedAt: null,
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("migration", () => {
|
|
298
|
+
test("is idempotent — running twice leaves a usable table", () => {
|
|
299
|
+
// makeDb() already ran the migration once; run it again.
|
|
300
|
+
migrateAddMemoryV3EverInjected(testDb);
|
|
301
|
+
|
|
302
|
+
recordInjected("conv-1", [{ slug: "topics/page-a", bytes: 100 }], 1_000);
|
|
303
|
+
expect(getActiveSlugs("conv-1")).toEqual(new Set(["topics/page-a"]));
|
|
304
|
+
});
|
|
305
|
+
});
|