@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
|
@@ -1,38 +1,229 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The memory-v3 {@link Injector}
|
|
3
|
-
* - `memory-v3-live` on → orchestrate, log, render the working-set selection
|
|
4
|
-
* into a `<memory>` block, and return it at v2's dynamic-memory placement.
|
|
5
|
-
* - `memory-v3-shadow` on (live off) → orchestrate + log only, return `null`.
|
|
6
|
-
* - both off → return `null` (no orchestration).
|
|
2
|
+
* The memory-v3 {@link Injector}s: frozen net-new cards + ephemeral spotlight.
|
|
7
3
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Two injectors share one orchestration result per turn (memoized via
|
|
5
|
+
* {@link observeTurnOnce} so re-entry assemblies — overflow convergence,
|
|
6
|
+
* post-compaction re-injection — reuse the turn's selections instead of
|
|
7
|
+
* re-running the selector):
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* - {@link memoryV3Injector} (id `memory-v3`, `after-memory-prefix`): the
|
|
10
|
+
* PERSISTENT layer. Renders only this turn's NET-NEW selections — selections
|
|
11
|
+
* not already in the everInjected store — as compact cards inside one
|
|
12
|
+
* `<memory>` block and returns the block. The everInjected store write
|
|
13
|
+
* (`recordInjected`) and the prune-valve schedule are DEFERRED to a commit
|
|
14
|
+
* callback the block carries (`meta[MEMORY_V3_COMMIT_META_KEY]`): runtime
|
|
15
|
+
* assembly invokes it only when the turn's tail is a user message — the
|
|
16
|
+
* same gate as metadata capture — so a turn whose block silently fails to
|
|
17
|
+
* attach never claims its cards in the store (which would suppress them
|
|
18
|
+
* until compaction). Runtime assembly splices the block onto the current
|
|
19
|
+
* user message and the user-prompt-submit hook persists the unwrapped inner
|
|
20
|
+
* text under `metadata.memoryV3InjectedBlock`; `conversation.ts` rehydrates
|
|
21
|
+
* it on load. The block is FROZEN thereafter: prior turns' card blocks stay
|
|
22
|
+
* byte-identical in history, so they ride the provider's cached prefix and
|
|
23
|
+
* survive restarts — mirroring v2's `memoryInjectedBlock` mechanism. An
|
|
24
|
+
* all-repeat turn returns an EMPTY-TEXT block: assembly attaches nothing,
|
|
25
|
+
* but the block's presence still keys v2 suppression (v3 ran and owns the
|
|
26
|
+
* `<memory>` layer this turn). A `null` return (failure / empty selection /
|
|
27
|
+
* every net-new card rendered empty) leaves v2's block intact — fallback to
|
|
28
|
+
* v2 rather than a memory-less turn.
|
|
29
|
+
*
|
|
30
|
+
* - {@link memoryV3SpotlightInjector} (id `memory-v3-spotlight`,
|
|
31
|
+
* `append-user-tail`): the EPHEMERAL layer. Renders the top `spotlight.n`
|
|
32
|
+
* selected finder hits' matched sections, plus the previous
|
|
33
|
+
* `spotlight.windowTurns` turns' entries from an in-memory per-conversation
|
|
34
|
+
* ring (a daemon restart simply re-warms it), as a `<memory_spotlight>`
|
|
35
|
+
* block at the current-message tail. Assembly strip-and-replaces this block
|
|
36
|
+
* every turn (scoped to this block id only); it is never persisted to
|
|
37
|
+
* metadata.
|
|
38
|
+
*
|
|
39
|
+
* Flag gating is unchanged: `memory-v3-live` attaches blocks; `memory-v3-shadow`
|
|
40
|
+
* (live off) logs what WOULD inject (net-new slugs + bytes + spotlight refs)
|
|
41
|
+
* and attaches nothing; both off → no orchestration.
|
|
42
|
+
*
|
|
43
|
+
* Both injectors apply the same personal-memory trust gate as v2
|
|
44
|
+
* ({@link isPersonalMemoryAllowed}): an untrusted remote actor's turn
|
|
45
|
+
* produces nothing — no orchestration, no cards, no spotlight, and nothing
|
|
46
|
+
* recorded or persisted. Memory pages, skill/CLI capability cards, and
|
|
47
|
+
* matched-section spotlights all surface private user content — and because
|
|
48
|
+
* v3 cards are persisted to message metadata and rehydrated forever, the gate
|
|
49
|
+
* must also keep an untrusted turn from recording or persisting anything.
|
|
50
|
+
*
|
|
51
|
+
* Known mirror-of-v2 limitation: cards attached by mid-turn re-entry
|
|
52
|
+
* assemblies (post-compaction re-injection) live only in memory — metadata is
|
|
53
|
+
* persisted at the first-call site only — so a restart drops them while the
|
|
54
|
+
* store still claims them, exactly as v2's `lastInjectedBlock` reinject does.
|
|
55
|
+
* The next compaction clears the store and resets both layers.
|
|
15
56
|
*/
|
|
16
57
|
|
|
17
58
|
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
18
59
|
import { getConfig } from "../../../config/loader.js";
|
|
60
|
+
import { isPersonalMemoryAllowed } from "../../../daemon/trust-context.js";
|
|
61
|
+
import {
|
|
62
|
+
wrapMemoryBlock,
|
|
63
|
+
wrapMemorySpotlightBlock,
|
|
64
|
+
} from "../../../memory/memory-marker.js";
|
|
19
65
|
import { getLogger } from "../../../util/logger.js";
|
|
20
66
|
import {
|
|
21
67
|
type InjectionBlock,
|
|
22
68
|
type Injector,
|
|
23
69
|
type TurnContext,
|
|
24
70
|
} from "../../types.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
71
|
+
import { isCapabilitySlug } from "./capabilities.js";
|
|
72
|
+
import { cardBytes } from "./card.js";
|
|
73
|
+
import { getActiveSlugs, recordInjected } from "./ever-injected-store.js";
|
|
74
|
+
import type { OrchestrateResult } from "./orchestrate.js";
|
|
75
|
+
import { renderV3CardContent } from "./page-content.js";
|
|
76
|
+
import { schedulePruneValve } from "./prune.js";
|
|
77
|
+
import {
|
|
78
|
+
renderCardsBlockInner,
|
|
79
|
+
renderSpotlightInner,
|
|
80
|
+
type SpotlightEntry,
|
|
81
|
+
} from "./render-injection.js";
|
|
27
82
|
import {
|
|
28
83
|
MEMORY_V3_LIVE,
|
|
29
84
|
MEMORY_V3_SHADOW,
|
|
30
85
|
observeTurn,
|
|
31
86
|
} from "./shadow-plugin.js";
|
|
32
|
-
import {
|
|
87
|
+
import {
|
|
88
|
+
MEMORY_V3_BLOCK_ID,
|
|
89
|
+
MEMORY_V3_COMMIT_META_KEY,
|
|
90
|
+
MEMORY_V3_SPOTLIGHT_BLOCK_ID,
|
|
91
|
+
type Slug,
|
|
92
|
+
} from "./types.js";
|
|
33
93
|
|
|
34
94
|
const log = getLogger("memory-v3-shadow");
|
|
35
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Cap on the per-conversation maps below. Least-recently-touched entries are
|
|
98
|
+
* evicted first; an evicted conversation simply re-warms (spotlight) or
|
|
99
|
+
* re-runs orchestration (memo) on its next turn.
|
|
100
|
+
*/
|
|
101
|
+
const MAX_TRACKED_CONVERSATIONS = 256;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* LRU-set `key` on `map`: delete-then-set so a re-touched key moves to the
|
|
105
|
+
* back of the Map's insertion order (a plain `set` on an existing key keeps
|
|
106
|
+
* its original position, which would evict the most long-lived ACTIVE
|
|
107
|
+
* conversation first). Eviction only fires when inserting a genuinely new
|
|
108
|
+
* key at the cap.
|
|
109
|
+
*/
|
|
110
|
+
function lruSet<V>(map: Map<string, V>, key: string, value: V): void {
|
|
111
|
+
if (map.has(key)) {
|
|
112
|
+
map.delete(key);
|
|
113
|
+
} else if (map.size >= MAX_TRACKED_CONVERSATIONS) {
|
|
114
|
+
const oldest = map.keys().next().value;
|
|
115
|
+
if (oldest !== undefined) map.delete(oldest);
|
|
116
|
+
}
|
|
117
|
+
map.set(key, value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── shared per-turn orchestration memo ─────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
interface ObservedTurn {
|
|
123
|
+
turnIndex: number;
|
|
124
|
+
result: Promise<OrchestrateResult | null>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Latest observed turn per conversation (both injectors + re-entry sites
|
|
128
|
+
* share one orchestration per turn). */
|
|
129
|
+
const observedTurns = new Map<string, ObservedTurn>();
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run {@link observeTurn} once per (conversation, turn) and memoize the
|
|
133
|
+
* promise. The cards and spotlight injectors both consume the result, and
|
|
134
|
+
* re-entry assemblies within the same turn (overflow convergence,
|
|
135
|
+
* post-compaction re-injection) reuse the turn's selections rather than
|
|
136
|
+
* paying a second selector call. A new `turnIndex` replaces the entry, so the
|
|
137
|
+
* memo never holds more than one turn per conversation.
|
|
138
|
+
*/
|
|
139
|
+
function observeTurnOnce(
|
|
140
|
+
conversationId: string,
|
|
141
|
+
turnIndex: number,
|
|
142
|
+
): Promise<OrchestrateResult | null> {
|
|
143
|
+
const cached = observedTurns.get(conversationId);
|
|
144
|
+
if (cached && cached.turnIndex === turnIndex) return cached.result;
|
|
145
|
+
const result = observeTurn(conversationId, turnIndex);
|
|
146
|
+
lruSet(observedTurns, conversationId, { turnIndex, result });
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── ephemeral spotlight ring ────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
interface SpotlightTurn {
|
|
153
|
+
turnIndex: number;
|
|
154
|
+
entries: SpotlightEntry[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Recent turns' spotlight entries per conversation, oldest → newest. */
|
|
158
|
+
const spotlightRings = new Map<string, SpotlightTurn[]>();
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Compute this turn's spotlight entries: the top `n` SELECTED finder hits'
|
|
162
|
+
* matched sections, in finder surfacing order (needle → dense → edge
|
|
163
|
+
* precedence). A finder hit on a stable-prefix page keeps its matched-section
|
|
164
|
+
* ref in the orchestrate result, so core/hot pages with current relevance
|
|
165
|
+
* spotlight too.
|
|
166
|
+
*/
|
|
167
|
+
function computeSpotlightEntries(
|
|
168
|
+
result: OrchestrateResult,
|
|
169
|
+
n: number,
|
|
170
|
+
): SpotlightEntry[] {
|
|
171
|
+
const selected = new Set<Slug>(result.selections.map((s) => s.slug));
|
|
172
|
+
const entries: SpotlightEntry[] = [];
|
|
173
|
+
for (const candidate of result.lanes.finder) {
|
|
174
|
+
if (entries.length >= n) break;
|
|
175
|
+
if (!selected.has(candidate.slug)) continue;
|
|
176
|
+
const section = result.matchedSections.get(candidate.slug);
|
|
177
|
+
if (!section) continue;
|
|
178
|
+
entries.push({
|
|
179
|
+
slug: candidate.slug,
|
|
180
|
+
title: section.title,
|
|
181
|
+
text: section.text,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return entries;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Fold this turn's entries into the conversation's ring and return the
|
|
189
|
+
* rendered window: current turn first, then previous turns newest-first,
|
|
190
|
+
* deduped by slug § heading, bounded by `n × (windowTurns + 1)` entries by
|
|
191
|
+
* construction. Re-observing the SAME turn (re-entry assembly) replaces that
|
|
192
|
+
* turn's entry rather than accumulating.
|
|
193
|
+
*/
|
|
194
|
+
function updateSpotlightWindow(
|
|
195
|
+
conversationId: string,
|
|
196
|
+
turnIndex: number,
|
|
197
|
+
entries: SpotlightEntry[],
|
|
198
|
+
windowTurns: number,
|
|
199
|
+
): SpotlightEntry[] {
|
|
200
|
+
const prior = (spotlightRings.get(conversationId) ?? []).filter(
|
|
201
|
+
(t) => t.turnIndex < turnIndex && t.turnIndex >= turnIndex - windowTurns,
|
|
202
|
+
);
|
|
203
|
+
lruSet(spotlightRings, conversationId, [...prior, { turnIndex, entries }]);
|
|
204
|
+
|
|
205
|
+
const window: SpotlightEntry[] = [];
|
|
206
|
+
const seen = new Set<string>();
|
|
207
|
+
// Newest first: this turn's entries, then prior turns newest → oldest.
|
|
208
|
+
for (const turn of [{ turnIndex, entries }, ...prior.reverse()]) {
|
|
209
|
+
for (const entry of turn.entries) {
|
|
210
|
+
const key = `${entry.slug}§${entry.title}`;
|
|
211
|
+
if (seen.has(key)) continue;
|
|
212
|
+
seen.add(key);
|
|
213
|
+
window.push(entry);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return window;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Test-only reset for the per-turn memo and the spotlight rings. */
|
|
220
|
+
export function resetMemoryV3InjectorStateForTests(): void {
|
|
221
|
+
observedTurns.clear();
|
|
222
|
+
spotlightRings.clear();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── injectors ───────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
36
227
|
export const memoryV3Injector: Injector = {
|
|
37
228
|
name: "memory-v3-shadow",
|
|
38
229
|
// High order so it sorts last; the live `<memory>` block uses the
|
|
@@ -41,26 +232,97 @@ export const memoryV3Injector: Injector = {
|
|
|
41
232
|
order: 1000,
|
|
42
233
|
async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
|
|
43
234
|
const config = getConfig();
|
|
235
|
+
if (config.memory.enabled === false) return null;
|
|
44
236
|
const live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
|
|
45
237
|
const shadow = isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config);
|
|
46
238
|
if (!live && !shadow) return null;
|
|
239
|
+
if (!isPersonalMemoryAllowed(ctx.trust)) return null;
|
|
47
240
|
|
|
48
|
-
const result = await
|
|
49
|
-
|
|
241
|
+
const result = await observeTurnOnce(ctx.conversationId, ctx.turnIndex);
|
|
242
|
+
// Empty selection falls back to v2 (return null): v2 suppression keys off
|
|
243
|
+
// BOTH the flag AND a produced block, so a selector failure or a turn with
|
|
244
|
+
// nothing selected ships v2 memory rather than no memory.
|
|
245
|
+
if (!result || result.selections.length === 0) return null;
|
|
50
246
|
|
|
51
247
|
try {
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
248
|
+
const active = getActiveSlugs(ctx.conversationId);
|
|
249
|
+
const netNew = result.selections
|
|
250
|
+
.map((s) => s.slug)
|
|
251
|
+
.filter((slug) => !active.has(slug));
|
|
252
|
+
|
|
253
|
+
// Render net-new cards, skipping slugs that resolve to no content
|
|
254
|
+
// (deleted pages, unresolvable capabilities) — nothing is attached for
|
|
255
|
+
// them, so nothing is recorded either.
|
|
256
|
+
const cards: Array<{ slug: Slug; card: string }> = [];
|
|
257
|
+
for (const slug of netNew) {
|
|
258
|
+
const card = await renderV3CardContent(slug);
|
|
259
|
+
if (card.trim().length > 0) cards.push({ slug, card });
|
|
260
|
+
}
|
|
261
|
+
// Every net-new card rendered empty: return null (fall back to v2)
|
|
262
|
+
// rather than an empty-text block — suppressing v2 with nothing to show
|
|
263
|
+
// would ship a memory-less turn. Distinct from the all-repeat case
|
|
264
|
+
// (empty `netNew`), where the empty block correctly keeps v2 suppressed
|
|
265
|
+
// because the cards already ride history.
|
|
266
|
+
if (netNew.length > 0 && cards.length === 0) return null;
|
|
267
|
+
const entries = cards.map(({ slug, card }) => ({
|
|
268
|
+
slug,
|
|
269
|
+
// Capability cards (skills / CLI commands) render with their own
|
|
270
|
+
// `# Skill:` / `# CLI command:` headers, which the prune valve's
|
|
271
|
+
// `# memory/concepts/<slug>.md` section grammar can never locate to
|
|
272
|
+
// free. Record them at zero bytes so they never inflate the freeable
|
|
273
|
+
// resident accounting (the valve would otherwise loop-fire on bytes
|
|
274
|
+
// it cannot free).
|
|
275
|
+
bytes: isCapabilitySlug(slug) ? 0 : cardBytes(card),
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
if (!live) {
|
|
279
|
+
// Shadow mode: log what WOULD inject, attach and record nothing.
|
|
280
|
+
const spotlightRefs = computeSpotlightEntries(
|
|
281
|
+
result,
|
|
282
|
+
config.memory.v3.spotlight.n,
|
|
283
|
+
).map((e) => `${e.slug}§${e.title}`);
|
|
284
|
+
log.info(
|
|
285
|
+
{
|
|
286
|
+
conversationId: ctx.conversationId,
|
|
287
|
+
turnIndex: ctx.turnIndex,
|
|
288
|
+
netNew: entries,
|
|
289
|
+
netNewBytes: entries.reduce((sum, e) => sum + e.bytes, 0),
|
|
290
|
+
spotlightRefs,
|
|
291
|
+
},
|
|
292
|
+
"memory-v3 shadow: would inject net-new cards + spotlight",
|
|
293
|
+
);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// The everInjected store write and the prune-valve schedule are
|
|
298
|
+
// DEFERRED to this commit callback, invoked by runtime assembly at the
|
|
299
|
+
// point where attachment is guaranteed (the turn's tail is a user
|
|
300
|
+
// message — the same gate as metadata capture). Recording here in
|
|
301
|
+
// `produce()` would let a never-attached turn (non-user tail) claim
|
|
302
|
+
// cards in the store, suppressing them until compaction. The valve is
|
|
303
|
+
// scheduled after `recordInjected` so the resident accounting includes
|
|
304
|
+
// this turn's cards; core/hot lane members are exempt — the selector's
|
|
305
|
+
// stable prefix must never be pruned out from under it.
|
|
306
|
+
const commit = (): void => {
|
|
307
|
+
recordInjected(ctx.conversationId, entries);
|
|
308
|
+
schedulePruneValve(ctx.conversationId, {
|
|
309
|
+
exemptSlugs: new Set<Slug>([
|
|
310
|
+
...result.lanes.core,
|
|
311
|
+
...result.lanes.hot,
|
|
312
|
+
]),
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Empty net-new → empty-text block: assembly attaches no content
|
|
317
|
+
// (`applyInjectionBlock` no-ops empty text) but the block's presence
|
|
318
|
+
// still marks v3 as this turn's `<memory>` source for v2 suppression.
|
|
319
|
+
const inner = renderCardsBlockInner(cards.map((c) => c.card));
|
|
59
320
|
return {
|
|
60
321
|
id: MEMORY_V3_BLOCK_ID,
|
|
61
|
-
text,
|
|
322
|
+
text: inner.length === 0 ? "" : wrapMemoryBlock(inner),
|
|
62
323
|
// Mirror v2's dynamic `<memory>` block placement.
|
|
63
324
|
placement: "after-memory-prefix",
|
|
325
|
+
meta: { [MEMORY_V3_COMMIT_META_KEY]: commit },
|
|
64
326
|
};
|
|
65
327
|
} catch (err) {
|
|
66
328
|
log.warn(
|
|
@@ -74,3 +336,48 @@ export const memoryV3Injector: Injector = {
|
|
|
74
336
|
}
|
|
75
337
|
},
|
|
76
338
|
};
|
|
339
|
+
|
|
340
|
+
export const memoryV3SpotlightInjector: Injector = {
|
|
341
|
+
name: "memory-v3-spotlight",
|
|
342
|
+
// After the cards injector so the shared memo is (usually) already primed.
|
|
343
|
+
order: 1001,
|
|
344
|
+
async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
|
|
345
|
+
const config = getConfig();
|
|
346
|
+
if (config.memory.enabled === false) return null;
|
|
347
|
+
// Live-only: shadow mode logs spotlight refs from the cards injector and
|
|
348
|
+
// must keep the turn untouched (no ring state either, so a later
|
|
349
|
+
// live-flag flip starts from a clean window).
|
|
350
|
+
if (!isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config)) return null;
|
|
351
|
+
if (!isPersonalMemoryAllowed(ctx.trust)) return null;
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const result = await observeTurnOnce(ctx.conversationId, ctx.turnIndex);
|
|
355
|
+
if (!result || result.selections.length === 0) return null;
|
|
356
|
+
|
|
357
|
+
const { n, windowTurns } = config.memory.v3.spotlight;
|
|
358
|
+
const current = computeSpotlightEntries(result, n);
|
|
359
|
+
const window = updateSpotlightWindow(
|
|
360
|
+
ctx.conversationId,
|
|
361
|
+
ctx.turnIndex,
|
|
362
|
+
current,
|
|
363
|
+
windowTurns,
|
|
364
|
+
);
|
|
365
|
+
if (window.length === 0) return null;
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
id: MEMORY_V3_SPOTLIGHT_BLOCK_ID,
|
|
369
|
+
text: wrapMemorySpotlightBlock(renderSpotlightInner(window)),
|
|
370
|
+
placement: "append-user-tail",
|
|
371
|
+
};
|
|
372
|
+
} catch (err) {
|
|
373
|
+
log.warn(
|
|
374
|
+
{
|
|
375
|
+
err: err instanceof Error ? err.message : String(err),
|
|
376
|
+
conversationId: ctx.conversationId,
|
|
377
|
+
},
|
|
378
|
+
"memory-v3 spotlight render failed (non-fatal) — skipping spotlight",
|
|
379
|
+
);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Memory v3 — `memory_v3_maintain` job handler.
|
|
3
3
|
*
|
|
4
4
|
* A flag-gated, best-effort self-maintenance pass over the v3 section dense
|
|
5
|
-
* store and the in-memory lanes. It runs
|
|
5
|
+
* store and the in-memory lanes. It runs four independent stages, in order:
|
|
6
6
|
*
|
|
7
7
|
* 1. **Section re-embed** — diff the page index by `modifiedAt` against the
|
|
8
8
|
* last successful pass (the high-water mark below), and for every page that
|
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
* (it only names live pages), so without this its section points would
|
|
23
23
|
* linger in Qdrant and the dense lane could still surface the deleted page.
|
|
24
24
|
* Synthetic capability rows are in the page index, so they are never pruned.
|
|
25
|
-
* 3. **
|
|
25
|
+
* 3. **Core-set validation** — load the maintainer-curated core set
|
|
26
|
+
* (`memory/core-pages.md`) and report entries whose page no longer exists
|
|
27
|
+
* in the page index (dangling slugs) via the log + outcome. The file is
|
|
28
|
+
* maintainer-owned, so this stage NEVER edits it — the maintainer fixes
|
|
29
|
+
* dangling entries at the next consolidation pass.
|
|
30
|
+
* 4. **Lane invalidation** — `invalidateLanes()` so the next turn rebuilds the
|
|
26
31
|
* in-memory section index, needle, and edge graph from the freshly-updated
|
|
27
32
|
* pages.
|
|
28
33
|
*
|
|
@@ -35,8 +40,8 @@
|
|
|
35
40
|
*
|
|
36
41
|
* Dependency-injectable: `deps` lets tests substitute the page-index reader,
|
|
37
42
|
* section builder, dense-store ops (including the prune-stage
|
|
38
|
-
* `listSectionArticles`/`listIndexedSlugs` collaborators),
|
|
39
|
-
* without process-global module mocks.
|
|
43
|
+
* `listSectionArticles`/`listIndexedSlugs` collaborators), the core-set loader,
|
|
44
|
+
* and `invalidateLanes` without process-global module mocks.
|
|
40
45
|
*/
|
|
41
46
|
|
|
42
47
|
import { isAssistantFeatureFlagEnabled } from "../../../config/assistant-feature-flags.js";
|
|
@@ -50,7 +55,8 @@ import { getPageIndex } from "../../../memory/v2/page-index.js";
|
|
|
50
55
|
import { readPage } from "../../../memory/v2/page-store.js";
|
|
51
56
|
import { getLogger } from "../../../util/logger.js";
|
|
52
57
|
import { getWorkspaceDir } from "../../../util/platform.js";
|
|
53
|
-
import { capabilityOrDiskBody } from "./capabilities.js";
|
|
58
|
+
import { capabilityOrDiskBody, isCapabilitySlug } from "./capabilities.js";
|
|
59
|
+
import { loadCoreSet as realLoadCoreSet } from "./core-set.js";
|
|
54
60
|
import {
|
|
55
61
|
deleteSectionsForArticle as realDeleteSectionsForArticle,
|
|
56
62
|
ensureSectionCollection as realEnsureSectionCollection,
|
|
@@ -126,6 +132,12 @@ export interface MaintainJobDeps {
|
|
|
126
132
|
* from this set. Includes synthetic capability rows so they are never pruned.
|
|
127
133
|
*/
|
|
128
134
|
listIndexedSlugs: () => Promise<Slug[]>;
|
|
135
|
+
/**
|
|
136
|
+
* The maintainer-curated core set from `memory/core-pages.md`. The validation
|
|
137
|
+
* stage diffs it against the live page-index slugs and REPORTS dangling
|
|
138
|
+
* entries only — the file is maintainer-owned and is never edited here.
|
|
139
|
+
*/
|
|
140
|
+
loadCoreSet: () => Slug[];
|
|
129
141
|
/** Drop the memoized v3 lanes so the next turn rebuilds them. */
|
|
130
142
|
invalidateLanes: () => void;
|
|
131
143
|
/** Active assistant config (for the dense-store/embedding calls). */
|
|
@@ -140,7 +152,12 @@ export interface MaintainJobDeps {
|
|
|
140
152
|
* collection-ensure step rather than the change-delta machinery.
|
|
141
153
|
*/
|
|
142
154
|
export interface BackfillJobDeps {
|
|
143
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* All page-index slugs, including synthetic skill/CLI capability rows.
|
|
157
|
+
* Called once at the start and once after the main loop — the second call
|
|
158
|
+
* catches capability rows whose startup seed had not yet listed them in the
|
|
159
|
+
* index when the first snapshot was taken.
|
|
160
|
+
*/
|
|
144
161
|
selectAllPages: () => Promise<Slug[]>;
|
|
145
162
|
/** Create the section collection if absent before the first upsert. */
|
|
146
163
|
ensureSectionCollection: typeof realEnsureSectionCollection;
|
|
@@ -188,6 +205,12 @@ export interface MaintainOutcome {
|
|
|
188
205
|
pruned: number;
|
|
189
206
|
/** Prune deletes that threw (and were contained). */
|
|
190
207
|
pruneFailures: number;
|
|
208
|
+
/**
|
|
209
|
+
* Core-set entries (`memory/core-pages.md`) whose page no longer exists in
|
|
210
|
+
* the page index. Reported for the maintainer to fix at the next
|
|
211
|
+
* consolidation pass — the file itself is never mutated.
|
|
212
|
+
*/
|
|
213
|
+
danglingCoreSlugs: Slug[];
|
|
191
214
|
/** Whether the in-memory lanes were invalidated. */
|
|
192
215
|
invalidated: boolean;
|
|
193
216
|
/** Stages that threw (and were contained). */
|
|
@@ -284,6 +307,7 @@ function defaultDeps(config: AssistantConfig): MaintainJobDeps {
|
|
|
284
307
|
commitEmbedHighWater,
|
|
285
308
|
listSectionArticles: () => realListSectionArticles(config),
|
|
286
309
|
listIndexedSlugs: () => selectAllPagesFromWorkspace(workspaceDir),
|
|
310
|
+
loadCoreSet: () => realLoadCoreSet(workspaceDir),
|
|
287
311
|
invalidateLanes: realInvalidateLanes,
|
|
288
312
|
config,
|
|
289
313
|
};
|
|
@@ -393,6 +417,22 @@ async function pruneDeletedPages(
|
|
|
393
417
|
* checkpoint is held (not advanced): a failed page was delete-then-upsert'd and
|
|
394
418
|
* so has no sections, and advancing past its mtime would hide it from the
|
|
395
419
|
* incremental selector forever — holding the mark lets the next pass re-embed it.
|
|
420
|
+
*
|
|
421
|
+
* Capability rows get an extra guard. Their stores (skill/CLI caches) are
|
|
422
|
+
* seeded fire-and-forget at daemon startup, so a backfill fired shortly after a
|
|
423
|
+
* restart can race the seed in two ways: (a) the rows are missing from the page
|
|
424
|
+
* index entirely when `selectAllPages` snapshots it, so they are silently
|
|
425
|
+
* absent from the work list; (b) the rows are listed but their content cache is
|
|
426
|
+
* still cold, so they render `""` — and the section chunker synthesizes a junk
|
|
427
|
+
* one-line head section from the bare slug, which would be embedded as if it
|
|
428
|
+
* were real content. Both end with capabilities effectively missing from the
|
|
429
|
+
* dense lane while the pass reports zero failures. So: a capability row whose
|
|
430
|
+
* body resolves empty is skipped without deleting its existing points (never
|
|
431
|
+
* replace good points with a blank), the page index is re-enumerated after the
|
|
432
|
+
* main loop (which spans long enough for the startup seed to finish) to pick up
|
|
433
|
+
* late-appearing capability rows, cold rows are retried, and any capability row
|
|
434
|
+
* that still resolves empty counts as a failure — holding the checkpoint and
|
|
435
|
+
* surfacing in the outcome instead of silently succeeding.
|
|
396
436
|
*/
|
|
397
437
|
export async function backfillAllSections(
|
|
398
438
|
config: AssistantConfig,
|
|
@@ -407,29 +447,65 @@ export async function backfillAllSections(
|
|
|
407
447
|
let articles = 0;
|
|
408
448
|
let sections = 0;
|
|
409
449
|
let failures = 0;
|
|
410
|
-
|
|
450
|
+
|
|
451
|
+
// "cold" is only reported for capability rows whose body resolves empty —
|
|
452
|
+
// their store has not seeded yet. Real pages never report cold: an empty
|
|
453
|
+
// on-disk body legitimately replaces the page's sections.
|
|
454
|
+
const embedOne = async (
|
|
455
|
+
slug: Slug,
|
|
456
|
+
): Promise<"embedded" | "cold" | "failed"> => {
|
|
411
457
|
try {
|
|
458
|
+
if (isCapabilitySlug(slug)) {
|
|
459
|
+
const body = await deps.readPageBody(slug);
|
|
460
|
+
if (body.trim().length === 0) return "cold";
|
|
461
|
+
}
|
|
412
462
|
const index = await deps.buildSectionIndex([slug], deps.readPageBody);
|
|
413
463
|
await deps.deleteSectionsForArticle(deps.config, slug);
|
|
414
464
|
await deps.upsertSections(deps.config, index.sections);
|
|
415
465
|
articles += 1;
|
|
416
466
|
sections += index.sections.length;
|
|
467
|
+
return "embedded";
|
|
417
468
|
} catch (err) {
|
|
418
469
|
failures += 1;
|
|
419
470
|
log.warn(
|
|
420
471
|
{ slug, err: err instanceof Error ? err.message : String(err) },
|
|
421
472
|
"memory-v3 backfill: page embed failed (non-fatal)",
|
|
422
473
|
);
|
|
474
|
+
return "failed";
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const coldCapabilities: Slug[] = [];
|
|
479
|
+
for (const slug of slugs) {
|
|
480
|
+
if ((await embedOne(slug)) === "cold") coldCapabilities.push(slug);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Late-capability sweep + cold retry. The main loop spans minutes on a real
|
|
484
|
+
// corpus, so the startup-seeded capability stores have typically warmed by
|
|
485
|
+
// now: re-enumerate the index to catch capability rows that were not listed
|
|
486
|
+
// yet at snapshot time, and retry the ones that rendered cold. A row that
|
|
487
|
+
// still resolves empty is a real failure.
|
|
488
|
+
const processed = new Set(slugs);
|
|
489
|
+
const lateCapabilities = (await deps.selectAllPages()).filter(
|
|
490
|
+
(slug) => isCapabilitySlug(slug) && !processed.has(slug),
|
|
491
|
+
);
|
|
492
|
+
for (const slug of [...coldCapabilities, ...lateCapabilities]) {
|
|
493
|
+
if ((await embedOne(slug)) === "cold") {
|
|
494
|
+
failures += 1;
|
|
495
|
+
log.warn(
|
|
496
|
+
{ slug },
|
|
497
|
+
"memory-v3 backfill: capability row still empty after retry (capability store not seeded?) — re-run backfill-sections once the store seeds",
|
|
498
|
+
);
|
|
423
499
|
}
|
|
424
500
|
}
|
|
425
501
|
|
|
426
502
|
// Advance the maintain high-water mark only when every page embedded cleanly,
|
|
427
503
|
// so the next incremental pass deltas from here rather than re-embedding
|
|
428
|
-
// everything. A
|
|
429
|
-
// sections
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
// idempotent and cheap.
|
|
504
|
+
// everything. A thrown failure was delete-then-upsert'd (it now has no
|
|
505
|
+
// sections) and an empty capability row was never embedded at all; advancing
|
|
506
|
+
// past them would hide them from `computeChangedPages` forever. Holding the
|
|
507
|
+
// checkpoint keeps failed pages above the mark so the next pass re-embeds
|
|
508
|
+
// them — re-embedding the succeeded pages too is idempotent and cheap.
|
|
433
509
|
if (failures === 0) {
|
|
434
510
|
deps.commitEmbedHighWater(startedAtMs);
|
|
435
511
|
} else {
|
|
@@ -463,6 +539,7 @@ export async function maintainJob(
|
|
|
463
539
|
reembedFailures: 0,
|
|
464
540
|
pruned: 0,
|
|
465
541
|
pruneFailures: 0,
|
|
542
|
+
danglingCoreSlugs: [],
|
|
466
543
|
invalidated: false,
|
|
467
544
|
failures: [],
|
|
468
545
|
};
|
|
@@ -527,7 +604,35 @@ export async function maintainJob(
|
|
|
527
604
|
);
|
|
528
605
|
}
|
|
529
606
|
|
|
530
|
-
// Stage 3:
|
|
607
|
+
// Stage 3: validate the maintainer-curated core set. Diff `core-pages.md`
|
|
608
|
+
// entries against the live page-index slugs and REPORT dangling entries
|
|
609
|
+
// (pages that were renamed or deleted) through the log + outcome — the file
|
|
610
|
+
// is maintainer-owned, so it is never auto-edited; the maintainer fixes it at
|
|
611
|
+
// the next consolidation pass. No hot/core recompute is needed in this job:
|
|
612
|
+
// the lanes (including the core and hot prefixes) are computed at lane init,
|
|
613
|
+
// and the invalidateLanes() stage below already forces a next-turn rebuild —
|
|
614
|
+
// that rebuild IS the consolidation-cadence refresh.
|
|
615
|
+
try {
|
|
616
|
+
const coreSlugs = deps.loadCoreSet();
|
|
617
|
+
if (coreSlugs.length > 0) {
|
|
618
|
+
const live = new Set(await deps.listIndexedSlugs());
|
|
619
|
+
outcome.danglingCoreSlugs = coreSlugs.filter((slug) => !live.has(slug));
|
|
620
|
+
if (outcome.danglingCoreSlugs.length > 0) {
|
|
621
|
+
log.warn(
|
|
622
|
+
{ dangling: outcome.danglingCoreSlugs },
|
|
623
|
+
"memory-v3 maintain: core-pages.md lists pages missing from the index — review the file at the next consolidation",
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
} catch (err) {
|
|
628
|
+
outcome.failures.push("core-validate");
|
|
629
|
+
log.warn(
|
|
630
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
631
|
+
"memory-v3 maintain: core-set validation failed (non-fatal)",
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Stage 4: rebuild section index + needle + edge graph on the next turn.
|
|
531
636
|
try {
|
|
532
637
|
deps.invalidateLanes();
|
|
533
638
|
outcome.invalidated = true;
|
|
@@ -545,6 +650,7 @@ export async function maintainJob(
|
|
|
545
650
|
reembedFailures: outcome.reembedFailures,
|
|
546
651
|
pruned: outcome.pruned,
|
|
547
652
|
pruneFailures: outcome.pruneFailures,
|
|
653
|
+
danglingCoreSlugs: outcome.danglingCoreSlugs,
|
|
548
654
|
invalidated: outcome.invalidated,
|
|
549
655
|
failures: outcome.failures,
|
|
550
656
|
},
|