@vellumai/assistant 0.7.2 → 0.8.0
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 +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- 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 +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- 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 +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -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__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- 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 +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -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-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- 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 +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- 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 +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- 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__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -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 +60 -5
- 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__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- 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-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- 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 +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- 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/app-builder/SKILL.md +1 -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 +13 -5
- package/src/config/loader.ts +199 -27
- 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 +76 -12
- 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-lifecycle-auto-analyze.test.ts +32 -0
- 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 +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- 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 +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- 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 +26 -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 +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- 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 +66 -15
- 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 +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -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 +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- 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 +34 -51
- 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 +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- 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 +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -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/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -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 +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- 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 +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -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/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- 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 +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -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/prompts/templates/SOUL.md +13 -28
- 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 +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- 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/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -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/filing-routes.ts +2 -3
- 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/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- 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/include-graph.ts +35 -13
- 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/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- 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 +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- 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 +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { InterfaceId } from "../channels/types.js";
|
|
2
|
+
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
3
|
+
import type { DiskPressureStatus } from "./disk-pressure-guard.js";
|
|
4
|
+
import type { ConversationType } from "./message-types/shared.js";
|
|
5
|
+
import type { TrustContext } from "./trust-context.js";
|
|
6
|
+
|
|
7
|
+
export type DiskPressureCleanupReason = "local-owner" | "guardian";
|
|
8
|
+
|
|
9
|
+
export type DiskPressureBlockReason =
|
|
10
|
+
| "background"
|
|
11
|
+
| "trusted-contact"
|
|
12
|
+
| "non-guardian"
|
|
13
|
+
| "unknown-remote";
|
|
14
|
+
|
|
15
|
+
export type DiskPressureTurnPolicyDecision =
|
|
16
|
+
| { action: "allow-normal" }
|
|
17
|
+
| { action: "allow-cleanup-mode"; reason: DiskPressureCleanupReason }
|
|
18
|
+
| { action: "block"; reason: DiskPressureBlockReason };
|
|
19
|
+
|
|
20
|
+
export type DiskPressureTurnTrustClass =
|
|
21
|
+
| TrustContext["trustClass"]
|
|
22
|
+
| "non_guardian"
|
|
23
|
+
| "non-guardian"
|
|
24
|
+
| (string & {});
|
|
25
|
+
|
|
26
|
+
export interface DiskPressureTurnTrustContext {
|
|
27
|
+
sourceChannel?: TrustContext["sourceChannel"] | (string & {});
|
|
28
|
+
trustClass?: DiskPressureTurnTrustClass;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DiskPressureTurnMetadata {
|
|
32
|
+
conversationType?: ConversationType | (string & {}) | null;
|
|
33
|
+
conversationGroupId?: string | null;
|
|
34
|
+
conversationSource?: string | null;
|
|
35
|
+
callSite?: LLMCallSite | (string & {}) | null;
|
|
36
|
+
isInteractive?: boolean | null;
|
|
37
|
+
sourceChannel?: TrustContext["sourceChannel"] | (string & {}) | null;
|
|
38
|
+
sourceInterface?: InterfaceId | "vellum" | (string & {}) | null;
|
|
39
|
+
trustContext?: DiskPressureTurnTrustContext | null;
|
|
40
|
+
isDirectWake?: boolean | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const BACKGROUND_CONVERSATION_TYPES = new Set(["background", "scheduled"]);
|
|
44
|
+
const BACKGROUND_GROUP_IDS = new Set(["system:background", "system:scheduled"]);
|
|
45
|
+
const BACKGROUND_SOURCES = new Set([
|
|
46
|
+
"auto-analysis",
|
|
47
|
+
"background",
|
|
48
|
+
"compaction",
|
|
49
|
+
"direct",
|
|
50
|
+
"filing",
|
|
51
|
+
"heartbeat",
|
|
52
|
+
"memory",
|
|
53
|
+
"notification",
|
|
54
|
+
"proactive-artifact",
|
|
55
|
+
"reminder",
|
|
56
|
+
"schedule",
|
|
57
|
+
"task",
|
|
58
|
+
"update-bulletin",
|
|
59
|
+
]);
|
|
60
|
+
const LOCAL_OWNER_INTERFACES = new Set(["macos", "web", "vellum", "cli"]);
|
|
61
|
+
|
|
62
|
+
export function classifyDiskPressureTurnPolicy(
|
|
63
|
+
status: DiskPressureStatus,
|
|
64
|
+
metadata: DiskPressureTurnMetadata,
|
|
65
|
+
): DiskPressureTurnPolicyDecision {
|
|
66
|
+
if (!status.enabled || !status.locked || status.overrideActive) {
|
|
67
|
+
return { action: "allow-normal" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!status.effectivelyLocked) {
|
|
71
|
+
return { action: "allow-normal" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isBackgroundTurn(metadata)) {
|
|
75
|
+
return { action: "block", reason: "background" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const trustClass = metadata.trustContext?.trustClass;
|
|
79
|
+
if (trustClass === "guardian") {
|
|
80
|
+
return { action: "allow-cleanup-mode", reason: "guardian" };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (trustClass === "trusted_contact") {
|
|
84
|
+
return { action: "block", reason: "trusted-contact" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isNonGuardianTrustClass(trustClass)) {
|
|
88
|
+
return { action: "block", reason: "non-guardian" };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (trustClass === "unknown") {
|
|
92
|
+
return { action: "block", reason: "unknown-remote" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (trustClass !== undefined) {
|
|
96
|
+
return { action: "block", reason: "non-guardian" };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isLocalOwnerTurnWithoutTrust(metadata)) {
|
|
100
|
+
return { action: "allow-cleanup-mode", reason: "local-owner" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { action: "block", reason: "unknown-remote" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isBackgroundTurn(metadata: DiskPressureTurnMetadata): boolean {
|
|
107
|
+
if (isExplicitLocalOwnerCleanupTurn(metadata)) return false;
|
|
108
|
+
if (metadata.isDirectWake) return true;
|
|
109
|
+
if (metadata.callSite != null && metadata.callSite !== "mainAgent") {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
metadata.conversationType != null &&
|
|
114
|
+
BACKGROUND_CONVERSATION_TYPES.has(metadata.conversationType)
|
|
115
|
+
) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
metadata.conversationGroupId != null &&
|
|
120
|
+
BACKGROUND_GROUP_IDS.has(metadata.conversationGroupId)
|
|
121
|
+
) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return (
|
|
125
|
+
metadata.conversationSource != null &&
|
|
126
|
+
BACKGROUND_SOURCES.has(metadata.conversationSource)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isNonGuardianTrustClass(
|
|
131
|
+
trustClass: DiskPressureTurnTrustClass | undefined,
|
|
132
|
+
): boolean {
|
|
133
|
+
return trustClass === "non_guardian" || trustClass === "non-guardian";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function isLocalOwnerTurnWithoutTrust(
|
|
137
|
+
metadata: DiskPressureTurnMetadata,
|
|
138
|
+
): boolean {
|
|
139
|
+
if (metadata.trustContext != null) return false;
|
|
140
|
+
|
|
141
|
+
const channel = metadata.sourceChannel;
|
|
142
|
+
const sourceInterface = metadata.sourceInterface;
|
|
143
|
+
if (channel !== "vellum" || sourceInterface == null) return false;
|
|
144
|
+
return LOCAL_OWNER_INTERFACES.has(sourceInterface);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isExplicitLocalOwnerCleanupTurn(
|
|
148
|
+
metadata: DiskPressureTurnMetadata,
|
|
149
|
+
): boolean {
|
|
150
|
+
if (metadata.isDirectWake !== true) return false;
|
|
151
|
+
const sourceInterface = metadata.sourceInterface;
|
|
152
|
+
if (
|
|
153
|
+
metadata.sourceChannel !== "vellum" ||
|
|
154
|
+
sourceInterface == null ||
|
|
155
|
+
!LOCAL_OWNER_INTERFACES.has(sourceInterface)
|
|
156
|
+
) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return (
|
|
160
|
+
metadata.trustContext == null ||
|
|
161
|
+
metadata.trustContext.trustClass === "guardian"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -63,6 +63,20 @@ export interface HistoryToolCall {
|
|
|
63
63
|
approvalReason?: string;
|
|
64
64
|
/** Snapshot of the auto-approve threshold at execution time. */
|
|
65
65
|
riskThreshold?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Display-only regex ladder for the rule editor (narrowest → broadest).
|
|
68
|
+
* Persisted on tool_use blocks by `annotatePersistedAssistantMessage` so
|
|
69
|
+
* historical chips render the same ladder as live tool_result events.
|
|
70
|
+
*/
|
|
71
|
+
riskScopeOptions?: Array<{ pattern: string; label: string }>;
|
|
72
|
+
/** Minimatch save patterns for the rule editor (narrowest → broadest). */
|
|
73
|
+
riskAllowlistOptions?: Array<{
|
|
74
|
+
label: string;
|
|
75
|
+
description: string;
|
|
76
|
+
pattern: string;
|
|
77
|
+
}>;
|
|
78
|
+
/** Directory scope ladder for the rule editor. */
|
|
79
|
+
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
export interface HistorySurface {
|
|
@@ -129,7 +143,6 @@ export interface ConversationCreateOptions {
|
|
|
129
143
|
isInteractive?: boolean;
|
|
130
144
|
/** Slack-only non-persisted notice injected into the active model turn. */
|
|
131
145
|
slackRuntimeContextNotice?: string;
|
|
132
|
-
memoryScopeId?: string;
|
|
133
146
|
/** Channel command intent metadata (e.g. Telegram /start). */
|
|
134
147
|
commandIntent?: { type: string; payload?: string; languageCode?: string };
|
|
135
148
|
|
|
@@ -369,6 +382,18 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
|
|
|
369
382
|
entry.approvalReason = block._approvalReason;
|
|
370
383
|
if (typeof block._riskThreshold === "string")
|
|
371
384
|
entry.riskThreshold = block._riskThreshold;
|
|
385
|
+
// Read back the 3 risk-option arrays persisted by
|
|
386
|
+
// `annotatePersistedAssistantMessage`. Validate the array shape only
|
|
387
|
+
// — element shapes are best-effort (we trust our own writer).
|
|
388
|
+
if (Array.isArray(block._riskScopeOptions))
|
|
389
|
+
entry.riskScopeOptions =
|
|
390
|
+
block._riskScopeOptions as HistoryToolCall["riskScopeOptions"];
|
|
391
|
+
if (Array.isArray(block._riskAllowlistOptions))
|
|
392
|
+
entry.riskAllowlistOptions =
|
|
393
|
+
block._riskAllowlistOptions as HistoryToolCall["riskAllowlistOptions"];
|
|
394
|
+
if (Array.isArray(block._riskDirectoryScopeOptions))
|
|
395
|
+
entry.riskDirectoryScopeOptions =
|
|
396
|
+
block._riskDirectoryScopeOptions as HistoryToolCall["riskDirectoryScopeOptions"];
|
|
372
397
|
toolCalls.push(entry);
|
|
373
398
|
if (id) pendingToolUses.set(id, entry);
|
|
374
399
|
contentOrder.push(`tool:${toolCalls.length - 1}`);
|
|
@@ -430,7 +430,7 @@ async function listSkillsWithCatalog(): Promise<SlimSkillResponse[]> {
|
|
|
430
430
|
|
|
431
431
|
// ─── Filtered skill listing ──────────────────────────────────────────────────
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
interface SkillListFilter {
|
|
434
434
|
origin?: string;
|
|
435
435
|
kind?: string;
|
|
436
436
|
q?: string;
|
|
@@ -698,7 +698,6 @@ export async function getSkill(
|
|
|
698
698
|
// depends on `catalog-cache.ts`, which would otherwise be reachable via this
|
|
699
699
|
// handler module). Re-exported here so handlers can import it alongside
|
|
700
700
|
// the other skill handler exports.
|
|
701
|
-
export type { SkillFileEntry } from "../../skills/catalog-files.js";
|
|
702
701
|
|
|
703
702
|
/**
|
|
704
703
|
* Returns true if `filePath` is a symlink whose resolved real path escapes
|
|
@@ -1428,7 +1427,7 @@ export async function inspectSkill(
|
|
|
1428
1427
|
}
|
|
1429
1428
|
}
|
|
1430
1429
|
|
|
1431
|
-
|
|
1430
|
+
interface DraftResult {
|
|
1432
1431
|
success: boolean;
|
|
1433
1432
|
draft?: {
|
|
1434
1433
|
skillId: string;
|
|
@@ -1583,7 +1582,7 @@ export async function draftSkill(params: {
|
|
|
1583
1582
|
}
|
|
1584
1583
|
}
|
|
1585
1584
|
|
|
1586
|
-
|
|
1585
|
+
interface CreateSkillParams {
|
|
1587
1586
|
skillId: string;
|
|
1588
1587
|
name: string;
|
|
1589
1588
|
description: string;
|
|
@@ -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) {
|
|
@@ -147,11 +166,17 @@ export class HostBashProxy {
|
|
|
147
166
|
pendingInteractions.register(requestId, {
|
|
148
167
|
conversationId,
|
|
149
168
|
kind: "host_bash",
|
|
150
|
-
rpcResolve: resolve,
|
|
169
|
+
rpcResolve: resolve as (v: unknown) => void,
|
|
151
170
|
rpcReject: reject,
|
|
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 },
|