@vellumai/assistant 0.7.2 → 0.7.3
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/ARCHITECTURE.md +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +27 -3
- package/src/config/loader.ts +127 -8
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +75 -11
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +0 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
2
|
+
import { resolveModelIntent } from "../providers/model-intents.js";
|
|
3
|
+
import type { ModelIntent } from "../providers/types.js";
|
|
1
4
|
import { loadRawConfig, saveRawConfig } from "./loader.js";
|
|
2
5
|
import {
|
|
3
6
|
DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
|
|
@@ -5,41 +8,82 @@ import {
|
|
|
5
8
|
} from "./schemas/llm.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* updates propagate automatically. User-created profiles (keyed by
|
|
12
|
-
* different names) are never touched.
|
|
11
|
+
* Template for a daemon-managed inference profile. The profile's model is
|
|
12
|
+
* resolved at seed time from `PROVIDER_MODEL_INTENTS` so the catalog stays the
|
|
13
|
+
* single source of truth for "which model does this intent map to?".
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
+
type ManagedProfileTemplate = Omit<ProfileEntry, "provider" | "model"> & {
|
|
16
|
+
intent: ModelIntent;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Anthropic-managed profiles. Always seeded so users can target Anthropic via
|
|
21
|
+
* their own key, even when the resolved default provider is something else.
|
|
22
|
+
*/
|
|
23
|
+
const ANTHROPIC_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
15
24
|
balanced: {
|
|
25
|
+
intent: "balanced",
|
|
16
26
|
source: "managed",
|
|
17
27
|
label: "Balanced",
|
|
18
28
|
description: "Good balance of quality, cost, and speed",
|
|
19
|
-
provider: "anthropic",
|
|
20
|
-
model: "claude-sonnet-4-6",
|
|
21
29
|
maxTokens: 16000,
|
|
22
30
|
effort: "high",
|
|
23
31
|
thinking: { enabled: true, streamThinking: true },
|
|
24
32
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
25
33
|
},
|
|
26
34
|
"quality-optimized": {
|
|
35
|
+
intent: "quality-optimized",
|
|
27
36
|
source: "managed",
|
|
28
37
|
label: "Quality",
|
|
29
38
|
description: "Best results with the most capable model",
|
|
30
|
-
provider: "anthropic",
|
|
31
|
-
model: "claude-opus-4-7",
|
|
32
39
|
maxTokens: 32000,
|
|
33
40
|
effort: "max",
|
|
34
41
|
thinking: { enabled: true, streamThinking: true },
|
|
35
42
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
36
43
|
},
|
|
37
44
|
"cost-optimized": {
|
|
45
|
+
intent: "latency-optimized",
|
|
38
46
|
source: "managed",
|
|
39
47
|
label: "Speed",
|
|
40
48
|
description: "Fastest responses at lower cost",
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
maxTokens: 8192,
|
|
50
|
+
effort: "low",
|
|
51
|
+
thinking: { enabled: false, streamThinking: false },
|
|
52
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom-provider profile templates. Materialized at seed time when the
|
|
58
|
+
* resolved default provider is non-Anthropic, using `resolveModelIntent` to
|
|
59
|
+
* pick the model.
|
|
60
|
+
*/
|
|
61
|
+
const CUSTOM_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
62
|
+
"custom-balanced": {
|
|
63
|
+
intent: "balanced",
|
|
64
|
+
source: "user",
|
|
65
|
+
label: "Balanced (Custom Provider)",
|
|
66
|
+
description: "Good balance of quality, cost, and speed",
|
|
67
|
+
maxTokens: 16000,
|
|
68
|
+
effort: "high",
|
|
69
|
+
thinking: { enabled: true, streamThinking: true },
|
|
70
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
71
|
+
},
|
|
72
|
+
"custom-quality-optimized": {
|
|
73
|
+
intent: "quality-optimized",
|
|
74
|
+
source: "user",
|
|
75
|
+
label: "Quality (Custom Provider)",
|
|
76
|
+
description: "Best results with the most capable model",
|
|
77
|
+
maxTokens: 32000,
|
|
78
|
+
effort: "max",
|
|
79
|
+
thinking: { enabled: true, streamThinking: true },
|
|
80
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
81
|
+
},
|
|
82
|
+
"custom-cost-optimized": {
|
|
83
|
+
intent: "latency-optimized",
|
|
84
|
+
source: "user",
|
|
85
|
+
label: "Speed (Custom Provider)",
|
|
86
|
+
description: "Fastest responses at lower cost",
|
|
43
87
|
maxTokens: 8192,
|
|
44
88
|
effort: "low",
|
|
45
89
|
thinking: { enabled: false, streamThinking: false },
|
|
@@ -48,26 +92,47 @@ const MANAGED_PROFILE_SEED_DATA: Record<string, ProfileEntry> = {
|
|
|
48
92
|
};
|
|
49
93
|
|
|
50
94
|
export const MANAGED_PROFILE_NAMES = new Set(
|
|
51
|
-
Object.keys(
|
|
95
|
+
Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
52
96
|
);
|
|
53
97
|
|
|
98
|
+
const SEEDED_PROFILE_NAMES = new Set([
|
|
99
|
+
...Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
100
|
+
...Object.keys(CUSTOM_PROFILE_TEMPLATES),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const KNOWN_PROVIDERS = new Set(PROVIDER_CATALOG.map((entry) => entry.id));
|
|
104
|
+
|
|
105
|
+
export type SeedInferenceProfilesOptions = {
|
|
106
|
+
/**
|
|
107
|
+
* Managed profile names supplied by the platform/default overlay for this
|
|
108
|
+
* startup. Those entries are already on disk by the time seeding runs and
|
|
109
|
+
* should remain authoritative for this boot.
|
|
110
|
+
*/
|
|
111
|
+
preserveProfileNames?: Iterable<string>;
|
|
112
|
+
preserveActiveProfile?: boolean;
|
|
113
|
+
};
|
|
114
|
+
|
|
54
115
|
/**
|
|
55
116
|
* Seed managed inference profiles into the workspace config.
|
|
56
117
|
*
|
|
57
|
-
* Called on every daemon startup after workspace migrations and
|
|
58
|
-
* the first `loadConfig()`.
|
|
59
|
-
* (
|
|
60
|
-
*
|
|
61
|
-
*
|
|
118
|
+
* Called on every daemon startup after workspace migrations and default-config
|
|
119
|
+
* overlay merge, but before the first `loadConfig()`. The 3 Anthropic-managed
|
|
120
|
+
* profiles (`balanced`, `quality-optimized`, `cost-optimized`) are always
|
|
121
|
+
* written so users can target Anthropic via their own key. When the resolved
|
|
122
|
+
* default provider is non-Anthropic, the 3 `custom-*` profiles are also
|
|
123
|
+
* materialized using `resolveModelIntent` against that provider, and
|
|
124
|
+
* `custom-balanced` becomes the active profile for fresh hatches.
|
|
62
125
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
126
|
+
* Default-config overlays can provide their own profile fragments and active
|
|
127
|
+
* profile. Lifecycle passes those explicit fields in `options`, letting local
|
|
128
|
+
* hatches still receive managed defaults while platform-owned profile choices
|
|
129
|
+
* remain authoritative.
|
|
66
130
|
*/
|
|
67
|
-
export function seedInferenceProfiles(
|
|
68
|
-
|
|
69
|
-
|
|
131
|
+
export function seedInferenceProfiles(
|
|
132
|
+
options: SeedInferenceProfilesOptions = {},
|
|
133
|
+
): void {
|
|
70
134
|
const config = loadRawConfig();
|
|
135
|
+
const preservedProfileNames = new Set(options.preserveProfileNames ?? []);
|
|
71
136
|
|
|
72
137
|
if (config.llm == null || typeof config.llm !== "object") {
|
|
73
138
|
config.llm = {};
|
|
@@ -79,25 +144,135 @@ export function seedInferenceProfiles(): void {
|
|
|
79
144
|
}
|
|
80
145
|
const profiles = llm.profiles as Record<string, Record<string, unknown>>;
|
|
81
146
|
|
|
82
|
-
|
|
83
|
-
|
|
147
|
+
const requestedProvider =
|
|
148
|
+
readString(readObject(llm.default)?.provider) ?? "anthropic";
|
|
149
|
+
const isKnownProvider = KNOWN_PROVIDERS.has(requestedProvider);
|
|
150
|
+
const resolvedProvider: NonNullable<ProfileEntry["provider"]> =
|
|
151
|
+
isKnownProvider
|
|
152
|
+
? (requestedProvider as NonNullable<ProfileEntry["provider"]>)
|
|
153
|
+
: "anthropic";
|
|
154
|
+
const isAnthropicDefault = resolvedProvider === "anthropic";
|
|
155
|
+
|
|
156
|
+
// Persist the resolved provider when the overlay supplied an unrecognized
|
|
157
|
+
// value, so the on-disk config doesn't keep emitting Zod warnings for
|
|
158
|
+
// `unknownprov` on every load.
|
|
159
|
+
if (!isKnownProvider) {
|
|
160
|
+
const defaultBlock = (readObject(llm.default) ?? {}) as Record<
|
|
161
|
+
string,
|
|
162
|
+
unknown
|
|
163
|
+
>;
|
|
164
|
+
defaultBlock.provider = resolvedProvider;
|
|
165
|
+
llm.default = defaultBlock;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const [name, template] of Object.entries(ANTHROPIC_PROFILE_TEMPLATES)) {
|
|
169
|
+
if (preservedProfileNames.has(name)) continue;
|
|
170
|
+
// Preserve a previously overlay-supplied profile whose provider matches
|
|
171
|
+
// the resolved default — that's the platform-managed case where the
|
|
172
|
+
// overlay file has already been consumed and archived. Only valid for
|
|
173
|
+
// non-Anthropic resolutions; when the default is Anthropic the daemon
|
|
174
|
+
// owns these names and re-seeds with Anthropic data so a stale openai
|
|
175
|
+
// `balanced` doesn't keep routing through the wrong provider after a
|
|
176
|
+
// re-hatch back to Anthropic.
|
|
177
|
+
const existing = readObject(profiles[name]);
|
|
178
|
+
const existingProvider = readString(existing?.provider);
|
|
179
|
+
if (
|
|
180
|
+
existing !== null &&
|
|
181
|
+
!isAnthropicDefault &&
|
|
182
|
+
existingProvider === resolvedProvider
|
|
183
|
+
) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
profiles[name] = materializeProfile(template, "anthropic");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!isAnthropicDefault) {
|
|
190
|
+
for (const [name, template] of Object.entries(CUSTOM_PROFILE_TEMPLATES)) {
|
|
191
|
+
if (preservedProfileNames.has(name)) continue;
|
|
192
|
+
if (readObject(profiles[name]) !== null) continue;
|
|
193
|
+
profiles[name] = materializeProfile(template, resolvedProvider);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const requestedActiveProfile = readString(llm.activeProfile);
|
|
198
|
+
const requestedActiveEntry =
|
|
199
|
+
requestedActiveProfile !== undefined
|
|
200
|
+
? readObject(profiles[requestedActiveProfile])
|
|
201
|
+
: null;
|
|
202
|
+
const requestedActiveExists = requestedActiveEntry !== null;
|
|
203
|
+
const shouldPreserveActiveProfile =
|
|
204
|
+
options.preserveActiveProfile === true && requestedActiveExists;
|
|
205
|
+
|
|
206
|
+
// Decide whether the existing active profile is still appropriate. A managed
|
|
207
|
+
// profile whose provider no longer matches the resolved default goes stale
|
|
208
|
+
// (e.g. re-hatching anthropic→openai leaves `balanced` pointing at anthropic;
|
|
209
|
+
// re-hatching openai→anthropic leaves `custom-balanced` pointing at openai).
|
|
210
|
+
// Either direction should land the user on the new default's `balanced`
|
|
211
|
+
// counterpart rather than routing the main agent to a stale provider.
|
|
212
|
+
// User-created profiles are left alone — those are the user's choice.
|
|
213
|
+
let keepActiveProfile = shouldPreserveActiveProfile;
|
|
214
|
+
if (!keepActiveProfile && requestedActiveExists) {
|
|
215
|
+
const isSeededName = SEEDED_PROFILE_NAMES.has(requestedActiveProfile!);
|
|
216
|
+
const activeProvider = readString(requestedActiveEntry?.provider);
|
|
217
|
+
const managedActiveProviderMismatch =
|
|
218
|
+
isSeededName && activeProvider !== resolvedProvider;
|
|
219
|
+
keepActiveProfile = !managedActiveProviderMismatch;
|
|
84
220
|
}
|
|
85
221
|
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
llm.activeProfile =
|
|
222
|
+
let activeProfileName: string;
|
|
223
|
+
if (keepActiveProfile) {
|
|
224
|
+
activeProfileName = requestedActiveProfile!;
|
|
225
|
+
} else {
|
|
226
|
+
activeProfileName = isAnthropicDefault ? "balanced" : "custom-balanced";
|
|
227
|
+
llm.activeProfile = activeProfileName;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Sync `llm.default.model` to the active profile's model so the providers
|
|
231
|
+
// registry sees a coherent provider/model pair. Only writes when the on-disk
|
|
232
|
+
// default model is missing or unambiguously belongs to a *different*
|
|
233
|
+
// provider's catalog (e.g. `claude-opus-4-7` paired with `provider: openai`).
|
|
234
|
+
// A user-supplied model not listed in any provider's catalog is preserved —
|
|
235
|
+
// ollama and openrouter expose far more models than `PROVIDER_CATALOG` lists,
|
|
236
|
+
// and silently overwriting `codellama`/`phi3`/etc. on every restart would
|
|
237
|
+
// break those users' configs. Skipped when the overlay owns the active
|
|
238
|
+
// profile (platform mode).
|
|
239
|
+
if (!shouldPreserveActiveProfile) {
|
|
240
|
+
const activeEntry = readObject(profiles[activeProfileName]);
|
|
241
|
+
const activeModel = readString(activeEntry?.model);
|
|
242
|
+
if (activeModel !== undefined) {
|
|
243
|
+
const defaultBlock = (readObject(llm.default) ?? {}) as Record<
|
|
244
|
+
string,
|
|
245
|
+
unknown
|
|
246
|
+
>;
|
|
247
|
+
const currentModel = readString(defaultBlock.model);
|
|
248
|
+
const modelBelongsToOtherProvider =
|
|
249
|
+
currentModel !== undefined &&
|
|
250
|
+
PROVIDER_CATALOG.some(
|
|
251
|
+
(p) =>
|
|
252
|
+
p.id !== resolvedProvider &&
|
|
253
|
+
p.models.some((m) => m.id === currentModel),
|
|
254
|
+
);
|
|
255
|
+
const shouldOverwriteDefaultModel =
|
|
256
|
+
currentModel === undefined || modelBelongsToOtherProvider;
|
|
257
|
+
if (shouldOverwriteDefaultModel) {
|
|
258
|
+
defaultBlock.model = activeModel;
|
|
259
|
+
llm.default = defaultBlock;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
92
262
|
}
|
|
93
263
|
|
|
94
264
|
const profileOrder = Array.isArray(llm.profileOrder)
|
|
95
265
|
? (llm.profileOrder as string[])
|
|
96
266
|
: [];
|
|
97
267
|
const orderSet = new Set(profileOrder);
|
|
98
|
-
|
|
268
|
+
const seededOrder = [
|
|
269
|
+
...Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
270
|
+
...(isAnthropicDefault ? [] : Object.keys(CUSTOM_PROFILE_TEMPLATES)),
|
|
271
|
+
];
|
|
272
|
+
for (const name of seededOrder) {
|
|
99
273
|
if (!orderSet.has(name)) {
|
|
100
274
|
profileOrder.push(name);
|
|
275
|
+
orderSet.add(name);
|
|
101
276
|
}
|
|
102
277
|
}
|
|
103
278
|
llm.profileOrder = profileOrder;
|
|
@@ -115,3 +290,25 @@ export function seedInferenceProfiles(): void {
|
|
|
115
290
|
|
|
116
291
|
saveRawConfig(config);
|
|
117
292
|
}
|
|
293
|
+
|
|
294
|
+
function materializeProfile(
|
|
295
|
+
template: ManagedProfileTemplate,
|
|
296
|
+
provider: NonNullable<ProfileEntry["provider"]>,
|
|
297
|
+
): ProfileEntry {
|
|
298
|
+
const { intent, ...rest } = template;
|
|
299
|
+
return {
|
|
300
|
+
...rest,
|
|
301
|
+
provider,
|
|
302
|
+
model: resolveModelIntent(provider, intent),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
307
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
308
|
+
? (value as Record<string, unknown>)
|
|
309
|
+
: null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function readString(value: unknown): string | undefined {
|
|
313
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
314
|
+
}
|
|
@@ -704,31 +704,6 @@ export function mergeContacts(
|
|
|
704
704
|
return getContactInternal(keepId)!;
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
/**
|
|
708
|
-
* Delete a contact by ID. Guardians cannot be deleted as a safety guard.
|
|
709
|
-
* Associated contactChannels and assistantContactMetadata rows are
|
|
710
|
-
* cascade-deleted by the DB schema's onDelete constraints.
|
|
711
|
-
*/
|
|
712
|
-
export function deleteContact(
|
|
713
|
-
contactId: string,
|
|
714
|
-
): "ok" | "not_found" | "is_guardian" {
|
|
715
|
-
const db = getDb();
|
|
716
|
-
|
|
717
|
-
const contact = db
|
|
718
|
-
.select()
|
|
719
|
-
.from(contacts)
|
|
720
|
-
.where(eq(contacts.id, contactId))
|
|
721
|
-
.get();
|
|
722
|
-
|
|
723
|
-
if (!contact) return "not_found";
|
|
724
|
-
if (contact.role === "guardian") return "is_guardian";
|
|
725
|
-
|
|
726
|
-
db.delete(contacts).where(eq(contacts.id, contactId)).run();
|
|
727
|
-
|
|
728
|
-
emitContactChange();
|
|
729
|
-
return "ok";
|
|
730
|
-
}
|
|
731
|
-
|
|
732
707
|
/**
|
|
733
708
|
* Find a contact by a specific channel address. Returns null if not found.
|
|
734
709
|
*/
|
|
@@ -19,29 +19,28 @@
|
|
|
19
19
|
* host_file_* are filtered out for chrome-extension regardless of the
|
|
20
20
|
* hasNoClient flag.
|
|
21
21
|
*
|
|
22
|
-
* Cross-client exception
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Cross-client exception: tools whose capabilities are in
|
|
23
|
+
* CROSS_CLIENT_EXPOSED_CAPABILITIES (host_bash, host_file) are allowed for
|
|
24
|
+
* non-host-proxy interfaces (e.g. "web") when at least one capable client
|
|
25
|
+
* is connected via the event hub. host_browser is excluded (chrome-extension
|
|
26
|
+
* is its own executor; web turns have no CDP target model).
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
29
30
|
|
|
30
31
|
// ── Module-level mocks ─────────────────────────────────────────────
|
|
31
32
|
|
|
32
|
-
// Control how many
|
|
33
|
-
|
|
33
|
+
// Control how many capable clients the hub reports per capability.
|
|
34
|
+
const mockClientCountByCapability = new Map<string, number>();
|
|
34
35
|
|
|
35
36
|
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
36
37
|
assistantEventHub: {
|
|
37
38
|
listClientsByCapability: (cap: string) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return [];
|
|
39
|
+
const count = mockClientCountByCapability.get(cap) ?? 0;
|
|
40
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
41
|
+
clientId: `mock-${cap}-client-${i}`,
|
|
42
|
+
capabilities: [cap],
|
|
43
|
+
}));
|
|
45
44
|
},
|
|
46
45
|
},
|
|
47
46
|
broadcastMessage: () => {},
|
|
@@ -72,7 +71,7 @@ function makeCtx(
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
beforeEach(() => {
|
|
75
|
-
|
|
74
|
+
mockClientCountByCapability.clear();
|
|
76
75
|
});
|
|
77
76
|
|
|
78
77
|
describe("isToolActiveForContext — host tool capability gating", () => {
|
|
@@ -213,7 +212,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
213
212
|
test("host_bash is active for web transport when a host_bash-capable client is connected", () => {
|
|
214
213
|
// Cross-client path: a web turn should see host_bash when a macOS client
|
|
215
214
|
// with host_bash capability is connected via the event hub.
|
|
216
|
-
|
|
215
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
217
216
|
expect(
|
|
218
217
|
isToolActiveForContext(
|
|
219
218
|
"host_bash",
|
|
@@ -224,7 +223,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
224
223
|
|
|
225
224
|
test("host_bash is NOT active for web transport when no capable client is connected", () => {
|
|
226
225
|
// No cross-client fallback: hub has no host_bash-capable subscribers.
|
|
227
|
-
|
|
226
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
228
227
|
expect(
|
|
229
228
|
isToolActiveForContext(
|
|
230
229
|
"host_bash",
|
|
@@ -233,11 +232,11 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
233
232
|
).toBe(false);
|
|
234
233
|
});
|
|
235
234
|
|
|
236
|
-
test("host_file_read is NOT active for web transport
|
|
237
|
-
// The cross-client exception is
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
235
|
+
test("host_file_read is NOT active for web transport when only a host_bash client is connected", () => {
|
|
236
|
+
// The cross-client exception is per-capability: a host_bash-capable
|
|
237
|
+
// client in the hub does not satisfy host_file's exposure check, since
|
|
238
|
+
// listClientsByCapability is queried with the tool's actual capability.
|
|
239
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
241
240
|
expect(
|
|
242
241
|
isToolActiveForContext(
|
|
243
242
|
"host_file_read",
|
|
@@ -249,7 +248,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
249
248
|
test("host_bash for macos transport is unaffected by the cross-client exception", () => {
|
|
250
249
|
// macos natively supports host_bash via host proxy — the supportsHostProxy
|
|
251
250
|
// check passes, so the cross-client branch is never reached.
|
|
252
|
-
|
|
251
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
253
252
|
expect(
|
|
254
253
|
isToolActiveForContext(
|
|
255
254
|
"host_bash",
|
|
@@ -262,7 +261,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
262
261
|
// Even with a capable client in the hub, the macos SSE path takes
|
|
263
262
|
// precedence — it passes the supportsHostProxy check, bypasses the
|
|
264
263
|
// cross-client branch, and reaches the hasNoClient gate.
|
|
265
|
-
|
|
264
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
266
265
|
expect(
|
|
267
266
|
isToolActiveForContext(
|
|
268
267
|
"host_bash",
|
|
@@ -275,7 +274,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
275
274
|
// Security boundary: chrome-extension only gets host_browser. The
|
|
276
275
|
// cross-client exception explicitly excludes chrome-extension transport
|
|
277
276
|
// regardless of how many host_bash-capable clients are in the hub.
|
|
278
|
-
|
|
277
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
279
278
|
expect(
|
|
280
279
|
isToolActiveForContext(
|
|
281
280
|
"host_bash",
|
|
@@ -287,7 +286,7 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
287
286
|
test("host_bash is NOT active for web transport when hasNoClient is true (no approval UI)", () => {
|
|
288
287
|
// hasNoClient gate: no interactive approval UI available for this turn.
|
|
289
288
|
// Cross-client exception must not bypass this gate.
|
|
290
|
-
|
|
289
|
+
mockClientCountByCapability.set("host_bash", 1);
|
|
291
290
|
expect(
|
|
292
291
|
isToolActiveForContext(
|
|
293
292
|
"host_bash",
|
|
@@ -297,6 +296,68 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
|
|
|
297
296
|
});
|
|
298
297
|
});
|
|
299
298
|
|
|
299
|
+
describe("isToolActiveForContext — cross-client exposure for host_file_*", () => {
|
|
300
|
+
const HOST_FILE_TOOLS = [
|
|
301
|
+
"host_file_read",
|
|
302
|
+
"host_file_write",
|
|
303
|
+
"host_file_edit",
|
|
304
|
+
"host_file_transfer",
|
|
305
|
+
] as const;
|
|
306
|
+
|
|
307
|
+
for (const tool of HOST_FILE_TOOLS) {
|
|
308
|
+
test(`${tool} is exposed for web transport when a host_file client is connected`, () => {
|
|
309
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
310
|
+
expect(
|
|
311
|
+
isToolActiveForContext(
|
|
312
|
+
tool,
|
|
313
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
314
|
+
),
|
|
315
|
+
).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test(`${tool} is NOT exposed for web when no host_file client is connected`, () => {
|
|
319
|
+
mockClientCountByCapability.set("host_file", 0);
|
|
320
|
+
expect(
|
|
321
|
+
isToolActiveForContext(
|
|
322
|
+
tool,
|
|
323
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
324
|
+
),
|
|
325
|
+
).toBe(false);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test(`${tool} is NOT exposed for chrome-extension (security boundary)`, () => {
|
|
329
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
330
|
+
expect(
|
|
331
|
+
isToolActiveForContext(
|
|
332
|
+
tool,
|
|
333
|
+
makeCtx({ hasNoClient: true, transportInterface: "chrome-extension" }),
|
|
334
|
+
),
|
|
335
|
+
).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test(`${tool} is NOT exposed when hasNoClient is true (no approval UI)`, () => {
|
|
339
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
340
|
+
expect(
|
|
341
|
+
isToolActiveForContext(
|
|
342
|
+
tool,
|
|
343
|
+
makeCtx({ hasNoClient: true, transportInterface: "web" }),
|
|
344
|
+
),
|
|
345
|
+
).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
test("listClientsByCapability is queried with the actual capability, not host_bash (regression guard for D5 latent bug)", () => {
|
|
350
|
+
mockClientCountByCapability.set("host_bash", 0);
|
|
351
|
+
mockClientCountByCapability.set("host_file", 1);
|
|
352
|
+
expect(
|
|
353
|
+
isToolActiveForContext(
|
|
354
|
+
"host_file_transfer",
|
|
355
|
+
makeCtx({ hasNoClient: false, transportInterface: "web" }),
|
|
356
|
+
),
|
|
357
|
+
).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
300
361
|
describe("HOST_TOOL_NAMES derivation", () => {
|
|
301
362
|
test("HOST_TOOL_NAMES is derived from HOST_TOOL_TO_CAPABILITY", () => {
|
|
302
363
|
// Sanity check: every tool in the names set has a capability mapping.
|
|
@@ -125,7 +125,7 @@ export function classifyKind(mimeType: string): "image" | "video" | "document" {
|
|
|
125
125
|
// Validation / cap enforcement
|
|
126
126
|
// ---------------------------------------------------------------------------
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
interface ValidatedDrafts {
|
|
129
129
|
accepted: AssistantAttachmentDraft[];
|
|
130
130
|
warnings: string[];
|
|
131
131
|
}
|
|
@@ -171,13 +171,13 @@ export interface DirectiveRequest {
|
|
|
171
171
|
mimeType: string | undefined;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
interface DirectiveParseResult {
|
|
175
175
|
cleanText: string;
|
|
176
176
|
directiveRequests: DirectiveRequest[];
|
|
177
177
|
parseWarnings: string[];
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
interface DirectiveDisplayDrainResult {
|
|
181
181
|
emitText: string;
|
|
182
182
|
bufferedRemainder: string;
|
|
183
183
|
}
|
|
@@ -362,7 +362,7 @@ export function drainDirectiveDisplayBuffer(
|
|
|
362
362
|
// Sandbox file resolution
|
|
363
363
|
// ---------------------------------------------------------------------------
|
|
364
364
|
|
|
365
|
-
|
|
365
|
+
interface ResolveResult {
|
|
366
366
|
draft: AssistantAttachmentDraft | null;
|
|
367
367
|
warning: string | null;
|
|
368
368
|
}
|