@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
|
@@ -11,13 +11,22 @@
|
|
|
11
11
|
* (PNG-hash loop guard) and the result-payload → ToolExecutionResult
|
|
12
12
|
* translation on top.
|
|
13
13
|
*
|
|
14
|
-
* **
|
|
15
|
-
* session at a time
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* **Session lock.** Only one conversation may hold an active app-control
|
|
15
|
+
* session at a time, and that session is bound to a specific target app.
|
|
16
|
+
* The lock is module-level (`activeAppControlSession`) because the session
|
|
17
|
+
* targets the user's actual desktop application, which is a host-wide
|
|
18
|
+
* resource. It is acquired on a successful `app_control_start` (storing
|
|
19
|
+
* `(conversationId, app)`) and released when the owning proxy's
|
|
20
|
+
* `dispose()` fires.
|
|
21
|
+
*
|
|
22
|
+
* `app_control_start` is the only tool that can acquire the lock — the
|
|
23
|
+
* user's medium-risk approval at start time is the consent boundary. All
|
|
24
|
+
* other tools (observe / press / combo / sequence / type / click / drag)
|
|
25
|
+
* require the calling conversation to own an active session targeting the
|
|
26
|
+
* same `app`; otherwise the call is rejected before any host dispatch.
|
|
27
|
+
* This prevents prompt-injected tool calls from sending raw input to
|
|
28
|
+
* arbitrary apps without the user having approved control of that
|
|
29
|
+
* specific app.
|
|
21
30
|
*
|
|
22
31
|
* **No step cap.** Unlike {@link HostCuProxy} which enforces a per-session
|
|
23
32
|
* step ceiling via `loadConfig().maxStepsPerSession`, app-control sessions
|
|
@@ -51,36 +60,111 @@ const REQUEST_TIMEOUT_MS = 60 * 1000;
|
|
|
51
60
|
const STUCK_REPEAT_THRESHOLD = 4;
|
|
52
61
|
|
|
53
62
|
// ---------------------------------------------------------------------------
|
|
54
|
-
//
|
|
63
|
+
// Module-level session lock
|
|
55
64
|
// ---------------------------------------------------------------------------
|
|
56
|
-
//
|
|
57
|
-
// Kept here (rather than imported from PR 5's tool registrations) so the
|
|
58
|
-
// proxy is independently testable. PR 5 must use these same string values.
|
|
59
|
-
|
|
60
|
-
const TOOL_START = "app_control_start";
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Active app-control session: the conversation that owns the lock and the
|
|
68
|
+
* `app` it was approved against. Set on a successful `app_control_start`;
|
|
69
|
+
* cleared by the owning proxy's `dispose()`.
|
|
70
|
+
*/
|
|
71
|
+
export interface ActiveAppControlSession {
|
|
72
|
+
conversationId: string;
|
|
73
|
+
/**
|
|
74
|
+
* The exact `app` string the user approved at start time (bundle ID or
|
|
75
|
+
* process name — preserved as-is). Compared case-insensitively against
|
|
76
|
+
* the `app` of subsequent non-start tool calls.
|
|
77
|
+
*/
|
|
78
|
+
app: string;
|
|
79
|
+
}
|
|
65
80
|
|
|
66
81
|
/**
|
|
67
|
-
*
|
|
68
|
-
* `undefined` if no session is active. Set on a successful
|
|
69
|
-
* `app_control_start`; cleared by the owning proxy's `dispose()`.
|
|
82
|
+
* Currently active session, or `undefined` when no session is held.
|
|
70
83
|
*
|
|
71
84
|
* Exported for test inspection only. Production code paths must not read
|
|
72
85
|
* or mutate this directly — use the proxy methods.
|
|
73
86
|
*/
|
|
74
|
-
let
|
|
87
|
+
let activeAppControlSession: ActiveAppControlSession | undefined;
|
|
75
88
|
|
|
76
|
-
/** Test-only helper: read current
|
|
77
|
-
export function
|
|
78
|
-
|
|
89
|
+
/** Test-only helper: read current session. */
|
|
90
|
+
export function _getActiveAppControlSession():
|
|
91
|
+
| ActiveAppControlSession
|
|
92
|
+
| undefined {
|
|
93
|
+
return activeAppControlSession;
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
/** Test-only helper: clear
|
|
82
|
-
export function
|
|
83
|
-
|
|
96
|
+
/** Test-only helper: clear session between test cases. */
|
|
97
|
+
export function _resetActiveAppControlSession(): void {
|
|
98
|
+
activeAppControlSession = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Test-only helper: prime the active session without a full `start` round-trip.
|
|
103
|
+
* Useful for tests that exercise non-start tool paths and don't need to
|
|
104
|
+
* verify the start flow itself.
|
|
105
|
+
*/
|
|
106
|
+
export function _setActiveAppControlSession(
|
|
107
|
+
session: ActiveAppControlSession,
|
|
108
|
+
): void {
|
|
109
|
+
activeAppControlSession = session;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate a non-start tool call against the active session. Returns a
|
|
114
|
+
* `ToolExecutionResult` (with `isError: true`) when the call should be
|
|
115
|
+
* rejected; returns `null` when the call is authorized to dispatch.
|
|
116
|
+
*
|
|
117
|
+
* `app` matching is case-insensitive (macOS bundle IDs are
|
|
118
|
+
* case-insensitive in practice) but strict on form: `"Safari"` and
|
|
119
|
+
* `"com.apple.Safari"` do not match — the user approved a specific string
|
|
120
|
+
* and substituting a different form requires a new approval.
|
|
121
|
+
*/
|
|
122
|
+
function checkNonStartAuthorization(
|
|
123
|
+
input: HostAppControlInput,
|
|
124
|
+
conversationId: string,
|
|
125
|
+
): ToolExecutionResult | null {
|
|
126
|
+
if (activeAppControlSession == null) {
|
|
127
|
+
return {
|
|
128
|
+
content:
|
|
129
|
+
"No app-control session is active. Call app_control_start to request " +
|
|
130
|
+
"user approval to control the target app, then retry.",
|
|
131
|
+
isError: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (activeAppControlSession.conversationId !== conversationId) {
|
|
135
|
+
return {
|
|
136
|
+
content:
|
|
137
|
+
`Another conversation (${activeAppControlSession.conversationId}) currently ` +
|
|
138
|
+
`holds the app-control session. Wait for it to finish, or call ` +
|
|
139
|
+
`app_control_stop from that conversation first.`,
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// `app` is required on every non-start variant of HostAppControlInput
|
|
144
|
+
// except `stop`, and `stop` short-circuits in conversation-surfaces and
|
|
145
|
+
// does not reach this method in production. A stop reaching here would
|
|
146
|
+
// be a defensive bug — surface it explicitly rather than dispatch.
|
|
147
|
+
const requestedApp = (input as { app?: string }).app;
|
|
148
|
+
if (requestedApp == null) {
|
|
149
|
+
return {
|
|
150
|
+
content:
|
|
151
|
+
"Tool input missing required 'app' field; cannot validate against " +
|
|
152
|
+
"the active app-control session.",
|
|
153
|
+
isError: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
requestedApp.toLowerCase() !== activeAppControlSession.app.toLowerCase()
|
|
158
|
+
) {
|
|
159
|
+
return {
|
|
160
|
+
content:
|
|
161
|
+
`Active app-control session targets ${activeAppControlSession.app}; ` +
|
|
162
|
+
`cannot send actions to ${requestedApp}. Call app_control_stop and ` +
|
|
163
|
+
`app_control_start to switch apps.`,
|
|
164
|
+
isError: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
84
168
|
}
|
|
85
169
|
|
|
86
170
|
// ---------------------------------------------------------------------------
|
|
@@ -91,7 +175,7 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
91
175
|
HostAppControlInput,
|
|
92
176
|
HostAppControlResultPayload
|
|
93
177
|
> {
|
|
94
|
-
/** Conversation that owns this proxy instance. Used by `dispose()` to release the
|
|
178
|
+
/** Conversation that owns this proxy instance. Used by `dispose()` to release the session lock only when this proxy is the holder. */
|
|
95
179
|
private readonly conversationId: string;
|
|
96
180
|
|
|
97
181
|
/** sha256 hex of the most recent observation's `pngBase64`, or undefined. */
|
|
@@ -143,21 +227,29 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
143
227
|
return { content: "Aborted", isError: true };
|
|
144
228
|
}
|
|
145
229
|
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
230
|
+
// Authorization gate. `start` acquires the session lock (the user's
|
|
231
|
+
// medium-risk approval is the consent boundary); all other tools must
|
|
232
|
+
// belong to the active session and target the same `app`. Without this
|
|
233
|
+
// gate, prompt-injected calls would bypass the start-time approval and
|
|
234
|
+
// send raw input to arbitrary apps.
|
|
235
|
+
if (input.tool === "start") {
|
|
149
236
|
if (
|
|
150
|
-
|
|
151
|
-
|
|
237
|
+
activeAppControlSession != null &&
|
|
238
|
+
activeAppControlSession.conversationId !== conversationId
|
|
152
239
|
) {
|
|
153
240
|
return {
|
|
154
241
|
content:
|
|
155
|
-
`Another conversation (${
|
|
242
|
+
`Another conversation (${activeAppControlSession.conversationId}) currently holds the ` +
|
|
156
243
|
`app-control session. Wait for it to finish, or call app_control_stop ` +
|
|
157
244
|
`from that conversation first.`,
|
|
158
245
|
isError: true,
|
|
159
246
|
};
|
|
160
247
|
}
|
|
248
|
+
} else {
|
|
249
|
+
const sessionError = checkNonStartAuthorization(input, conversationId);
|
|
250
|
+
if (sessionError != null) {
|
|
251
|
+
return sessionError;
|
|
252
|
+
}
|
|
161
253
|
}
|
|
162
254
|
|
|
163
255
|
try {
|
|
@@ -167,7 +259,7 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
167
259
|
conversationId,
|
|
168
260
|
signal,
|
|
169
261
|
);
|
|
170
|
-
return this.handleSuccess(
|
|
262
|
+
return this.handleSuccess(input, payload);
|
|
171
263
|
} catch (err) {
|
|
172
264
|
if (err instanceof HostProxyRequestError) {
|
|
173
265
|
if (err.reason === "timeout") {
|
|
@@ -192,7 +284,7 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
192
284
|
// ---------------------------------------------------------------------------
|
|
193
285
|
|
|
194
286
|
private handleSuccess(
|
|
195
|
-
|
|
287
|
+
input: HostAppControlInput,
|
|
196
288
|
payload: HostAppControlResultPayload,
|
|
197
289
|
): ToolExecutionResult {
|
|
198
290
|
// Update PNG-hash loop tracking only for the "running" state — other
|
|
@@ -212,9 +304,13 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
212
304
|
}
|
|
213
305
|
}
|
|
214
306
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
307
|
+
// Store the exact `app` form for validation against subsequent
|
|
308
|
+
// non-start tool calls.
|
|
309
|
+
if (input.tool === "start" && payload.state === "running") {
|
|
310
|
+
activeAppControlSession = {
|
|
311
|
+
conversationId: this.conversationId,
|
|
312
|
+
app: input.app,
|
|
313
|
+
};
|
|
218
314
|
}
|
|
219
315
|
|
|
220
316
|
return this.formatResult(payload, stuck);
|
|
@@ -281,13 +377,13 @@ export class HostAppControlProxy extends HostProxyBase<
|
|
|
281
377
|
// ---------------------------------------------------------------------------
|
|
282
378
|
|
|
283
379
|
/**
|
|
284
|
-
* Reject pending requests via the base, then release the
|
|
380
|
+
* Reject pending requests via the base, then release the session lock
|
|
285
381
|
* if this proxy is the holder. Idempotent: safe to call multiple times.
|
|
286
382
|
*/
|
|
287
383
|
override dispose(): void {
|
|
288
384
|
super.dispose();
|
|
289
|
-
if (
|
|
290
|
-
|
|
385
|
+
if (activeAppControlSession?.conversationId === this.conversationId) {
|
|
386
|
+
activeAppControlSession = undefined;
|
|
291
387
|
}
|
|
292
388
|
}
|
|
293
389
|
}
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
assistantEventHub,
|
|
6
6
|
broadcastMessage,
|
|
7
7
|
} from "../runtime/assistant-event-hub.js";
|
|
8
|
+
import {
|
|
9
|
+
ambiguousSameUserError,
|
|
10
|
+
enforceSameActorOrErrorResult,
|
|
11
|
+
pickSameUserAutoResolve,
|
|
12
|
+
} from "../runtime/auth/same-actor.js";
|
|
8
13
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
9
14
|
import { formatShellOutput } from "../tools/shared/shell-output.js";
|
|
10
15
|
import type { ToolExecutionResult } from "../tools/types.js";
|
|
@@ -13,7 +18,6 @@ import { getLogger } from "../util/logger.js";
|
|
|
13
18
|
|
|
14
19
|
const log = getLogger("host-bash-proxy");
|
|
15
20
|
|
|
16
|
-
|
|
17
21
|
export class HostBashProxy {
|
|
18
22
|
private static _instance: HostBashProxy | null = null;
|
|
19
23
|
|
|
@@ -62,14 +66,14 @@ export class HostBashProxy {
|
|
|
62
66
|
},
|
|
63
67
|
conversationId: string,
|
|
64
68
|
signal?: AbortSignal,
|
|
69
|
+
// Principal ID of the actor on whose behalf this request is initiated.
|
|
70
|
+
sourceActorPrincipalId?: string,
|
|
65
71
|
): Promise<ToolExecutionResult> {
|
|
66
72
|
if (signal?.aborted) {
|
|
67
73
|
const result = formatShellOutput("", "Aborted", null, false, 0);
|
|
68
74
|
return Promise.resolve(result);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
const capableClients = assistantEventHub.listClientsByCapability("host_bash");
|
|
72
|
-
|
|
73
77
|
let resolvedTargetClientId: string | undefined;
|
|
74
78
|
|
|
75
79
|
if (input.targetClientId) {
|
|
@@ -81,14 +85,37 @@ export class HostBashProxy {
|
|
|
81
85
|
});
|
|
82
86
|
}
|
|
83
87
|
resolvedTargetClientId = input.targetClientId;
|
|
84
|
-
} else
|
|
85
|
-
// Auto-resolve
|
|
86
|
-
|
|
88
|
+
} else {
|
|
89
|
+
// Auto-resolve to the unique same-user client. Reject (rather than
|
|
90
|
+
// broadcast) when multiple same-user clients are connected so that
|
|
91
|
+
// a single targeted-style request cannot fan out across every one
|
|
92
|
+
// of the user's machines. Zero same-user matches falls through to
|
|
93
|
+
// the existing untargeted code path.
|
|
94
|
+
const resolved = pickSameUserAutoResolve({
|
|
95
|
+
hub: assistantEventHub,
|
|
96
|
+
capability: "host_bash",
|
|
97
|
+
sourceActorPrincipalId,
|
|
98
|
+
});
|
|
99
|
+
if (resolved.kind === "ambiguous") {
|
|
100
|
+
return Promise.resolve(ambiguousSameUserError("host_bash"));
|
|
101
|
+
}
|
|
102
|
+
resolvedTargetClientId =
|
|
103
|
+
resolved.kind === "match" ? resolved.clientId : undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Targeted requests must be bound to the same authenticated user as the
|
|
107
|
+
// target client. Fail closed at request time — before pendingInteractions
|
|
108
|
+
// registration and before broadcast — so a same-daemon caller cannot
|
|
109
|
+
// execute on another user's connected client.
|
|
110
|
+
if (resolvedTargetClientId != null) {
|
|
111
|
+
const rejection = enforceSameActorOrErrorResult({
|
|
112
|
+
hub: assistantEventHub,
|
|
113
|
+
sourceActorPrincipalId,
|
|
114
|
+
targetClientId: resolvedTargetClientId,
|
|
115
|
+
op: "host_bash",
|
|
116
|
+
});
|
|
117
|
+
if (rejection) return Promise.resolve(rejection);
|
|
87
118
|
}
|
|
88
|
-
// capableClients.length === 0 or > 1 without explicit target: resolvedTargetClientId
|
|
89
|
-
// stays undefined and falls through to untargeted broadcast — the existing timeout/error
|
|
90
|
-
// path handles the zero-client case, and multi-client ambiguity is enforced at the tool
|
|
91
|
-
// executor layer (not here) once target_client_id is exposed in the tool schema.
|
|
92
119
|
|
|
93
120
|
const requestId = uuid();
|
|
94
121
|
|
|
@@ -108,15 +135,7 @@ export class HostBashProxy {
|
|
|
108
135
|
const timeoutMessage = resolvedTargetClientId
|
|
109
136
|
? `Host bash proxy timed out waiting for response from client ${resolvedTargetClientId}`
|
|
110
137
|
: "Host bash proxy timed out waiting for client response";
|
|
111
|
-
resolve(
|
|
112
|
-
formatShellOutput(
|
|
113
|
-
"",
|
|
114
|
-
timeoutMessage,
|
|
115
|
-
null,
|
|
116
|
-
true,
|
|
117
|
-
timeoutSec,
|
|
118
|
-
),
|
|
119
|
-
);
|
|
138
|
+
resolve(formatShellOutput("", timeoutMessage, null, true, timeoutSec));
|
|
120
139
|
}, proxyTimeoutSec * 1000);
|
|
121
140
|
|
|
122
141
|
if (signal) {
|
|
@@ -152,6 +171,12 @@ export class HostBashProxy {
|
|
|
152
171
|
timer,
|
|
153
172
|
detachAbort,
|
|
154
173
|
targetClientId: resolvedTargetClientId,
|
|
174
|
+
targetActorPrincipalId:
|
|
175
|
+
resolvedTargetClientId != null
|
|
176
|
+
? assistantEventHub.getActorPrincipalIdForClient(
|
|
177
|
+
resolvedTargetClientId,
|
|
178
|
+
)
|
|
179
|
+
: undefined,
|
|
155
180
|
metadata: { timeoutSec },
|
|
156
181
|
});
|
|
157
182
|
|
|
@@ -166,8 +191,8 @@ export class HostBashProxy {
|
|
|
166
191
|
timeout_seconds: input.timeout_seconds,
|
|
167
192
|
targetClientId: resolvedTargetClientId,
|
|
168
193
|
...(input.env && Object.keys(input.env).length > 0
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
? { env: input.env }
|
|
195
|
+
: {}),
|
|
171
196
|
},
|
|
172
197
|
conversationId,
|
|
173
198
|
{ targetClientId: resolvedTargetClientId },
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
assistantEventHub,
|
|
24
24
|
broadcastMessage,
|
|
25
25
|
} from "../runtime/assistant-event-hub.js";
|
|
26
|
+
import { enforceSameActorOrErrorResult } from "../runtime/auth/same-actor.js";
|
|
26
27
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
27
28
|
import type { ToolExecutionResult } from "../tools/types.js";
|
|
28
29
|
import { AssistantError, ErrorCode } from "../util/errors.js";
|
|
@@ -121,6 +122,16 @@ export class HostCuProxy {
|
|
|
121
122
|
// Request / resolve lifecycle
|
|
122
123
|
// ---------------------------------------------------------------------------
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Send a CU request to the connected desktop client.
|
|
127
|
+
*
|
|
128
|
+
* When `targetClientId` is supplied, the proxy validates that the target
|
|
129
|
+
* exists and advertises the `host_cu` capability, mirroring HostFileProxy's
|
|
130
|
+
* resolver-side checks so that the proxy is safe to call as a standalone
|
|
131
|
+
* API. It additionally enforces that the caller (`sourceActorPrincipalId`)
|
|
132
|
+
* and the target client share the same actor principal — cross-user
|
|
133
|
+
* targeted dispatch is rejected.
|
|
134
|
+
*/
|
|
124
135
|
request(
|
|
125
136
|
toolName: string,
|
|
126
137
|
input: Record<string, unknown>,
|
|
@@ -129,6 +140,7 @@ export class HostCuProxy {
|
|
|
129
140
|
reasoning?: string,
|
|
130
141
|
signal?: AbortSignal,
|
|
131
142
|
targetClientId?: string,
|
|
143
|
+
sourceActorPrincipalId?: string,
|
|
132
144
|
): Promise<ToolExecutionResult> {
|
|
133
145
|
if (signal?.aborted) {
|
|
134
146
|
return Promise.resolve({
|
|
@@ -144,6 +156,38 @@ export class HostCuProxy {
|
|
|
144
156
|
});
|
|
145
157
|
}
|
|
146
158
|
|
|
159
|
+
// Existence + capability validation for explicit targets. Mirrors
|
|
160
|
+
// HostFileProxy's resolver-side guard so that the proxy is safe even
|
|
161
|
+
// when called outside the conversation-surfaces dispatch (which has
|
|
162
|
+
// its own validation layer).
|
|
163
|
+
if (targetClientId != null) {
|
|
164
|
+
const client = assistantEventHub.getClientById(targetClientId);
|
|
165
|
+
if (!client) {
|
|
166
|
+
return Promise.resolve({
|
|
167
|
+
content: `No connected client with id '${targetClientId}' supports host_cu. Run \`assistant clients list --capability host_cu\` to see available clients.`,
|
|
168
|
+
isError: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!client.capabilities.includes("host_cu")) {
|
|
172
|
+
return Promise.resolve({
|
|
173
|
+
content: `Client '${targetClientId}' does not support host_cu. Run \`assistant clients list --capability host_cu\` to see available clients.`,
|
|
174
|
+
isError: true,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Same-user enforcement: targeted CU dispatch must be owned by the
|
|
179
|
+
// same actor on both sides. This is the authoritative gate — the
|
|
180
|
+
// dispatch layer (conversation-surfaces.ts) skips its own check
|
|
181
|
+
// and relies on the proxy.
|
|
182
|
+
const rejection = enforceSameActorOrErrorResult({
|
|
183
|
+
hub: assistantEventHub,
|
|
184
|
+
sourceActorPrincipalId,
|
|
185
|
+
targetClientId,
|
|
186
|
+
op: "host_cu",
|
|
187
|
+
});
|
|
188
|
+
if (rejection) return Promise.resolve(rejection);
|
|
189
|
+
}
|
|
190
|
+
|
|
147
191
|
const requestId = uuid();
|
|
148
192
|
|
|
149
193
|
return new Promise<ToolExecutionResult>((resolve, reject) => {
|
|
@@ -170,9 +214,7 @@ export class HostCuProxy {
|
|
|
170
214
|
type: "host_cu_cancel",
|
|
171
215
|
requestId,
|
|
172
216
|
conversationId,
|
|
173
|
-
...(targetClientId != null
|
|
174
|
-
? { targetClientId }
|
|
175
|
-
: {}),
|
|
217
|
+
...(targetClientId != null ? { targetClientId } : {}),
|
|
176
218
|
},
|
|
177
219
|
conversationId,
|
|
178
220
|
{ targetClientId },
|
|
@@ -193,6 +235,10 @@ export class HostCuProxy {
|
|
|
193
235
|
conversationId,
|
|
194
236
|
kind: "host_cu",
|
|
195
237
|
targetClientId,
|
|
238
|
+
targetActorPrincipalId:
|
|
239
|
+
targetClientId != null
|
|
240
|
+
? assistantEventHub.getActorPrincipalIdForClient(targetClientId)
|
|
241
|
+
: undefined,
|
|
196
242
|
rpcResolve: resolve,
|
|
197
243
|
rpcReject: reject,
|
|
198
244
|
timer,
|
|
@@ -4,6 +4,11 @@ import {
|
|
|
4
4
|
assistantEventHub,
|
|
5
5
|
broadcastMessage,
|
|
6
6
|
} from "../runtime/assistant-event-hub.js";
|
|
7
|
+
import {
|
|
8
|
+
ambiguousSameUserError,
|
|
9
|
+
enforceSameActorOrErrorResult,
|
|
10
|
+
pickSameUserAutoResolve,
|
|
11
|
+
} from "../runtime/auth/same-actor.js";
|
|
7
12
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
8
13
|
import { readImageBase64 } from "../tools/shared/filesystem/image-read.js";
|
|
9
14
|
import type { ToolExecutionResult } from "../tools/types.js";
|
|
@@ -68,15 +73,18 @@ export class HostFileProxy {
|
|
|
68
73
|
conversationId: string,
|
|
69
74
|
signal?: AbortSignal,
|
|
70
75
|
targetClientId?: string,
|
|
76
|
+
sourceActorPrincipalId?: string,
|
|
71
77
|
): Promise<ToolExecutionResult> {
|
|
72
78
|
if (signal?.aborted) {
|
|
73
79
|
return Promise.resolve({ content: "Aborted", isError: true });
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
// Resolve targetClientId: explicit → validate; single
|
|
77
|
-
// Callers may embed targetClientId in
|
|
78
|
-
// the
|
|
79
|
-
|
|
82
|
+
// Resolve targetClientId: explicit → validate; single same-user
|
|
83
|
+
// capable client → auto-resolve. Callers may embed targetClientId in
|
|
84
|
+
// the input object (tool handlers) or pass it as the 4th parameter
|
|
85
|
+
// (legacy). Prefer the explicit param; fall back to input field.
|
|
86
|
+
let resolvedTargetClientId: string | undefined =
|
|
87
|
+
targetClientId ?? input.targetClientId;
|
|
80
88
|
if (resolvedTargetClientId != null) {
|
|
81
89
|
const client = assistantEventHub.getClientById(resolvedTargetClientId);
|
|
82
90
|
if (!client) {
|
|
@@ -92,10 +100,32 @@ export class HostFileProxy {
|
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
102
|
} else {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
// Auto-resolve to the unique same-user client. Reject ambiguous
|
|
104
|
+
// (multi-machine) cases so a single targeted-style request cannot
|
|
105
|
+
// fan out across the user's machines.
|
|
106
|
+
const resolved = pickSameUserAutoResolve({
|
|
107
|
+
hub: assistantEventHub,
|
|
108
|
+
capability: "host_file",
|
|
109
|
+
sourceActorPrincipalId,
|
|
110
|
+
});
|
|
111
|
+
if (resolved.kind === "ambiguous") {
|
|
112
|
+
return Promise.resolve(ambiguousSameUserError("host_file"));
|
|
98
113
|
}
|
|
114
|
+
resolvedTargetClientId =
|
|
115
|
+
resolved.kind === "match" ? resolved.clientId : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Same-user check: targeted host_file requests must be bound to the same
|
|
119
|
+
// authenticated user identity that opened the target client's SSE stream.
|
|
120
|
+
// Prevents cross-user routing through actor token mis-targeting.
|
|
121
|
+
if (resolvedTargetClientId != null) {
|
|
122
|
+
const rejection = enforceSameActorOrErrorResult({
|
|
123
|
+
hub: assistantEventHub,
|
|
124
|
+
sourceActorPrincipalId,
|
|
125
|
+
targetClientId: resolvedTargetClientId,
|
|
126
|
+
op: "host_file",
|
|
127
|
+
});
|
|
128
|
+
if (rejection) return Promise.resolve(rejection);
|
|
99
129
|
}
|
|
100
130
|
|
|
101
131
|
const requestId = uuid();
|
|
@@ -150,6 +180,12 @@ export class HostFileProxy {
|
|
|
150
180
|
conversationId,
|
|
151
181
|
kind: "host_file",
|
|
152
182
|
targetClientId: resolvedTargetClientId,
|
|
183
|
+
targetActorPrincipalId:
|
|
184
|
+
resolvedTargetClientId != null
|
|
185
|
+
? assistantEventHub.getActorPrincipalIdForClient(
|
|
186
|
+
resolvedTargetClientId,
|
|
187
|
+
)
|
|
188
|
+
: undefined,
|
|
153
189
|
rpcResolve: resolve,
|
|
154
190
|
rpcReject: reject,
|
|
155
191
|
timer,
|