@vellumai/assistant 0.8.0 → 0.8.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/AGENTS.md +11 -0
- package/Dockerfile +5 -4
- package/README.md +2 -2
- package/docker-entrypoint.sh +16 -0
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
- package/eslint-rules/cli-no-daemon-internals.js +283 -0
- package/eslint.config.mjs +12 -0
- package/knip.json +2 -1
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
- package/openapi.yaml +4847 -1698
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +52 -4
- package/scripts/sync-llm-catalog.ts +165 -0
- package/scripts/sync-web-search-catalog.ts +107 -0
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
- package/src/__tests__/anthropic-provider.test.ts +92 -2
- package/src/__tests__/app-control-flow.test.ts +7 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
- package/src/__tests__/avatar-identity-sync.test.ts +87 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/call-site-routing-provider.test.ts +172 -45
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
- package/src/__tests__/channel-policy.test.ts +12 -0
- package/src/__tests__/checker.test.ts +89 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
- package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
- package/src/__tests__/config-loader-backfill.test.ts +526 -102
- package/src/__tests__/config-loader-corrupt.test.ts +68 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
- package/src/__tests__/config-schema-cmd.test.ts +63 -29
- package/src/__tests__/config-schema.test.ts +14 -3
- package/src/__tests__/config-set-platform-guard.test.ts +75 -152
- package/src/__tests__/config-set-route.test.ts +198 -0
- package/src/__tests__/config-watcher.test.ts +6 -0
- package/src/__tests__/contacts-tools.test.ts +51 -199
- package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
- package/src/__tests__/context-search-agent-runner.test.ts +22 -138
- package/src/__tests__/context-search-conversations-source.test.ts +42 -16
- package/src/__tests__/context-search-fanout.test.ts +20 -157
- package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
- package/src/__tests__/context-search-types.test.ts +7 -2
- package/src/__tests__/context-window-manager.test.ts +389 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/conversation-fork-crud.test.ts +241 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
- package/src/__tests__/conversation-lifecycle.test.ts +124 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
- package/src/__tests__/conversation-process-callsite.test.ts +21 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
- package/src/__tests__/conversation-slash-commands.test.ts +194 -2
- package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -6
- package/src/__tests__/daemon-credential-client.test.ts +56 -1
- package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
- package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
- package/src/__tests__/db-proxy-transaction.test.ts +206 -0
- package/src/__tests__/external-plugin-loader.test.ts +458 -0
- package/src/__tests__/filing-service.test.ts +23 -3
- package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
- package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
- package/src/__tests__/heartbeat-service.test.ts +50 -233
- package/src/__tests__/history-repair.test.ts +89 -0
- package/src/__tests__/host-app-control-proxy.test.ts +109 -1
- package/src/__tests__/host-app-control-routes.test.ts +247 -1
- package/src/__tests__/host-browser-proxy.test.ts +416 -20
- package/src/__tests__/host-browser-routes.test.ts +325 -33
- package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
- package/src/__tests__/inference-profile-reaper.test.ts +154 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
- package/src/__tests__/install-skill-routing.test.ts +2 -2
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
- package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
- package/src/__tests__/llm-catalog-parity.test.ts +146 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
- package/src/__tests__/llm-resolver.test.ts +46 -0
- package/src/__tests__/managed-profile-guard.test.ts +131 -2
- package/src/__tests__/mcp-auth-routes.test.ts +1 -0
- package/src/__tests__/mcp-cli.test.ts +182 -220
- package/src/__tests__/mcp-health-check.test.ts +56 -27
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
- package/src/__tests__/message-complete-display-id.test.ts +175 -0
- package/src/__tests__/notification-platform-adapter.test.ts +229 -0
- package/src/__tests__/oauth-cli.test.ts +38 -2009
- package/src/__tests__/oauth-commands-routes.test.ts +711 -0
- package/src/__tests__/oauth-connect-routes.test.ts +174 -11
- package/src/__tests__/oauth-providers-routes.test.ts +14 -10
- package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
- package/src/__tests__/openai-responses-provider.test.ts +17 -0
- package/src/__tests__/plugin-bootstrap.test.ts +31 -2
- package/src/__tests__/plugin-route-contribution.test.ts +31 -3
- package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
- package/src/__tests__/plugin-types.test.ts +13 -11
- package/src/__tests__/process-message-background-slack.test.ts +46 -0
- package/src/__tests__/profile-entry-status.test.ts +43 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
- package/src/__tests__/provider-registry-ollama.test.ts +12 -4
- package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
- package/src/__tests__/relay-server.test.ts +118 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
- package/src/__tests__/schedule-retry.test.ts +56 -4
- package/src/__tests__/schedule-routes.test.ts +104 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
- package/src/__tests__/scheduler-recurrence.test.ts +87 -34
- package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
- package/src/__tests__/scheduler-wake.test.ts +0 -63
- package/src/__tests__/secret-allowlist.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
- package/src/__tests__/shell-credential-ref.test.ts +95 -3
- package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +2 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
- package/src/__tests__/suggestion-routes.test.ts +3 -3
- package/src/__tests__/sync-message-contract.test.ts +63 -0
- package/src/__tests__/task-scheduler.test.ts +88 -23
- package/src/__tests__/update-bulletin-job.test.ts +96 -193
- package/src/__tests__/usage-cli.test.ts +11 -73
- package/src/__tests__/user-plugin-loader.test.ts +145 -0
- package/src/__tests__/vercel-config.test.ts +168 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
- package/src/__tests__/web-search.test.ts +303 -2
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
- package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
- package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
- package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
- package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
- package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
- package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
- package/src/acp/__tests__/helpers/which-stub.ts +4 -2
- package/src/acp/resolve-agent.test.ts +25 -0
- package/src/acp/resolve-agent.ts +13 -2
- package/src/acp/session-manager.ts +14 -0
- package/src/approvals/guardian-request-resolvers.ts +32 -87
- package/src/calls/relay-server.ts +35 -0
- package/src/calls/relay-setup-router.ts +36 -0
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-session-bridge.ts +23 -4
- package/src/channels/config.ts +14 -1
- package/src/channels/types.ts +1 -0
- package/src/cli/AGENTS.md +164 -4
- package/src/cli/__tests__/notifications.test.ts +54 -0
- package/src/cli/commands/__tests__/avatar.test.ts +540 -0
- package/src/cli/commands/__tests__/backup.test.ts +236 -776
- package/src/cli/commands/__tests__/cache.test.ts +1 -1
- package/src/cli/commands/__tests__/changelog.test.ts +593 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
- package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
- package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
- package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
- package/src/cli/commands/__tests__/email-core.test.ts +579 -0
- package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
- package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
- package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
- package/src/cli/commands/__tests__/skills.test.ts +563 -0
- package/src/cli/commands/__tests__/status.test.ts +249 -0
- package/src/cli/commands/__tests__/stt.test.ts +320 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
- package/src/cli/commands/__tests__/tts.test.ts +321 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
- package/src/cli/commands/attachment.ts +8 -3
- package/src/cli/commands/audit.ts +95 -64
- package/src/cli/commands/auth.ts +61 -58
- package/src/cli/commands/avatar.ts +276 -390
- package/src/cli/commands/backup.ts +409 -505
- package/src/cli/commands/bash.ts +9 -5
- package/src/cli/commands/browser.ts +28 -9
- package/src/cli/commands/cache.ts +9 -4
- package/src/cli/commands/changelog.ts +414 -0
- package/src/cli/commands/channel-verification-sessions.ts +238 -317
- package/src/cli/commands/clients.ts +8 -3
- package/src/cli/commands/completions.ts +9 -9
- package/src/cli/commands/config.ts +102 -72
- package/src/cli/commands/contacts.ts +575 -696
- package/src/cli/commands/conversations-defer.ts +17 -69
- package/src/cli/commands/conversations-import.ts +90 -253
- package/src/cli/commands/conversations.ts +346 -436
- package/src/cli/commands/credential-execution.ts +9 -6
- package/src/cli/commands/credentials.ts +456 -736
- package/src/cli/commands/domain.ts +128 -206
- package/src/cli/commands/email.ts +606 -794
- package/src/cli/commands/gateway.ts +8 -1
- package/src/cli/commands/image-generation.ts +157 -205
- package/src/cli/commands/inference-providers.ts +352 -0
- package/src/cli/commands/inference-session.ts +415 -0
- package/src/cli/commands/inference.ts +87 -65
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.ts +103 -287
- package/src/cli/commands/memory-v2.ts +162 -516
- package/src/cli/commands/notifications.ts +33 -7
- package/src/cli/commands/oauth/apps.ts +292 -261
- package/src/cli/commands/oauth/connect.ts +176 -297
- package/src/cli/commands/oauth/disconnect.ts +16 -215
- package/src/cli/commands/oauth/index.ts +49 -45
- package/src/cli/commands/oauth/mode.ts +43 -199
- package/src/cli/commands/oauth/ping.ts +17 -125
- package/src/cli/commands/oauth/providers.ts +732 -921
- package/src/cli/commands/oauth/request.ts +60 -350
- package/src/cli/commands/oauth/shared.ts +11 -121
- package/src/cli/commands/oauth/status.ts +31 -121
- package/src/cli/commands/oauth/token.ts +13 -55
- package/src/cli/commands/pending.ts +19 -10
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
- package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
- package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
- package/src/cli/commands/platform/connect.ts +16 -80
- package/src/cli/commands/platform/disconnect.ts +14 -112
- package/src/cli/commands/platform/index.ts +177 -246
- package/src/cli/commands/routes.ts +153 -336
- package/src/cli/commands/sequence.ts +316 -360
- package/src/cli/commands/skills.ts +449 -671
- package/src/cli/commands/status.ts +58 -37
- package/src/cli/commands/stt.ts +94 -262
- package/src/cli/commands/task.ts +14 -40
- package/src/cli/commands/trust.ts +8 -3
- package/src/cli/commands/tts.ts +162 -167
- package/src/cli/commands/ui.ts +35 -42
- package/src/cli/commands/usage.ts +188 -126
- package/src/cli/commands/watchers.ts +8 -3
- package/src/cli/commands/webhooks.ts +99 -193
- package/src/cli/lib/__tests__/register-command.test.ts +85 -0
- package/src/cli/lib/daemon-credential-client.ts +4 -5
- package/src/cli/lib/nested-value.ts +44 -0
- package/src/cli/lib/open-browser.ts +36 -0
- package/src/cli/lib/register-command.ts +19 -0
- package/src/cli/lib/time-ago.ts +34 -0
- package/src/cli/program.ts +2 -4
- package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
- package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
- package/src/cli/utils/conversation-id.ts +30 -0
- package/src/cli/utils/parse-duration.ts +41 -0
- package/src/config/acp-defaults.test.ts +5 -1
- package/src/config/acp-defaults.ts +11 -4
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
- package/src/config/bundled-skills/contacts/SKILL.md +12 -45
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +16 -1
- package/src/config/loader.ts +76 -14
- package/src/config/raw-config-utils.ts +2 -30
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
- package/src/config/schemas/call-site-catalog.ts +29 -7
- package/src/config/schemas/llm-request-logs.ts +57 -0
- package/src/config/schemas/llm.ts +52 -2
- package/src/config/schemas/memory-retrospective.ts +48 -0
- package/src/config/schemas/memory-v2.ts +32 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +15 -12
- package/src/config/seed-inference-profiles.ts +195 -134
- package/src/contacts/contact-store.ts +0 -61
- package/src/context/window-manager.ts +191 -5
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
- package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
- package/src/daemon/approval-generators.ts +23 -29
- package/src/daemon/config-watcher.ts +2 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
- package/src/daemon/conversation-agent-loop.ts +127 -97
- package/src/daemon/conversation-error.ts +21 -0
- package/src/daemon/conversation-lifecycle.ts +46 -5
- package/src/daemon/conversation-process.ts +36 -19
- package/src/daemon/conversation-runtime-assembly.ts +14 -5
- package/src/daemon/conversation-slash.ts +175 -23
- package/src/daemon/conversation-store.ts +17 -10
- package/src/daemon/conversation-surfaces.ts +76 -12
- package/src/daemon/conversation-tool-setup.ts +24 -14
- package/src/daemon/conversation.ts +48 -9
- package/src/daemon/external-plugins-bootstrap.ts +18 -8
- package/src/daemon/guardian-action-generators.ts +7 -22
- package/src/daemon/handlers/config-model.ts +8 -126
- package/src/daemon/handlers/config-slack-channel.ts +10 -7
- package/src/daemon/handlers/config-vercel.ts +3 -1
- package/src/daemon/handlers/skills.ts +84 -5
- package/src/daemon/history-repair.ts +33 -6
- package/src/daemon/host-app-control-proxy.ts +44 -19
- package/src/daemon/host-bash-proxy.ts +85 -158
- package/src/daemon/host-browser-proxy.ts +96 -35
- package/src/daemon/host-proxy-base.ts +13 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -1
- package/src/daemon/identity-helpers.ts +19 -0
- package/src/daemon/lifecycle.ts +42 -43
- package/src/daemon/meet-host-supervisor.ts +15 -15
- package/src/daemon/memory-v2-startup.ts +9 -2
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/bookmarks.ts +18 -0
- package/src/daemon/message-types/conversations.ts +12 -9
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/sync.ts +60 -0
- package/src/daemon/pkb-reminder-builder.test.ts +54 -13
- package/src/daemon/pkb-reminder-builder.ts +21 -7
- package/src/daemon/process-message.ts +56 -23
- package/src/daemon/server.ts +23 -18
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/daemon/tool-side-effects.ts +6 -4
- package/src/daemon/wake-target-adapter.ts +11 -0
- package/src/export/transcript-formatter.ts +61 -2
- package/src/filing/filing-service.ts +40 -53
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +148 -127
- package/src/home/__tests__/feed-types.test.ts +63 -131
- package/src/home/__tests__/feed-writer.test.ts +77 -278
- package/src/home/__tests__/post-connect-feed.test.ts +9 -12
- package/src/home/feed-types.ts +19 -73
- package/src/home/feed-writer.ts +25 -156
- package/src/home/post-connect-feed.ts +1 -3
- package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
- package/src/ipc/__tests__/email-ipc.test.ts +506 -0
- package/src/ipc/__tests__/exit-helper.test.ts +104 -0
- package/src/ipc/__tests__/streaming-client.test.ts +237 -0
- package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
- package/src/ipc/assistant-server.ts +55 -6
- package/src/ipc/cli-client.ts +370 -50
- package/src/ipc/routes/db-proxy-transaction.ts +151 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
- package/src/ipc/skill-routes/events.ts +30 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
- package/src/live-voice/live-voice-session-manager.ts +11 -4
- package/src/live-voice/live-voice-session.ts +14 -6
- package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
- package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
- package/src/memory/__tests__/conversation-types.test.ts +36 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
- package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
- package/src/memory/bookmark-crud.ts +179 -0
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
- package/src/memory/context-search/agent-protocol.ts +5 -1
- package/src/memory/context-search/agent-runner.ts +60 -85
- package/src/memory/context-search/limits.ts +1 -4
- package/src/memory/context-search/search.ts +23 -113
- package/src/memory/context-search/sources/conversations.ts +18 -6
- package/src/memory/context-search/sources/memory-v2.ts +39 -14
- package/src/memory/context-search/sources/memory.ts +7 -0
- package/src/memory/context-search/sources/workspace.ts +13 -10
- package/src/memory/context-search/types.ts +1 -1
- package/src/memory/conversation-bootstrap.ts +11 -0
- package/src/memory/conversation-crud.ts +312 -10
- package/src/memory/conversation-queries.ts +9 -5
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/conversation-types.ts +16 -0
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.ts +2 -1
- package/src/memory/embedding-runtime-manager.ts +1 -2
- package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
- package/src/memory/graph/conversation-graph-memory.ts +76 -5
- package/src/memory/graph/extraction.ts +4 -0
- package/src/memory/graph/graph-memory-state-store.ts +16 -3
- package/src/memory/graph/tool-handlers.ts +17 -7
- package/src/memory/graph/tools.ts +44 -5
- package/src/memory/indexer.ts +17 -0
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
- package/src/memory/jobs/embed-concept-page.ts +45 -9
- package/src/memory/jobs-store.ts +51 -1
- package/src/memory/jobs-worker.ts +52 -3
- package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
- package/src/memory/llm-request-log-source-local.ts +26 -0
- package/src/memory/llm-request-log-source.ts +97 -0
- package/src/memory/llm-request-log-store.ts +1 -1
- package/src/memory/memory-retrospective-constants.ts +13 -0
- package/src/memory/memory-retrospective-enqueue.ts +114 -0
- package/src/memory/memory-retrospective-job.ts +351 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
- package/src/memory/memory-retrospective-state.ts +162 -0
- package/src/memory/memory-retrospective-trigger-check.ts +91 -0
- package/src/memory/memory-v2-activation-log-store.ts +49 -5
- package/src/memory/memory-v2-concept-frequency.ts +4 -0
- package/src/memory/message-content.ts +38 -1
- package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
- package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
- package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
- package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
- package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
- package/src/memory/migrations/242-message-bookmarks.ts +38 -0
- package/src/memory/migrations/243-provider-connections.ts +68 -0
- package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
- package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
- package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
- package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
- package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
- package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/published-pages-store.ts +16 -0
- package/src/memory/schema/bookmarks.ts +38 -0
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/inference.ts +29 -0
- package/src/memory/schema/memory-core.ts +9 -0
- package/src/memory/search/semantic.ts +1 -4
- package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
- package/src/memory/v2/__tests__/activation.test.ts +11 -4
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
- package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
- package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
- package/src/memory/v2/__tests__/injection.test.ts +628 -10
- package/src/memory/v2/__tests__/migration.test.ts +7 -3
- package/src/memory/v2/__tests__/page-index.test.ts +277 -0
- package/src/memory/v2/__tests__/page-store.test.ts +14 -1
- package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
- package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
- package/src/memory/v2/__tests__/reranker.test.ts +4 -4
- package/src/memory/v2/__tests__/router.test.ts +516 -0
- package/src/memory/v2/__tests__/sim.test.ts +45 -1
- package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
- package/src/memory/v2/__tests__/static-context.test.ts +7 -22
- package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
- package/src/memory/v2/activation-store.ts +34 -5
- package/src/memory/v2/activation.ts +40 -27
- package/src/memory/v2/backfill-jobs.ts +17 -84
- package/src/memory/v2/consolidation-job.ts +85 -78
- package/src/memory/v2/frontmatter-sweep.ts +91 -0
- package/src/memory/v2/injection.ts +440 -109
- package/src/memory/v2/migration.ts +117 -20
- package/src/memory/v2/page-index.ts +191 -0
- package/src/memory/v2/page-store.ts +3 -0
- package/src/memory/v2/prompts/consolidation.ts +9 -7
- package/src/memory/v2/prompts/router.ts +192 -0
- package/src/memory/v2/qdrant.ts +100 -87
- package/src/memory/v2/reranker.ts +14 -7
- package/src/memory/v2/router.ts +322 -0
- package/src/memory/v2/sim.ts +25 -12
- package/src/memory/v2/skill-store.ts +118 -29
- package/src/memory/v2/static-context.ts +16 -9
- package/src/memory/v2/sweep-job.ts +122 -96
- package/src/memory/v2/types.ts +10 -6
- package/src/memory/validation.ts +13 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
- package/src/notifications/__tests__/signal-registry.test.ts +17 -0
- package/src/notifications/adapters/platform.ts +171 -0
- package/src/notifications/conversation-pairing.ts +2 -2
- package/src/notifications/copy-composer.ts +15 -0
- package/src/notifications/destination-resolver.ts +21 -0
- package/src/notifications/emit-signal.ts +28 -1
- package/src/notifications/home-feed-side-effect.ts +111 -0
- package/src/notifications/signal.ts +5 -0
- package/src/permissions/checker.ts +12 -0
- package/src/permissions/ipc-risk-types.ts +2 -0
- package/src/plugin-api/index.ts +13 -0
- package/src/plugin-api/package.json +12 -0
- package/src/plugin-api/types.ts +62 -0
- package/src/plugins/defaults/injectors.ts +19 -3
- package/src/plugins/external-plugin-loader.ts +294 -0
- package/src/plugins/types.ts +46 -30
- package/src/plugins/user-loader.ts +64 -41
- package/src/proactive-artifact/job.test.ts +12 -4
- package/src/proactive-artifact/job.ts +4 -0
- package/src/proactive-artifact/trigger-state.test.ts +9 -0
- package/src/proactive-artifact/trigger-state.ts +4 -0
- package/src/prompts/__tests__/system-prompt.test.ts +105 -0
- package/src/prompts/system-prompt.ts +22 -1
- package/src/prompts/update-bulletin-job.ts +61 -73
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
- package/src/providers/__tests__/inference.test.ts +288 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
- package/src/providers/__tests__/retry-callsite.test.ts +14 -32
- package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
- package/src/providers/anthropic/client.ts +95 -26
- package/src/providers/call-site-routing.ts +94 -16
- package/src/providers/connection-resolution.ts +163 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
- package/src/providers/inference/adapter-factory.ts +173 -0
- package/src/providers/inference/auth.ts +112 -0
- package/src/providers/inference/backfill.ts +196 -0
- package/src/providers/inference/connections.ts +356 -0
- package/src/providers/inference/resolve-auth.ts +65 -0
- package/src/providers/model-catalog.ts +104 -6
- package/src/providers/openai/responses-provider.ts +4 -2
- package/src/providers/provider-env-vars.ts +17 -7
- package/src/providers/provider-secret-catalog.ts +49 -30
- package/src/providers/provider-send-message.ts +41 -20
- package/src/providers/registry.ts +143 -159
- package/src/providers/retry.ts +18 -10
- package/src/providers/search-provider-catalog.ts +121 -0
- package/src/runtime/AGENTS.md +18 -5
- package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
- package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
- package/src/runtime/actor-trust-resolver.ts +32 -10
- package/src/runtime/agent-wake.ts +35 -6
- package/src/runtime/assistant-event-hub.ts +3 -85
- package/src/runtime/auth/route-policy.ts +303 -8
- package/src/runtime/auth/same-actor.ts +2 -0
- package/src/runtime/background-job-runner.ts +339 -0
- package/src/runtime/btw-sidechain.ts +1 -0
- package/src/runtime/http-router.ts +36 -1
- package/src/runtime/http-server.ts +31 -5
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
- package/src/runtime/middleware/request-logger.ts +62 -1
- package/src/runtime/pre-first-message-gate.ts +83 -0
- package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
- package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.ts +10 -8
- package/src/runtime/routes/app-management-routes.ts +228 -3
- package/src/runtime/routes/approval-routes.ts +0 -18
- package/src/runtime/routes/audit-routes.ts +43 -0
- package/src/runtime/routes/auth-routes.ts +72 -0
- package/src/runtime/routes/avatar-routes.ts +273 -20
- package/src/runtime/routes/backup-routes.ts +406 -2
- package/src/runtime/routes/bookmark-routes.ts +154 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +0 -160
- package/src/runtime/routes/conversation-cli-routes.ts +192 -0
- package/src/runtime/routes/conversation-management-routes.ts +30 -43
- package/src/runtime/routes/conversation-query-routes.ts +334 -86
- package/src/runtime/routes/conversation-routes.ts +31 -10
- package/src/runtime/routes/conversations-import-routes.ts +229 -0
- package/src/runtime/routes/credential-routes.ts +540 -0
- package/src/runtime/routes/debug-routes.ts +2 -2
- package/src/runtime/routes/document-pdf-renderer.ts +5 -1
- package/src/runtime/routes/domain-routes.ts +167 -0
- package/src/runtime/routes/email-routes.ts +603 -0
- package/src/runtime/routes/errors.ts +2 -2
- package/src/runtime/routes/events-routes.ts +192 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -78
- package/src/runtime/routes/host-app-control-routes.ts +44 -2
- package/src/runtime/routes/host-browser-routes.ts +103 -22
- package/src/runtime/routes/http-adapter.ts +2 -0
- package/src/runtime/routes/identity-routes.ts +5 -0
- package/src/runtime/routes/image-generation-routes.ts +99 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
- package/src/runtime/routes/index.ts +36 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
- package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
- package/src/runtime/routes/inference-send-routes.ts +115 -0
- package/src/runtime/routes/integrations/twilio.ts +1 -0
- package/src/runtime/routes/mcp-auth-routes.ts +283 -9
- package/src/runtime/routes/memory-v2-routes.ts +13 -398
- package/src/runtime/routes/notification-routes.ts +2 -0
- package/src/runtime/routes/oauth-apps.ts +112 -7
- package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
- package/src/runtime/routes/oauth-connect-routes.ts +67 -5
- package/src/runtime/routes/oauth-providers.ts +298 -8
- package/src/runtime/routes/platform-routes.ts +336 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -1
- package/src/runtime/routes/playground/reset-circuit.ts +2 -1
- package/src/runtime/routes/playground/state.ts +2 -1
- package/src/runtime/routes/publish-routes.ts +221 -0
- package/src/runtime/routes/schedule-routes.ts +82 -0
- package/src/runtime/routes/sequence-routes.ts +291 -0
- package/src/runtime/routes/settings-routes.ts +2 -10
- package/src/runtime/routes/skills-routes.ts +31 -1
- package/src/runtime/routes/stt-routes.ts +240 -3
- package/src/runtime/routes/surface-action-routes.ts +43 -7
- package/src/runtime/routes/tts-routes.ts +67 -0
- package/src/runtime/routes/types.ts +32 -0
- package/src/runtime/routes/user-routes-cli.ts +243 -0
- package/src/runtime/routes/webhook-routes.ts +165 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/sync/sync-publisher.test.ts +105 -0
- package/src/runtime/sync/sync-publisher.ts +21 -0
- package/src/schedule/scheduler.ts +200 -123
- package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
- package/src/security/secret-patterns.ts +3 -0
- package/src/sequence/engine.ts +38 -40
- package/src/subagent/manager.ts +20 -15
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
- package/src/tools/browser/browser-execution.ts +15 -4
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
- package/src/tools/browser/cdp-client/factory.ts +66 -5
- package/src/tools/browser/runtime-check.ts +77 -0
- package/src/tools/memory/register.test.ts +3 -3
- package/src/tools/memory/register.ts +9 -1
- package/src/tools/network/__tests__/web-search.test.ts +156 -0
- package/src/tools/network/web-search.ts +280 -37
- package/src/tools/permission-checker.ts +13 -5
- package/src/tools/subagent/spawn.ts +3 -3
- package/src/tools/terminal/shell.ts +44 -0
- package/src/usage/attribution.ts +3 -2
- package/src/util/pricing.ts +86 -160
- package/src/watcher/__tests__/engine.test.ts +301 -0
- package/src/watcher/constants.ts +7 -0
- package/src/watcher/engine.ts +90 -90
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
- package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
- package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
- package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
- package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
- package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
- package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
- package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
- package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
- package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
- package/src/workspace/migrations/registry.ts +22 -0
- package/src/workspace/migrations/runner.ts +13 -2
- package/src/workspace/migrations/types.ts +13 -3
- package/src/workspace/provider-commit-message-generator.ts +3 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -498
- package/src/__tests__/credentials-cli.test.ts +0 -1225
- package/src/__tests__/memory-admin-recall.test.ts +0 -213
- package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
- package/src/cli/commands/__tests__/email-download.test.ts +0 -260
- package/src/cli/commands/__tests__/email-list.test.ts +0 -216
- package/src/cli/commands/__tests__/email-register.test.ts +0 -186
- package/src/cli/commands/__tests__/email-send.test.ts +0 -416
- package/src/cli/commands/__tests__/email-status.test.ts +0 -185
- package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
- package/src/cli/commands/__tests__/routes.test.ts +0 -562
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
- package/src/cli/commands/autonomy.ts +0 -365
- package/src/cli/commands/memory.ts +0 -424
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
- package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
- package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
- package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
- package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
- package/src/cli/lib/daemon-avatar-client.ts +0 -37
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
- package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
- package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
- package/src/home/__tests__/emit-feed-event.test.ts +0 -169
- package/src/home/__tests__/feed-population-integration.test.ts +0 -312
- package/src/home/__tests__/feed-scheduler.test.ts +0 -222
- package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
- package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
- package/src/home/__tests__/rollup-producer.test.ts +0 -507
- package/src/home/assistant-feed-authoring.ts +0 -135
- package/src/home/emit-feed-event.ts +0 -169
- package/src/home/feed-scheduler.ts +0 -281
- package/src/home/platform-gmail-digest.ts +0 -163
- package/src/home/rewrite-command-preview.ts +0 -66
- package/src/home/rewrite-feed-title.ts +0 -58
- package/src/home/rollup-producer.ts +0 -426
- package/src/memory/admin.ts +0 -326
- package/src/memory/context-search/sources/pkb.ts +0 -476
- package/src/memory/graph/compaction.ts +0 -299
- /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
|
@@ -1879,7 +1879,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
1879
1879
|
},
|
|
1880
1880
|
];
|
|
1881
1881
|
|
|
1882
|
-
const FLAT_REMINDER = buildPkbReminder([]);
|
|
1882
|
+
const FLAT_REMINDER = buildPkbReminder([], false);
|
|
1883
1883
|
|
|
1884
1884
|
// Use a platform-agnostic absolute workspace root so the tests work on
|
|
1885
1885
|
// macOS and Linux runners alike. `pkbRoot` sits under `pkbWorkingDir` to
|
|
@@ -2135,7 +2135,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2135
2135
|
role: "user",
|
|
2136
2136
|
content: [
|
|
2137
2137
|
{ type: "text", text: "hello" },
|
|
2138
|
-
{ type: "text", text: buildPkbReminder([]) },
|
|
2138
|
+
{ type: "text", text: buildPkbReminder([], false) },
|
|
2139
2139
|
],
|
|
2140
2140
|
};
|
|
2141
2141
|
const hintedMessage: Message = {
|
|
@@ -2144,7 +2144,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2144
2144
|
{ type: "text", text: "hello" },
|
|
2145
2145
|
{
|
|
2146
2146
|
type: "text",
|
|
2147
|
-
text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"]),
|
|
2147
|
+
text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"], false),
|
|
2148
2148
|
},
|
|
2149
2149
|
],
|
|
2150
2150
|
};
|
|
@@ -4760,7 +4760,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
4760
4760
|
mode: "full",
|
|
4761
4761
|
});
|
|
4762
4762
|
|
|
4763
|
-
const expected = buildPkbReminder([]);
|
|
4763
|
+
const expected = buildPkbReminder([], false);
|
|
4764
4764
|
expect(blocks.pkbSystemReminder).toBe(expected);
|
|
4765
4765
|
});
|
|
4766
4766
|
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
3
|
|
|
4
|
+
import {
|
|
5
|
+
invalidateConfigCache,
|
|
6
|
+
loadRawConfig,
|
|
7
|
+
} from "../config/loader.js";
|
|
3
8
|
import {
|
|
4
9
|
classifySlash,
|
|
5
10
|
resolveSlash,
|
|
6
11
|
type SlashContext,
|
|
7
12
|
} from "../daemon/conversation-slash.js";
|
|
13
|
+
import { getWorkspaceConfigPath } from "../util/platform.js";
|
|
8
14
|
|
|
9
15
|
function makeSlashContext(overrides: Partial<SlashContext> = {}): SlashContext {
|
|
10
16
|
return {
|
|
@@ -37,12 +43,12 @@ describe("resolveSlash /commands interface-aware help", () => {
|
|
|
37
43
|
"/commands — List all available commands",
|
|
38
44
|
"/compact — Force context compaction immediately",
|
|
39
45
|
"/context — Show conversation context usage",
|
|
46
|
+
"/model — List or switch inference profile",
|
|
40
47
|
"/models — List all available models",
|
|
41
48
|
"/status — Show conversation status and context usage",
|
|
42
49
|
"/btw — Ask a side question while the assistant is working",
|
|
43
50
|
"/fork — Fork the current conversation into a new branch",
|
|
44
51
|
]);
|
|
45
|
-
expect(lines).not.toContain("/model — Switch the active model");
|
|
46
52
|
});
|
|
47
53
|
|
|
48
54
|
test("renders iOS command help with /fork", async () => {
|
|
@@ -53,6 +59,7 @@ describe("resolveSlash /commands interface-aware help", () => {
|
|
|
53
59
|
"/commands — List all available commands",
|
|
54
60
|
"/compact — Force context compaction immediately",
|
|
55
61
|
"/context — Show conversation context usage",
|
|
62
|
+
"/model — List or switch inference profile",
|
|
56
63
|
"/models — List all available models",
|
|
57
64
|
"/status — Show conversation status and context usage",
|
|
58
65
|
"/btw — Ask a side question while the assistant is working",
|
|
@@ -68,6 +75,7 @@ describe("resolveSlash /commands interface-aware help", () => {
|
|
|
68
75
|
"/commands — List all available commands",
|
|
69
76
|
"/compact — Force context compaction immediately",
|
|
70
77
|
"/context — Show conversation context usage",
|
|
78
|
+
"/model — List or switch inference profile",
|
|
71
79
|
"/models — List all available models",
|
|
72
80
|
"/status — Show conversation status and context usage",
|
|
73
81
|
"/btw — Ask a side question while the assistant is working",
|
|
@@ -80,6 +88,7 @@ describe("resolveSlash /commands interface-aware help", () => {
|
|
|
80
88
|
"/commands — List all available commands",
|
|
81
89
|
"/compact — Force context compaction immediately",
|
|
82
90
|
"/context — Show conversation context usage",
|
|
91
|
+
"/model — List or switch inference profile",
|
|
83
92
|
"/models — List all available models",
|
|
84
93
|
"/status — Show conversation status and context usage",
|
|
85
94
|
]);
|
|
@@ -90,6 +99,7 @@ describe("resolveSlash /commands interface-aware help", () => {
|
|
|
90
99
|
expect(lines).toEqual([
|
|
91
100
|
"/commands — List all available commands",
|
|
92
101
|
"/compact — Force context compaction immediately",
|
|
102
|
+
"/model — List or switch inference profile",
|
|
93
103
|
"/models — List all available models",
|
|
94
104
|
]);
|
|
95
105
|
});
|
|
@@ -128,6 +138,55 @@ describe("resolveSlash command contract", () => {
|
|
|
128
138
|
});
|
|
129
139
|
});
|
|
130
140
|
|
|
141
|
+
describe("resolveSlash /compact target override", () => {
|
|
142
|
+
test("plain /compact returns no override", async () => {
|
|
143
|
+
const result = await resolveSlash("/compact");
|
|
144
|
+
expect(result).toEqual({ kind: "compact" });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("/compact <integer> sets explicit token target", async () => {
|
|
148
|
+
const result = await resolveSlash("/compact 30000");
|
|
149
|
+
expect(result).toEqual({
|
|
150
|
+
kind: "compact",
|
|
151
|
+
targetInputTokensOverride: 30000,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("/compact <n>k expands to thousands", async () => {
|
|
156
|
+
const result = await resolveSlash("/compact 30k");
|
|
157
|
+
expect(result).toEqual({
|
|
158
|
+
kind: "compact",
|
|
159
|
+
targetInputTokensOverride: 30_000,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("/compact <n>m expands to millions", async () => {
|
|
164
|
+
const result = await resolveSlash("/compact 1.5M");
|
|
165
|
+
expect(result).toEqual({
|
|
166
|
+
kind: "compact",
|
|
167
|
+
targetInputTokensOverride: 1_500_000,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("/compact rejects malformed args with usage hint", async () => {
|
|
172
|
+
const result = await resolveSlash("/compact bogus");
|
|
173
|
+
expect(result.kind).toBe("unknown");
|
|
174
|
+
if (result.kind !== "unknown") throw new Error("expected unknown");
|
|
175
|
+
expect(result.message).toContain("`bogus`");
|
|
176
|
+
expect(result.message).toContain("/compact");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("/compact rejects zero", async () => {
|
|
180
|
+
const result = await resolveSlash("/compact 0");
|
|
181
|
+
expect(result.kind).toBe("unknown");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("/compact rejects negative numbers", async () => {
|
|
185
|
+
const result = await resolveSlash("/compact -50");
|
|
186
|
+
expect(result.kind).toBe("unknown");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
131
190
|
describe("classifySlash is a pure classifier matching resolveSlash kinds", () => {
|
|
132
191
|
// Lookahead in `buildPassthroughBatch` must not run `resolveSlash`'s side
|
|
133
192
|
// effects. The pure classifier is synchronous, takes no side-effecting
|
|
@@ -141,11 +200,16 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
|
|
|
141
200
|
{ input: "/status", kind: "unknown" },
|
|
142
201
|
{ input: "/commands", kind: "unknown" },
|
|
143
202
|
{ input: "/compact", kind: "compact" },
|
|
203
|
+
{ input: "/compact 30000", kind: "compact" },
|
|
204
|
+
{ input: "/compact 30k", kind: "compact" },
|
|
205
|
+
{ input: "/compact 1.5M", kind: "compact" },
|
|
206
|
+
{ input: "/compact bogus", kind: "unknown" },
|
|
144
207
|
{ input: "/model", kind: "unknown" },
|
|
145
208
|
{ input: "/model foo", kind: "unknown" },
|
|
146
209
|
{ input: "/opus", kind: "unknown" },
|
|
147
210
|
{ input: "hello", kind: "passthrough" },
|
|
148
211
|
{ input: " /compact ", kind: "compact" },
|
|
212
|
+
{ input: " /compact 50k ", kind: "compact" },
|
|
149
213
|
{ input: "/models foo", kind: "passthrough" },
|
|
150
214
|
];
|
|
151
215
|
|
|
@@ -160,3 +224,131 @@ describe("classifySlash is a pure classifier matching resolveSlash kinds", () =>
|
|
|
160
224
|
});
|
|
161
225
|
}
|
|
162
226
|
});
|
|
227
|
+
|
|
228
|
+
// ── /model — inference profile switcher ────────────────────────────
|
|
229
|
+
|
|
230
|
+
function writeFixtureConfig(config: Record<string, unknown>): void {
|
|
231
|
+
writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config), "utf-8");
|
|
232
|
+
invalidateConfigCache();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
describe("resolveSlash /model — inference profile switcher", () => {
|
|
236
|
+
beforeEach(() => {
|
|
237
|
+
writeFixtureConfig({
|
|
238
|
+
llm: {
|
|
239
|
+
profiles: {
|
|
240
|
+
balanced: {
|
|
241
|
+
label: "Balanced",
|
|
242
|
+
description: "Default mix of speed and quality",
|
|
243
|
+
},
|
|
244
|
+
"cost-optimized": {
|
|
245
|
+
label: "Cost-optimized",
|
|
246
|
+
description: "Cheaper models, slower",
|
|
247
|
+
},
|
|
248
|
+
"short-context": {
|
|
249
|
+
label: "Short context",
|
|
250
|
+
status: "disabled",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
profileOrder: ["balanced", "cost-optimized", "short-context"],
|
|
254
|
+
activeProfile: "balanced",
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
afterEach(() => {
|
|
260
|
+
invalidateConfigCache();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("`/model` lists profiles with current marker, status, and description", async () => {
|
|
264
|
+
const result = await resolveSlash("/model");
|
|
265
|
+
expect(result.kind).toBe("unknown");
|
|
266
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
267
|
+
expect(result.message).toContain("Inference profiles:");
|
|
268
|
+
expect(result.message).toContain(
|
|
269
|
+
"`balanced` (Balanced) **[current]** — Default mix of speed and quality",
|
|
270
|
+
);
|
|
271
|
+
expect(result.message).toContain(
|
|
272
|
+
"`cost-optimized` (Cost-optimized) — Cheaper models, slower",
|
|
273
|
+
);
|
|
274
|
+
expect(result.message).toContain(
|
|
275
|
+
"`short-context` (Short context) *(disabled)*",
|
|
276
|
+
);
|
|
277
|
+
expect(result.message).toContain("Switch with `/model <name>`.");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("`/model <name>` switches the active profile and writes config to disk", async () => {
|
|
281
|
+
const result = await resolveSlash("/model cost-optimized");
|
|
282
|
+
expect(result.kind).toBe("unknown");
|
|
283
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
284
|
+
expect(result.message).toBe(
|
|
285
|
+
"Switched to profile `cost-optimized` (Cost-optimized).",
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const persisted = loadRawConfig() as {
|
|
289
|
+
llm?: { activeProfile?: string };
|
|
290
|
+
};
|
|
291
|
+
expect(persisted.llm?.activeProfile).toBe("cost-optimized");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("`/model <unknown>` returns an error with available profile names", async () => {
|
|
295
|
+
const result = await resolveSlash("/model gemini");
|
|
296
|
+
expect(result.kind).toBe("unknown");
|
|
297
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
298
|
+
expect(result.message).toContain("Profile `gemini` not found.");
|
|
299
|
+
expect(result.message).toContain("`balanced`");
|
|
300
|
+
expect(result.message).toContain("`cost-optimized`");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("`/model <disabled>` refuses to switch and points at Settings", async () => {
|
|
304
|
+
const result = await resolveSlash("/model short-context");
|
|
305
|
+
expect(result.kind).toBe("unknown");
|
|
306
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
307
|
+
expect(result.message).toBe(
|
|
308
|
+
"Profile `short-context` is disabled. Enable it in **Settings → Models & Services** first.",
|
|
309
|
+
);
|
|
310
|
+
// Disk should NOT have been written.
|
|
311
|
+
const persisted = loadRawConfig() as {
|
|
312
|
+
llm?: { activeProfile?: string };
|
|
313
|
+
};
|
|
314
|
+
expect(persisted.llm?.activeProfile).toBe("balanced");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("`/model <current>` is a no-op with a friendly message", async () => {
|
|
318
|
+
const result = await resolveSlash("/model balanced");
|
|
319
|
+
expect(result.kind).toBe("unknown");
|
|
320
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
321
|
+
expect(result.message).toBe(
|
|
322
|
+
"Already using profile `balanced` (Balanced).",
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("`/model` with no profiles defined points at Settings", async () => {
|
|
327
|
+
writeFixtureConfig({
|
|
328
|
+
llm: { profiles: {}, profileOrder: [] },
|
|
329
|
+
});
|
|
330
|
+
const result = await resolveSlash("/model");
|
|
331
|
+
expect(result.kind).toBe("unknown");
|
|
332
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
333
|
+
expect(result.message).toBe(
|
|
334
|
+
"No inference profiles are defined. Use **Settings → Models & Services** to create one.",
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("`/model <name>` trims surrounding whitespace from the argument", async () => {
|
|
339
|
+
const result = await resolveSlash("/model cost-optimized ");
|
|
340
|
+
expect(result.kind).toBe("unknown");
|
|
341
|
+
if (result.kind !== "unknown") throw new Error("expected unknown kind");
|
|
342
|
+
expect(result.message).toBe(
|
|
343
|
+
"Switched to profile `cost-optimized` (Cost-optimized).",
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("`/models` (plural) is not parsed as a /model invocation", async () => {
|
|
348
|
+
// /models is a separate command handled elsewhere; this test guards the
|
|
349
|
+
// boundary so we don't accidentally swallow it as a typo'd /model.
|
|
350
|
+
expect(classifySlash("/models")).toBe("unknown");
|
|
351
|
+
// /models foo is passthrough (existing behavior).
|
|
352
|
+
expect(classifySlash("/models foo")).toBe("passthrough");
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -11,6 +11,16 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
11
11
|
|
|
12
12
|
const sentMessages: unknown[] = [];
|
|
13
13
|
let mockHasClient = true;
|
|
14
|
+
// Default principal id used for both ctx.trustContext and clients in the
|
|
15
|
+
// existing single-user tests. Tests that exercise cross-user behaviour
|
|
16
|
+
// override this on individual clients and on the SurfaceConversationContext.
|
|
17
|
+
const DEFAULT_PRINCIPAL = "user-1";
|
|
18
|
+
type MockClient = {
|
|
19
|
+
clientId: string;
|
|
20
|
+
capabilities: string[];
|
|
21
|
+
actorPrincipalId?: string;
|
|
22
|
+
};
|
|
23
|
+
let mockHubClients: MockClient[] = [];
|
|
14
24
|
|
|
15
25
|
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
16
26
|
broadcastMessage: (msg: unknown) => sentMessages.push(msg),
|
|
@@ -19,6 +29,12 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
19
29
|
cap === "host_app_control" && mockHasClient
|
|
20
30
|
? { id: "mock-client" }
|
|
21
31
|
: null,
|
|
32
|
+
listClientsByCapability: (cap: string) =>
|
|
33
|
+
mockHubClients.filter((c) => c.capabilities.includes(cap)),
|
|
34
|
+
getClientById: (id: string) =>
|
|
35
|
+
mockHubClients.find((c) => c.clientId === id),
|
|
36
|
+
getActorPrincipalIdForClient: (id: string) =>
|
|
37
|
+
mockHubClients.find((c) => c.clientId === id)?.actorPrincipalId,
|
|
22
38
|
},
|
|
23
39
|
}));
|
|
24
40
|
|
|
@@ -56,9 +72,18 @@ function buildMockContext(
|
|
|
56
72
|
setHostAppControlProxy?: (
|
|
57
73
|
proxy: InstanceType<typeof HostAppControlProxy> | undefined,
|
|
58
74
|
) => void,
|
|
75
|
+
trustGuardianPrincipalId: string | null = DEFAULT_PRINCIPAL,
|
|
59
76
|
): SurfaceConversationContext {
|
|
60
77
|
return {
|
|
61
78
|
conversationId,
|
|
79
|
+
trustContext:
|
|
80
|
+
trustGuardianPrincipalId != null
|
|
81
|
+
? {
|
|
82
|
+
sourceChannel: "vellum",
|
|
83
|
+
trustClass: "guardian",
|
|
84
|
+
guardianPrincipalId: trustGuardianPrincipalId,
|
|
85
|
+
}
|
|
86
|
+
: undefined,
|
|
62
87
|
traceEmitter: { emit: () => {} },
|
|
63
88
|
sendToClient: () => {},
|
|
64
89
|
pendingSurfaceActions: new Map(),
|
|
@@ -86,11 +111,13 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
86
111
|
beforeEach(() => {
|
|
87
112
|
sentMessages.length = 0;
|
|
88
113
|
mockHasClient = true;
|
|
114
|
+
mockHubClients = [];
|
|
89
115
|
_resetActiveAppControlSession();
|
|
90
116
|
});
|
|
91
117
|
|
|
92
118
|
afterEach(() => {
|
|
93
119
|
_resetActiveAppControlSession();
|
|
120
|
+
mockHubClients = [];
|
|
94
121
|
});
|
|
95
122
|
|
|
96
123
|
// -------------------------------------------------------------------------
|
|
@@ -130,15 +157,18 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
130
157
|
proxy.dispose();
|
|
131
158
|
});
|
|
132
159
|
|
|
133
|
-
test("
|
|
160
|
+
test("app_control_stop succeeds idempotently when no proxy is attached", async () => {
|
|
161
|
+
// Stop is local-only and runs BEFORE the isAvailable() gate so a
|
|
162
|
+
// disconnected client cannot strand the singleton lock. With no proxy
|
|
163
|
+
// attached at all, it must still succeed as a no-op without dispatching.
|
|
134
164
|
const ctx = buildMockContext();
|
|
135
165
|
|
|
136
166
|
const result = await surfaceProxyResolver(ctx, "app_control_stop", {
|
|
137
167
|
tool: "stop",
|
|
138
168
|
});
|
|
139
169
|
|
|
140
|
-
expect(result.isError).toBe(
|
|
141
|
-
expect(result.content).toContain("
|
|
170
|
+
expect(result.isError).toBe(false);
|
|
171
|
+
expect(result.content.toLowerCase()).toContain("stopped");
|
|
142
172
|
expect(sentMessages).toHaveLength(0);
|
|
143
173
|
});
|
|
144
174
|
});
|
|
@@ -325,4 +355,294 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
325
355
|
ownerProxy.dispose();
|
|
326
356
|
});
|
|
327
357
|
});
|
|
358
|
+
|
|
359
|
+
// -------------------------------------------------------------------------
|
|
360
|
+
// target_client_id validation — mirrors host_cu's targetClientId tests in
|
|
361
|
+
// cu-unified-flow.test.ts. The resolver validates the explicit target
|
|
362
|
+
// before recordAction-equivalents so an invalid or cross-user id never
|
|
363
|
+
// reaches the proxy.
|
|
364
|
+
// -------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
describe("target_client_id validation", () => {
|
|
367
|
+
test("returns fast error when target_client_id does not match any connected client", async () => {
|
|
368
|
+
mockHubClients = [
|
|
369
|
+
{
|
|
370
|
+
clientId: "client-a",
|
|
371
|
+
capabilities: ["host_app_control"],
|
|
372
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
376
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
377
|
+
_setActiveAppControlSession({
|
|
378
|
+
conversationId: "conv-1",
|
|
379
|
+
app: "com.example.editor",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const result = await surfaceProxyResolver(ctx, "app_control_observe", {
|
|
383
|
+
app: "com.example.editor",
|
|
384
|
+
target_client_id: "missing-client",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
expect(result.isError).toBe(true);
|
|
388
|
+
expect(result.content).toContain("missing-client");
|
|
389
|
+
expect(result.content).toContain("host_app_control");
|
|
390
|
+
// No envelope dispatched — fail-fast before request().
|
|
391
|
+
expect(sentMessages).toHaveLength(0);
|
|
392
|
+
|
|
393
|
+
proxy.dispose();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("returns fast error when target_client_id points to a client without host_app_control capability", async () => {
|
|
397
|
+
mockHubClients = [
|
|
398
|
+
{
|
|
399
|
+
clientId: "wrong-cap-client",
|
|
400
|
+
capabilities: ["host_bash"], // not host_app_control
|
|
401
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
405
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
406
|
+
_setActiveAppControlSession({
|
|
407
|
+
conversationId: "conv-1",
|
|
408
|
+
app: "com.example.editor",
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const result = await surfaceProxyResolver(ctx, "app_control_observe", {
|
|
412
|
+
app: "com.example.editor",
|
|
413
|
+
target_client_id: "wrong-cap-client",
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(result.isError).toBe(true);
|
|
417
|
+
expect(result.content).toContain("wrong-cap-client");
|
|
418
|
+
expect(result.content).toContain("does not support host_app_control");
|
|
419
|
+
expect(sentMessages).toHaveLength(0);
|
|
420
|
+
|
|
421
|
+
proxy.dispose();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("dispatches with targetClientId when target_client_id is valid", async () => {
|
|
425
|
+
mockHubClients = [
|
|
426
|
+
{
|
|
427
|
+
clientId: "client-a",
|
|
428
|
+
capabilities: ["host_app_control"],
|
|
429
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
clientId: "client-b",
|
|
433
|
+
capabilities: ["host_app_control"],
|
|
434
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
435
|
+
},
|
|
436
|
+
];
|
|
437
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
438
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
439
|
+
_setActiveAppControlSession({
|
|
440
|
+
conversationId: "conv-1",
|
|
441
|
+
app: "com.example.editor",
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
445
|
+
app: "com.example.editor",
|
|
446
|
+
target_client_id: "client-b",
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Exactly one envelope dispatched, addressed to client-b.
|
|
450
|
+
expect(sentMessages).toHaveLength(1);
|
|
451
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
452
|
+
expect(sent.type).toBe("host_app_control_request");
|
|
453
|
+
expect(sent.targetClientId).toBe("client-b");
|
|
454
|
+
|
|
455
|
+
proxy.resolve(sent.requestId as string, {
|
|
456
|
+
requestId: "ignored-by-proxy",
|
|
457
|
+
state: "running",
|
|
458
|
+
});
|
|
459
|
+
const result = await resultPromise;
|
|
460
|
+
expect(result.isError).toBe(false);
|
|
461
|
+
|
|
462
|
+
proxy.dispose();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// -------------------------------------------------------------------------
|
|
467
|
+
// Multi-client ambiguity guard — when the LLM omits target_client_id and
|
|
468
|
+
// multiple same-user host_app_control clients are connected, the resolver
|
|
469
|
+
// must error rather than broadcast (one app-control session per client).
|
|
470
|
+
// -------------------------------------------------------------------------
|
|
471
|
+
|
|
472
|
+
describe("multi-client ambiguity guard", () => {
|
|
473
|
+
test("errors when multiple same-user clients connected and no target_client_id given", async () => {
|
|
474
|
+
mockHubClients = [
|
|
475
|
+
{
|
|
476
|
+
clientId: "client-a",
|
|
477
|
+
capabilities: ["host_app_control"],
|
|
478
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
clientId: "client-b",
|
|
482
|
+
capabilities: ["host_app_control"],
|
|
483
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
484
|
+
},
|
|
485
|
+
];
|
|
486
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
487
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
488
|
+
_setActiveAppControlSession({
|
|
489
|
+
conversationId: "conv-1",
|
|
490
|
+
app: "com.example.editor",
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const result = await surfaceProxyResolver(ctx, "app_control_observe", {
|
|
494
|
+
app: "com.example.editor",
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
expect(result.isError).toBe(true);
|
|
498
|
+
expect(result.content).toContain(
|
|
499
|
+
"multiple clients support host_app_control",
|
|
500
|
+
);
|
|
501
|
+
expect(result.content).toContain("target_client_id");
|
|
502
|
+
// No envelope dispatched.
|
|
503
|
+
expect(sentMessages).toHaveLength(0);
|
|
504
|
+
|
|
505
|
+
proxy.dispose();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("auto-resolves to the unique same-user client when cross-user clients are also present", async () => {
|
|
509
|
+
mockHubClients = [
|
|
510
|
+
{
|
|
511
|
+
clientId: "client-mine",
|
|
512
|
+
capabilities: ["host_app_control"],
|
|
513
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
clientId: "client-other",
|
|
517
|
+
capabilities: ["host_app_control"],
|
|
518
|
+
actorPrincipalId: "user-2",
|
|
519
|
+
},
|
|
520
|
+
];
|
|
521
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
522
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
523
|
+
_setActiveAppControlSession({
|
|
524
|
+
conversationId: "conv-1",
|
|
525
|
+
app: "com.example.editor",
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
529
|
+
app: "com.example.editor",
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Resolver must explicitly target the same-user client to prevent the
|
|
533
|
+
// proxy from broadcasting the action across the cross-user client too.
|
|
534
|
+
expect(sentMessages).toHaveLength(1);
|
|
535
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
536
|
+
expect(sent.targetClientId).toBe("client-mine");
|
|
537
|
+
|
|
538
|
+
proxy.resolve(sent.requestId as string, {
|
|
539
|
+
requestId: "ignored-by-proxy",
|
|
540
|
+
state: "running",
|
|
541
|
+
});
|
|
542
|
+
const result = await resultPromise;
|
|
543
|
+
expect(result.isError).toBe(false);
|
|
544
|
+
|
|
545
|
+
proxy.dispose();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test("single same-user client with no target proceeds without forcing targetClientId", async () => {
|
|
549
|
+
mockHubClients = [
|
|
550
|
+
{
|
|
551
|
+
clientId: "only-client",
|
|
552
|
+
capabilities: ["host_app_control"],
|
|
553
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
554
|
+
},
|
|
555
|
+
];
|
|
556
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
557
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
558
|
+
_setActiveAppControlSession({
|
|
559
|
+
conversationId: "conv-1",
|
|
560
|
+
app: "com.example.editor",
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
564
|
+
app: "com.example.editor",
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
expect(sentMessages).toHaveLength(1);
|
|
568
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
569
|
+
// No cross-user ambiguity → resolver leaves targetClientId undefined,
|
|
570
|
+
// letting the proxy use its existing single-client routing.
|
|
571
|
+
expect(sent.targetClientId).toBeUndefined();
|
|
572
|
+
|
|
573
|
+
proxy.resolve(sent.requestId as string, {
|
|
574
|
+
requestId: "ignored-by-proxy",
|
|
575
|
+
state: "running",
|
|
576
|
+
});
|
|
577
|
+
const result = await resultPromise;
|
|
578
|
+
expect(result.isError).toBe(false);
|
|
579
|
+
|
|
580
|
+
proxy.dispose();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// -------------------------------------------------------------------------
|
|
585
|
+
// Same-user enforcement — even when target_client_id is provided, a
|
|
586
|
+
// cross-user client can never be addressed.
|
|
587
|
+
// -------------------------------------------------------------------------
|
|
588
|
+
|
|
589
|
+
describe("same-user enforcement", () => {
|
|
590
|
+
test("rejects targeted dispatch from a different actor principal", async () => {
|
|
591
|
+
mockHubClients = [
|
|
592
|
+
{
|
|
593
|
+
clientId: "other-user-client",
|
|
594
|
+
capabilities: ["host_app_control"],
|
|
595
|
+
actorPrincipalId: "user-2",
|
|
596
|
+
},
|
|
597
|
+
];
|
|
598
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
599
|
+
const ctx = buildMockContext(proxy, "conv-1");
|
|
600
|
+
_setActiveAppControlSession({
|
|
601
|
+
conversationId: "conv-1",
|
|
602
|
+
app: "com.example.editor",
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const result = await surfaceProxyResolver(ctx, "app_control_observe", {
|
|
606
|
+
app: "com.example.editor",
|
|
607
|
+
target_client_id: "other-user-client",
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
expect(result.isError).toBe(true);
|
|
611
|
+
// No envelope dispatched.
|
|
612
|
+
expect(sentMessages).toHaveLength(0);
|
|
613
|
+
|
|
614
|
+
proxy.dispose();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test("rejects when the conversation has no source actor principal", async () => {
|
|
618
|
+
mockHubClients = [
|
|
619
|
+
{
|
|
620
|
+
clientId: "client-a",
|
|
621
|
+
capabilities: ["host_app_control"],
|
|
622
|
+
actorPrincipalId: DEFAULT_PRINCIPAL,
|
|
623
|
+
},
|
|
624
|
+
];
|
|
625
|
+
const proxy = new HostAppControlProxy("conv-1");
|
|
626
|
+
const ctx = buildMockContext(
|
|
627
|
+
proxy,
|
|
628
|
+
"conv-1",
|
|
629
|
+
undefined,
|
|
630
|
+
/* trustGuardianPrincipalId */ null,
|
|
631
|
+
);
|
|
632
|
+
_setActiveAppControlSession({
|
|
633
|
+
conversationId: "conv-1",
|
|
634
|
+
app: "com.example.editor",
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const result = await surfaceProxyResolver(ctx, "app_control_observe", {
|
|
638
|
+
app: "com.example.editor",
|
|
639
|
+
target_client_id: "client-a",
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
expect(result.isError).toBe(true);
|
|
643
|
+
expect(sentMessages).toHaveLength(0);
|
|
644
|
+
|
|
645
|
+
proxy.dispose();
|
|
646
|
+
});
|
|
647
|
+
});
|
|
328
648
|
});
|