@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
|
@@ -4,23 +4,30 @@
|
|
|
4
4
|
* SCOPE / ALTITUDE. A full daemon-assembly integration (plugin registry → flag
|
|
5
5
|
* read → runtime assembly → provider call) is too heavy and too mock-fragile
|
|
6
6
|
* for a unit test. Instead this composes the REAL v3 live-path units with a
|
|
7
|
-
* mocked select provider + stubbed dense lane and
|
|
7
|
+
* mocked select provider + stubbed dense lane, synthetic fixtures, and a real
|
|
8
|
+
* in-memory everInjected store:
|
|
8
9
|
*
|
|
9
|
-
* orchestrate (
|
|
10
|
-
*
|
|
11
|
-
* →
|
|
10
|
+
* orchestrate (cache-ordered pool → ONE selectPool call → this turn's
|
|
11
|
+
* selections)
|
|
12
|
+
* → net-new filter against the everInjected store (`getActiveSlugs`)
|
|
13
|
+
* → renderCard + renderCardsBlockInner (the frozen card block)
|
|
14
|
+
* → recordInjected
|
|
12
15
|
*
|
|
13
|
-
* That is
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* the v3 layer without the daemon. The provider is stubbed (no network).
|
|
16
|
+
* That is the behavioral contract the live path wires together: the injector
|
|
17
|
+
* renders only the turn's NET-NEW selections as cards, records them, and the
|
|
18
|
+
* resulting block is FROZEN into history — prior turns' blocks are never
|
|
19
|
+
* re-rendered or stripped (the cache contract; the old `stripAllMemoryInjections`
|
|
20
|
+
* whole-layer replace is gone). The provider is stubbed (no network).
|
|
19
21
|
*/
|
|
20
22
|
|
|
23
|
+
import { Database } from "bun:sqlite";
|
|
21
24
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
22
25
|
|
|
23
|
-
import {
|
|
26
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
27
|
+
|
|
28
|
+
import { wrapMemoryBlock } from "../../../../memory/memory-marker.js";
|
|
29
|
+
import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
30
|
+
import * as schema from "../../../../memory/schema.js";
|
|
24
31
|
import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
|
|
25
32
|
import type {
|
|
26
33
|
ContentBlock,
|
|
@@ -28,6 +35,7 @@ import type {
|
|
|
28
35
|
Provider,
|
|
29
36
|
ProviderResponse,
|
|
30
37
|
} from "../../../../providers/types.js";
|
|
38
|
+
import { cardBytes, renderCard } from "../card.js";
|
|
31
39
|
import type { EdgeGraph } from "../edge.js";
|
|
32
40
|
import { buildEdgeGraph } from "../edge.js";
|
|
33
41
|
import { buildSectionNeedle } from "../section-needle.js";
|
|
@@ -65,9 +73,35 @@ mock.module("../dense.js", () => ({
|
|
|
65
73
|
denseMockActive ? [] : realDense.denseLane(...args),
|
|
66
74
|
}));
|
|
67
75
|
|
|
76
|
+
// In-memory everInjected store backing the net-new dedup, swapped in only
|
|
77
|
+
// while this file's tests run.
|
|
78
|
+
const realDbConnection = {
|
|
79
|
+
...(await import("../../../../memory/db-connection.js")),
|
|
80
|
+
};
|
|
81
|
+
let testSqlite: Database;
|
|
82
|
+
let testDb = makeDb();
|
|
83
|
+
function makeDb() {
|
|
84
|
+
testSqlite = new Database(":memory:");
|
|
85
|
+
const db = drizzle(testSqlite, { schema });
|
|
86
|
+
migrateAddMemoryV3EverInjected(db);
|
|
87
|
+
return db;
|
|
88
|
+
}
|
|
89
|
+
mock.module("../../../../memory/db-connection.js", () => ({
|
|
90
|
+
...realDbConnection,
|
|
91
|
+
getDb: () => (denseMockActive ? testDb : realDbConnection.getDb()),
|
|
92
|
+
getSqliteFrom: (db: unknown) =>
|
|
93
|
+
denseMockActive
|
|
94
|
+
? testSqlite
|
|
95
|
+
: realDbConnection.getSqliteFrom(
|
|
96
|
+
db as Parameters<typeof realDbConnection.getSqliteFrom>[0],
|
|
97
|
+
),
|
|
98
|
+
}));
|
|
99
|
+
|
|
68
100
|
const { orchestrate } = await import("../orchestrate.js");
|
|
69
|
-
const {
|
|
70
|
-
|
|
101
|
+
const { renderCardsBlockInner, V3_CARDS_INJECTION_HEADER } =
|
|
102
|
+
await import("../render-injection.js");
|
|
103
|
+
const { getActiveSlugs, recordInjected, residentBytes } =
|
|
104
|
+
await import("../ever-injected-store.js");
|
|
71
105
|
|
|
72
106
|
// ---------------------------------------------------------------------------
|
|
73
107
|
// Fixtures: a tiny corpus whose section text contains a distinctive term per
|
|
@@ -93,7 +127,6 @@ function makeEntries(): PageIndexEntry[] {
|
|
|
93
127
|
}
|
|
94
128
|
|
|
95
129
|
const config = {} as never;
|
|
96
|
-
const contentOf = async (slug: Slug): Promise<string> => `body for ${slug}`;
|
|
97
130
|
|
|
98
131
|
async function buildLanes(): Promise<{
|
|
99
132
|
sectionIndex: SectionIndex;
|
|
@@ -130,20 +163,39 @@ function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
|
|
|
130
163
|
};
|
|
131
164
|
}
|
|
132
165
|
|
|
133
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Parse the two-segment selector input into the globally-numbered pool slug
|
|
168
|
+
* list: stable-prefix cards (`<candidate_cards>`, identified by their
|
|
169
|
+
* `[i] # memory/concepts/<slug>.md` header line) then finder lines
|
|
170
|
+
* (`<candidates>`, `[i] slug — descriptor`).
|
|
171
|
+
*/
|
|
134
172
|
function candidateSlugs(messages: Message[]): Slug[] {
|
|
173
|
+
const entries: Array<{ id: number; slug: string }> = [];
|
|
135
174
|
for (const msg of messages) {
|
|
136
175
|
for (const block of msg.content) {
|
|
137
176
|
if (block.type !== "text") continue;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
177
|
+
const cards = /<candidate_cards>\n([\s\S]*?)\n<\/candidate_cards>/.exec(
|
|
178
|
+
block.text,
|
|
179
|
+
);
|
|
180
|
+
if (cards) {
|
|
181
|
+
for (const m of cards[1].matchAll(
|
|
182
|
+
/^\[(\d+)\] # memory\/concepts\/(.+)\.md$/gm,
|
|
183
|
+
)) {
|
|
184
|
+
entries.push({ id: Number(m[1]), slug: m[2]! });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const finder = /<candidates>\n([\s\S]*?)\n<\/candidates>/.exec(
|
|
188
|
+
block.text,
|
|
189
|
+
);
|
|
190
|
+
if (finder) {
|
|
191
|
+
for (const line of finder[1].split("\n")) {
|
|
192
|
+
const m = /^\[(\d+)\] (\S+)(?: — |$)/.exec(line);
|
|
193
|
+
if (m) entries.push({ id: Number(m[1]), slug: m[2]! });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
144
196
|
}
|
|
145
197
|
}
|
|
146
|
-
return
|
|
198
|
+
return entries.sort((a, b) => a.id - b.id).map((e) => e.slug);
|
|
147
199
|
}
|
|
148
200
|
|
|
149
201
|
/** Provider that selects (and optionally pins) the pooled candidates in `keep`. */
|
|
@@ -163,41 +215,45 @@ function selectProvider(keep: Slug[], pin: Slug[] = []): Provider {
|
|
|
163
215
|
};
|
|
164
216
|
}
|
|
165
217
|
|
|
166
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Run one turn through the live composition: orchestrate → net-new filter
|
|
220
|
+
* against the store → card render → record. Returns the wrapped block ("" when
|
|
221
|
+
* the turn had no net-new cards) — the same shape the injector attaches.
|
|
222
|
+
*/
|
|
167
223
|
async function runTurn(
|
|
224
|
+
conversationId: string,
|
|
168
225
|
turnNumber: number,
|
|
169
226
|
query: string,
|
|
170
227
|
keep: Slug[],
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
workingSet: InstanceType<typeof WorkingSet>;
|
|
175
|
-
},
|
|
176
|
-
) {
|
|
177
|
-
providerStub = selectProvider(keep, pin);
|
|
228
|
+
deps: { lanes: Awaited<ReturnType<typeof buildLanes>> },
|
|
229
|
+
): Promise<{ block: string; netNew: Slug[] }> {
|
|
230
|
+
providerStub = selectProvider(keep);
|
|
178
231
|
const result = await orchestrate(makeTurn(turnNumber, query), {
|
|
179
232
|
sectionIndex: deps.lanes.sectionIndex,
|
|
180
233
|
needle: deps.lanes.needle,
|
|
181
234
|
denseConfig: config,
|
|
182
235
|
edgeGraph: deps.lanes.edgeGraph,
|
|
183
|
-
|
|
236
|
+
coreSlugs: [],
|
|
237
|
+
hotSlugs: [],
|
|
238
|
+
prefixCards: new Map(),
|
|
184
239
|
});
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
240
|
+
const active = getActiveSlugs(conversationId);
|
|
241
|
+
const netNew = result.selections
|
|
242
|
+
.map((s) => s.slug)
|
|
243
|
+
.filter((slug) => !active.has(slug));
|
|
244
|
+
const cards = netNew.map((slug) => renderCard(slug, PAGES[slug]!));
|
|
245
|
+
recordInjected(
|
|
246
|
+
conversationId,
|
|
247
|
+
cards.map((card, i) => ({ slug: netNew[i]!, bytes: cardBytes(card) })),
|
|
189
248
|
);
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Count `<memory>\n…\n</memory>` blocks in a rendered string. */
|
|
194
|
-
function countMemoryBlocks(text: string): number {
|
|
195
|
-
return (text.match(/<memory>\n/g) ?? []).length;
|
|
249
|
+
const inner = renderCardsBlockInner(cards);
|
|
250
|
+
return { block: inner.length === 0 ? "" : wrapMemoryBlock(inner), netNew };
|
|
196
251
|
}
|
|
197
252
|
|
|
198
253
|
beforeEach(() => {
|
|
199
254
|
denseMockActive = true;
|
|
200
255
|
providerStub = null;
|
|
256
|
+
testDb = makeDb();
|
|
201
257
|
});
|
|
202
258
|
|
|
203
259
|
afterAll(() => {
|
|
@@ -205,139 +261,104 @@ afterAll(() => {
|
|
|
205
261
|
});
|
|
206
262
|
|
|
207
263
|
// ---------------------------------------------------------------------------
|
|
208
|
-
//
|
|
209
|
-
//
|
|
264
|
+
// Net-new accumulation: each turn renders ONLY pages not already frozen into
|
|
265
|
+
// history; an all-repeat turn renders nothing new.
|
|
210
266
|
// ---------------------------------------------------------------------------
|
|
211
267
|
|
|
212
|
-
describe("memory-v3 live —
|
|
213
|
-
test("
|
|
268
|
+
describe("memory-v3 live — net-new card accumulation", () => {
|
|
269
|
+
test("turn 2 re-selecting turn 1's page renders zero new cards", async () => {
|
|
214
270
|
const lanes = await buildLanes();
|
|
215
|
-
const workingSet = new WorkingSet();
|
|
216
271
|
|
|
217
|
-
const t1 = await runTurn(1,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
expect(t1.result.currentSelections.map((s) => s.slug)).toContain("page-a");
|
|
222
|
-
expect(t1.block).toContain("body for page-a");
|
|
272
|
+
const t1 = await runTurn("conv-1", 1, "apple", ["page-a"], { lanes });
|
|
273
|
+
expect(t1.netNew).toEqual(["page-a"]);
|
|
274
|
+
expect(t1.block).toContain("# memory/concepts/page-a.md");
|
|
275
|
+
expect(t1.block).toContain("lead a");
|
|
223
276
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
expect(
|
|
229
|
-
"page-a",
|
|
230
|
-
);
|
|
231
|
-
expect(t2.result.finalInjection).toContain("page-a");
|
|
232
|
-
expect(t2.block).toContain("body for page-a");
|
|
277
|
+
// All-repeat turn: no new persistent bytes.
|
|
278
|
+
const t2 = await runTurn("conv-1", 2, "apple", ["page-a"], { lanes });
|
|
279
|
+
expect(t2.netNew).toEqual([]);
|
|
280
|
+
expect(t2.block).toBe("");
|
|
281
|
+
expect(residentBytes("conv-1")).toBeGreaterThan(0);
|
|
233
282
|
});
|
|
234
|
-
});
|
|
235
283
|
|
|
236
|
-
|
|
237
|
-
// Eviction reflected: a non-pinned page selected only early ages past the
|
|
238
|
-
// eviction window and drops out of a later turn's injected block.
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
|
|
241
|
-
describe("memory-v3 live — eviction reflected in the injected block", () => {
|
|
242
|
-
test("a stale non-pinned page drops out; the pinned page persists", async () => {
|
|
284
|
+
test("a topic shift renders only the newly-selected page's card", async () => {
|
|
243
285
|
const lanes = await buildLanes();
|
|
244
|
-
// Small window: a non-pinned entry unseen for >2 turns evicts. page-b is
|
|
245
|
-
// selected only in turn 1, so by turn 4 (4-1=3 > 2) it ages out; pinned
|
|
246
|
-
// page-a never evicts; page-c is re-selected every later turn.
|
|
247
|
-
const workingSet = new WorkingSet(150, 2);
|
|
248
|
-
|
|
249
|
-
const t1 = await runTurn(
|
|
250
|
-
1,
|
|
251
|
-
"apple banana",
|
|
252
|
-
["page-a", "page-b"],
|
|
253
|
-
["page-a"],
|
|
254
|
-
{ lanes, workingSet },
|
|
255
|
-
);
|
|
256
|
-
expect(t1.result.finalInjection).toContain("page-b");
|
|
257
|
-
expect(t1.block).toContain("body for page-b");
|
|
258
|
-
|
|
259
|
-
// Turns 2–3 keep page-b inside the window (3-1=2, not > 2); re-select page-c.
|
|
260
|
-
await runTurn(2, "cherry", ["page-c"], [], { lanes, workingSet });
|
|
261
|
-
const t3 = await runTurn(3, "cherry", ["page-c"], [], {
|
|
262
|
-
lanes,
|
|
263
|
-
workingSet,
|
|
264
|
-
});
|
|
265
|
-
expect(t3.result.finalInjection).toContain("page-b");
|
|
266
286
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const t4 = await runTurn(4, "cherry", ["page-c"], [], {
|
|
287
|
+
await runTurn("conv-1", 1, "apple", ["page-a"], { lanes });
|
|
288
|
+
const t2 = await runTurn("conv-1", 2, "cherry", ["page-a", "page-c"], {
|
|
270
289
|
lanes,
|
|
271
|
-
workingSet,
|
|
272
290
|
});
|
|
273
|
-
expect(
|
|
274
|
-
expect(
|
|
275
|
-
expect(
|
|
276
|
-
expect(t4.result.finalInjection).toContain("page-c");
|
|
291
|
+
expect(t2.netNew).toEqual(["page-c"]);
|
|
292
|
+
expect(t2.block).toContain("# memory/concepts/page-c.md");
|
|
293
|
+
expect(t2.block).not.toContain("# memory/concepts/page-a.md");
|
|
277
294
|
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// ---------------------------------------------------------------------------
|
|
281
|
-
// Single source: orchestrate → render produces exactly one coherent `<memory>`
|
|
282
|
-
// block per turn.
|
|
283
|
-
// ---------------------------------------------------------------------------
|
|
284
295
|
|
|
285
|
-
|
|
286
|
-
test("each turn renders exactly one <memory> block", async () => {
|
|
296
|
+
test("each non-empty turn block is one <memory> block with the read-affordance header", async () => {
|
|
287
297
|
const lanes = await buildLanes();
|
|
288
|
-
const workingSet = new WorkingSet();
|
|
289
|
-
|
|
290
298
|
const queries = ["apple", "banana", "cherry"];
|
|
291
299
|
for (const [i, query] of queries.entries()) {
|
|
292
|
-
const { block } = await runTurn(i + 1, query, [SLUGS[i]!],
|
|
300
|
+
const { block } = await runTurn("conv-1", i + 1, query, [SLUGS[i]!], {
|
|
293
301
|
lanes,
|
|
294
|
-
workingSet,
|
|
295
302
|
});
|
|
296
|
-
expect(
|
|
303
|
+
expect((block.match(/<memory>\n/g) ?? []).length).toBe(1);
|
|
297
304
|
expect(block.startsWith("<memory>\n")).toBe(true);
|
|
298
305
|
expect(block.endsWith("\n</memory>")).toBe(true);
|
|
306
|
+
expect(block).toContain(V3_CARDS_INJECTION_HEADER);
|
|
299
307
|
}
|
|
300
308
|
});
|
|
301
309
|
});
|
|
302
310
|
|
|
303
311
|
// ---------------------------------------------------------------------------
|
|
304
|
-
//
|
|
305
|
-
//
|
|
312
|
+
// Frozen history: blocks splice onto their own turn's user message and are
|
|
313
|
+
// NEVER touched on later turns — prior messages stay byte-identical (the
|
|
314
|
+
// cache contract that replaced the old all-turns strip).
|
|
306
315
|
// ---------------------------------------------------------------------------
|
|
307
316
|
|
|
308
|
-
describe("memory-v3 live —
|
|
309
|
-
test("
|
|
317
|
+
describe("memory-v3 live — frozen card blocks in history", () => {
|
|
318
|
+
test("prior turns' messages stay byte-identical as new turns inject", async () => {
|
|
310
319
|
const lanes = await buildLanes();
|
|
311
|
-
const workingSet = new WorkingSet();
|
|
312
|
-
|
|
313
|
-
const baseHistory: Message[] = [
|
|
314
|
-
{ role: "user", content: [{ type: "text", text: "first user message" }] },
|
|
315
|
-
{ role: "assistant", content: [{ type: "text", text: "ok" }] },
|
|
316
|
-
{
|
|
317
|
-
role: "user",
|
|
318
|
-
content: [{ type: "text", text: "second user message" }],
|
|
319
|
-
},
|
|
320
|
-
{ role: "assistant", content: [{ type: "text", text: "sure" }] },
|
|
321
|
-
{ role: "user", content: [{ type: "text", text: "third user message" }] },
|
|
322
|
-
];
|
|
323
|
-
|
|
324
320
|
const memBlock = (text: string): ContentBlock => ({ type: "text", text });
|
|
321
|
+
|
|
322
|
+
const history: Message[] = [];
|
|
323
|
+
const snapshots: string[] = [];
|
|
325
324
|
const queries = ["apple", "banana", "cherry"];
|
|
326
|
-
let injected: Message[] = baseHistory;
|
|
327
325
|
for (const [i, query] of queries.entries()) {
|
|
328
|
-
|
|
326
|
+
history.push({
|
|
327
|
+
role: "user",
|
|
328
|
+
content: [{ type: "text", text: `user message ${i + 1}` }],
|
|
329
|
+
});
|
|
330
|
+
const { block } = await runTurn("conv-1", i + 1, query, [SLUGS[i]!], {
|
|
329
331
|
lanes,
|
|
330
|
-
workingSet,
|
|
331
332
|
});
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
333
|
+
// The injector splices the block onto the CURRENT tail only; prior
|
|
334
|
+
// messages are never revisited.
|
|
335
|
+
if (block.length > 0) {
|
|
336
|
+
const tail = history[history.length - 1]!;
|
|
337
|
+
tail.content = [memBlock(block), ...tail.content];
|
|
338
|
+
}
|
|
339
|
+
snapshots.push(JSON.stringify(history.slice(0, -1)));
|
|
340
|
+
history.push({
|
|
341
|
+
role: "assistant",
|
|
342
|
+
content: [{ type: "text", text: "ok" }],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Every snapshot of the pre-tail history is a byte-prefix of the final
|
|
347
|
+
// history: nothing a later turn did rewrote an earlier message.
|
|
348
|
+
const finalJson = JSON.stringify(history);
|
|
349
|
+
for (const snapshot of snapshots) {
|
|
350
|
+
expect(finalJson.startsWith(snapshot.slice(0, -1))).toBe(true);
|
|
338
351
|
}
|
|
339
352
|
|
|
340
|
-
|
|
341
|
-
|
|
353
|
+
// Accumulation: all three turns' cards are present, one block per turn.
|
|
354
|
+
const allText = history
|
|
355
|
+
.flatMap((m) => m.content)
|
|
356
|
+
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
357
|
+
.map((b) => b.text)
|
|
358
|
+
.join("\n");
|
|
359
|
+
expect((allText.match(/<memory>\n/g) ?? []).length).toBe(3);
|
|
360
|
+
for (const slug of SLUGS) {
|
|
361
|
+
expect(allText).toContain(`# memory/concepts/${slug}.md`);
|
|
362
|
+
}
|
|
342
363
|
});
|
|
343
364
|
});
|
|
@@ -84,6 +84,9 @@ describe("maintainJob", () => {
|
|
|
84
84
|
// dedicated prune tests below override both collaborators.
|
|
85
85
|
listSectionArticles: async () => [],
|
|
86
86
|
listIndexedSlugs: async () => [],
|
|
87
|
+
// Core-validation stage off by default: empty core set ⇒ nothing to
|
|
88
|
+
// check. The dedicated core tests below override this.
|
|
89
|
+
loadCoreSet: () => [],
|
|
87
90
|
invalidateLanes: () => {
|
|
88
91
|
calls.invalidate += 1;
|
|
89
92
|
},
|
|
@@ -271,6 +274,68 @@ describe("maintainJob", () => {
|
|
|
271
274
|
expect(calls.invalidate).toBe(1);
|
|
272
275
|
});
|
|
273
276
|
|
|
277
|
+
test("reports dangling core entries without mutating anything", async () => {
|
|
278
|
+
setOverridesForTesting({ [FLAG_SHADOW]: true });
|
|
279
|
+
// The core file lists a live page, a renamed/deleted page, and a synthetic
|
|
280
|
+
// capability slug; only the missing page is reported. The stage is
|
|
281
|
+
// report-only: no deletes, no upserts, and the maintainer-owned file is
|
|
282
|
+
// untouched (the injected loader is read-only by construction).
|
|
283
|
+
const { deps: d, calls } = deps({
|
|
284
|
+
loadCoreSet: () => ["page-live", "page-gone", "skills/example"],
|
|
285
|
+
listIndexedSlugs: async () => ["page-live", "skills/example"],
|
|
286
|
+
});
|
|
287
|
+
const outcome = await maintainJob(JOB, CONFIG, d);
|
|
288
|
+
|
|
289
|
+
expect(outcome.danglingCoreSlugs).toEqual(["page-gone"]);
|
|
290
|
+
expect(outcome.failures).toEqual([]);
|
|
291
|
+
// Report-only: the dangling entry triggered no dense-store mutation.
|
|
292
|
+
expect(calls.deleted).toEqual([]);
|
|
293
|
+
expect(calls.upserted).toEqual([]);
|
|
294
|
+
// The pass still invalidates the lanes.
|
|
295
|
+
expect(calls.invalidate).toBe(1);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("reports nothing when every core entry is still in the index", async () => {
|
|
299
|
+
setOverridesForTesting({ [FLAG_SHADOW]: true });
|
|
300
|
+
const { deps: d } = deps({
|
|
301
|
+
loadCoreSet: () => ["page-a", "page-b"],
|
|
302
|
+
listIndexedSlugs: async () => ["page-a", "page-b", "page-c"],
|
|
303
|
+
});
|
|
304
|
+
const outcome = await maintainJob(JOB, CONFIG, d);
|
|
305
|
+
expect(outcome.danglingCoreSlugs).toEqual([]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("an empty core set skips validation entirely", async () => {
|
|
309
|
+
setOverridesForTesting({ [FLAG_SHADOW]: true });
|
|
310
|
+
let indexReads = 0;
|
|
311
|
+
const { deps: d } = deps({
|
|
312
|
+
loadCoreSet: () => [],
|
|
313
|
+
listIndexedSlugs: async () => {
|
|
314
|
+
indexReads += 1;
|
|
315
|
+
return [];
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
const outcome = await maintainJob(JOB, CONFIG, d);
|
|
319
|
+
expect(outcome.danglingCoreSlugs).toEqual([]);
|
|
320
|
+
// The prune stage reads the index once; the core stage adds no second read.
|
|
321
|
+
expect(indexReads).toBe(1);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("a thrown core-validation stage is contained and does not abort lane invalidation", async () => {
|
|
325
|
+
setOverridesForTesting({ [FLAG_SHADOW]: true });
|
|
326
|
+
const { deps: d, calls } = deps({
|
|
327
|
+
loadCoreSet: () => {
|
|
328
|
+
throw new Error("core boom");
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
const outcome = await maintainJob(JOB, CONFIG, d);
|
|
332
|
+
|
|
333
|
+
expect(outcome.failures).toContain("core-validate");
|
|
334
|
+
expect(outcome.danglingCoreSlugs).toEqual([]);
|
|
335
|
+
expect(calls.invalidate).toBe(1);
|
|
336
|
+
expect(outcome.invalidated).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
274
339
|
test("a thrown prune stage is contained and does not abort lane invalidation", async () => {
|
|
275
340
|
setOverridesForTesting({ [FLAG_SHADOW]: true });
|
|
276
341
|
const { deps: d, calls } = deps({
|
|
@@ -453,4 +518,99 @@ describe("backfillAllSections", () => {
|
|
|
453
518
|
// next pass re-embed it.
|
|
454
519
|
expect(calls.committed).toEqual([]);
|
|
455
520
|
});
|
|
521
|
+
|
|
522
|
+
test("capability row that renders empty embeds on the post-loop retry once the store seeds", async () => {
|
|
523
|
+
// Models the startup race: the skill store is cold when the main loop
|
|
524
|
+
// reaches the capability row (renders ""), and has seeded by the time the
|
|
525
|
+
// retry pass runs.
|
|
526
|
+
let skillReads = 0;
|
|
527
|
+
const readPageBody = async (slug: Slug): Promise<string> => {
|
|
528
|
+
if (slug === "skills/example") {
|
|
529
|
+
skillReads += 1;
|
|
530
|
+
return skillReads === 1
|
|
531
|
+
? ""
|
|
532
|
+
: "# Skill: example\nexample capability body";
|
|
533
|
+
}
|
|
534
|
+
return `body for ${slug}`;
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const { deps: d, calls } = deps({
|
|
538
|
+
selectAllPages: async () => ["skills/example", "page-a"],
|
|
539
|
+
readPageBody,
|
|
540
|
+
});
|
|
541
|
+
const outcome = await backfillAllSections(CONFIG, d);
|
|
542
|
+
|
|
543
|
+
// The cold row was skipped without building on the first pass, then built
|
|
544
|
+
// and embedded on the retry.
|
|
545
|
+
expect(calls.built).toEqual([["page-a"], ["skills/example"]]);
|
|
546
|
+
// The cold render never deleted the row's existing points; the retry did.
|
|
547
|
+
expect(calls.deleted).toEqual(["page-a", "skills/example"]);
|
|
548
|
+
expect(
|
|
549
|
+
calls.upserted.flat().filter((s) => s.article === "skills/example")
|
|
550
|
+
.length,
|
|
551
|
+
).toBeGreaterThan(0);
|
|
552
|
+
expect(outcome.articles).toBe(2);
|
|
553
|
+
expect(outcome.failures).toBe(0);
|
|
554
|
+
expect(calls.committed).toEqual([4242]);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("capability row still empty after retry is a failure: never deleted, checkpoint HELD", async () => {
|
|
558
|
+
const { deps: d, calls } = deps({
|
|
559
|
+
selectAllPages: async () => ["page-a", "skills/example"],
|
|
560
|
+
readPageBody: async (slug) =>
|
|
561
|
+
slug === "skills/example" ? "" : `body for ${slug}`,
|
|
562
|
+
});
|
|
563
|
+
const outcome = await backfillAllSections(CONFIG, d);
|
|
564
|
+
|
|
565
|
+
// The empty render must never wipe previously-good points with nothing.
|
|
566
|
+
expect(calls.deleted).toEqual(["page-a"]);
|
|
567
|
+
expect(
|
|
568
|
+
calls.upserted.flat().filter((s) => s.article === "skills/example"),
|
|
569
|
+
).toEqual([]);
|
|
570
|
+
expect(outcome.articles).toBe(1);
|
|
571
|
+
expect(outcome.failures).toBe(1);
|
|
572
|
+
expect(calls.committed).toEqual([]);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test("capability row missing from the first index snapshot is swept up after the main loop", async () => {
|
|
576
|
+
// Models the other face of the startup race: the capability store had not
|
|
577
|
+
// listed its rows in the page index yet when `selectAllPages` first ran,
|
|
578
|
+
// and has by the time the pass re-enumerates.
|
|
579
|
+
let listCalls = 0;
|
|
580
|
+
const { deps: d, calls } = deps({
|
|
581
|
+
selectAllPages: async () => {
|
|
582
|
+
listCalls += 1;
|
|
583
|
+
return listCalls === 1 ? ["page-a"] : ["page-a", "skills/example"];
|
|
584
|
+
},
|
|
585
|
+
readPageBody: async (slug) =>
|
|
586
|
+
slug === "skills/example"
|
|
587
|
+
? "# Skill: example\nexample capability body"
|
|
588
|
+
: `body for ${slug}`,
|
|
589
|
+
});
|
|
590
|
+
const outcome = await backfillAllSections(CONFIG, d);
|
|
591
|
+
|
|
592
|
+
expect(listCalls).toBe(2);
|
|
593
|
+
// Only the late CAPABILITY row is swept in; page-a is not re-embedded.
|
|
594
|
+
expect(calls.built).toEqual([["page-a"], ["skills/example"]]);
|
|
595
|
+
expect(calls.deleted).toEqual(["page-a", "skills/example"]);
|
|
596
|
+
expect(outcome.articles).toBe(2);
|
|
597
|
+
expect(outcome.failures).toBe(0);
|
|
598
|
+
expect(calls.committed).toEqual([4242]);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test("a real page with an empty body still empties its sections (capability guard does not apply)", async () => {
|
|
602
|
+
const { deps: d, calls } = deps({
|
|
603
|
+
selectAllPages: async () => ["page-empty"],
|
|
604
|
+
readPageBody: async () => "",
|
|
605
|
+
});
|
|
606
|
+
const outcome = await backfillAllSections(CONFIG, d);
|
|
607
|
+
|
|
608
|
+
// Existing behavior for on-disk pages: an empty body still replaces the
|
|
609
|
+
// page's sections (the chunker synthesizes a minimal head section) — only
|
|
610
|
+
// capability rows treat an empty body as "store not seeded".
|
|
611
|
+
expect(calls.deleted).toEqual(["page-empty"]);
|
|
612
|
+
expect(outcome.articles).toBe(1);
|
|
613
|
+
expect(outcome.failures).toBe(0);
|
|
614
|
+
expect(calls.committed).toEqual([4242]);
|
|
615
|
+
});
|
|
456
616
|
});
|