@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,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Verifies that `emitNotificationSignal` invokes the home-feed side
|
|
3
|
-
* effect after dispatching, and that the side effect's background-only
|
|
4
|
-
* filter correctly suppresses interactive signals.
|
|
5
|
-
*
|
|
6
|
-
* The side effect itself is unit-tested in `home-feed-side-effect.test.ts`;
|
|
7
|
-
* this test exercises the wire-up boundary in `emit-signal.ts` by mocking
|
|
8
|
-
* at the `appendFeedItem` / `getConversation` boundary so the real
|
|
9
|
-
* background-filter logic runs end-to-end.
|
|
10
|
-
*/
|
|
11
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
-
|
|
13
|
-
import type { FeedItem } from "../../home/feed-types.js";
|
|
14
|
-
import type {
|
|
15
|
-
NotificationDecision,
|
|
16
|
-
NotificationDeliveryResult,
|
|
17
|
-
} from "../types.js";
|
|
18
|
-
|
|
19
|
-
// ── Module mocks ───────────────────────────────────────────────────────
|
|
20
|
-
//
|
|
21
|
-
// `mock.module` is hoisted, so these intercepts apply before the module
|
|
22
|
-
// under test resolves its imports. Closures over module-scoped state
|
|
23
|
-
// let each test reset and inspect captured calls afterwards.
|
|
24
|
-
|
|
25
|
-
const appendCalls: FeedItem[] = [];
|
|
26
|
-
let conversationRow: { conversationType: string } | null = null;
|
|
27
|
-
|
|
28
|
-
mock.module("../../home/feed-writer.js", () => ({
|
|
29
|
-
appendFeedItem: async (item: FeedItem) => {
|
|
30
|
-
appendCalls.push(item);
|
|
31
|
-
},
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
// home-feed-side-effect.ts only consumes `getConversation`.
|
|
35
|
-
mock.module("../../memory/conversation-crud.js", () => ({
|
|
36
|
-
getConversation: () => conversationRow,
|
|
37
|
-
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
38
|
-
}));
|
|
39
|
-
|
|
40
|
-
// Stub the broadcaster so emit-signal's `getBroadcaster()` does not need
|
|
41
|
-
// to build real adapters or pull in `conversation-pairing.ts` transitively
|
|
42
|
-
// (which would force us to mirror the full `conversation-crud.js` surface).
|
|
43
|
-
class StubBroadcaster {
|
|
44
|
-
setOnConversationCreated() {}
|
|
45
|
-
async broadcastDecision() {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
mock.module("../broadcaster.js", () => ({
|
|
50
|
-
NotificationBroadcaster: StubBroadcaster,
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
mock.module("../adapters/macos.js", () => ({
|
|
54
|
-
VellumAdapter: class {},
|
|
55
|
-
isGuardianSensitiveEvent: () => false,
|
|
56
|
-
}));
|
|
57
|
-
mock.module("../adapters/slack.js", () => ({
|
|
58
|
-
SlackAdapter: class {},
|
|
59
|
-
}));
|
|
60
|
-
mock.module("../adapters/telegram.js", () => ({
|
|
61
|
-
TelegramAdapter: class {},
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
// Stub out the persistence + channel resolution layers so emitNotificationSignal
|
|
65
|
-
// can run end-to-end without touching the DB / contacts store / LLM.
|
|
66
|
-
|
|
67
|
-
mock.module("../events-store.js", () => ({
|
|
68
|
-
createEvent: (params: { id: string }) => ({
|
|
69
|
-
id: params.id,
|
|
70
|
-
sourceEventName: "schedule.notify",
|
|
71
|
-
sourceChannel: "scheduler",
|
|
72
|
-
sourceContextId: "conv-source-1",
|
|
73
|
-
attentionHintsJson: "{}",
|
|
74
|
-
payloadJson: "{}",
|
|
75
|
-
dedupeKey: null,
|
|
76
|
-
createdAt: 0,
|
|
77
|
-
updatedAt: 0,
|
|
78
|
-
}),
|
|
79
|
-
updateEventDedupeKey: () => {},
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
mock.module("../decisions-store.js", () => ({
|
|
83
|
-
updateDecision: () => {},
|
|
84
|
-
}));
|
|
85
|
-
|
|
86
|
-
mock.module("../../contacts/contact-store.js", () => ({
|
|
87
|
-
findGuardianForChannel: () => null,
|
|
88
|
-
listGuardianChannels: () => [],
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
const stubDecision: NotificationDecision = {
|
|
92
|
-
shouldNotify: true,
|
|
93
|
-
selectedChannels: ["vellum"],
|
|
94
|
-
reasoningSummary: "test",
|
|
95
|
-
renderedCopy: {
|
|
96
|
-
vellum: { title: "Background job done", body: "Summary of what happened." },
|
|
97
|
-
},
|
|
98
|
-
dedupeKey: "dk-1",
|
|
99
|
-
confidence: 1,
|
|
100
|
-
fallbackUsed: false,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
mock.module("../decision-engine.js", () => ({
|
|
104
|
-
evaluateSignal: async () => stubDecision,
|
|
105
|
-
// Pass the decision through unchanged — emit-signal calls this after
|
|
106
|
-
// evaluateSignal and only re-persists when the reference changes.
|
|
107
|
-
enforceRoutingIntent: (decision: NotificationDecision) => decision,
|
|
108
|
-
}));
|
|
109
|
-
|
|
110
|
-
mock.module("../deterministic-checks.js", () => ({
|
|
111
|
-
runDeterministicChecks: async () => ({ passed: true }),
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
const stubDeliveryResults: NotificationDeliveryResult[] = [
|
|
115
|
-
{
|
|
116
|
-
channel: "vellum",
|
|
117
|
-
destination: "vellum-client",
|
|
118
|
-
status: "sent",
|
|
119
|
-
conversationId: "conv-vellum-1",
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
mock.module("../runtime-dispatch.js", () => ({
|
|
124
|
-
dispatchDecision: async () => ({
|
|
125
|
-
dispatched: true,
|
|
126
|
-
reason: "Dispatched to 1/1 channels",
|
|
127
|
-
deliveryResults: stubDeliveryResults,
|
|
128
|
-
}),
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
const { emitNotificationSignal } = await import("../emit-signal.js");
|
|
132
|
-
|
|
133
|
-
// ── Tests ──────────────────────────────────────────────────────────────
|
|
134
|
-
|
|
135
|
-
beforeEach(() => {
|
|
136
|
-
appendCalls.length = 0;
|
|
137
|
-
conversationRow = null;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe("emitNotificationSignal home-feed wire-up", () => {
|
|
141
|
-
test("background-conversation signal triggers appendFeedItem", async () => {
|
|
142
|
-
conversationRow = { conversationType: "background" };
|
|
143
|
-
|
|
144
|
-
const result = await emitNotificationSignal({
|
|
145
|
-
sourceEventName: "schedule.notify",
|
|
146
|
-
sourceChannel: "scheduler",
|
|
147
|
-
sourceContextId: "conv-source-1",
|
|
148
|
-
contextPayload: { title: "Background job done" },
|
|
149
|
-
attentionHints: {
|
|
150
|
-
requiresAction: false,
|
|
151
|
-
urgency: "medium",
|
|
152
|
-
isAsyncBackground: false,
|
|
153
|
-
visibleInSourceNow: false,
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
expect(result.dispatched).toBe(true);
|
|
158
|
-
expect(appendCalls).toHaveLength(1);
|
|
159
|
-
const appended = appendCalls[0]!;
|
|
160
|
-
expect(appended.id).toBe(`notif:${result.signalId}`);
|
|
161
|
-
expect(appended.title).toBe("Background job done");
|
|
162
|
-
expect(appended.summary).toBe("Summary of what happened.");
|
|
163
|
-
// The feed item's conversationId points to the source conversation
|
|
164
|
-
// that emitted the signal, not the conversation the notification
|
|
165
|
-
// pipeline spawned to handle it.
|
|
166
|
-
expect(appended.conversationId).toBe("conv-source-1");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("interactive standard conversation does NOT trigger appendFeedItem", async () => {
|
|
170
|
-
conversationRow = { conversationType: "standard" };
|
|
171
|
-
|
|
172
|
-
const result = await emitNotificationSignal({
|
|
173
|
-
sourceEventName: "schedule.notify",
|
|
174
|
-
sourceChannel: "scheduler",
|
|
175
|
-
sourceContextId: "conv-source-1",
|
|
176
|
-
attentionHints: {
|
|
177
|
-
requiresAction: false,
|
|
178
|
-
urgency: "medium",
|
|
179
|
-
isAsyncBackground: false,
|
|
180
|
-
visibleInSourceNow: true,
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
expect(result.dispatched).toBe(true);
|
|
185
|
-
expect(appendCalls).toHaveLength(0);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "Synthetic, generic eval turns for the memory-v3 orchestrator. No real content. Domains/slugs are placeholders (domain-a/topic-x, page-a). Each turn names which leaves L1 should open and which page IDs L2 should select per opened leaf; expectedFinalInjection is the orchestrator's deduped slug list for that turn given a SHARED working set across the sequence.",
|
|
3
|
-
"turns": [
|
|
4
|
-
{
|
|
5
|
-
"name": "turn 1 — open topic-x, select page-a (pinned) and page-b",
|
|
6
|
-
"currentMessage": "tell me about page a",
|
|
7
|
-
"routeIds": [1],
|
|
8
|
-
"leafSelections": {
|
|
9
|
-
"domain-a/topic-x": { "ids": [1, 2], "pinned_ids": [1] }
|
|
10
|
-
},
|
|
11
|
-
"expectedOpenedLeaves": ["domain-a/topic-x"],
|
|
12
|
-
"expectedFinalInjection": ["page-a", "page-b"]
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "turn 2 — open topic-y, select page-c; page-a carries forward (pinned)",
|
|
16
|
-
"currentMessage": "now about page c",
|
|
17
|
-
"routeIds": [2],
|
|
18
|
-
"leafSelections": {
|
|
19
|
-
"domain-a/topic-y": { "ids": [1], "pinned_ids": [] }
|
|
20
|
-
},
|
|
21
|
-
"expectedOpenedLeaves": ["domain-a/topic-y"],
|
|
22
|
-
"expectedFinalInjection": ["page-c", "page-a", "page-b"]
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"name": "turn 3 — open both leaves, select page-b and page-c",
|
|
26
|
-
"currentMessage": "compare page b and page c",
|
|
27
|
-
"routeIds": [1, 2],
|
|
28
|
-
"leafSelections": {
|
|
29
|
-
"domain-a/topic-x": { "ids": [2], "pinned_ids": [] },
|
|
30
|
-
"domain-a/topic-y": { "ids": [1], "pinned_ids": [] }
|
|
31
|
-
},
|
|
32
|
-
"expectedOpenedLeaves": ["domain-a/topic-x", "domain-a/topic-y"],
|
|
33
|
-
"expectedFinalInjection": ["page-b", "page-c", "page-a"]
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "Synthetic, generic turns for the memory-v3 LIVE-injection integration test. No real content. The tree has two leaves (domain-a/topic-x → [page-a, page-b], domain-a/topic-y → [page-c]); L1 id 1 → topic-x, id 2 → topic-y. Each turn names which leaves L1 opens and which page IDs L2 selects (and pins) per opened leaf. Driven through a SHARED WorkingSet across the sequence so carry-forward and window eviction are exercised end-to-end. The WorkingSet uses a small evictWindow so a non-pinned page selected only in turn 1 ages out by a later turn while a pinned page persists.",
|
|
3
|
-
"turns": [
|
|
4
|
-
{
|
|
5
|
-
"name": "turn 1 — open topic-x, select page-a (pinned) + page-b (unpinned)",
|
|
6
|
-
"currentMessage": "tell me about page a",
|
|
7
|
-
"routeIds": [1],
|
|
8
|
-
"leafSelections": {
|
|
9
|
-
"domain-a/topic-x": { "ids": [1, 2], "pinned_ids": [1] }
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"name": "turn 2 — open topic-y, select page-c; page-a + page-b carry forward",
|
|
14
|
-
"currentMessage": "now about page c",
|
|
15
|
-
"routeIds": [2],
|
|
16
|
-
"leafSelections": {
|
|
17
|
-
"domain-a/topic-y": { "ids": [1], "pinned_ids": [] }
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"name": "turn 3 — open topic-y again, re-select page-c (refreshes its lastSeen)",
|
|
22
|
-
"currentMessage": "more on page c",
|
|
23
|
-
"routeIds": [2],
|
|
24
|
-
"leafSelections": {
|
|
25
|
-
"domain-a/topic-y": { "ids": [1], "pinned_ids": [] }
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"name": "turn 4 — open topic-y, re-select page-c; page-b now stale enough to age out",
|
|
30
|
-
"currentMessage": "still page c",
|
|
31
|
-
"routeIds": [2],
|
|
32
|
-
"leafSelections": {
|
|
33
|
-
"domain-a/topic-y": { "ids": [1], "pinned_ids": [] }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { WorkingSetEntry } from "../types.js";
|
|
4
|
-
import { ScoreFn, WorkingSet } from "../working-set.js";
|
|
5
|
-
|
|
6
|
-
describe("WorkingSet.evict", () => {
|
|
7
|
-
test("evicts a page unseen past the window", () => {
|
|
8
|
-
const ws = new WorkingSet(150, 5);
|
|
9
|
-
ws.recordSelection("alpha", 0, false);
|
|
10
|
-
|
|
11
|
-
// currentTurn 6, lastSeen 0 → gap 6 > window 5 → evict.
|
|
12
|
-
ws.evict(6, new Set());
|
|
13
|
-
|
|
14
|
-
expect(ws.union().has("alpha")).toBe(false);
|
|
15
|
-
expect(ws.size()).toBe(0);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("keeps a page still inside the window", () => {
|
|
19
|
-
const ws = new WorkingSet(150, 5);
|
|
20
|
-
ws.recordSelection("alpha", 0, false);
|
|
21
|
-
|
|
22
|
-
// gap 5 is not > 5 → keep.
|
|
23
|
-
ws.evict(5, new Set());
|
|
24
|
-
|
|
25
|
-
expect(ws.union().has("alpha")).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("pinned page never evicts past the window", () => {
|
|
29
|
-
const ws = new WorkingSet(150, 5);
|
|
30
|
-
ws.recordSelection("alpha", 0, true);
|
|
31
|
-
|
|
32
|
-
ws.evict(100, new Set());
|
|
33
|
-
|
|
34
|
-
expect(ws.union().has("alpha")).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("exceeding maxPages evicts the least-recently-selected non-pinned page first", () => {
|
|
38
|
-
const ws = new WorkingSet(2, 1000);
|
|
39
|
-
ws.recordSelection("oldest", 1, false);
|
|
40
|
-
ws.recordSelection("middle", 2, false);
|
|
41
|
-
ws.recordSelection("newest", 3, false);
|
|
42
|
-
|
|
43
|
-
// No window eviction (window huge); cap is 2 → drop lowest lastSeenTurn.
|
|
44
|
-
ws.evict(3, new Set());
|
|
45
|
-
|
|
46
|
-
expect(ws.size()).toBe(2);
|
|
47
|
-
expect(ws.union().has("oldest")).toBe(false);
|
|
48
|
-
expect(ws.union().has("middle")).toBe(true);
|
|
49
|
-
expect(ws.union().has("newest")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("cap eviction never drops pinned pages even when over capacity", () => {
|
|
53
|
-
const ws = new WorkingSet(1, 1000);
|
|
54
|
-
ws.recordSelection("pinned-a", 1, true);
|
|
55
|
-
ws.recordSelection("pinned-b", 2, true);
|
|
56
|
-
ws.recordSelection("loose", 3, false);
|
|
57
|
-
|
|
58
|
-
ws.evict(3, new Set());
|
|
59
|
-
|
|
60
|
-
// Only the non-pinned page can go; pinned pages stay even over cap.
|
|
61
|
-
expect(ws.union().has("pinned-a")).toBe(true);
|
|
62
|
-
expect(ws.union().has("pinned-b")).toBe(true);
|
|
63
|
-
expect(ws.union().has("loose")).toBe(false);
|
|
64
|
-
expect(ws.size()).toBe(2);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("core slugs passed to evict are dropped if present", () => {
|
|
68
|
-
const ws = new WorkingSet(150, 5);
|
|
69
|
-
ws.recordSelection("alpha", 5, false);
|
|
70
|
-
ws.recordSelection("beta", 5, true); // pinned, but core still wins
|
|
71
|
-
ws.recordSelection("gamma", 5, false);
|
|
72
|
-
|
|
73
|
-
ws.evict(5, new Set(["alpha", "beta"]));
|
|
74
|
-
|
|
75
|
-
expect(ws.union().has("alpha")).toBe(false);
|
|
76
|
-
expect(ws.union().has("beta")).toBe(false);
|
|
77
|
-
expect(ws.union().has("gamma")).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("custom scoreFn reorders cap-evictions", () => {
|
|
81
|
-
// Score by selectedAtTurn instead of lastSeenTurn: the page selected
|
|
82
|
-
// earliest becomes most evictable regardless of recent activity.
|
|
83
|
-
const bySelectedAt: ScoreFn = (e: WorkingSetEntry) => e.selectedAtTurn;
|
|
84
|
-
const ws = new WorkingSet(2, 1000, bySelectedAt);
|
|
85
|
-
|
|
86
|
-
ws.recordSelection("first", 0, false);
|
|
87
|
-
ws.recordSelection("second", 1, false);
|
|
88
|
-
ws.recordSelection("third", 2, false);
|
|
89
|
-
// Bump "first" so plain LRU would protect it, but selectedAt still lowest.
|
|
90
|
-
ws.recordSelection("first", 9, false);
|
|
91
|
-
|
|
92
|
-
ws.evict(9, new Set());
|
|
93
|
-
|
|
94
|
-
expect(ws.size()).toBe(2);
|
|
95
|
-
// Under custom score, "first" (selectedAt 0) evicts despite recent touch.
|
|
96
|
-
expect(ws.union().has("first")).toBe(false);
|
|
97
|
-
expect(ws.union().has("second")).toBe(true);
|
|
98
|
-
expect(ws.union().has("third")).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("default no-arg constructor still works for existing callers", () => {
|
|
102
|
-
const ws = new WorkingSet();
|
|
103
|
-
ws.recordSelection("alpha", 0, false);
|
|
104
|
-
expect(ws.size()).toBe(1);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { WorkingSet } from "../working-set.js";
|
|
4
|
-
|
|
5
|
-
describe("WorkingSet (skeleton)", () => {
|
|
6
|
-
test("recording a selection adds it to union()", () => {
|
|
7
|
-
const ws = new WorkingSet();
|
|
8
|
-
ws.recordSelection("alpha", 1, false);
|
|
9
|
-
|
|
10
|
-
expect(ws.union().has("alpha")).toBe(true);
|
|
11
|
-
expect(ws.size()).toBe(1);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("recording the same slug twice preserves selectedAtTurn and updates lastSeenTurn", () => {
|
|
15
|
-
const ws = new WorkingSet();
|
|
16
|
-
ws.recordSelection("alpha", 1, false);
|
|
17
|
-
ws.recordSelection("alpha", 5, false);
|
|
18
|
-
|
|
19
|
-
expect(ws.size()).toBe(1);
|
|
20
|
-
const entry = [...ws.union()];
|
|
21
|
-
expect(entry).toEqual(["alpha"]);
|
|
22
|
-
// selectedAtTurn preserved at original turn, lastSeenTurn advanced.
|
|
23
|
-
const internal = (
|
|
24
|
-
ws as unknown as {
|
|
25
|
-
entries: Map<string, { selectedAtTurn: number; lastSeenTurn: number }>;
|
|
26
|
-
}
|
|
27
|
-
).entries.get("alpha");
|
|
28
|
-
expect(internal?.selectedAtTurn).toBe(1);
|
|
29
|
-
expect(internal?.lastSeenTurn).toBe(5);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("pinned propagates: non-pinned then pinned keeps pinned true", () => {
|
|
33
|
-
const ws = new WorkingSet();
|
|
34
|
-
ws.recordSelection("alpha", 1, false);
|
|
35
|
-
ws.recordSelection("alpha", 2, true);
|
|
36
|
-
|
|
37
|
-
const internal = (
|
|
38
|
-
ws as unknown as {
|
|
39
|
-
entries: Map<string, { pinned: boolean }>;
|
|
40
|
-
}
|
|
41
|
-
).entries.get("alpha");
|
|
42
|
-
expect(internal?.pinned).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { Slug, WorkingSetEntry } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Scores a working-set entry for cap eviction. Lower score = more evictable.
|
|
5
|
-
* This is the pluggable seam for a future salience-weighted policy; the
|
|
6
|
-
* default is plain LRU on `lastSeenTurn`.
|
|
7
|
-
*/
|
|
8
|
-
export type ScoreFn = (entry: WorkingSetEntry) => number;
|
|
9
|
-
|
|
10
|
-
/** Default eviction score: least-recently-seen entries evict first. */
|
|
11
|
-
function lruScore(entry: WorkingSetEntry): number {
|
|
12
|
-
return entry.lastSeenTurn;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class WorkingSet {
|
|
16
|
-
private entries = new Map<Slug, WorkingSetEntry>();
|
|
17
|
-
|
|
18
|
-
constructor(
|
|
19
|
-
private readonly maxPages = 150,
|
|
20
|
-
private readonly evictWindow = 5,
|
|
21
|
-
private readonly scoreFn: ScoreFn = lruScore,
|
|
22
|
-
) {}
|
|
23
|
-
|
|
24
|
-
recordSelection(slug: Slug, turn: number, pinned: boolean): void {
|
|
25
|
-
const existing = this.entries.get(slug);
|
|
26
|
-
this.entries.set(slug, {
|
|
27
|
-
slug,
|
|
28
|
-
selectedAtTurn: existing?.selectedAtTurn ?? turn,
|
|
29
|
-
pinned: existing?.pinned || pinned,
|
|
30
|
-
lastSeenTurn: turn,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
union(): Set<Slug> {
|
|
35
|
-
return new Set(this.entries.keys());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
size(): number {
|
|
39
|
-
return this.entries.size;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Prune the working set, in order:
|
|
44
|
-
* 1. Core slugs never belong here — drop any entry whose slug is core.
|
|
45
|
-
* The section-lane pipeline has no core set, so `coreSlugs` defaults to
|
|
46
|
-
* empty (no-op); the tree pipeline passed its core slugs here. Stale and
|
|
47
|
-
* cap eviction semantics are identical either way.
|
|
48
|
-
* 2. Pinned entries never evict.
|
|
49
|
-
* 3. Window eviction: drop a non-pinned entry unseen for more than
|
|
50
|
-
* `evictWindow` turns.
|
|
51
|
-
* 4. Cap eviction: while over `maxPages`, evict the lowest-scoring
|
|
52
|
-
* non-pinned entry first (ties broken deterministically).
|
|
53
|
-
*/
|
|
54
|
-
evict(currentTurn: number, coreSlugs: Set<Slug> = new Set()): void {
|
|
55
|
-
// 1. Core slugs are owned by core, not the working set.
|
|
56
|
-
for (const slug of this.entries.keys()) {
|
|
57
|
-
if (coreSlugs.has(slug)) {
|
|
58
|
-
this.entries.delete(slug);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 2 + 3. Window eviction for stale non-pinned entries.
|
|
63
|
-
for (const [slug, entry] of this.entries) {
|
|
64
|
-
if (
|
|
65
|
-
!entry.pinned &&
|
|
66
|
-
currentTurn - entry.lastSeenTurn > this.evictWindow
|
|
67
|
-
) {
|
|
68
|
-
this.entries.delete(slug);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 4. Cap eviction: evict the lowest-scoring non-pinned entry until the
|
|
73
|
-
// set fits. Sort once (ascending score, then slug for determinism) and
|
|
74
|
-
// walk the candidates.
|
|
75
|
-
if (this.entries.size <= this.maxPages) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const evictable = [...this.entries.values()]
|
|
79
|
-
.filter((entry) => !entry.pinned)
|
|
80
|
-
.sort(
|
|
81
|
-
(a, b) =>
|
|
82
|
-
this.scoreFn(a) - this.scoreFn(b) || (a.slug < b.slug ? -1 : 1),
|
|
83
|
-
);
|
|
84
|
-
for (const entry of evictable) {
|
|
85
|
-
if (this.entries.size <= this.maxPages) {
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
this.entries.delete(entry.slug);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External plugin runtime — `globalThis.__vellumPluginRuntime` bridge.
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ DO NOT DELETE THIS AS "UNUSED". There is intentionally NO in-repo consumer:
|
|
5
|
-
* the consumers are out-of-repo, user-authored workspace plugins under
|
|
6
|
-
* `<workspaceDir>/plugins/*`, which a repo-wide reference search cannot see. On
|
|
7
|
-
* a `bun --compile` daemon (the shipped macOS app) this bridge is the ONLY way
|
|
8
|
-
* such a plugin can reach the daemon's real `assistantEventHub` /
|
|
9
|
-
* `getSecureKeyAsync` singletons — an absolute-path `import` re-loads a disjoint
|
|
10
|
-
* copy (see the module-identity note below). Removing it silently breaks every
|
|
11
|
-
* event-subscribing workspace plugin on the next app rebuild. This was already
|
|
12
|
-
* removed once on the false "unused" assumption (PR #33825) and restored here.
|
|
13
|
-
*
|
|
14
|
-
* Workspace-local plugins (`<workspaceDir>/plugins/*`) get dynamic-imported by
|
|
15
|
-
* {@link loadUserPlugins}. They need to call `registerPlugin()` from their
|
|
16
|
-
* module body and they typically also want to read secrets, subscribe to
|
|
17
|
-
* runtime events, etc.
|
|
18
|
-
*
|
|
19
|
-
* Importing those symbols by absolute path
|
|
20
|
-
* (`/abs/path/to/assistant/src/plugins/registry.js`) works when the daemon is
|
|
21
|
-
* running from source — both the daemon and the dynamic-imported plugin
|
|
22
|
-
* resolve the same on-disk file and share module identity. It DOES NOT work
|
|
23
|
-
* when the daemon is a `bun --compile` binary: the daemon's modules are baked
|
|
24
|
-
* into the executable, and any absolute-path import re-loads a fresh disk
|
|
25
|
-
* copy. `registerPlugin()` then writes into a disjoint registry instance and
|
|
26
|
-
* the daemon never sees the plugin.
|
|
27
|
-
*
|
|
28
|
-
* The fix is to expose a single, stable handle on `globalThis` that plugins
|
|
29
|
-
* read at module-load time. The daemon's bundled modules attach themselves
|
|
30
|
-
* here once at startup; plugins consume the same instance regardless of how
|
|
31
|
-
* the daemon was built.
|
|
32
|
-
*
|
|
33
|
-
* Plugins use the bridge like this:
|
|
34
|
-
*
|
|
35
|
-
* const runtime = (globalThis as { __vellumPluginRuntime?: ... })
|
|
36
|
-
* .__vellumPluginRuntime;
|
|
37
|
-
* if (!runtime || runtime.version !== 1) throw new Error("...");
|
|
38
|
-
* const { registerPlugin, assistantEventHub, getSecureKeyAsync } = runtime;
|
|
39
|
-
*
|
|
40
|
-
* Type-only imports (`import type { Plugin } from "..."`) remain free to use
|
|
41
|
-
* absolute paths or workspace-local copies — the TypeScript compiler erases
|
|
42
|
-
* them and they have no module-identity effect at runtime.
|
|
43
|
-
*
|
|
44
|
-
* See `experimental/plugins/README.md` for the full authoring contract and
|
|
45
|
-
* `assistant/examples/plugins/echo/` for a worked example.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
49
|
-
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
50
|
-
import { registerPlugin } from "./registry.js";
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The handle plugins read from `globalThis.__vellumPluginRuntime`.
|
|
54
|
-
*
|
|
55
|
-
* `version` is the contract version. Plugins should assert against the
|
|
56
|
-
* version they were authored against and refuse to register if they see a
|
|
57
|
-
* different one — bumping it is a breaking change to the plugin surface.
|
|
58
|
-
*
|
|
59
|
-
* The field set is intentionally small: the most-commonly-needed symbols
|
|
60
|
-
* across both static (module-load-time) and dynamic (init-time) plugin
|
|
61
|
-
* lifecycle. Plugins that need richer runtime state can still receive it
|
|
62
|
-
* through {@link PluginInitContext} during `init()`.
|
|
63
|
-
*/
|
|
64
|
-
export interface VellumPluginRuntime {
|
|
65
|
-
readonly version: 1;
|
|
66
|
-
readonly registerPlugin: typeof registerPlugin;
|
|
67
|
-
readonly assistantEventHub: typeof assistantEventHub;
|
|
68
|
-
readonly getSecureKeyAsync: typeof getSecureKeyAsync;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Stable globalThis key. Don't rename — plugins reference it by string. */
|
|
72
|
-
const RUNTIME_GLOBAL_KEY = "__vellumPluginRuntime" as const;
|
|
73
|
-
|
|
74
|
-
interface GlobalWithRuntime {
|
|
75
|
-
[RUNTIME_GLOBAL_KEY]?: VellumPluginRuntime;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Install the plugin runtime bridge on `globalThis`. Idempotent — repeat
|
|
80
|
-
* calls are no-ops, so it's safe to invoke from tests that also touch the
|
|
81
|
-
* lifecycle path.
|
|
82
|
-
*
|
|
83
|
-
* Must be called BEFORE {@link loadUserPlugins} runs, otherwise plugins that
|
|
84
|
-
* touch `globalThis.__vellumPluginRuntime` in their module body will throw.
|
|
85
|
-
*/
|
|
86
|
-
export function installPluginRuntime(): void {
|
|
87
|
-
const g = globalThis as GlobalWithRuntime;
|
|
88
|
-
if (g[RUNTIME_GLOBAL_KEY]) return;
|
|
89
|
-
g[RUNTIME_GLOBAL_KEY] = {
|
|
90
|
-
version: 1,
|
|
91
|
-
registerPlugin,
|
|
92
|
-
assistantEventHub,
|
|
93
|
-
getSecureKeyAsync,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Read the installed runtime. Returns `undefined` if {@link installPluginRuntime}
|
|
99
|
-
* hasn't been called yet — plugins should treat this as a fatal error.
|
|
100
|
-
*
|
|
101
|
-
* Exposed mainly for tests and for the rare in-process consumer that wants
|
|
102
|
-
* to read the bridge from the same module graph that installed it.
|
|
103
|
-
*/
|
|
104
|
-
export function getPluginRuntime(): VellumPluginRuntime | undefined {
|
|
105
|
-
return (globalThis as GlobalWithRuntime)[RUNTIME_GLOBAL_KEY];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Tear down the runtime handle. Test-only — production code never uninstalls
|
|
110
|
-
* because the runtime lifetime is the daemon lifetime.
|
|
111
|
-
*/
|
|
112
|
-
export function uninstallPluginRuntimeForTests(): void {
|
|
113
|
-
delete (globalThis as GlobalWithRuntime)[RUNTIME_GLOBAL_KEY];
|
|
114
|
-
}
|