@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
|
@@ -42,11 +42,7 @@ import {
|
|
|
42
42
|
registerPlugin,
|
|
43
43
|
resetPluginRegistryForTests,
|
|
44
44
|
} from "../plugins/registry.js";
|
|
45
|
-
import {
|
|
46
|
-
type Plugin,
|
|
47
|
-
PluginExecutionError,
|
|
48
|
-
type PluginInitContext,
|
|
49
|
-
} from "../plugins/types.js";
|
|
45
|
+
import { type Plugin, type PluginInitContext } from "../plugins/types.js";
|
|
50
46
|
import { APP_VERSION } from "../version.js";
|
|
51
47
|
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
52
48
|
|
|
@@ -174,9 +170,9 @@ describe("plugin bootstrap", () => {
|
|
|
174
170
|
expect(received?.credentials).toEqual({ "some-key": "super-secret-value" });
|
|
175
171
|
});
|
|
176
172
|
|
|
177
|
-
test("credential resolution:
|
|
173
|
+
test("credential resolution: a plugin whose required credential is missing is skipped, not fatal", async () => {
|
|
174
|
+
// GIVEN a plugin that requires a credential the store cannot resolve
|
|
178
175
|
getSecureKeyAsyncMock.mockImplementation(async () => undefined);
|
|
179
|
-
|
|
180
176
|
registerPlugin(
|
|
181
177
|
buildPlugin(
|
|
182
178
|
"missing-cred",
|
|
@@ -185,16 +181,14 @@ describe("plugin bootstrap", () => {
|
|
|
185
181
|
),
|
|
186
182
|
);
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
expect(msg).toContain("missing-cred");
|
|
197
|
-
expect(msg).toContain("absent-key");
|
|
184
|
+
// WHEN bootstrap runs
|
|
185
|
+
// THEN it completes without throwing — the unresolvable credential is
|
|
186
|
+
// contained to that plugin (same per-plugin isolation as an init throw)
|
|
187
|
+
await bootstrapPlugins();
|
|
188
|
+
|
|
189
|
+
// AND the plugin is dropped from the registry
|
|
190
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
191
|
+
expect(names).not.toContain("missing-cred");
|
|
198
192
|
});
|
|
199
193
|
|
|
200
194
|
test("version mismatch: external plugin loader rejects when peerDependency unsatisfied", async () => {
|
|
@@ -208,7 +202,8 @@ describe("plugin bootstrap", () => {
|
|
|
208
202
|
expect(() => registerPlugin(plugin)).not.toThrow();
|
|
209
203
|
});
|
|
210
204
|
|
|
211
|
-
test("plugin init throw: bootstrap
|
|
205
|
+
test("plugin init throw: bootstrap contains the failure and does not throw", async () => {
|
|
206
|
+
// GIVEN a plugin whose init throws
|
|
212
207
|
registerPlugin(
|
|
213
208
|
buildPlugin("broken", {
|
|
214
209
|
async init() {
|
|
@@ -217,65 +212,91 @@ describe("plugin bootstrap", () => {
|
|
|
217
212
|
}),
|
|
218
213
|
);
|
|
219
214
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
expect(
|
|
229
|
-
expect(msg).toContain("kaboom");
|
|
215
|
+
// WHEN bootstrap runs
|
|
216
|
+
// THEN it completes without throwing — a single plugin's init failure is
|
|
217
|
+
// contained to that plugin rather than aborting the whole plugin layer
|
|
218
|
+
await bootstrapPlugins();
|
|
219
|
+
|
|
220
|
+
// AND the failing plugin is dropped from the registry so its hooks never
|
|
221
|
+
// participate in the turn lifecycle
|
|
222
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
223
|
+
expect(names).not.toContain("broken");
|
|
230
224
|
});
|
|
231
225
|
|
|
232
|
-
test("
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// the shutdown hook is only registered once the entire loop completes.
|
|
238
|
-
const callOrder: string[] = [];
|
|
226
|
+
test("mid-list init failure: surrounding plugins still initialize and survive", async () => {
|
|
227
|
+
// GIVEN two healthy plugins surrounding one whose init throws, registered
|
|
228
|
+
// in order
|
|
229
|
+
const initialized: string[] = [];
|
|
230
|
+
const shutDownDuringBootstrap: string[] = [];
|
|
239
231
|
registerPlugin(
|
|
240
|
-
buildPlugin("
|
|
241
|
-
async init() {
|
|
232
|
+
buildPlugin("before", {
|
|
233
|
+
async init() {
|
|
234
|
+
initialized.push("before");
|
|
235
|
+
},
|
|
242
236
|
async onShutdown() {
|
|
243
|
-
|
|
237
|
+
shutDownDuringBootstrap.push("before");
|
|
244
238
|
},
|
|
245
239
|
}),
|
|
246
240
|
);
|
|
247
241
|
registerPlugin(
|
|
248
|
-
buildPlugin("
|
|
249
|
-
async init() {
|
|
250
|
-
|
|
251
|
-
callOrder.push("survivor-b");
|
|
242
|
+
buildPlugin("failing", {
|
|
243
|
+
async init() {
|
|
244
|
+
throw new Error("mid-bootstrap failure");
|
|
252
245
|
},
|
|
253
246
|
}),
|
|
254
247
|
);
|
|
255
248
|
registerPlugin(
|
|
256
|
-
buildPlugin("
|
|
249
|
+
buildPlugin("after", {
|
|
257
250
|
async init() {
|
|
258
|
-
|
|
251
|
+
initialized.push("after");
|
|
259
252
|
},
|
|
260
253
|
async onShutdown() {
|
|
261
|
-
|
|
262
|
-
// added to the active list that teardown walks.
|
|
263
|
-
callOrder.push("failing");
|
|
254
|
+
shutDownDuringBootstrap.push("after");
|
|
264
255
|
},
|
|
265
256
|
}),
|
|
266
257
|
);
|
|
267
258
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
//
|
|
278
|
-
|
|
259
|
+
// WHEN bootstrap runs
|
|
260
|
+
await bootstrapPlugins();
|
|
261
|
+
|
|
262
|
+
// THEN both healthy plugins initialized — the one registered after the
|
|
263
|
+
// failure is not skipped
|
|
264
|
+
expect(initialized).toEqual(["before", "after"]);
|
|
265
|
+
// AND neither healthy plugin was torn down by the failure (their
|
|
266
|
+
// onShutdown only runs at real shutdown, not during bootstrap)
|
|
267
|
+
expect(shutDownDuringBootstrap).toEqual([]);
|
|
268
|
+
// AND the failing plugin is dropped while both survivors remain registered
|
|
269
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
270
|
+
expect(names).toContain("before");
|
|
271
|
+
expect(names).toContain("after");
|
|
272
|
+
expect(names).not.toContain("failing");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("user-plugin init failure leaves the first-party defaults registered", async () => {
|
|
276
|
+
// GIVEN the first-party defaults are registered ahead of user plugins, as
|
|
277
|
+
// at daemon startup
|
|
278
|
+
registerDefaultPlugins();
|
|
279
|
+
// AND a user plugin registered after them whose init throws
|
|
280
|
+
registerPlugin(
|
|
281
|
+
buildPlugin("breaking-user-plugin", {
|
|
282
|
+
async init() {
|
|
283
|
+
throw new Error("user plugin boom");
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// WHEN bootstrap runs
|
|
289
|
+
await bootstrapPlugins();
|
|
290
|
+
|
|
291
|
+
// THEN the failing user plugin is dropped from the registry
|
|
292
|
+
const names = getRegisteredPlugins().map((p) => p.manifest.name);
|
|
293
|
+
expect(names).not.toContain("breaking-user-plugin");
|
|
294
|
+
// AND every first-party default survived, so core turn behavior (memory
|
|
295
|
+
// retrieval, history repair, title generation) keeps running in degraded
|
|
296
|
+
// mode instead of being torn down with the failing plugin
|
|
297
|
+
expect(names).toContain("default-memory-retrieval");
|
|
298
|
+
expect(names).toContain("default-history-repair");
|
|
299
|
+
expect(names).toContain("default-title-generate");
|
|
279
300
|
});
|
|
280
301
|
|
|
281
302
|
test("shutdown order: onShutdown fires in reverse registration order", async () => {
|
|
@@ -378,11 +399,11 @@ describe("plugin bootstrap", () => {
|
|
|
378
399
|
expect(initFired).toBe(true);
|
|
379
400
|
});
|
|
380
401
|
|
|
381
|
-
test("requiresFlag disabled: init does not fire and no tools/routes
|
|
402
|
+
test("requiresFlag disabled: init does not fire and no tools/routes are registered", async () => {
|
|
382
403
|
setOverridesForTesting({ "plugin-gated-disabled": false });
|
|
383
404
|
|
|
384
405
|
let initFired = false;
|
|
385
|
-
// Attach tool/route
|
|
406
|
+
// Attach tool/route contributions alongside init. If gating works,
|
|
386
407
|
// none of them should land in their respective registries.
|
|
387
408
|
const plugin = buildPlugin(
|
|
388
409
|
"gated-off",
|
|
@@ -409,24 +430,14 @@ describe("plugin bootstrap", () => {
|
|
|
409
430
|
handler: async () => new Response("ok"),
|
|
410
431
|
},
|
|
411
432
|
],
|
|
412
|
-
skills: [
|
|
413
|
-
{
|
|
414
|
-
id: "gated-off/skill",
|
|
415
|
-
name: "gated-off-skill",
|
|
416
|
-
description: "should not be catalogued",
|
|
417
|
-
body: "# unused",
|
|
418
|
-
},
|
|
419
|
-
],
|
|
420
433
|
},
|
|
421
434
|
{ requiresFlag: ["plugin-gated-disabled"] },
|
|
422
435
|
);
|
|
423
436
|
registerPlugin(plugin);
|
|
424
437
|
|
|
425
|
-
// Grab tool / route
|
|
438
|
+
// Grab tool / route introspection helpers lazily so the import
|
|
426
439
|
// side effect happens after `mock.module` has taken effect.
|
|
427
440
|
const { getTool } = await import("../tools/registry.js");
|
|
428
|
-
const { getPluginSkillRefCount } =
|
|
429
|
-
await import("../plugins/plugin-skill-contributions.js");
|
|
430
441
|
const { matchSkillRoute } =
|
|
431
442
|
await import("../runtime/skill-route-registry.js");
|
|
432
443
|
|
|
@@ -438,8 +449,6 @@ describe("plugin bootstrap", () => {
|
|
|
438
449
|
expect(getTool("gated-off-tool")).toBeUndefined();
|
|
439
450
|
// No route wired up — `matchSkillRoute` returns null when nothing matches.
|
|
440
451
|
expect(matchSkillRoute("/_plugin/gated-off/status", "GET")).toBeNull();
|
|
441
|
-
// No skill catalogued under this plugin's name — ref count stays 0.
|
|
442
|
-
expect(getPluginSkillRefCount("gated-off")).toBe(0);
|
|
443
452
|
});
|
|
444
453
|
|
|
445
454
|
test("requiresFlag absent: plugin activates unconditionally", async () => {
|
|
@@ -218,9 +218,7 @@ describe("plugin tool contributions", () => {
|
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
test("tools are only registered after init() succeeds", async () => {
|
|
221
|
-
//
|
|
222
|
-
// aborts with a PluginExecutionError, and nothing from this plugin
|
|
223
|
-
// should leak into the tool registry.
|
|
221
|
+
// GIVEN a plugin that declares a tool but throws during init()
|
|
224
222
|
const plugin = buildPlugin("delta-broken", {
|
|
225
223
|
async init() {
|
|
226
224
|
throw new Error("boom");
|
|
@@ -229,7 +227,12 @@ describe("plugin tool contributions", () => {
|
|
|
229
227
|
});
|
|
230
228
|
registerPlugin(plugin);
|
|
231
229
|
|
|
232
|
-
|
|
230
|
+
// WHEN bootstrap runs
|
|
231
|
+
// THEN it does not throw — the init failure is contained to this plugin
|
|
232
|
+
await bootstrapPlugins();
|
|
233
|
+
|
|
234
|
+
// AND the failing plugin's tool is rolled back, never leaking into the
|
|
235
|
+
// registry
|
|
233
236
|
expect(getTool("delta-tool")).toBeUndefined();
|
|
234
237
|
});
|
|
235
238
|
});
|
|
@@ -78,14 +78,6 @@ describe("plugin core types", () => {
|
|
|
78
78
|
handler: async () => new Response("ok", { status: 200 }),
|
|
79
79
|
},
|
|
80
80
|
],
|
|
81
|
-
skills: [
|
|
82
|
-
{
|
|
83
|
-
id: "sample-skill",
|
|
84
|
-
name: "Sample Skill",
|
|
85
|
-
description: "Demo plugin-contributed skill",
|
|
86
|
-
body: "## Sample\n\nPlugin-provided skill body.",
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
81
|
} satisfies Plugin;
|
|
90
82
|
|
|
91
83
|
// Minimal runtime check so the test body is non-empty.
|
|
@@ -19,19 +19,11 @@ function makeConfig(): AssistantConfig {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
describe("getVisibleProviderCatalog", () => {
|
|
22
|
-
test("
|
|
22
|
+
test("shows openai-compatible endpoints unconditionally (GA'ed)", () => {
|
|
23
23
|
setOverridesForTesting({});
|
|
24
24
|
|
|
25
25
|
const visible = getVisibleProviderCatalog(makeConfig());
|
|
26
26
|
|
|
27
|
-
expect(visible.find((p) => p.id === "openai-compatible")).toBeUndefined();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("shows openai-compatible endpoints when its flag is enabled", () => {
|
|
31
|
-
setOverridesForTesting({ "openai-compatible-endpoints": true });
|
|
32
|
-
|
|
33
|
-
const visible = getVisibleProviderCatalog(makeConfig());
|
|
34
|
-
|
|
35
27
|
expect(visible.find((p) => p.id === "openai-compatible")).toBeDefined();
|
|
36
28
|
});
|
|
37
29
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Silence the logger.
|
|
4
|
+
mock.module("../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
12
|
+
import { getDb } from "../memory/db-connection.js";
|
|
13
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
14
|
+
import { pruneOldConversationsJob } from "../memory/job-handlers/cleanup.js";
|
|
15
|
+
import type { MemoryJob } from "../memory/jobs-store.js";
|
|
16
|
+
import {
|
|
17
|
+
conversations,
|
|
18
|
+
skillLoadedEvents,
|
|
19
|
+
toolInvocations,
|
|
20
|
+
} from "../memory/schema.js";
|
|
21
|
+
|
|
22
|
+
initializeDb();
|
|
23
|
+
|
|
24
|
+
const STALE_ID = "conv-prune-job-stale";
|
|
25
|
+
const FRESH_ID = "conv-prune-job-fresh";
|
|
26
|
+
|
|
27
|
+
const JOB = { payload: {} } as unknown as MemoryJob;
|
|
28
|
+
const CONFIG = {
|
|
29
|
+
memory: { cleanup: { conversationRetentionDays: 30 } },
|
|
30
|
+
} as unknown as AssistantConfig;
|
|
31
|
+
|
|
32
|
+
function seedConversation(id: string, updatedAt: number): void {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
db.insert(conversations)
|
|
35
|
+
.values({ id, title: "test", createdAt: updatedAt, updatedAt })
|
|
36
|
+
.run();
|
|
37
|
+
db.insert(toolInvocations)
|
|
38
|
+
.values({
|
|
39
|
+
id: `ti-${id}`,
|
|
40
|
+
conversationId: id,
|
|
41
|
+
toolName: "calendar_list_events",
|
|
42
|
+
input: "{}",
|
|
43
|
+
result: "{}",
|
|
44
|
+
decision: "allow",
|
|
45
|
+
riskLevel: "low",
|
|
46
|
+
durationMs: 12,
|
|
47
|
+
createdAt: updatedAt,
|
|
48
|
+
})
|
|
49
|
+
.run();
|
|
50
|
+
db.insert(skillLoadedEvents)
|
|
51
|
+
.values({
|
|
52
|
+
id: `sl-${id}`,
|
|
53
|
+
createdAt: updatedAt,
|
|
54
|
+
conversationId: id,
|
|
55
|
+
skillName: "test-skill",
|
|
56
|
+
})
|
|
57
|
+
.run();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function countRows(conversationId: string): {
|
|
61
|
+
invocations: number;
|
|
62
|
+
skillLoads: number;
|
|
63
|
+
} {
|
|
64
|
+
const db = getDb();
|
|
65
|
+
return {
|
|
66
|
+
invocations: db
|
|
67
|
+
.select()
|
|
68
|
+
.from(toolInvocations)
|
|
69
|
+
.all()
|
|
70
|
+
.filter((r) => r.conversationId === conversationId).length,
|
|
71
|
+
skillLoads: db
|
|
72
|
+
.select()
|
|
73
|
+
.from(skillLoadedEvents)
|
|
74
|
+
.all()
|
|
75
|
+
.filter((r) => r.conversationId === conversationId).length,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe("pruneOldConversationsJob", () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
const db = getDb();
|
|
82
|
+
db.delete(skillLoadedEvents).run();
|
|
83
|
+
db.delete(toolInvocations).run();
|
|
84
|
+
db.delete(conversations).run();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("deletes non-cascading rows — including skill_loaded_events — for pruned conversations only", () => {
|
|
88
|
+
const staleUpdatedAt = Date.now() - 60 * 86_400_000;
|
|
89
|
+
seedConversation(STALE_ID, staleUpdatedAt);
|
|
90
|
+
seedConversation(FRESH_ID, Date.now());
|
|
91
|
+
|
|
92
|
+
pruneOldConversationsJob(JOB, CONFIG);
|
|
93
|
+
|
|
94
|
+
expect(countRows(STALE_ID)).toEqual({ invocations: 0, skillLoads: 0 });
|
|
95
|
+
expect(countRows(FRESH_ID)).toEqual({ invocations: 1, skillLoads: 1 });
|
|
96
|
+
const remaining = getDb().select().from(conversations).all();
|
|
97
|
+
expect(remaining.map((c) => c.id)).toEqual([FRESH_ID]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -6,17 +6,26 @@ import {
|
|
|
6
6
|
__resetRegistryForTesting,
|
|
7
7
|
getAllToolDefinitions,
|
|
8
8
|
getAllTools,
|
|
9
|
+
getCoreToolOverride,
|
|
9
10
|
getSkillRefCount,
|
|
10
11
|
getSkillToolNames,
|
|
11
12
|
getTool,
|
|
12
13
|
getToolOwner,
|
|
14
|
+
getWorkspaceToolNames,
|
|
13
15
|
initializeTools,
|
|
16
|
+
registerPluginTools,
|
|
14
17
|
registerSkillTools,
|
|
15
18
|
registerTool,
|
|
19
|
+
registerWorkspaceTools,
|
|
16
20
|
unregisterSkillTools,
|
|
21
|
+
unregisterWorkspaceTool,
|
|
17
22
|
} from "../tools/registry.js";
|
|
18
23
|
import { eagerModuleToolNames, explicitTools } from "../tools/tool-manifest.js";
|
|
19
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
Tool,
|
|
26
|
+
ToolContext,
|
|
27
|
+
ToolExecutionResult,
|
|
28
|
+
} from "../tools/types.js";
|
|
20
29
|
|
|
21
30
|
// Clean up global registry after this file completes to prevent
|
|
22
31
|
// contamination of subsequent test files in combined runs.
|
|
@@ -414,3 +423,233 @@ describe("skill tool reference counting", () => {
|
|
|
414
423
|
expect(getSkillRefCount("nonexistent-skill")).toBe(0);
|
|
415
424
|
});
|
|
416
425
|
});
|
|
426
|
+
|
|
427
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
428
|
+
// Workspace tool overrides
|
|
429
|
+
//
|
|
430
|
+
// Tests the registry surface exposed by registerWorkspaceTools /
|
|
431
|
+
// unregisterWorkspaceTool / getCoreToolOverride / getWorkspaceToolNames.
|
|
432
|
+
// The loader (workspace-tools/loader.ts) is tested separately — these
|
|
433
|
+
// tests treat the registry contract as the source of truth and exercise
|
|
434
|
+
// it directly with synthetic tool objects.
|
|
435
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
function makeWorkspaceInput(
|
|
438
|
+
name: string,
|
|
439
|
+
workspacePath = `/workspace/tools/${name}.ts`,
|
|
440
|
+
): { tool: Tool; workspacePath: string } {
|
|
441
|
+
return {
|
|
442
|
+
workspacePath,
|
|
443
|
+
tool: {
|
|
444
|
+
name,
|
|
445
|
+
category: "workspace",
|
|
446
|
+
description: `Workspace ${name}`,
|
|
447
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
448
|
+
executionTarget: "sandbox",
|
|
449
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
450
|
+
async execute(
|
|
451
|
+
_input: Record<string, unknown>,
|
|
452
|
+
_context: ToolContext,
|
|
453
|
+
): Promise<ToolExecutionResult> {
|
|
454
|
+
return { content: `workspace ${name}`, isError: false };
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function makeLoadedPluginTool(name: string): Tool {
|
|
461
|
+
return {
|
|
462
|
+
name,
|
|
463
|
+
category: "plugin",
|
|
464
|
+
description: `Plugin ${name}`,
|
|
465
|
+
defaultRiskLevel: RiskLevel.Low,
|
|
466
|
+
executionTarget: "sandbox",
|
|
467
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
468
|
+
async execute(
|
|
469
|
+
_input: Record<string, unknown>,
|
|
470
|
+
_context: ToolContext,
|
|
471
|
+
): Promise<ToolExecutionResult> {
|
|
472
|
+
return { content: `plugin ${name}`, isError: false };
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
describe("workspace tool registry", () => {
|
|
478
|
+
beforeEach(() => {
|
|
479
|
+
__resetRegistryForTesting();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("net-new workspace tool registers without stashing anything", () => {
|
|
483
|
+
__clearRegistryForTesting();
|
|
484
|
+
|
|
485
|
+
const accepted = registerWorkspaceTools([makeWorkspaceInput("new_tool")]);
|
|
486
|
+
|
|
487
|
+
expect(accepted).toHaveLength(1);
|
|
488
|
+
const tool = getTool("new_tool");
|
|
489
|
+
expect(tool).toBeDefined();
|
|
490
|
+
expect(getToolOwner("new_tool")).toEqual({
|
|
491
|
+
kind: "workspace",
|
|
492
|
+
id: "/workspace/tools/new_tool.ts",
|
|
493
|
+
});
|
|
494
|
+
expect(getCoreToolOverride("new_tool")).toBeUndefined();
|
|
495
|
+
expect(getWorkspaceToolNames()).toEqual(["new_tool"]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("workspace tool with same name as core stashes the original", () => {
|
|
499
|
+
__clearRegistryForTesting();
|
|
500
|
+
const coreTool = makeFakeTool("override_target");
|
|
501
|
+
registerTool(coreTool);
|
|
502
|
+
expect(getTool("override_target")).toEqual(coreTool);
|
|
503
|
+
|
|
504
|
+
registerWorkspaceTools([makeWorkspaceInput("override_target")]);
|
|
505
|
+
|
|
506
|
+
// Live registry now points at the workspace tool.
|
|
507
|
+
const live = getTool("override_target");
|
|
508
|
+
expect(live).toBeDefined();
|
|
509
|
+
expect(getToolOwner("override_target")?.kind).toBe("workspace");
|
|
510
|
+
expect(getToolOwner("override_target")?.kind).toBe("workspace");
|
|
511
|
+
|
|
512
|
+
// Stash preserves the original for later restoration.
|
|
513
|
+
expect(getCoreToolOverride("override_target")).toEqual(coreTool);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("unregister restores the stashed core tool when one exists", () => {
|
|
517
|
+
__clearRegistryForTesting();
|
|
518
|
+
const coreTool = makeFakeTool("restore_me");
|
|
519
|
+
registerTool(coreTool);
|
|
520
|
+
registerWorkspaceTools([makeWorkspaceInput("restore_me")]);
|
|
521
|
+
expect(getToolOwner("restore_me")?.kind).toBe("workspace");
|
|
522
|
+
|
|
523
|
+
unregisterWorkspaceTool("restore_me");
|
|
524
|
+
|
|
525
|
+
expect(getTool("restore_me")).toEqual(coreTool);
|
|
526
|
+
expect(getCoreToolOverride("restore_me")).toBeUndefined();
|
|
527
|
+
expect(getWorkspaceToolNames()).toEqual([]);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("unregister deletes the tool when no core counterpart was stashed", () => {
|
|
531
|
+
__clearRegistryForTesting();
|
|
532
|
+
registerWorkspaceTools([makeWorkspaceInput("only_workspace")]);
|
|
533
|
+
expect(getTool("only_workspace")).toBeDefined();
|
|
534
|
+
|
|
535
|
+
unregisterWorkspaceTool("only_workspace");
|
|
536
|
+
|
|
537
|
+
expect(getTool("only_workspace")).toBeUndefined();
|
|
538
|
+
expect(getCoreToolOverride("only_workspace")).toBeUndefined();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("unregister is a no-op on a non-workspace name", () => {
|
|
542
|
+
__clearRegistryForTesting();
|
|
543
|
+
const coreTool = makeFakeTool("not_workspace");
|
|
544
|
+
registerTool(coreTool);
|
|
545
|
+
|
|
546
|
+
unregisterWorkspaceTool("not_workspace");
|
|
547
|
+
unregisterWorkspaceTool("never_existed");
|
|
548
|
+
|
|
549
|
+
expect(getTool("not_workspace")).toEqual(coreTool);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test("re-registering an existing workspace tool throws (single canonical source)", () => {
|
|
553
|
+
__clearRegistryForTesting();
|
|
554
|
+
registerWorkspaceTools([
|
|
555
|
+
makeWorkspaceInput("dupe", "/workspace/tools/dupe"),
|
|
556
|
+
]);
|
|
557
|
+
|
|
558
|
+
expect(() =>
|
|
559
|
+
registerWorkspaceTools([
|
|
560
|
+
makeWorkspaceInput("dupe", "/workspace/tools/dupe"),
|
|
561
|
+
]),
|
|
562
|
+
).toThrow(/already registered/);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("duplicate names within a single batch throw (no partial application)", () => {
|
|
566
|
+
__clearRegistryForTesting();
|
|
567
|
+
|
|
568
|
+
expect(() =>
|
|
569
|
+
registerWorkspaceTools([
|
|
570
|
+
makeWorkspaceInput("twin"),
|
|
571
|
+
makeWorkspaceInput("twin", "/workspace/tools/twin-alt"),
|
|
572
|
+
]),
|
|
573
|
+
).toThrow(/duplicate name/);
|
|
574
|
+
|
|
575
|
+
// Mutation phase never ran — registry stays empty for this name.
|
|
576
|
+
expect(getTool("twin")).toBeUndefined();
|
|
577
|
+
expect(getWorkspaceToolNames()).toEqual([]);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("batch rejection leaves earlier-batch tools un-mutated", () => {
|
|
581
|
+
__clearRegistryForTesting();
|
|
582
|
+
|
|
583
|
+
// Pre-occupy "guarded" as a skill tool — workspace tools cannot
|
|
584
|
+
// override skill tools (only core), so the batch must throw and
|
|
585
|
+
// leave the otherwise-fine "ok_a" / "ok_b" entries unregistered.
|
|
586
|
+
registerSkillTools("skill-a", [makeSkillTool("guarded")]);
|
|
587
|
+
|
|
588
|
+
expect(() =>
|
|
589
|
+
registerWorkspaceTools([
|
|
590
|
+
makeWorkspaceInput("ok_a"),
|
|
591
|
+
makeWorkspaceInput("guarded"),
|
|
592
|
+
makeWorkspaceInput("ok_b"),
|
|
593
|
+
]),
|
|
594
|
+
).toThrow(/conflicts with an existing/);
|
|
595
|
+
|
|
596
|
+
expect(getTool("ok_a")).toBeUndefined();
|
|
597
|
+
expect(getTool("ok_b")).toBeUndefined();
|
|
598
|
+
// The skill tool is still in the registry, untouched.
|
|
599
|
+
expect(getToolOwner("guarded")?.kind).toBe("skill");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("workspace tool blocks subsequent plugin registration with same name", () => {
|
|
603
|
+
__clearRegistryForTesting();
|
|
604
|
+
registerWorkspaceTools([makeWorkspaceInput("contested")]);
|
|
605
|
+
|
|
606
|
+
// Plugin attempts to register over a workspace-owned name. The
|
|
607
|
+
// registry's plugin-registration path warn-skips rather than
|
|
608
|
+
// throwing, mirroring the core-conflict behavior.
|
|
609
|
+
const accepted = registerPluginTools("some-plugin", [
|
|
610
|
+
makeLoadedPluginTool("contested"),
|
|
611
|
+
]);
|
|
612
|
+
|
|
613
|
+
expect(accepted).toEqual([]);
|
|
614
|
+
expect(getToolOwner("contested")?.kind).toBe("workspace");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("workspace tool blocks subsequent skill registration with same name", () => {
|
|
618
|
+
__clearRegistryForTesting();
|
|
619
|
+
registerWorkspaceTools([makeWorkspaceInput("skill_attempt")]);
|
|
620
|
+
|
|
621
|
+
const accepted = registerSkillTools("some-skill", [
|
|
622
|
+
makeSkillTool("skill_attempt"),
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
expect(accepted).toEqual([]);
|
|
626
|
+
expect(getToolOwner("skill_attempt")?.kind).toBe("workspace");
|
|
627
|
+
expect(getSkillToolNames()).toEqual([]);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("__resetRegistryForTesting clears the override stash", () => {
|
|
631
|
+
__clearRegistryForTesting();
|
|
632
|
+
const coreTool = makeFakeTool("baseline_tool");
|
|
633
|
+
registerTool(coreTool);
|
|
634
|
+
registerWorkspaceTools([makeWorkspaceInput("baseline_tool")]);
|
|
635
|
+
expect(getCoreToolOverride("baseline_tool")).toEqual(coreTool);
|
|
636
|
+
|
|
637
|
+
__resetRegistryForTesting();
|
|
638
|
+
|
|
639
|
+
expect(getCoreToolOverride("baseline_tool")).toBeUndefined();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("workspace tool overrides surface as the live tool in getAllToolDefinitions", () => {
|
|
643
|
+
__clearRegistryForTesting();
|
|
644
|
+
const coreTool = makeFakeTool("listed_tool");
|
|
645
|
+
registerTool(coreTool);
|
|
646
|
+
registerWorkspaceTools([makeWorkspaceInput("listed_tool")]);
|
|
647
|
+
|
|
648
|
+
const definitions = getAllToolDefinitions();
|
|
649
|
+
const ours = definitions.find((d) => d.name === "listed_tool");
|
|
650
|
+
expect(ours).toBeDefined();
|
|
651
|
+
// The live workspace tool defines its own description; ensure the
|
|
652
|
+
// definition does not leak the core tool's description.
|
|
653
|
+
expect(ours?.description).toBe("Workspace listed_tool");
|
|
654
|
+
});
|
|
655
|
+
});
|
|
@@ -125,6 +125,9 @@ mock.module("../tools/registry.js", () => ({
|
|
|
125
125
|
|
|
126
126
|
mock.module("../permissions/gateway-threshold-reader.js", () => ({
|
|
127
127
|
getAutoApproveThreshold: async () => "medium",
|
|
128
|
+
// Refresh failure ("null") keeps the original decision — these tests
|
|
129
|
+
// exercise the cached-threshold paths only.
|
|
130
|
+
refreshAutoApproveThreshold: async () => null,
|
|
128
131
|
_clearGlobalCacheForTesting: () => {},
|
|
129
132
|
}));
|
|
130
133
|
|