@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
|
@@ -28,8 +28,15 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
28
28
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
29
29
|
|
|
30
30
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
31
|
+
import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
31
32
|
import * as schema from "../../../../memory/schema.js";
|
|
32
|
-
import type {
|
|
33
|
+
import type { HotSetEntry, HotSetOptions } from "../hot-set.js";
|
|
34
|
+
import type { OrchestrateResult } from "../orchestrate.js";
|
|
35
|
+
import {
|
|
36
|
+
MEMORY_V3_COMMIT_META_KEY,
|
|
37
|
+
type SectionIndex,
|
|
38
|
+
type SelectionSource,
|
|
39
|
+
} from "../types.js";
|
|
33
40
|
|
|
34
41
|
// `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
|
|
35
42
|
// re-mock in `afterAll` reverts it for files that load LATER in the same
|
|
@@ -62,6 +69,8 @@ const realSkillStore = {
|
|
|
62
69
|
const realCliCommandStore = {
|
|
63
70
|
...(await import("../../../../memory/v2/cli-command-store.js")),
|
|
64
71
|
};
|
|
72
|
+
const realCoreSet = { ...(await import("../core-set.js")) };
|
|
73
|
+
const realHotSet = { ...(await import("../hot-set.js")) };
|
|
65
74
|
|
|
66
75
|
let shadowMockActive = false;
|
|
67
76
|
|
|
@@ -69,6 +78,7 @@ let shadowMockActive = false;
|
|
|
69
78
|
|
|
70
79
|
let liveEnabled = false;
|
|
71
80
|
let shadowEnabled = false;
|
|
81
|
+
let memoryEnabled = true;
|
|
72
82
|
let messages: Array<{ role: string; content: string }> = [];
|
|
73
83
|
|
|
74
84
|
// A synthetic skill capability slug the page index carries. Its rendered
|
|
@@ -77,30 +87,36 @@ let messages: Array<{ role: string; content: string }> = [];
|
|
|
77
87
|
const CAPABILITY_SLUG = "skills/example";
|
|
78
88
|
const CAPABILITY_CONTENT = "use the kumquat skill to do the thing";
|
|
79
89
|
|
|
80
|
-
// The orchestrate result the spy returns. `
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// for the slugs that had one (page-1/page-2) — consumed by the
|
|
85
|
-
// progressive disclosure, independent of source attribution.
|
|
86
|
-
const orchestrateSpy = mock(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
90
|
+
// The orchestrate result the spy returns. `lanes` records where each pooled
|
|
91
|
+
// slug lived: page-core in the core lane, page-hot in the hot lane, and the
|
|
92
|
+
// finder entries page-1 → "needle", page-2 → "dense", page-3 → "edge";
|
|
93
|
+
// `attributeSelections` reads it directly. `matchedSections` carries the
|
|
94
|
+
// matched section for the slugs that had one (page-1/page-2) — consumed by the
|
|
95
|
+
// live injector's progressive disclosure, independent of source attribution.
|
|
96
|
+
const orchestrateSpy = mock(
|
|
97
|
+
async (): Promise<OrchestrateResult> => ({
|
|
98
|
+
selections: [
|
|
99
|
+
{ slug: "page-core", pinned: false },
|
|
100
|
+
{ slug: "page-hot", pinned: false },
|
|
101
|
+
{ slug: "page-1", pinned: true },
|
|
102
|
+
{ slug: "page-2", pinned: false },
|
|
103
|
+
{ slug: "page-3", pinned: false },
|
|
104
|
+
],
|
|
105
|
+
matchedSections: new Map([
|
|
106
|
+
["page-1", { article: "page-1", title: "", text: "x", ordinal: 0 }],
|
|
107
|
+
["page-2", { article: "page-2", title: "", text: "y", ordinal: 0 }],
|
|
108
|
+
]),
|
|
109
|
+
lanes: {
|
|
110
|
+
core: ["page-core"],
|
|
111
|
+
hot: ["page-hot"],
|
|
112
|
+
finder: [
|
|
113
|
+
{ slug: "page-1", descriptor: "", lane: "needle" },
|
|
114
|
+
{ slug: "page-2", descriptor: "", lane: "dense" },
|
|
115
|
+
{ slug: "page-3", descriptor: "", lane: "edge" },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
104
120
|
|
|
105
121
|
let sectionBuilds = 0;
|
|
106
122
|
let needleBuilds = 0;
|
|
@@ -108,6 +124,14 @@ let edgeBuilds = 0;
|
|
|
108
124
|
let ensureCollectionCalls = 0;
|
|
109
125
|
let ensureCollectionThrows = false;
|
|
110
126
|
|
|
127
|
+
// Stable-prefix lane inputs, driven per test: what the curated core file
|
|
128
|
+
// yields and what the frecency hot set computes. `hotSetOpts` captures the
|
|
129
|
+
// options `initLanes` passed so the test can assert the core exclusion and the
|
|
130
|
+
// config plumbing.
|
|
131
|
+
let coreSetSlugs: string[] = [];
|
|
132
|
+
let hotSetResult: HotSetEntry[] = [];
|
|
133
|
+
let hotSetOpts: HotSetOptions | null = null;
|
|
134
|
+
|
|
111
135
|
// The `pageBody` resolver `initLanes` passes to `buildSectionIndex` (its second
|
|
112
136
|
// arg), captured by the stub below so a test can drive it directly: a capability
|
|
113
137
|
// slug must resolve to its rendered capability content, an on-disk slug to its
|
|
@@ -122,12 +146,21 @@ function makeDb() {
|
|
|
122
146
|
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
123
147
|
const db = drizzle(testSqlite, { schema });
|
|
124
148
|
migrateAddMemoryV3Selections(db);
|
|
149
|
+
// The live injector's net-new dedup reads/writes the everInjected store.
|
|
150
|
+
migrateAddMemoryV3EverInjected(db);
|
|
125
151
|
return db;
|
|
126
152
|
}
|
|
127
153
|
|
|
154
|
+
// The fake index lists the LIVE pages — `initLanes` filters the core and hot
|
|
155
|
+
// lanes against `byArticle`, so membership here is what "the page exists"
|
|
156
|
+
// means to the stable-prefix lanes.
|
|
128
157
|
const FAKE_SECTION_INDEX: SectionIndex = {
|
|
129
158
|
sections: [],
|
|
130
|
-
byArticle: new Map(
|
|
159
|
+
byArticle: new Map([
|
|
160
|
+
["page-1", []],
|
|
161
|
+
["page-2", []],
|
|
162
|
+
[CAPABILITY_SLUG, []],
|
|
163
|
+
]),
|
|
131
164
|
};
|
|
132
165
|
|
|
133
166
|
// ─── module mocks (installed before the plugin import) ──────────────────────
|
|
@@ -144,8 +177,10 @@ mock.module("../../../../config/assistant-feature-flags.js", () => ({
|
|
|
144
177
|
mock.module("../../../../config/loader.js", () => ({
|
|
145
178
|
getConfig: () => ({
|
|
146
179
|
memory: {
|
|
180
|
+
enabled: memoryEnabled,
|
|
147
181
|
v3: {
|
|
148
|
-
|
|
182
|
+
hotSet: { k: 40, halfLifeDays: 14 },
|
|
183
|
+
spotlight: { n: 6, windowTurns: 2 },
|
|
149
184
|
needleK: 100,
|
|
150
185
|
denseK: 100,
|
|
151
186
|
edge: { hubDegree: 30, seedCount: 18, perSeed: 6, cap: 45 },
|
|
@@ -155,6 +190,26 @@ mock.module("../../../../config/loader.js", () => ({
|
|
|
155
190
|
}),
|
|
156
191
|
}));
|
|
157
192
|
|
|
193
|
+
// Stable-prefix lanes: the curated core loader and the frecency hot set are
|
|
194
|
+
// stubbed to controllable values; `initLanes` owns the existence filtering and
|
|
195
|
+
// the core exclusion these tests assert.
|
|
196
|
+
mock.module("../core-set.js", () => ({
|
|
197
|
+
...realCoreSet,
|
|
198
|
+
loadCoreSet: (...args: Parameters<typeof realCoreSet.loadCoreSet>) =>
|
|
199
|
+
shadowMockActive ? coreSetSlugs : realCoreSet.loadCoreSet(...args),
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
mock.module("../hot-set.js", () => ({
|
|
203
|
+
...realHotSet,
|
|
204
|
+
computeHotSet: (
|
|
205
|
+
...args: Parameters<typeof realHotSet.computeHotSet>
|
|
206
|
+
): HotSetEntry[] => {
|
|
207
|
+
if (!shadowMockActive) return realHotSet.computeHotSet(...args);
|
|
208
|
+
hotSetOpts = args[1];
|
|
209
|
+
return hotSetResult;
|
|
210
|
+
},
|
|
211
|
+
}));
|
|
212
|
+
|
|
158
213
|
// Spread the real module so every export the live path transitively imports
|
|
159
214
|
// stays present; only `getMessages` is overridden.
|
|
160
215
|
mock.module("../../../../memory/conversation-crud.js", () => ({
|
|
@@ -305,9 +360,14 @@ mock.module("../orchestrate.js", () => ({
|
|
|
305
360
|
}));
|
|
306
361
|
|
|
307
362
|
// Import AFTER mocks so the plugin binds to them.
|
|
308
|
-
const {
|
|
309
|
-
|
|
310
|
-
|
|
363
|
+
const {
|
|
364
|
+
runShadowObservation,
|
|
365
|
+
resetShadowLanesForTests,
|
|
366
|
+
invalidateLanes,
|
|
367
|
+
attributeSelections,
|
|
368
|
+
} = await import("../shadow-plugin.js");
|
|
369
|
+
const { memoryV3Injector, resetMemoryV3InjectorStateForTests } =
|
|
370
|
+
await import("../injector.js");
|
|
311
371
|
|
|
312
372
|
afterAll(() => {
|
|
313
373
|
shadowMockActive = false;
|
|
@@ -325,6 +385,7 @@ beforeEach(() => {
|
|
|
325
385
|
shadowMockActive = true;
|
|
326
386
|
liveEnabled = false;
|
|
327
387
|
shadowEnabled = false;
|
|
388
|
+
memoryEnabled = true;
|
|
328
389
|
messages = [
|
|
329
390
|
{
|
|
330
391
|
role: "user",
|
|
@@ -338,21 +399,45 @@ beforeEach(() => {
|
|
|
338
399
|
ensureCollectionCalls = 0;
|
|
339
400
|
ensureCollectionThrows = false;
|
|
340
401
|
capturedPageBody = null;
|
|
402
|
+
coreSetSlugs = [];
|
|
403
|
+
hotSetResult = [];
|
|
404
|
+
hotSetOpts = null;
|
|
341
405
|
testDb = makeDb();
|
|
342
406
|
resetShadowLanesForTests();
|
|
407
|
+
// The injector memoizes one orchestration per (conversation, turn); clear it
|
|
408
|
+
// so tests reusing the same ids observe fresh orchestrations.
|
|
409
|
+
resetMemoryV3InjectorStateForTests();
|
|
343
410
|
});
|
|
344
411
|
|
|
345
|
-
/** Invoke the memory-v3 injector's `produce()` for a turn
|
|
346
|
-
|
|
347
|
-
|
|
412
|
+
/** Invoke the memory-v3 injector's `produce()` for a turn and, when a block
|
|
413
|
+
* is produced, invoke its attachment-commit callback — simulating runtime
|
|
414
|
+
* assembly's user-tail commit point, where the everInjected-store write now
|
|
415
|
+
* happens. */
|
|
416
|
+
async function produce(conversationId: string, turnIndex: number) {
|
|
417
|
+
const block = await memoryV3Injector.produce({
|
|
348
418
|
requestId: "r1",
|
|
349
419
|
conversationId,
|
|
350
420
|
turnIndex,
|
|
351
421
|
trust: {} as never,
|
|
352
422
|
});
|
|
423
|
+
const commit = block?.meta?.[MEMORY_V3_COMMIT_META_KEY];
|
|
424
|
+
if (typeof commit === "function") (commit as () => void)();
|
|
425
|
+
return block;
|
|
353
426
|
}
|
|
354
427
|
|
|
355
428
|
describe("memory-v3 shadow plugin", () => {
|
|
429
|
+
test("global memory disabled → observation is skipped even when v3 flags are on", async () => {
|
|
430
|
+
shadowEnabled = true;
|
|
431
|
+
liveEnabled = true;
|
|
432
|
+
memoryEnabled = false;
|
|
433
|
+
|
|
434
|
+
await runShadowObservation("conv-1", 0);
|
|
435
|
+
|
|
436
|
+
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
437
|
+
expect(sectionBuilds).toBe(0);
|
|
438
|
+
expect(readRows()).toHaveLength(0);
|
|
439
|
+
});
|
|
440
|
+
|
|
356
441
|
test("shadow flag OFF → orchestrate not called, no DB writes", async () => {
|
|
357
442
|
shadowEnabled = false;
|
|
358
443
|
await runShadowObservation("conv-1", 0);
|
|
@@ -367,20 +452,38 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
367
452
|
|
|
368
453
|
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
369
454
|
const rows = readRows();
|
|
370
|
-
// Each selection is attributed to the lane that
|
|
371
|
-
// (`result.
|
|
372
|
-
// dense-only page-2 logs "dense", not "needle".
|
|
455
|
+
// Each selection is attributed to the lane that pooled it
|
|
456
|
+
// (`result.lanes`), not re-derived from section presence — so the
|
|
457
|
+
// dense-only page-2 logs "dense", not "needle". The result is
|
|
458
|
+
// current-turn selections only.
|
|
373
459
|
expect(rows).toEqual([
|
|
374
|
-
{ slug: "carried", source: "carry-forward", pinned: 0 },
|
|
375
460
|
// page-1 was surfaced by the needle lane → "needle", pinned.
|
|
376
461
|
{ slug: "page-1", source: "needle", pinned: 1 },
|
|
377
462
|
// page-2 was surfaced by the dense lane → "dense".
|
|
378
463
|
{ slug: "page-2", source: "dense", pinned: 0 },
|
|
379
464
|
// page-3 was surfaced by the edge lane → "edge".
|
|
380
465
|
{ slug: "page-3", source: "edge", pinned: 0 },
|
|
466
|
+
// page-core / page-hot sit in the stable prefix → "core" / "hot".
|
|
467
|
+
{ slug: "page-core", source: "core", pinned: 0 },
|
|
468
|
+
{ slug: "page-hot", source: "hot", pinned: 0 },
|
|
381
469
|
]);
|
|
382
470
|
});
|
|
383
471
|
|
|
472
|
+
test("a selection of a core page a finder also hit attributes to core (pool position wins)", () => {
|
|
473
|
+
const rows = attributeSelections({
|
|
474
|
+
selections: [{ slug: "page-core", pinned: false }],
|
|
475
|
+
matchedSections: new Map(),
|
|
476
|
+
lanes: {
|
|
477
|
+
core: ["page-core"],
|
|
478
|
+
hot: [],
|
|
479
|
+
// The needle also hit the core page this turn — the row still logs
|
|
480
|
+
// "core" because that is where the candidate lived in the pool.
|
|
481
|
+
finder: [{ slug: "page-core", descriptor: "", lane: "needle" }],
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
expect(rows).toEqual([{ slug: "page-core", source: "core", pinned: 0 }]);
|
|
485
|
+
});
|
|
486
|
+
|
|
384
487
|
test("the turn passed to orchestrate carries the latest user message", async () => {
|
|
385
488
|
shadowEnabled = true;
|
|
386
489
|
await runShadowObservation("conv-1", 0);
|
|
@@ -405,12 +508,38 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
405
508
|
sectionIndex?: unknown;
|
|
406
509
|
needle?: unknown;
|
|
407
510
|
edgeGraph?: unknown;
|
|
408
|
-
|
|
511
|
+
coreSlugs?: unknown;
|
|
512
|
+
hotSlugs?: unknown;
|
|
409
513
|
};
|
|
410
514
|
expect(deps.sectionIndex).toBeDefined();
|
|
411
515
|
expect(deps.needle).toBeDefined();
|
|
412
516
|
expect(deps.edgeGraph).toBeDefined();
|
|
413
|
-
expect(deps.
|
|
517
|
+
expect(deps.coreSlugs).toEqual([]);
|
|
518
|
+
expect(deps.hotSlugs).toEqual([]);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test("initLanes filters core to existing pages and excludes core from the hot set", async () => {
|
|
522
|
+
shadowEnabled = true;
|
|
523
|
+
// The core file lists a live page and a dangling slug; the hot set returns
|
|
524
|
+
// a live page and a deleted one (selection rows can outlive their pages).
|
|
525
|
+
coreSetSlugs = ["page-1", "missing-page"];
|
|
526
|
+
hotSetResult = [
|
|
527
|
+
{ slug: "page-2", score: 2 },
|
|
528
|
+
{ slug: "gone-page", score: 1 },
|
|
529
|
+
];
|
|
530
|
+
await runShadowObservation("conv-1", 0);
|
|
531
|
+
|
|
532
|
+
const deps = (
|
|
533
|
+
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
534
|
+
)[0]![1] as { coreSlugs: string[]; hotSlugs: string[] };
|
|
535
|
+
// Dangling core entries and deleted hot pages never reach the pool.
|
|
536
|
+
expect(deps.coreSlugs).toEqual(["page-1"]);
|
|
537
|
+
expect(deps.hotSlugs).toEqual(["page-2"]);
|
|
538
|
+
// The hot set was computed with the (filtered) core excluded and the
|
|
539
|
+
// configured k / half-life (14 days, in ms).
|
|
540
|
+
expect(hotSetOpts?.excludeSlugs).toEqual(new Set(["page-1"]));
|
|
541
|
+
expect(hotSetOpts?.k).toBe(40);
|
|
542
|
+
expect(hotSetOpts?.halfLifeMs).toBe(14 * 24 * 60 * 60 * 1000);
|
|
414
543
|
});
|
|
415
544
|
|
|
416
545
|
test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
|
|
@@ -431,7 +560,7 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
431
560
|
expect(readRows().length).toBeGreaterThan(0);
|
|
432
561
|
});
|
|
433
562
|
|
|
434
|
-
test("live on → produce returns the
|
|
563
|
+
test("live on → produce returns the net-new CARD block and logs", async () => {
|
|
435
564
|
liveEnabled = true;
|
|
436
565
|
shadowEnabled = false;
|
|
437
566
|
const block = await produce("conv-1", 0);
|
|
@@ -439,25 +568,37 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
439
568
|
expect(block!.placement).toBe("after-memory-prefix");
|
|
440
569
|
expect(block!.text.startsWith("<memory>\n")).toBe(true);
|
|
441
570
|
expect(block!.text.endsWith("\n</memory>")).toBe(true);
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
571
|
+
// Turn 1: every selection is net-new and renders as a compact card —
|
|
572
|
+
// the page header plus the page's head section (the fixture body has no
|
|
573
|
+
// `## ` headings, so the whole body is the head and no TOC line renders).
|
|
574
|
+
for (const slug of ["page-core", "page-hot", "page-1", "page-2"]) {
|
|
575
|
+
expect(block!.text).toContain(
|
|
576
|
+
`# memory/concepts/${slug}.md\nbody for ${slug}`,
|
|
577
|
+
);
|
|
578
|
+
}
|
|
448
579
|
// Selections are still logged in live mode.
|
|
449
580
|
expect(readRows().length).toBeGreaterThan(0);
|
|
450
581
|
});
|
|
451
582
|
|
|
583
|
+
test("live on → a later turn re-selecting the same pages renders an EMPTY block (net-new dedup)", async () => {
|
|
584
|
+
liveEnabled = true;
|
|
585
|
+
shadowEnabled = false;
|
|
586
|
+
const first = await produce("conv-1", 0);
|
|
587
|
+
expect(first!.text.length).toBeGreaterThan(0);
|
|
588
|
+
// Same orchestrate fixture on the next turn → zero net-new cards. The
|
|
589
|
+
// block is still produced (its presence keys v2 suppression downstream).
|
|
590
|
+
const repeat = await produce("conv-1", 1);
|
|
591
|
+
expect(repeat).not.toBeNull();
|
|
592
|
+
expect(repeat!.text).toBe("");
|
|
593
|
+
});
|
|
594
|
+
|
|
452
595
|
test("live on but empty selection → produce returns null", async () => {
|
|
453
596
|
liveEnabled = true;
|
|
454
597
|
shadowEnabled = false;
|
|
455
598
|
orchestrateSpy.mockImplementationOnce(async () => ({
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
sectionBySlug: new Map(),
|
|
460
|
-
laneBySlug: new Map(),
|
|
599
|
+
selections: [],
|
|
600
|
+
matchedSections: new Map(),
|
|
601
|
+
lanes: { core: [], hot: [], finder: [] },
|
|
461
602
|
}));
|
|
462
603
|
const block = await produce("conv-1", 0);
|
|
463
604
|
expect(block).toBeNull();
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
MemoryRoutingTurn,
|
|
5
|
-
SelectionSource,
|
|
6
|
-
Slug,
|
|
7
|
-
WorkingSetEntry,
|
|
8
|
-
} from "../types.js";
|
|
3
|
+
import type { MemoryRoutingTurn, SelectionSource, Slug } from "../types.js";
|
|
9
4
|
|
|
10
5
|
test("v3 core types instantiate", () => {
|
|
11
6
|
const slug: Slug = "page-123";
|
|
12
7
|
|
|
13
|
-
const entry: WorkingSetEntry = {
|
|
14
|
-
slug,
|
|
15
|
-
selectedAtTurn: 1,
|
|
16
|
-
pinned: false,
|
|
17
|
-
lastSeenTurn: 2,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
8
|
const turnContext: MemoryRoutingTurn = {
|
|
21
9
|
conversationId: "conv-xyz",
|
|
22
10
|
turnNumber: 3,
|
|
@@ -24,9 +12,9 @@ test("v3 core types instantiate", () => {
|
|
|
24
12
|
recentContext: "prior turns",
|
|
25
13
|
};
|
|
26
14
|
|
|
27
|
-
const source: SelectionSource = "
|
|
15
|
+
const source: SelectionSource = "needle";
|
|
28
16
|
|
|
29
|
-
expect(
|
|
17
|
+
expect(slug).toBe("page-123");
|
|
30
18
|
expect(turnContext.turnNumber).toBe(3);
|
|
31
|
-
expect(source).toBe("
|
|
19
|
+
expect(source).toBe("needle");
|
|
32
20
|
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { cardBytes, renderCard } from "./card.js";
|
|
4
|
+
|
|
5
|
+
describe("renderCard", () => {
|
|
6
|
+
test("standard article renders head section + section TOC", () => {
|
|
7
|
+
const raw = [
|
|
8
|
+
"---",
|
|
9
|
+
"summary: A page about topic A.",
|
|
10
|
+
"---",
|
|
11
|
+
"# Topic A",
|
|
12
|
+
"",
|
|
13
|
+
"Lead paragraph one about topic A.",
|
|
14
|
+
"",
|
|
15
|
+
"Lead paragraph two with more detail.",
|
|
16
|
+
"",
|
|
17
|
+
"## History",
|
|
18
|
+
"history body",
|
|
19
|
+
"",
|
|
20
|
+
"## Design",
|
|
21
|
+
"design body",
|
|
22
|
+
"",
|
|
23
|
+
"## Open Questions",
|
|
24
|
+
"open questions body",
|
|
25
|
+
].join("\n");
|
|
26
|
+
|
|
27
|
+
expect(renderCard("page-a", raw)).toBe(
|
|
28
|
+
[
|
|
29
|
+
"# memory/concepts/page-a.md",
|
|
30
|
+
"# Topic A",
|
|
31
|
+
"",
|
|
32
|
+
"Lead paragraph one about topic A.",
|
|
33
|
+
"",
|
|
34
|
+
"Lead paragraph two with more detail.",
|
|
35
|
+
"",
|
|
36
|
+
"[sections: §History · §Design · §Open Questions]",
|
|
37
|
+
].join("\n"),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("kind: index page renders its links: map instead of section names", () => {
|
|
42
|
+
const longNote = "x".repeat(100);
|
|
43
|
+
const raw = [
|
|
44
|
+
"---",
|
|
45
|
+
"kind: index",
|
|
46
|
+
"links:",
|
|
47
|
+
" - page-a — short note about page a",
|
|
48
|
+
` - page-b — ${longNote}`,
|
|
49
|
+
" - page-c",
|
|
50
|
+
"---",
|
|
51
|
+
"# Index of Topics",
|
|
52
|
+
"",
|
|
53
|
+
"Lead line.",
|
|
54
|
+
"",
|
|
55
|
+
"## Pages",
|
|
56
|
+
"generic section body",
|
|
57
|
+
].join("\n");
|
|
58
|
+
|
|
59
|
+
const card = renderCard("topics-index", raw);
|
|
60
|
+
expect(card).toBe(
|
|
61
|
+
[
|
|
62
|
+
"# memory/concepts/topics-index.md",
|
|
63
|
+
"# Index of Topics",
|
|
64
|
+
"",
|
|
65
|
+
"Lead line.",
|
|
66
|
+
"",
|
|
67
|
+
`[linked: page-a — short note about page a · page-b — ${"x".repeat(80)}… · page-c]`,
|
|
68
|
+
].join("\n"),
|
|
69
|
+
);
|
|
70
|
+
// Section names never appear on an index card.
|
|
71
|
+
expect(card).not.toContain("§Pages");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("kind: index without usable links falls back to section names", () => {
|
|
75
|
+
const raw = [
|
|
76
|
+
"---",
|
|
77
|
+
"kind: index",
|
|
78
|
+
"---",
|
|
79
|
+
"# Bare Index",
|
|
80
|
+
"",
|
|
81
|
+
"## Pages",
|
|
82
|
+
"body",
|
|
83
|
+
].join("\n");
|
|
84
|
+
|
|
85
|
+
expect(renderCard("bare-index", raw)).toBe(
|
|
86
|
+
[
|
|
87
|
+
"# memory/concepts/bare-index.md",
|
|
88
|
+
"# Bare Index",
|
|
89
|
+
"",
|
|
90
|
+
"[sections: §Pages]",
|
|
91
|
+
].join("\n"),
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("sectionless page omits the TOC line", () => {
|
|
96
|
+
const raw = [
|
|
97
|
+
"---",
|
|
98
|
+
"summary: s",
|
|
99
|
+
"---",
|
|
100
|
+
"# Sectionless",
|
|
101
|
+
"",
|
|
102
|
+
"Just a lead.",
|
|
103
|
+
].join("\n");
|
|
104
|
+
|
|
105
|
+
expect(renderCard("page-b", raw)).toBe(
|
|
106
|
+
["# memory/concepts/page-b.md", "# Sectionless", "", "Just a lead."].join(
|
|
107
|
+
"\n",
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("frontmatter-less text renders head + TOC", () => {
|
|
113
|
+
const raw = [
|
|
114
|
+
"# No Frontmatter",
|
|
115
|
+
"",
|
|
116
|
+
"Lead.",
|
|
117
|
+
"",
|
|
118
|
+
"## Only Section",
|
|
119
|
+
"body",
|
|
120
|
+
].join("\n");
|
|
121
|
+
|
|
122
|
+
expect(renderCard("page-c", raw)).toBe(
|
|
123
|
+
[
|
|
124
|
+
"# memory/concepts/page-c.md",
|
|
125
|
+
"# No Frontmatter",
|
|
126
|
+
"",
|
|
127
|
+
"Lead.",
|
|
128
|
+
"",
|
|
129
|
+
"[sections: §Only Section]",
|
|
130
|
+
].join("\n"),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("body starting at a ## heading renders header + TOC with no head block", () => {
|
|
135
|
+
const raw = ["## First", "body", "", "## Second", "body"].join("\n");
|
|
136
|
+
|
|
137
|
+
expect(renderCard("page-d", raw)).toBe(
|
|
138
|
+
["# memory/concepts/page-d.md", "", "[sections: §First · §Second]"].join(
|
|
139
|
+
"\n",
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("long lead injects whole (no length cap)", () => {
|
|
145
|
+
const longLead = `Long lead. ${"y".repeat(10_000)}`;
|
|
146
|
+
const raw = ["# Long", "", longLead, "", "## Section", "body"].join("\n");
|
|
147
|
+
|
|
148
|
+
expect(renderCard("page-e", raw)).toContain(longLead);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("deterministic for the same input", () => {
|
|
152
|
+
const raw = ["# T", "", "Lead.", "", "## S", "b"].join("\n");
|
|
153
|
+
expect(renderCard("page-f", raw)).toBe(renderCard("page-f", raw));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("cardBytes", () => {
|
|
158
|
+
test("counts UTF-8 bytes, not characters", () => {
|
|
159
|
+
expect(cardBytes("abc")).toBe(3);
|
|
160
|
+
expect(cardBytes("§")).toBe(2); // U+00A7 is 2 bytes in UTF-8
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("accounts for a rendered card's full byte footprint", () => {
|
|
164
|
+
const card = renderCard(
|
|
165
|
+
"page-a",
|
|
166
|
+
["# T", "", "Lead.", "", "## S", "b"].join("\n"),
|
|
167
|
+
);
|
|
168
|
+
// The card contains exactly one multibyte char (the TOC's "§"), so its
|
|
169
|
+
// byte footprint is one over its character length.
|
|
170
|
+
expect(card.match(/§/g)).toHaveLength(1);
|
|
171
|
+
expect(cardBytes(card)).toBe(card.length + 1);
|
|
172
|
+
});
|
|
173
|
+
});
|