@vellumai/assistant 0.8.10 → 0.8.11-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the default `history-repair` plugin's
|
|
2
|
+
* Tests for the default `history-repair` plugin's hooks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* `user-prompt-submit`:
|
|
5
5
|
* - The hook normalizes `latestMessages` exactly as `repairHistory` would for
|
|
6
6
|
* the three documented repair classes (orphan tool_result, missing
|
|
7
7
|
* tool_result, consecutive same-role messages) and is a no-op for a
|
|
@@ -11,6 +11,20 @@
|
|
|
11
11
|
* - Chain ordering: because defaults register first, the default repair hook
|
|
12
12
|
* runs ahead of a later-registered user hook, which therefore observes an
|
|
13
13
|
* already-normalized history.
|
|
14
|
+
*
|
|
15
|
+
* `post-model-call` (error-recovery):
|
|
16
|
+
* - On a provider rejection carrying a repairable ordering error, the hook
|
|
17
|
+
* deep-repairs the messages, marks the per-conversation bound, and asks the
|
|
18
|
+
* loop to continue.
|
|
19
|
+
* - Bounded to one pass per turn via that bound: a second consecutive ordering
|
|
20
|
+
* rejection is left to surface (the bound is cleared by the `stop` hook, not
|
|
21
|
+
* here).
|
|
22
|
+
* - Ignores non-ordering errors, tool-bearing turns, and finalized (non-error)
|
|
23
|
+
* replies — none of which it ever touches the bound for.
|
|
24
|
+
*
|
|
25
|
+
* `stop` (terminal cleanup):
|
|
26
|
+
* - Clears the repair bound unconditionally on the definitive terminal stop, so
|
|
27
|
+
* the next turn always repairs afresh no matter how the turn ended.
|
|
14
28
|
*/
|
|
15
29
|
|
|
16
30
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
@@ -18,10 +32,22 @@ import { beforeEach, describe, expect, test } from "bun:test";
|
|
|
18
32
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
19
33
|
import type {
|
|
20
34
|
PluginLogger,
|
|
35
|
+
PostModelCallContext,
|
|
36
|
+
StopContext,
|
|
21
37
|
UserPromptSubmitContext,
|
|
22
38
|
} from "../plugin-api/types.js";
|
|
39
|
+
import postModelCall from "../plugins/defaults/history-repair/hooks/post-model-call.js";
|
|
40
|
+
import stop from "../plugins/defaults/history-repair/hooks/stop.js";
|
|
23
41
|
import userPromptSubmit from "../plugins/defaults/history-repair/hooks/user-prompt-submit.js";
|
|
24
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
isOrderingRepairAttempted,
|
|
44
|
+
markOrderingRepairAttempted,
|
|
45
|
+
resetRepairStateStoreForTests,
|
|
46
|
+
} from "../plugins/defaults/history-repair/repair-state-store.js";
|
|
47
|
+
import {
|
|
48
|
+
deepRepairHistory,
|
|
49
|
+
repairHistory,
|
|
50
|
+
} from "../plugins/defaults/history-repair/terminal.js";
|
|
25
51
|
import { defaultHistoryRepairPlugin } from "../plugins/defaults/index.js";
|
|
26
52
|
import { runHook } from "../plugins/pipeline.js";
|
|
27
53
|
import {
|
|
@@ -30,6 +56,10 @@ import {
|
|
|
30
56
|
} from "../plugins/registry.js";
|
|
31
57
|
import type { Message } from "../providers/types.js";
|
|
32
58
|
|
|
59
|
+
/** Provider rejection text matched by `isRepairableOrderingError`. */
|
|
60
|
+
const ORDERING_ERROR_MESSAGE =
|
|
61
|
+
"messages: tool_use ids must have a corresponding tool_result";
|
|
62
|
+
|
|
33
63
|
const noopLogger: PluginLogger = {
|
|
34
64
|
info: () => {},
|
|
35
65
|
warn: () => {},
|
|
@@ -42,6 +72,8 @@ function makeCtx(messages: Message[]): UserPromptSubmitContext {
|
|
|
42
72
|
conversationId: "conv-test",
|
|
43
73
|
userMessageId: "msg-test",
|
|
44
74
|
requestId: "req-test",
|
|
75
|
+
modelProfileKey: null,
|
|
76
|
+
isNonInteractive: false,
|
|
45
77
|
prompt: "",
|
|
46
78
|
originalMessages: messages,
|
|
47
79
|
latestMessages: messages,
|
|
@@ -162,3 +194,196 @@ describe("history-repair user-prompt-submit hook — via runHook", () => {
|
|
|
162
194
|
expect(observed as Message[] | null).toEqual(expected.messages);
|
|
163
195
|
});
|
|
164
196
|
});
|
|
197
|
+
|
|
198
|
+
// ─── post-model-call hook (error-recovery) ───────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/** An ordering-violating history: a leading assistant turn deep-repair strips. */
|
|
201
|
+
function orderingViolatingHistory(): Message[] {
|
|
202
|
+
return [
|
|
203
|
+
{ role: "assistant", content: [{ type: "text", text: "orphaned" }] },
|
|
204
|
+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function makePostModelCallCtx(
|
|
209
|
+
overrides: Partial<PostModelCallContext> = {},
|
|
210
|
+
): PostModelCallContext {
|
|
211
|
+
return {
|
|
212
|
+
conversationId: "conv-pmc",
|
|
213
|
+
callSite: "mainAgent",
|
|
214
|
+
content: [],
|
|
215
|
+
messages: [],
|
|
216
|
+
stopReason: null,
|
|
217
|
+
decision: "stop",
|
|
218
|
+
logger: noopLogger,
|
|
219
|
+
...overrides,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe("history-repair post-model-call hook — direct", () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
resetRepairStateStoreForTests();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("repairable ordering error → deep-repairs and continues", async () => {
|
|
229
|
+
// GIVEN a provider rejection carrying a repairable ordering error over a
|
|
230
|
+
// history with a leading assistant turn.
|
|
231
|
+
const messages = orderingViolatingHistory();
|
|
232
|
+
const expected = deepRepairHistory(messages).messages;
|
|
233
|
+
const ctx = makePostModelCallCtx({
|
|
234
|
+
messages,
|
|
235
|
+
error: new Error(ORDERING_ERROR_MESSAGE),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// WHEN the hook runs.
|
|
239
|
+
await postModelCall(ctx);
|
|
240
|
+
|
|
241
|
+
// THEN it rewrites the messages to the deep-repaired history and asks the
|
|
242
|
+
// loop to retry.
|
|
243
|
+
expect(ctx.decision).toBe("continue");
|
|
244
|
+
expect(ctx.messages).toEqual(expected);
|
|
245
|
+
expect(isOrderingRepairAttempted(ctx.conversationId)).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("second consecutive ordering rejection is left to surface", async () => {
|
|
249
|
+
// GIVEN a turn whose first ordering rejection already triggered a repair.
|
|
250
|
+
const conversationId = "conv-bounded";
|
|
251
|
+
markOrderingRepairAttempted(conversationId);
|
|
252
|
+
const messages = orderingViolatingHistory();
|
|
253
|
+
const ctx = makePostModelCallCtx({
|
|
254
|
+
conversationId,
|
|
255
|
+
messages,
|
|
256
|
+
error: new Error(ORDERING_ERROR_MESSAGE),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// WHEN a second ordering rejection reaches the hook.
|
|
260
|
+
await postModelCall(ctx);
|
|
261
|
+
|
|
262
|
+
// THEN it does not repair again — the error surfaces.
|
|
263
|
+
expect(ctx.decision).toBe("stop");
|
|
264
|
+
expect(ctx.messages).toEqual(messages);
|
|
265
|
+
|
|
266
|
+
// AND the bound stays marked — the hook never clears it; the `stop` hook
|
|
267
|
+
// clears it at the turn boundary.
|
|
268
|
+
expect(isOrderingRepairAttempted(conversationId)).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("a non-ordering error while continuing is a no-op", async () => {
|
|
272
|
+
// GIVEN this turn already repaired an ordering rejection (bound marked),
|
|
273
|
+
// then an earlier hook recovered a different error and set the decision to
|
|
274
|
+
// continue.
|
|
275
|
+
const conversationId = "conv-cross-hook";
|
|
276
|
+
markOrderingRepairAttempted(conversationId);
|
|
277
|
+
const ctx = makePostModelCallCtx({
|
|
278
|
+
conversationId,
|
|
279
|
+
decision: "continue",
|
|
280
|
+
error: new Error("image dimensions exceed max allowed size"),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// WHEN the history-repair hook runs after that earlier hook.
|
|
284
|
+
await postModelCall(ctx);
|
|
285
|
+
|
|
286
|
+
// THEN it leaves the in-flight continue and the bound alone — it only acts
|
|
287
|
+
// on a repairable ordering rejection.
|
|
288
|
+
expect(ctx.decision).toBe("continue");
|
|
289
|
+
expect(isOrderingRepairAttempted(conversationId)).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("a mid-turn tool-bearing turn is a no-op", async () => {
|
|
293
|
+
// GIVEN this turn already repaired an ordering rejection (bound marked),
|
|
294
|
+
// then the model returned a tool-bearing turn the loop continues on its
|
|
295
|
+
// own.
|
|
296
|
+
const conversationId = "conv-tool-turn";
|
|
297
|
+
markOrderingRepairAttempted(conversationId);
|
|
298
|
+
const ctx = makePostModelCallCtx({
|
|
299
|
+
conversationId,
|
|
300
|
+
content: [{ type: "tool_use", id: "tu_1", name: "bash", input: {} }],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// WHEN the hook runs over the tool-bearing turn.
|
|
304
|
+
await postModelCall(ctx);
|
|
305
|
+
|
|
306
|
+
// THEN it leaves the decision and the bound alone — there is no provider
|
|
307
|
+
// rejection to act on.
|
|
308
|
+
expect(ctx.decision).toBe("stop");
|
|
309
|
+
expect(isOrderingRepairAttempted(conversationId)).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("non-ordering error is left untouched", async () => {
|
|
313
|
+
// GIVEN a provider rejection that is not an ordering violation.
|
|
314
|
+
const messages = orderingViolatingHistory();
|
|
315
|
+
const ctx = makePostModelCallCtx({
|
|
316
|
+
messages,
|
|
317
|
+
error: new Error("rate limit exceeded"),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// WHEN the hook runs.
|
|
321
|
+
await postModelCall(ctx);
|
|
322
|
+
|
|
323
|
+
// THEN it defers — the decision and history are unchanged.
|
|
324
|
+
expect(ctx.decision).toBe("stop");
|
|
325
|
+
expect(ctx.messages).toEqual(messages);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("finalized (non-error) reply is ignored", async () => {
|
|
329
|
+
// GIVEN a finalized reply — the model returned content, no error.
|
|
330
|
+
const messages = orderingViolatingHistory();
|
|
331
|
+
const ctx = makePostModelCallCtx({
|
|
332
|
+
messages,
|
|
333
|
+
content: [{ type: "text", text: "done" }],
|
|
334
|
+
stopReason: "end_turn",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// WHEN the hook runs.
|
|
338
|
+
await postModelCall(ctx);
|
|
339
|
+
|
|
340
|
+
// THEN the model response is left untouched — repair only applies to
|
|
341
|
+
// ordering rejections.
|
|
342
|
+
expect(ctx.decision).toBe("stop");
|
|
343
|
+
expect(ctx.messages).toEqual(messages);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ─── stop hook (terminal cleanup) ────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
function makeStopCtx(overrides: Partial<StopContext> = {}): StopContext {
|
|
350
|
+
return {
|
|
351
|
+
conversationId: "conv-stop",
|
|
352
|
+
messages: [],
|
|
353
|
+
exitReason: "no_tool_calls",
|
|
354
|
+
logger: noopLogger,
|
|
355
|
+
...overrides,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
describe("history-repair stop hook — direct", () => {
|
|
360
|
+
beforeEach(() => {
|
|
361
|
+
resetRepairStateStoreForTests();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("a terminal stop clears the repair bound", async () => {
|
|
365
|
+
// GIVEN a turn marked a repair-retry.
|
|
366
|
+
const conversationId = "conv-backstop";
|
|
367
|
+
markOrderingRepairAttempted(conversationId);
|
|
368
|
+
|
|
369
|
+
// WHEN the terminal stop hook runs.
|
|
370
|
+
await stop(makeStopCtx({ conversationId }));
|
|
371
|
+
|
|
372
|
+
// THEN the bound is cleared so the next turn repairs afresh.
|
|
373
|
+
expect(isOrderingRepairAttempted(conversationId)).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("clears the bound regardless of how the turn ended", async () => {
|
|
377
|
+
// GIVEN a turn marked a repair-retry that ends on an abort rather than a
|
|
378
|
+
// finalized reply.
|
|
379
|
+
const conversationId = "conv-abort";
|
|
380
|
+
markOrderingRepairAttempted(conversationId);
|
|
381
|
+
|
|
382
|
+
// WHEN the terminal stop hook runs for that exit.
|
|
383
|
+
await stop(makeStopCtx({ conversationId, exitReason: "aborted_pre_call" }));
|
|
384
|
+
|
|
385
|
+
// THEN the bound is still cleared — `stop` is the definitive terminal, so
|
|
386
|
+
// the next turn always repairs afresh.
|
|
387
|
+
expect(isOrderingRepairAttempted(conversationId)).toBe(false);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
@@ -372,6 +372,49 @@ describe("HostAppControlProxy", () => {
|
|
|
372
372
|
proxy.dispose();
|
|
373
373
|
});
|
|
374
374
|
|
|
375
|
+
test("non-start tool with mismatched target client is rejected", async () => {
|
|
376
|
+
mockActorMap.set("client-A", "user-1");
|
|
377
|
+
mockActorMap.set("client-B", "user-1");
|
|
378
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
379
|
+
const startCtrl = new AbortController();
|
|
380
|
+
|
|
381
|
+
const pStart = proxy.request(
|
|
382
|
+
"app_control_start",
|
|
383
|
+
{ tool: "start", app: "com.example.editor" },
|
|
384
|
+
"conv-1",
|
|
385
|
+
startCtrl.signal,
|
|
386
|
+
"user-1",
|
|
387
|
+
"client-A",
|
|
388
|
+
);
|
|
389
|
+
proxy.resolve(
|
|
390
|
+
(sentMessages[0] as Record<string, unknown>).requestId as string,
|
|
391
|
+
payload({ pngBase64: PNG_A }),
|
|
392
|
+
);
|
|
393
|
+
await pStart;
|
|
394
|
+
sentMessages.length = 0;
|
|
395
|
+
|
|
396
|
+
const result = await proxy.request(
|
|
397
|
+
"app_control_type",
|
|
398
|
+
{
|
|
399
|
+
tool: "type",
|
|
400
|
+
app: "com.example.editor",
|
|
401
|
+
text: "hello",
|
|
402
|
+
},
|
|
403
|
+
"conv-1",
|
|
404
|
+
new AbortController().signal,
|
|
405
|
+
"user-1",
|
|
406
|
+
"client-B",
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
expect(result.isError).toBe(true);
|
|
410
|
+
expect(result.content).toContain("client-A");
|
|
411
|
+
expect(result.content).toContain("client-B");
|
|
412
|
+
expect(result.content).toContain("app_control_start");
|
|
413
|
+
expect(sentMessages).toHaveLength(0);
|
|
414
|
+
|
|
415
|
+
proxy.dispose();
|
|
416
|
+
});
|
|
417
|
+
|
|
375
418
|
test("non-start tool with case-different but otherwise matching app is allowed", async () => {
|
|
376
419
|
const proxy = new HostAppControlProxy("conv-1");
|
|
377
420
|
const startCtrl = new AbortController();
|
|
@@ -1297,6 +1340,7 @@ describe("HostAppControlProxy", () => {
|
|
|
1297
1340
|
_setActiveAppControlSession({
|
|
1298
1341
|
conversationId: "conv-1",
|
|
1299
1342
|
app: "com.example.app",
|
|
1343
|
+
targetClientId: "client-A",
|
|
1300
1344
|
});
|
|
1301
1345
|
|
|
1302
1346
|
const resultPromise = proxy.request(
|
|
@@ -1332,6 +1376,7 @@ describe("HostAppControlProxy", () => {
|
|
|
1332
1376
|
_setActiveAppControlSession({
|
|
1333
1377
|
conversationId: "conv-1",
|
|
1334
1378
|
app: "com.example.app",
|
|
1379
|
+
targetClientId: "client-A",
|
|
1335
1380
|
});
|
|
1336
1381
|
|
|
1337
1382
|
const resultPromise = proxy.request(
|
|
@@ -44,6 +44,8 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
44
44
|
mockClients.find((c) => c.capabilities.includes(cap)),
|
|
45
45
|
listClientsByCapability: (cap: string) =>
|
|
46
46
|
mockClients.filter((c) => c.capabilities.includes(cap)),
|
|
47
|
+
listClientsByInterface: (interfaceId: string) =>
|
|
48
|
+
mockClients.filter((c) => c.interfaceId === interfaceId),
|
|
47
49
|
getActorPrincipalIdForClient: (clientId: string) =>
|
|
48
50
|
mockClients.find((c) => c.clientId === clientId)?.actorPrincipalId,
|
|
49
51
|
},
|
|
@@ -278,6 +280,53 @@ describe("HostBrowserProxy", () => {
|
|
|
278
280
|
});
|
|
279
281
|
});
|
|
280
282
|
|
|
283
|
+
describe("actor-scoped availability", () => {
|
|
284
|
+
const EXT_A: MockClient = {
|
|
285
|
+
clientId: "ext-a",
|
|
286
|
+
interfaceId: "chrome-extension",
|
|
287
|
+
actorPrincipalId: "actor-a",
|
|
288
|
+
capabilities: ["host_browser"],
|
|
289
|
+
};
|
|
290
|
+
const BRIDGE_B: MockClient = {
|
|
291
|
+
clientId: "bridge-b",
|
|
292
|
+
interfaceId: "macos",
|
|
293
|
+
actorPrincipalId: "actor-b",
|
|
294
|
+
capabilities: ["host_browser"],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
test("isAvailable(actor) only counts that actor's clients", () => {
|
|
298
|
+
mockClients = [EXT_A, BRIDGE_B];
|
|
299
|
+
expect(proxy.isAvailable("actor-a")).toBe(true);
|
|
300
|
+
expect(proxy.isAvailable("actor-b")).toBe(true);
|
|
301
|
+
expect(proxy.isAvailable("actor-c")).toBe(false);
|
|
302
|
+
// Legacy no-actor form still counts any client.
|
|
303
|
+
expect(proxy.isAvailable()).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("hasExtensionClient(actor) ignores other actors' extensions", () => {
|
|
307
|
+
mockClients = [EXT_A, BRIDGE_B];
|
|
308
|
+
expect(proxy.hasExtensionClient("actor-a")).toBe(true);
|
|
309
|
+
// actor-b has only the bridge — actor-a's extension must not count.
|
|
310
|
+
expect(proxy.hasExtensionClient("actor-b")).toBe(false);
|
|
311
|
+
expect(proxy.hasExtensionClient()).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("hasExtensionClient(actor) is false for a client without an actor binding", () => {
|
|
315
|
+
// Strict matching mirrors resolveTargetClient: a legacy extension
|
|
316
|
+
// connection without an actor is not dispatchable for an
|
|
317
|
+
// actor-authenticated caller, so it must not count as available.
|
|
318
|
+
mockClients = [
|
|
319
|
+
{
|
|
320
|
+
clientId: "legacy-ext",
|
|
321
|
+
interfaceId: "chrome-extension",
|
|
322
|
+
capabilities: ["host_browser"],
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
expect(proxy.hasExtensionClient("actor-a")).toBe(false);
|
|
326
|
+
expect(proxy.hasExtensionClient()).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
281
330
|
describe("dispose", () => {
|
|
282
331
|
test("rejects all pending requests and emits cancels", async () => {
|
|
283
332
|
const p1 = proxy.request(
|
|
@@ -493,11 +542,12 @@ describe("HostBrowserProxy", () => {
|
|
|
493
542
|
expect(getPublishedMessages()).toHaveLength(0);
|
|
494
543
|
});
|
|
495
544
|
|
|
496
|
-
test("
|
|
545
|
+
test("prefers the chrome-extension client over a more-recently-active macOS bridge", async () => {
|
|
497
546
|
// Mock `listClientsByCapability` returns mockClients in array
|
|
498
547
|
// order, which mirrors production's `lastActiveAt`-desc ordering.
|
|
499
|
-
// The
|
|
500
|
-
//
|
|
548
|
+
// The macOS bridge is listed first (most recent heartbeat), but
|
|
549
|
+
// chrome-extension clients are deterministically preferred for
|
|
550
|
+
// auto-resolution; LLMs pin the bridge via `target_client_id`.
|
|
501
551
|
mockClients = [
|
|
502
552
|
{
|
|
503
553
|
clientId: "macos-client",
|
|
@@ -525,7 +575,7 @@ describe("HostBrowserProxy", () => {
|
|
|
525
575
|
const requestId = sent.requestId as string;
|
|
526
576
|
|
|
527
577
|
const pending = pendingInteractions.get(requestId);
|
|
528
|
-
expect(pending?.targetClientId).toBe("
|
|
578
|
+
expect(pending?.targetClientId).toBe("ext-client");
|
|
529
579
|
|
|
530
580
|
resolveResult(requestId, { content: "ok", isError: false });
|
|
531
581
|
await resultPromise;
|
|
@@ -739,7 +789,7 @@ describe("HostBrowserProxy", () => {
|
|
|
739
789
|
expect(getPublishedMessages()).toHaveLength(0);
|
|
740
790
|
});
|
|
741
791
|
|
|
742
|
-
test("no targetClientId auto-resolves
|
|
792
|
+
test("no targetClientId auto-resolves with extension preferred over the bridge", async () => {
|
|
743
793
|
mockClients = [
|
|
744
794
|
{
|
|
745
795
|
clientId: "macos-client",
|
|
@@ -755,9 +805,9 @@ describe("HostBrowserProxy", () => {
|
|
|
755
805
|
},
|
|
756
806
|
];
|
|
757
807
|
|
|
758
|
-
// No targetClientId —
|
|
759
|
-
//
|
|
760
|
-
//
|
|
808
|
+
// No targetClientId — auto-resolution partitions chrome-extension
|
|
809
|
+
// clients ahead of other transports, then picks the first
|
|
810
|
+
// same-actor entry (lastActiveAt-desc within each group).
|
|
761
811
|
const resultPromise = proxy.request(
|
|
762
812
|
{ cdpMethod: "Page.navigate", cdpParams: { url: "https://a.test" } },
|
|
763
813
|
"session-1",
|
|
@@ -769,10 +819,205 @@ describe("HostBrowserProxy", () => {
|
|
|
769
819
|
const requestId = (getPublishedMessages()[0] as Record<string, unknown>)
|
|
770
820
|
.requestId as string;
|
|
771
821
|
const pending = pendingInteractions.get(requestId);
|
|
772
|
-
expect(pending?.targetClientId).toBe("
|
|
822
|
+
expect(pending?.targetClientId).toBe("ext-client");
|
|
773
823
|
|
|
774
824
|
resolveResult(requestId, { content: "ok", isError: false });
|
|
775
825
|
await resultPromise;
|
|
776
826
|
});
|
|
777
827
|
});
|
|
828
|
+
|
|
829
|
+
// ---------------------------------------------------------------------------
|
|
830
|
+
// Method-aware routing
|
|
831
|
+
//
|
|
832
|
+
// `Vellum.*` pseudo-methods are implemented only by the Chrome
|
|
833
|
+
// extension dispatcher; the macOS bridge speaks raw CDP against
|
|
834
|
+
// localhost:9222 and fails on all of them. Auto-resolution therefore
|
|
835
|
+
// restricts pseudo-methods to chrome-extension clients and prefers
|
|
836
|
+
// the extension over the bridge for raw CDP, instead of racing on
|
|
837
|
+
// heartbeat-driven lastActiveAt ordering.
|
|
838
|
+
// ---------------------------------------------------------------------------
|
|
839
|
+
|
|
840
|
+
describe("method-aware routing", () => {
|
|
841
|
+
const MACOS_CLIENT: MockClient = {
|
|
842
|
+
clientId: "macos-client",
|
|
843
|
+
interfaceId: "macos",
|
|
844
|
+
actorPrincipalId: "user-1",
|
|
845
|
+
capabilities: ["host_browser"],
|
|
846
|
+
};
|
|
847
|
+
const EXT_CLIENT: MockClient = {
|
|
848
|
+
clientId: "ext-client",
|
|
849
|
+
interfaceId: "chrome-extension",
|
|
850
|
+
actorPrincipalId: "user-1",
|
|
851
|
+
capabilities: ["host_browser"],
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
function expectExtensionRequired(result: {
|
|
855
|
+
content: string;
|
|
856
|
+
isError: boolean;
|
|
857
|
+
}): void {
|
|
858
|
+
expect(result.isError).toBe(true);
|
|
859
|
+
const parsed = JSON.parse(result.content) as {
|
|
860
|
+
code: string;
|
|
861
|
+
message: string;
|
|
862
|
+
};
|
|
863
|
+
expect(parsed.code).toBe("extension_required");
|
|
864
|
+
expect(parsed.message).toContain("Chrome extension");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
test("pseudo-method routes to the extension even when the bridge is more recently active", async () => {
|
|
868
|
+
mockClients = [MACOS_CLIENT, EXT_CLIENT];
|
|
869
|
+
|
|
870
|
+
const resultPromise = proxy.request(
|
|
871
|
+
{ cdpMethod: "Vellum.listTabs", cdpParams: {} },
|
|
872
|
+
"session-1",
|
|
873
|
+
undefined,
|
|
874
|
+
"user-1",
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
expect(getPublishedMessages()).toHaveLength(1);
|
|
878
|
+
const requestId = (getPublishedMessages()[0] as Record<string, unknown>)
|
|
879
|
+
.requestId as string;
|
|
880
|
+
expect(pendingInteractions.get(requestId)?.targetClientId).toBe(
|
|
881
|
+
"ext-client",
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
resolveResult(requestId, { content: '{"tabs":[]}', isError: false });
|
|
885
|
+
await resultPromise;
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("pseudo-method with only the macOS bridge connected resolves extension_required without broadcasting", async () => {
|
|
889
|
+
mockClients = [MACOS_CLIENT];
|
|
890
|
+
|
|
891
|
+
const result = await proxy.request(
|
|
892
|
+
{ cdpMethod: "Vellum.selectTab", cdpParams: { tabId: 42 } },
|
|
893
|
+
"session-1",
|
|
894
|
+
undefined,
|
|
895
|
+
"user-1",
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
expectExtensionRequired(result);
|
|
899
|
+
expect(getPublishedMessages()).toHaveLength(0);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
test("pseudo-method with no clients resolves extension_required instead of the legacy rejection", async () => {
|
|
903
|
+
mockClients = [];
|
|
904
|
+
|
|
905
|
+
const result = await proxy.request(
|
|
906
|
+
{ cdpMethod: "Vellum.createTab", cdpParams: {} },
|
|
907
|
+
"session-1",
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
expectExtensionRequired(result);
|
|
911
|
+
expect(getPublishedMessages()).toHaveLength(0);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("explicit targetClientId at the macOS bridge + pseudo-method resolves extension_required naming the interface", async () => {
|
|
915
|
+
mockClients = [MACOS_CLIENT, EXT_CLIENT];
|
|
916
|
+
|
|
917
|
+
const result = await proxy.request(
|
|
918
|
+
{ cdpMethod: "Vellum.closeTab", cdpParams: { tabId: 7 } },
|
|
919
|
+
"session-1",
|
|
920
|
+
undefined,
|
|
921
|
+
"user-1",
|
|
922
|
+
"macos-client",
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
expectExtensionRequired(result);
|
|
926
|
+
const parsed = JSON.parse(result.content) as { message: string };
|
|
927
|
+
expect(parsed.message).toContain('"macos"');
|
|
928
|
+
expect(getPublishedMessages()).toHaveLength(0);
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
test("explicit targetClientId at the extension + pseudo-method dispatches normally", async () => {
|
|
932
|
+
mockClients = [MACOS_CLIENT, EXT_CLIENT];
|
|
933
|
+
|
|
934
|
+
const resultPromise = proxy.request(
|
|
935
|
+
{ cdpMethod: "Vellum.closeTab", cdpParams: { tabId: 7 } },
|
|
936
|
+
"session-1",
|
|
937
|
+
undefined,
|
|
938
|
+
"user-1",
|
|
939
|
+
"ext-client",
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
expect(getPublishedMessages()).toHaveLength(1);
|
|
943
|
+
const requestId = (getPublishedMessages()[0] as Record<string, unknown>)
|
|
944
|
+
.requestId as string;
|
|
945
|
+
expect(pendingInteractions.get(requestId)?.targetClientId).toBe(
|
|
946
|
+
"ext-client",
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
resolveResult(requestId, {
|
|
950
|
+
content: '{"closed":true,"tabId":7}',
|
|
951
|
+
isError: false,
|
|
952
|
+
});
|
|
953
|
+
await resultPromise;
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test("same-actor gate wins over the pseudo-method check for cross-actor explicit targets", async () => {
|
|
957
|
+
mockClients = [
|
|
958
|
+
{
|
|
959
|
+
clientId: "other-user-ext",
|
|
960
|
+
interfaceId: "chrome-extension",
|
|
961
|
+
actorPrincipalId: "user-2",
|
|
962
|
+
capabilities: ["host_browser"],
|
|
963
|
+
},
|
|
964
|
+
];
|
|
965
|
+
|
|
966
|
+
const result = await proxy.request(
|
|
967
|
+
{ cdpMethod: "Vellum.listTabs", cdpParams: {} },
|
|
968
|
+
"session-1",
|
|
969
|
+
undefined,
|
|
970
|
+
"user-1",
|
|
971
|
+
"other-user-ext",
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
expect(result.isError).toBe(true);
|
|
975
|
+
expect(result.content).toContain("Submitting actor does not match");
|
|
976
|
+
expect(result.content).not.toContain("extension_required");
|
|
977
|
+
expect(getPublishedMessages()).toHaveLength(0);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test("pseudo-method where the only extension belongs to another actor resolves extension_required", async () => {
|
|
981
|
+
mockClients = [
|
|
982
|
+
{
|
|
983
|
+
clientId: "other-user-ext",
|
|
984
|
+
interfaceId: "chrome-extension",
|
|
985
|
+
actorPrincipalId: "user-2",
|
|
986
|
+
capabilities: ["host_browser"],
|
|
987
|
+
},
|
|
988
|
+
MACOS_CLIENT,
|
|
989
|
+
];
|
|
990
|
+
|
|
991
|
+
const result = await proxy.request(
|
|
992
|
+
{ cdpMethod: "Vellum.listTabs", cdpParams: {} },
|
|
993
|
+
"session-1",
|
|
994
|
+
undefined,
|
|
995
|
+
"user-1",
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
expectExtensionRequired(result);
|
|
999
|
+
expect(getPublishedMessages()).toHaveLength(0);
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
test("raw CDP with only the bridge connected still routes to the bridge", async () => {
|
|
1003
|
+
mockClients = [MACOS_CLIENT];
|
|
1004
|
+
|
|
1005
|
+
const resultPromise = proxy.request(
|
|
1006
|
+
{ cdpMethod: "Page.captureScreenshot", cdpParams: {} },
|
|
1007
|
+
"session-1",
|
|
1008
|
+
undefined,
|
|
1009
|
+
"user-1",
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
expect(getPublishedMessages()).toHaveLength(1);
|
|
1013
|
+
const requestId = (getPublishedMessages()[0] as Record<string, unknown>)
|
|
1014
|
+
.requestId as string;
|
|
1015
|
+
expect(pendingInteractions.get(requestId)?.targetClientId).toBe(
|
|
1016
|
+
"macos-client",
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
resolveResult(requestId, { content: '{"data":"..."}', isError: false });
|
|
1020
|
+
await resultPromise;
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
778
1023
|
});
|
|
@@ -237,6 +237,7 @@ describe("identity routes — health endpoint", () => {
|
|
|
237
237
|
expect(body.memory).toBeDefined();
|
|
238
238
|
expect(body.cpu).toBeDefined();
|
|
239
239
|
expect(body.migrations).toBeDefined();
|
|
240
|
+
expect(body.capabilities).toEqual({ memoryOptOut: true });
|
|
240
241
|
|
|
241
242
|
// Profiler should either be absent or show enabled: false
|
|
242
243
|
if ("profiler" in body) {
|