@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
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the conversation surface API — the explicit opt-in that promotes
|
|
3
|
+
* a background/scheduled conversation into the default ("standard")
|
|
4
|
+
* conversation listing so clients show it in the Recents sidebar grouping.
|
|
5
|
+
*
|
|
6
|
+
* Exercises:
|
|
7
|
+
* - POST /v1/conversations/:id/surface (set + clear, validation, 404)
|
|
8
|
+
* - GET /v1/conversations default listing inclusion of surfaced rows
|
|
9
|
+
* - GET /v1/conversations?conversationType=background|scheduled unchanged
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../../../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Stub the event hub to avoid spinning up real SSE infrastructure.
|
|
22
|
+
mock.module("../../assistant-event-hub.js", () => ({
|
|
23
|
+
assistantEventHub: {
|
|
24
|
+
publish: async () => {},
|
|
25
|
+
subscribe: () => () => {},
|
|
26
|
+
},
|
|
27
|
+
broadcastMessage: () => {},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Spy on the sync publish so we can assert the invalidation fires after a
|
|
31
|
+
// successful write without standing up the real broadcast machinery.
|
|
32
|
+
const publishedListAndMetadata: Array<{
|
|
33
|
+
reason: string;
|
|
34
|
+
conversationIds: string | string[];
|
|
35
|
+
}> = [];
|
|
36
|
+
mock.module("../../sync/resource-sync-events.js", () => ({
|
|
37
|
+
publishConversationListChanged: () => {},
|
|
38
|
+
publishConversationTitleChanged: () => {},
|
|
39
|
+
publishConversationListAndMetadataChanged: (
|
|
40
|
+
reason: string,
|
|
41
|
+
conversationIds: string | string[],
|
|
42
|
+
) => {
|
|
43
|
+
publishedListAndMetadata.push({ reason, conversationIds });
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
createConversation,
|
|
49
|
+
getConversation,
|
|
50
|
+
} from "../../../memory/conversation-crud.js";
|
|
51
|
+
import { getDb } from "../../../memory/db-connection.js";
|
|
52
|
+
import { initializeDb } from "../../../memory/db-init.js";
|
|
53
|
+
import { conversations } from "../../../memory/schema.js";
|
|
54
|
+
import { ROUTES as CONVERSATION_LIST_ROUTES } from "../conversation-list-routes.js";
|
|
55
|
+
import { ROUTES as CONVERSATION_MANAGEMENT_ROUTES } from "../conversation-management-routes.js";
|
|
56
|
+
import { BadRequestError, NotFoundError } from "../errors.js";
|
|
57
|
+
import type { RouteDefinition } from "../types.js";
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// DB bootstrap
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
initializeDb();
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function findHandler(routes: RouteDefinition[], operationId: string) {
|
|
70
|
+
const route = routes.find((r) => r.operationId === operationId);
|
|
71
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
72
|
+
return route.handler;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const surfaceHandler = findHandler(
|
|
76
|
+
CONVERSATION_MANAGEMENT_ROUTES,
|
|
77
|
+
"surfaceConversation",
|
|
78
|
+
);
|
|
79
|
+
const listHandler = findHandler(CONVERSATION_LIST_ROUTES, "listConversations");
|
|
80
|
+
const reorderHandler = findHandler(
|
|
81
|
+
CONVERSATION_MANAGEMENT_ROUTES,
|
|
82
|
+
"reorderConversations",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
interface SurfaceResponse {
|
|
86
|
+
ok: boolean;
|
|
87
|
+
conversationId: string;
|
|
88
|
+
surfacedAt: number | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ConversationSummary {
|
|
92
|
+
id: string;
|
|
93
|
+
title: string;
|
|
94
|
+
conversationType: string;
|
|
95
|
+
surfacedAt?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface ListResponse {
|
|
99
|
+
conversations: ConversationSummary[];
|
|
100
|
+
nextOffset: number;
|
|
101
|
+
hasMore: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function clearConversations(): void {
|
|
105
|
+
getDb().delete(conversations).run();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function seed(
|
|
109
|
+
title: string,
|
|
110
|
+
conversationType: "standard" | "background" | "scheduled",
|
|
111
|
+
): string {
|
|
112
|
+
return createConversation({ title, conversationType, source: "test" }).id;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function listIds(queryParams: Record<string, string> = {}): string[] {
|
|
116
|
+
const response = listHandler({ queryParams }) as ListResponse;
|
|
117
|
+
return response.conversations.map((c) => c.id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Tests
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
describe("POST /v1/conversations/:id/surface", () => {
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
clearConversations();
|
|
127
|
+
publishedListAndMetadata.length = 0;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("surfaces a background conversation and returns the timestamp", () => {
|
|
131
|
+
const id = seed("Background run", "background");
|
|
132
|
+
const before = Date.now();
|
|
133
|
+
const response = surfaceHandler({
|
|
134
|
+
pathParams: { id },
|
|
135
|
+
body: { surfaced: true },
|
|
136
|
+
}) as SurfaceResponse;
|
|
137
|
+
expect(response.ok).toBe(true);
|
|
138
|
+
expect(response.conversationId).toBe(id);
|
|
139
|
+
expect(response.surfacedAt).toBeGreaterThanOrEqual(before);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("publishes a shape-changing list invalidation after the write", () => {
|
|
143
|
+
const id = seed("Background run", "background");
|
|
144
|
+
surfaceHandler({ pathParams: { id }, body: { surfaced: true } });
|
|
145
|
+
expect(publishedListAndMetadata).toEqual([
|
|
146
|
+
{ reason: "reordered", conversationIds: id },
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("clearing returns surfacedAt null", () => {
|
|
151
|
+
const id = seed("Background run", "background");
|
|
152
|
+
surfaceHandler({ pathParams: { id }, body: { surfaced: true } });
|
|
153
|
+
const response = surfaceHandler({
|
|
154
|
+
pathParams: { id },
|
|
155
|
+
body: { surfaced: false },
|
|
156
|
+
}) as SurfaceResponse;
|
|
157
|
+
expect(response.surfacedAt).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("404s for an unknown conversation id", () => {
|
|
161
|
+
expect(() =>
|
|
162
|
+
surfaceHandler({
|
|
163
|
+
pathParams: { id: crypto.randomUUID() },
|
|
164
|
+
body: { surfaced: true },
|
|
165
|
+
}),
|
|
166
|
+
).toThrow(NotFoundError);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("400s when surfaced is missing or not a boolean", () => {
|
|
170
|
+
const id = seed("Background run", "background");
|
|
171
|
+
expect(() => surfaceHandler({ pathParams: { id }, body: {} })).toThrow(
|
|
172
|
+
BadRequestError,
|
|
173
|
+
);
|
|
174
|
+
expect(() =>
|
|
175
|
+
surfaceHandler({ pathParams: { id }, body: { surfaced: "yes" } }),
|
|
176
|
+
).toThrow(BadRequestError);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("GET /v1/conversations — surfaced rows in the default listing", () => {
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
clearConversations();
|
|
183
|
+
publishedListAndMetadata.length = 0;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("background/scheduled rows stay out of the default listing until surfaced", () => {
|
|
187
|
+
const standardId = seed("Standard chat", "standard");
|
|
188
|
+
seed("Background run", "background");
|
|
189
|
+
seed("Scheduled run", "scheduled");
|
|
190
|
+
expect(listIds()).toEqual([standardId]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("surfacing promotes a background conversation into the default listing", () => {
|
|
194
|
+
const standardId = seed("Standard chat", "standard");
|
|
195
|
+
const backgroundId = seed("Background run", "background");
|
|
196
|
+
surfaceHandler({
|
|
197
|
+
pathParams: { id: backgroundId },
|
|
198
|
+
body: { surfaced: true },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const response = listHandler({ queryParams: {} }) as ListResponse;
|
|
202
|
+
expect(response.conversations.map((c) => c.id).sort()).toEqual(
|
|
203
|
+
[standardId, backgroundId].sort(),
|
|
204
|
+
);
|
|
205
|
+
const surfaced = response.conversations.find((c) => c.id === backgroundId)!;
|
|
206
|
+
expect(surfaced.conversationType).toBe("background");
|
|
207
|
+
expect(surfaced.surfacedAt).toBeGreaterThan(0);
|
|
208
|
+
const standard = response.conversations.find((c) => c.id === standardId)!;
|
|
209
|
+
expect(standard.surfacedAt).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("surfacing promotes a scheduled conversation into the default listing", () => {
|
|
213
|
+
const scheduledId = seed("Scheduled run", "scheduled");
|
|
214
|
+
surfaceHandler({
|
|
215
|
+
pathParams: { id: scheduledId },
|
|
216
|
+
body: { surfaced: true },
|
|
217
|
+
});
|
|
218
|
+
expect(listIds()).toEqual([scheduledId]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("clearing the promotion removes the row from the default listing again", () => {
|
|
222
|
+
const backgroundId = seed("Background run", "background");
|
|
223
|
+
surfaceHandler({
|
|
224
|
+
pathParams: { id: backgroundId },
|
|
225
|
+
body: { surfaced: true },
|
|
226
|
+
});
|
|
227
|
+
expect(listIds()).toEqual([backgroundId]);
|
|
228
|
+
|
|
229
|
+
surfaceHandler({
|
|
230
|
+
pathParams: { id: backgroundId },
|
|
231
|
+
body: { surfaced: false },
|
|
232
|
+
});
|
|
233
|
+
expect(listIds()).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("filtered background/scheduled listings keep returning surfaced rows", () => {
|
|
237
|
+
const backgroundId = seed("Background run", "background");
|
|
238
|
+
const scheduledId = seed("Scheduled run", "scheduled");
|
|
239
|
+
surfaceHandler({
|
|
240
|
+
pathParams: { id: backgroundId },
|
|
241
|
+
body: { surfaced: true },
|
|
242
|
+
});
|
|
243
|
+
surfaceHandler({
|
|
244
|
+
pathParams: { id: scheduledId },
|
|
245
|
+
body: { surfaced: true },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// "background" is the back-compat umbrella (background + scheduled).
|
|
249
|
+
expect(listIds({ conversationType: "background" }).sort()).toEqual(
|
|
250
|
+
[backgroundId, scheduledId].sort(),
|
|
251
|
+
);
|
|
252
|
+
expect(listIds({ conversationType: "scheduled" })).toEqual([scheduledId]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("moving a surfaced conversation to system:background clears the promotion", () => {
|
|
256
|
+
const backgroundId = seed("Background run", "background");
|
|
257
|
+
surfaceHandler({
|
|
258
|
+
pathParams: { id: backgroundId },
|
|
259
|
+
body: { surfaced: true },
|
|
260
|
+
});
|
|
261
|
+
expect(listIds()).toEqual([backgroundId]);
|
|
262
|
+
|
|
263
|
+
// Move-to-Group sends only groupId/isPinned through the reorder
|
|
264
|
+
// endpoint — the demotion out of Recents must happen in the same
|
|
265
|
+
// server-side write, with no second API call from the client.
|
|
266
|
+
reorderHandler({
|
|
267
|
+
body: {
|
|
268
|
+
updates: [
|
|
269
|
+
{ conversationId: backgroundId, groupId: "system:background" },
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(getConversation(backgroundId)?.surfacedAt).toBeNull();
|
|
275
|
+
expect(listIds()).toEqual([]);
|
|
276
|
+
expect(listIds({ conversationType: "background" })).toEqual([backgroundId]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("moving a surfaced conversation to system:scheduled clears the promotion", () => {
|
|
280
|
+
const scheduledId = seed("Scheduled run", "scheduled");
|
|
281
|
+
surfaceHandler({
|
|
282
|
+
pathParams: { id: scheduledId },
|
|
283
|
+
body: { surfaced: true },
|
|
284
|
+
});
|
|
285
|
+
expect(listIds()).toEqual([scheduledId]);
|
|
286
|
+
|
|
287
|
+
reorderHandler({
|
|
288
|
+
body: {
|
|
289
|
+
updates: [{ conversationId: scheduledId, groupId: "system:scheduled" }],
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(getConversation(scheduledId)?.surfacedAt).toBeNull();
|
|
294
|
+
expect(listIds()).toEqual([]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("moving a surfaced conversation to a non-demoting group keeps the promotion", () => {
|
|
298
|
+
const backgroundId = seed("Background run", "background");
|
|
299
|
+
surfaceHandler({
|
|
300
|
+
pathParams: { id: backgroundId },
|
|
301
|
+
body: { surfaced: true },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
reorderHandler({
|
|
305
|
+
body: {
|
|
306
|
+
updates: [{ conversationId: backgroundId, groupId: "system:all" }],
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(getConversation(backgroundId)?.surfacedAt).toBeGreaterThan(0);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("archived surfaced rows stay out of the default (active) listing", () => {
|
|
314
|
+
const backgroundId = seed("Background run", "background");
|
|
315
|
+
surfaceHandler({
|
|
316
|
+
pathParams: { id: backgroundId },
|
|
317
|
+
body: { surfaced: true },
|
|
318
|
+
});
|
|
319
|
+
getDb().update(conversations).set({ archivedAt: Date.now() }).run();
|
|
320
|
+
expect(listIds()).toEqual([]);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -29,7 +29,6 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
29
29
|
|
|
30
30
|
// ── Real imports (after mocks) ────────────────────────────────────────────────
|
|
31
31
|
|
|
32
|
-
import { setOverridesForTesting } from "../../../__tests__/feature-flag-test-helpers.js";
|
|
33
32
|
import { getDb } from "../../../memory/db-connection.js";
|
|
34
33
|
import { initializeDb } from "../../../memory/db-init.js";
|
|
35
34
|
import { providerConnections } from "../../../memory/schema/inference.js";
|
|
@@ -91,7 +90,6 @@ function seedConnection(opts: {
|
|
|
91
90
|
beforeEach(() => {
|
|
92
91
|
clearConnections();
|
|
93
92
|
fakeConfig = {};
|
|
94
|
-
setOverridesForTesting({ "openai-compatible-endpoints": true });
|
|
95
93
|
});
|
|
96
94
|
|
|
97
95
|
// ── GET list ─────────────────────────────────────────────────────────────────
|
|
@@ -160,36 +158,6 @@ describe("GET inference/provider-connections (list)", () => {
|
|
|
160
158
|
expect(result.connections).toEqual([]);
|
|
161
159
|
});
|
|
162
160
|
|
|
163
|
-
test("hides openai-compatible rows when the feature flag is disabled", async () => {
|
|
164
|
-
setOverridesForTesting({ "openai-compatible-endpoints": false });
|
|
165
|
-
seedConnection({
|
|
166
|
-
name: "my-openai",
|
|
167
|
-
provider: "openai",
|
|
168
|
-
auth: { type: "platform" },
|
|
169
|
-
});
|
|
170
|
-
seedConnection({
|
|
171
|
-
name: "my-compatible",
|
|
172
|
-
provider: "openai-compatible",
|
|
173
|
-
auth: { type: "api_key", credential: "ref/k" },
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const result = (await call(
|
|
177
|
-
findHandler("inference_provider_connections_list"),
|
|
178
|
-
{},
|
|
179
|
-
)) as { connections: Array<{ name: string }> };
|
|
180
|
-
|
|
181
|
-
expect(result.connections.map((c) => c.name)).toEqual(["my-openai"]);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("rejects openai-compatible provider filter when the feature flag is disabled", async () => {
|
|
185
|
-
setOverridesForTesting({ "openai-compatible-endpoints": false });
|
|
186
|
-
|
|
187
|
-
await expect(
|
|
188
|
-
call(findHandler("inference_provider_connections_list"), {
|
|
189
|
-
queryParams: { provider: "openai-compatible" },
|
|
190
|
-
}),
|
|
191
|
-
).rejects.toBeInstanceOf(BadRequestError);
|
|
192
|
-
});
|
|
193
161
|
});
|
|
194
162
|
|
|
195
163
|
// ── GET single ────────────────────────────────────────────────────────────────
|
|
@@ -219,20 +187,6 @@ describe("GET inference/provider-connections/:name (single)", () => {
|
|
|
219
187
|
).rejects.toBeInstanceOf(NotFoundError);
|
|
220
188
|
});
|
|
221
189
|
|
|
222
|
-
test("throws 404 for openai-compatible row when the feature flag is disabled", async () => {
|
|
223
|
-
setOverridesForTesting({ "openai-compatible-endpoints": false });
|
|
224
|
-
seedConnection({
|
|
225
|
-
name: "my-compatible",
|
|
226
|
-
provider: "openai-compatible",
|
|
227
|
-
auth: { type: "api_key", credential: "ref/k" },
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
await expect(
|
|
231
|
-
call(findHandler("inference_provider_connections_get"), {
|
|
232
|
-
pathParams: { name: "my-compatible" },
|
|
233
|
-
}),
|
|
234
|
-
).rejects.toBeInstanceOf(NotFoundError);
|
|
235
|
-
});
|
|
236
190
|
});
|
|
237
191
|
|
|
238
192
|
// ── POST create ───────────────────────────────────────────────────────────────
|
|
@@ -317,22 +271,6 @@ describe("POST inference/provider-connections (create)", () => {
|
|
|
317
271
|
).rejects.toBeInstanceOf(BadRequestError);
|
|
318
272
|
});
|
|
319
273
|
|
|
320
|
-
test("rejects openai-compatible creation when the feature flag is disabled", async () => {
|
|
321
|
-
setOverridesForTesting({ "openai-compatible-endpoints": false });
|
|
322
|
-
|
|
323
|
-
await expect(
|
|
324
|
-
call(findHandler("inference_provider_connections_create"), {
|
|
325
|
-
body: {
|
|
326
|
-
name: "my-compatible",
|
|
327
|
-
provider: "openai-compatible",
|
|
328
|
-
auth: { type: "api_key", credential: "ref/k" },
|
|
329
|
-
base_url: "http://localhost:8080/v1",
|
|
330
|
-
models: [{ id: "local-model" }],
|
|
331
|
-
},
|
|
332
|
-
}),
|
|
333
|
-
).rejects.toBeInstanceOf(BadRequestError);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
274
|
test("throws 400 when auth schema is invalid (api_key without credential)", async () => {
|
|
337
275
|
await expect(
|
|
338
276
|
call(findHandler("inference_provider_connections_create"), {
|
|
@@ -369,8 +369,12 @@ describe("GET /v1/plugins/search", () => {
|
|
|
369
369
|
catalog(ref, [
|
|
370
370
|
{
|
|
371
371
|
name: "simple-memory",
|
|
372
|
-
path: "
|
|
373
|
-
source: {
|
|
372
|
+
path: "github:vellum-ai/simple-memory@ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
373
|
+
source: {
|
|
374
|
+
kind: "github",
|
|
375
|
+
repo: "vellum-ai/simple-memory",
|
|
376
|
+
ref: "ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
377
|
+
},
|
|
374
378
|
},
|
|
375
379
|
{
|
|
376
380
|
name: "caveman",
|
|
@@ -402,8 +406,12 @@ describe("GET /v1/plugins/search", () => {
|
|
|
402
406
|
matches: [
|
|
403
407
|
{
|
|
404
408
|
name: "simple-memory",
|
|
405
|
-
path: "
|
|
406
|
-
source: {
|
|
409
|
+
path: "github:vellum-ai/simple-memory@ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
410
|
+
source: {
|
|
411
|
+
kind: "github",
|
|
412
|
+
repo: "vellum-ai/simple-memory",
|
|
413
|
+
ref: "ed09a4c01bf18e4ac8859faee94cb65c7cbd1ca3",
|
|
414
|
+
},
|
|
407
415
|
},
|
|
408
416
|
],
|
|
409
417
|
});
|
|
@@ -458,7 +466,7 @@ describe("GET /v1/plugins/search", () => {
|
|
|
458
466
|
test("PluginCatalogUnavailableError → ServiceUnavailableError (503)", async () => {
|
|
459
467
|
getCatalogSpy.mockImplementation(async () => {
|
|
460
468
|
throw new PluginCatalogUnavailableError(
|
|
461
|
-
"GitHub contents listing failed for
|
|
469
|
+
"GitHub contents listing failed for plugins @ main: HTTP 403",
|
|
462
470
|
403,
|
|
463
471
|
);
|
|
464
472
|
});
|
|
@@ -489,8 +497,12 @@ describe("GET /v1/plugins/search", () => {
|
|
|
489
497
|
const frozenMatches = Object.freeze([
|
|
490
498
|
Object.freeze({
|
|
491
499
|
name: "a",
|
|
492
|
-
path: "
|
|
493
|
-
source: {
|
|
500
|
+
path: "github:acme/a@bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
501
|
+
source: {
|
|
502
|
+
kind: "github",
|
|
503
|
+
repo: "acme/a",
|
|
504
|
+
ref: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
505
|
+
} as const,
|
|
494
506
|
}),
|
|
495
507
|
]) as readonly PluginSearchMatch[];
|
|
496
508
|
getCatalogSpy.mockImplementation(async (ref) =>
|
|
@@ -506,7 +518,11 @@ describe("GET /v1/plugins/search", () => {
|
|
|
506
518
|
result.matches.push({
|
|
507
519
|
name: "b",
|
|
508
520
|
path: "x",
|
|
509
|
-
source: {
|
|
521
|
+
source: {
|
|
522
|
+
kind: "github",
|
|
523
|
+
repo: "acme/b",
|
|
524
|
+
ref: "cccccccccccccccccccccccccccccccccccccccc",
|
|
525
|
+
},
|
|
510
526
|
}),
|
|
511
527
|
).not.toThrow();
|
|
512
528
|
});
|
|
@@ -620,7 +636,11 @@ function pluginDetails(overrides: Partial<PluginDetails> = {}): PluginDetails {
|
|
|
620
636
|
homepage: overrides.homepage ?? null,
|
|
621
637
|
license: overrides.license ?? null,
|
|
622
638
|
version: overrides.version ?? null,
|
|
623
|
-
source: overrides.source ?? {
|
|
639
|
+
source: overrides.source ?? {
|
|
640
|
+
kind: "github",
|
|
641
|
+
repo: "JuliusBrussee/caveman",
|
|
642
|
+
ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
|
|
643
|
+
},
|
|
624
644
|
readme: overrides.readme ?? null,
|
|
625
645
|
ref: overrides.ref ?? "main",
|
|
626
646
|
};
|
|
@@ -766,8 +786,8 @@ describe("POST /v1/plugins/install", () => {
|
|
|
766
786
|
|
|
767
787
|
test("ignores a caller-supplied ref and pins to the curated default", async () => {
|
|
768
788
|
// Security boundary: installing from an unreviewed ref (a PR branch,
|
|
769
|
-
// fork ref, ...) could load attacker-controlled marketplace
|
|
770
|
-
//
|
|
789
|
+
// fork ref, ...) could load attacker-controlled marketplace code, so the
|
|
790
|
+
// HTTP route never honors a body `ref` — it always resolves
|
|
771
791
|
// against the curated default ref.
|
|
772
792
|
installSpy.mockImplementation(async (opts) => ({
|
|
773
793
|
name: opts.name,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import { installAcpConfigStub } from "../../acp/__tests__/helpers/acp-config-stub.js";
|
|
40
40
|
import { installWhichStub } from "../../acp/__tests__/helpers/which-stub.js";
|
|
41
41
|
import type { AcpSessionState } from "../../acp/index.js";
|
|
42
|
+
import * as pendingInteractions from "../pending-interactions.js";
|
|
42
43
|
|
|
43
44
|
const config = await installAcpConfigStub();
|
|
44
45
|
const which = installWhichStub();
|
|
@@ -170,6 +171,31 @@ mock.module("../../tools/credentials/broker.js", () => ({
|
|
|
170
171
|
},
|
|
171
172
|
}));
|
|
172
173
|
|
|
174
|
+
// Drive the spawn route's high-risk approval gate (ATL-822). `spawnSession`
|
|
175
|
+
// registers a `directResolve` confirmation in `pendingInteractions` and then
|
|
176
|
+
// broadcasts a `confirmation_request`. Real broadcasting needs the event hub /
|
|
177
|
+
// SSE wiring, so the mock auto-resolves the freshly registered interaction
|
|
178
|
+
// the same way `POST /v1/confirm` would (resolve + directResolve). Tests flip
|
|
179
|
+
// `approvalBehavior` to exercise allow / deny, and read `confirmationRequests`
|
|
180
|
+
// to assert the prompt's risk shape. `interaction_resolved` and other event
|
|
181
|
+
// types are ignored.
|
|
182
|
+
type ApprovalBehavior = "allow" | "deny";
|
|
183
|
+
let approvalBehavior: ApprovalBehavior = "allow";
|
|
184
|
+
const confirmationRequests: Array<Record<string, unknown>> = [];
|
|
185
|
+
|
|
186
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
187
|
+
broadcastMessage: (msg: { type?: string; requestId?: string }) => {
|
|
188
|
+
if (msg?.type !== "confirmation_request") return;
|
|
189
|
+
confirmationRequests.push(msg as Record<string, unknown>);
|
|
190
|
+
const decision = approvalBehavior;
|
|
191
|
+
const interaction = pendingInteractions.resolve(
|
|
192
|
+
msg.requestId as string,
|
|
193
|
+
decision === "allow" ? "approved" : "rejected",
|
|
194
|
+
);
|
|
195
|
+
interaction?.directResolve?.(decision);
|
|
196
|
+
},
|
|
197
|
+
}));
|
|
198
|
+
|
|
173
199
|
import { eq } from "drizzle-orm";
|
|
174
200
|
|
|
175
201
|
import { getDb, getSqlite } from "../../memory/db-connection.js";
|
|
@@ -193,6 +219,8 @@ beforeEach(() => {
|
|
|
193
219
|
capturedSpawns.length = 0;
|
|
194
220
|
vaultStore.clear();
|
|
195
221
|
metadataStore.clear();
|
|
222
|
+
approvalBehavior = "allow";
|
|
223
|
+
confirmationRequests.length = 0;
|
|
196
224
|
});
|
|
197
225
|
|
|
198
226
|
// ---------------------------------------------------------------------------
|
|
@@ -224,6 +252,84 @@ describe("POST /v1/acp/spawn", () => {
|
|
|
224
252
|
await expect(handler({ body: { agent: "claude" } })).rejects.toThrow(
|
|
225
253
|
"agent, task, and conversationId are required",
|
|
226
254
|
);
|
|
255
|
+
// Guard runs before the approval gate — no prompt is surfaced.
|
|
256
|
+
expect(confirmationRequests).toHaveLength(0);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// POST /v1/acp/spawn — high-risk approval gate (ATL-822)
|
|
262
|
+
//
|
|
263
|
+
// Spawning an ACP agent is a host subprocess with auto-allowed
|
|
264
|
+
// filesystem/terminal access. The skill-tool path gets the descriptor's
|
|
265
|
+
// `risk: "high"` prompt via ToolExecutor; this route reaches the session
|
|
266
|
+
// manager directly, so it surfaces the same confirmation itself and refuses
|
|
267
|
+
// to spawn unless a guardian approves.
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
describe("POST /v1/acp/spawn — approval gate", () => {
|
|
271
|
+
test("surfaces a high-risk host confirmation before spawning", async () => {
|
|
272
|
+
seedVaultToken("test-token-abc123");
|
|
273
|
+
|
|
274
|
+
const handler = getSpawnHandler();
|
|
275
|
+
await handler({
|
|
276
|
+
body: {
|
|
277
|
+
agent: "claude",
|
|
278
|
+
task: "do a thing",
|
|
279
|
+
conversationId: "conv-1",
|
|
280
|
+
cwd: "/work/repo",
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(confirmationRequests).toHaveLength(1);
|
|
285
|
+
const prompt = confirmationRequests[0];
|
|
286
|
+
expect(prompt.toolName).toBe("acp_spawn");
|
|
287
|
+
expect(prompt.riskLevel).toBe("high");
|
|
288
|
+
expect(prompt.executionTarget).toBe("host");
|
|
289
|
+
expect(prompt.conversationId).toBe("conv-1");
|
|
290
|
+
expect((prompt.input as { cwd?: string }).cwd).toBe("/work/repo");
|
|
291
|
+
// Prompt resolved "allow" → spawn proceeds.
|
|
292
|
+
expect(capturedSpawns).toHaveLength(1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("throws ForbiddenError and does not spawn when the guardian denies", async () => {
|
|
296
|
+
approvalBehavior = "deny";
|
|
297
|
+
seedVaultToken("test-token-abc123");
|
|
298
|
+
|
|
299
|
+
const handler = getSpawnHandler();
|
|
300
|
+
await expect(
|
|
301
|
+
handler({
|
|
302
|
+
body: {
|
|
303
|
+
agent: "claude",
|
|
304
|
+
task: "do a thing",
|
|
305
|
+
conversationId: "conv-1",
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
).rejects.toThrow(/guardian approval/i);
|
|
309
|
+
|
|
310
|
+
// Denied before any host side effects: no subprocess, no pending leak.
|
|
311
|
+
expect(capturedSpawns).toHaveLength(0);
|
|
312
|
+
expect(pendingInteractions.getAll()).toHaveLength(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("denies (and does not spawn) when the client disconnects before approval", async () => {
|
|
316
|
+
seedVaultToken("test-token-abc123");
|
|
317
|
+
const controller = new AbortController();
|
|
318
|
+
controller.abort();
|
|
319
|
+
|
|
320
|
+
const handler = getSpawnHandler();
|
|
321
|
+
await expect(
|
|
322
|
+
handler({
|
|
323
|
+
body: {
|
|
324
|
+
agent: "claude",
|
|
325
|
+
task: "do a thing",
|
|
326
|
+
conversationId: "conv-1",
|
|
327
|
+
},
|
|
328
|
+
abortSignal: controller.signal,
|
|
329
|
+
}),
|
|
330
|
+
).rejects.toThrow(/guardian approval/i);
|
|
331
|
+
|
|
332
|
+
expect(capturedSpawns).toHaveLength(0);
|
|
227
333
|
});
|
|
228
334
|
});
|
|
229
335
|
|