@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
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkDiskPressureBackgroundGate,
|
|
3
|
+
diskPressureBackgroundSkipLogFields,
|
|
4
|
+
shouldLogDiskPressureBackgroundSkip,
|
|
5
|
+
} from "../daemon/disk-pressure-background-gate.js";
|
|
1
6
|
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
2
7
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
3
8
|
import { getConversation } from "../memory/conversation-crud.js";
|
|
@@ -129,7 +134,7 @@ export function startScheduler(
|
|
|
129
134
|
};
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
async function runScheduleOnce(
|
|
137
|
+
export async function runScheduleOnce(
|
|
133
138
|
processMessage: ScheduleMessageProcessor,
|
|
134
139
|
notifyScheduleOneShot: ScheduleNotifyModeNotifier,
|
|
135
140
|
watcherNotifier?: WatcherNotifier,
|
|
@@ -139,6 +144,20 @@ async function runScheduleOnce(
|
|
|
139
144
|
const now = Date.now();
|
|
140
145
|
let processed = 0;
|
|
141
146
|
|
|
147
|
+
const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
|
|
148
|
+
if (diskPressureGate.action === "skip") {
|
|
149
|
+
if (shouldLogDiskPressureBackgroundSkip("scheduler")) {
|
|
150
|
+
log.warn(
|
|
151
|
+
{
|
|
152
|
+
source: "schedule",
|
|
153
|
+
...diskPressureBackgroundSkipLogFields(diskPressureGate),
|
|
154
|
+
},
|
|
155
|
+
"Schedule tick skipped during disk pressure cleanup mode",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
// ── Schedules (recurring cron/RRULE + one-shot) ─────────────────────
|
|
143
162
|
const jobs = claimDueSchedules(now);
|
|
144
163
|
for (const job of jobs) {
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
21
23
|
|
|
22
24
|
import type {
|
|
23
25
|
SecureKeyBackend,
|
|
@@ -28,6 +30,7 @@ import { getIsContainerized } from "../config/env-registry.js";
|
|
|
28
30
|
import type { CesClient } from "../credential-execution/client.js";
|
|
29
31
|
import { getAnyProviderEnvVar } from "../providers/provider-env-vars.js";
|
|
30
32
|
import { getLogger } from "../util/logger.js";
|
|
33
|
+
import { getProtectedDir } from "../util/platform.js";
|
|
31
34
|
import { createCesCredentialBackend } from "./ces-credential-client.js";
|
|
32
35
|
import { CesRpcCredentialBackend } from "./ces-rpc-credential-backend.js";
|
|
33
36
|
import type {
|
|
@@ -584,6 +587,58 @@ export function getActiveBackendName(): string {
|
|
|
584
587
|
return _resolvedBackend?.name ?? "none";
|
|
585
588
|
}
|
|
586
589
|
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
// Backend introspection
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
|
|
594
|
+
export type BackendInfo =
|
|
595
|
+
| {
|
|
596
|
+
backend: "encrypted-store";
|
|
597
|
+
storePath: string;
|
|
598
|
+
storeKeyPath: string;
|
|
599
|
+
storeExists: boolean;
|
|
600
|
+
storeKeyExists: boolean;
|
|
601
|
+
}
|
|
602
|
+
| { backend: "ces-rpc"; ready: boolean }
|
|
603
|
+
| { backend: "ces-http"; url: string }
|
|
604
|
+
| { backend: "none" };
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Resolve the active credential backend (triggering resolution if not yet
|
|
608
|
+
* done) and return introspection details specific to that backend.
|
|
609
|
+
*
|
|
610
|
+
* Useful for `credentials status` — shows which store this process is talking
|
|
611
|
+
* to, so path/socket mismatches between the CLI and daemon are immediately
|
|
612
|
+
* visible.
|
|
613
|
+
*/
|
|
614
|
+
export function getActiveBackendInfoAsync(): Promise<BackendInfo> {
|
|
615
|
+
return withCredentialTimeout(async () => {
|
|
616
|
+
const backend = await resolveBackendAsync();
|
|
617
|
+
if (backend.name === "encrypted-store") {
|
|
618
|
+
const protectedDir = getProtectedDir();
|
|
619
|
+
const storePath = join(protectedDir, "keys.enc");
|
|
620
|
+
const storeKeyPath = join(protectedDir, "store.key");
|
|
621
|
+
return {
|
|
622
|
+
backend: "encrypted-store" as const,
|
|
623
|
+
storePath,
|
|
624
|
+
storeKeyPath,
|
|
625
|
+
storeExists: existsSync(storePath),
|
|
626
|
+
storeKeyExists: existsSync(storeKeyPath),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (backend.name === "ces-rpc") {
|
|
630
|
+
return { backend: "ces-rpc" as const, ready: backend.isAvailable() };
|
|
631
|
+
}
|
|
632
|
+
if (backend.name === "ces-http") {
|
|
633
|
+
return {
|
|
634
|
+
backend: "ces-http" as const,
|
|
635
|
+
url: process.env.CES_CREDENTIAL_URL ?? "",
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return { backend: "none" as const };
|
|
639
|
+
}, { backend: "none" as const });
|
|
640
|
+
}
|
|
641
|
+
|
|
587
642
|
/** @internal Test-only: reset the cached backends so they're re-created. */
|
|
588
643
|
export function _resetBackend(): void {
|
|
589
644
|
_cesClient = undefined;
|
|
@@ -61,14 +61,10 @@ type IncludeValidationResult =
|
|
|
61
61
|
| IncludeValidationError
|
|
62
62
|
| IncludeValidationCycleError;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
* Validate the include graph starting from the given root skill ID.
|
|
66
|
-
* Uses three-state DFS (unseen/visiting/done) to detect both missing children
|
|
67
|
-
* and cycles. Returns the first error encountered in DFS order.
|
|
68
|
-
*/
|
|
69
|
-
export function validateIncludes(
|
|
64
|
+
function validateIncludeGraph(
|
|
70
65
|
rootId: string,
|
|
71
66
|
catalogIndex: Map<string, SkillSummary>,
|
|
67
|
+
options: { failOnMissing: boolean },
|
|
72
68
|
): IncludeValidationResult {
|
|
73
69
|
const visited: string[] = [];
|
|
74
70
|
type State = "unseen" | "visiting" | "done";
|
|
@@ -97,13 +93,16 @@ export function validateIncludes(
|
|
|
97
93
|
if (skill?.includes) {
|
|
98
94
|
for (const childId of skill.includes) {
|
|
99
95
|
if (!catalogIndex.has(childId)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
if (options.failOnMissing) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: "missing",
|
|
100
|
+
missingChildId: childId,
|
|
101
|
+
parentId: id,
|
|
102
|
+
path: [...ancestry],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
107
106
|
}
|
|
108
107
|
const childError = dfs(childId);
|
|
109
108
|
if (childError) return childError;
|
|
@@ -120,6 +119,29 @@ export function validateIncludes(
|
|
|
120
119
|
return { ok: true, visited };
|
|
121
120
|
}
|
|
122
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Validate the include graph starting from the given root skill ID.
|
|
124
|
+
* Uses three-state DFS (unseen/visiting/done) to detect both missing children
|
|
125
|
+
* and cycles. Returns the first error encountered in DFS order.
|
|
126
|
+
*/
|
|
127
|
+
export function validateIncludes(
|
|
128
|
+
rootId: string,
|
|
129
|
+
catalogIndex: Map<string, SkillSummary>,
|
|
130
|
+
): IncludeValidationResult {
|
|
131
|
+
return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate only cycle safety for a graph that may intentionally have missing
|
|
136
|
+
* advisory includes. Missing child IDs are skipped during traversal.
|
|
137
|
+
*/
|
|
138
|
+
export function validateIncludeCycles(
|
|
139
|
+
rootId: string,
|
|
140
|
+
catalogIndex: Map<string, SkillSummary>,
|
|
141
|
+
): IncludeValidationResult {
|
|
142
|
+
return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: false });
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
/**
|
|
124
146
|
* Recursively traverse the include graph starting from the given root skill ID.
|
|
125
147
|
* Returns all visited skill IDs in DFS pre-order.
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
type RemoteSkillProvider = "clawhub" | "skillssh";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
| "safe"
|
|
5
|
-
| "low"
|
|
6
|
-
| "medium"
|
|
7
|
-
| "high"
|
|
8
|
-
| "critical"
|
|
9
|
-
| "unknown";
|
|
3
|
+
type SkillsShRisk = "safe" | "low" | "medium" | "high" | "critical" | "unknown";
|
|
10
4
|
export type SkillsShRiskThreshold = Exclude<SkillsShRisk, "unknown">;
|
|
11
5
|
|
|
12
6
|
export interface RemoteSkillPolicy {
|
|
@@ -50,17 +44,17 @@ interface SkillsShRemoteSkillCandidate extends RemoteSkillCandidateBase {
|
|
|
50
44
|
audit?: SkillsShAuditState | null;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
|
|
47
|
+
type RemoteSkillCandidate =
|
|
54
48
|
| ClawhubRemoteSkillCandidate
|
|
55
49
|
| SkillsShRemoteSkillCandidate;
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
type RemoteSkillDenyReason =
|
|
58
52
|
| "clawhub_suspicious"
|
|
59
53
|
| "clawhub_malware_blocked"
|
|
60
54
|
| "clawhub_moderation_missing"
|
|
61
55
|
| "skillssh_risk_exceeds_threshold";
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
type RemoteSkillInstallDecision =
|
|
64
58
|
| { ok: true }
|
|
65
59
|
| { ok: false; reason: RemoteSkillDenyReason };
|
|
66
60
|
|
package/src/subagent/index.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
export { mergeSkillIds } from "./manager.js";
|
|
2
|
-
export type {
|
|
3
|
-
SubagentConfig,
|
|
4
|
-
SubagentRole,
|
|
5
|
-
SubagentRoleConfig,
|
|
6
|
-
SubagentState,
|
|
7
|
-
SubagentStatus,
|
|
8
|
-
} from "./types.js";
|
|
2
|
+
export type { SubagentRole } from "./types.js";
|
|
9
3
|
export { SUBAGENT_ROLE_REGISTRY, TERMINAL_STATUSES } from "./types.js";
|
|
10
4
|
|
|
11
5
|
import { SubagentManager } from "./manager.js";
|
package/src/subagent/manager.ts
CHANGED
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
import { v4 as uuid } from "uuid";
|
|
12
12
|
|
|
13
13
|
import { getConfig } from "../config/loader.js";
|
|
14
|
-
import {
|
|
15
|
-
Conversation,
|
|
16
|
-
type ConversationMemoryPolicy,
|
|
17
|
-
} from "../daemon/conversation.js";
|
|
14
|
+
import { Conversation } from "../daemon/conversation.js";
|
|
18
15
|
import { findConversation } from "../daemon/conversation-store.js";
|
|
19
16
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
20
17
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
@@ -231,16 +228,6 @@ export class SubagentManager {
|
|
|
231
228
|
const maxTokens = appConfig.llm.default.maxTokens;
|
|
232
229
|
const workingDir = getSandboxWorkingDir();
|
|
233
230
|
|
|
234
|
-
const memoryPolicy: ConversationMemoryPolicy = isFork
|
|
235
|
-
? {
|
|
236
|
-
scopeId: "default",
|
|
237
|
-
includeDefaultFallback: false,
|
|
238
|
-
}
|
|
239
|
-
: {
|
|
240
|
-
scopeId: `subagent:${subagentId}`,
|
|
241
|
-
includeDefaultFallback: true,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
231
|
// ── Initialise state ────────────────────────────────────────────
|
|
245
232
|
const now = Date.now();
|
|
246
233
|
// For forks, default sendResultToUser to false (silent) unless explicitly true.
|
|
@@ -289,7 +276,6 @@ export class SubagentManager {
|
|
|
289
276
|
maxTokens,
|
|
290
277
|
wrappedSendToClient,
|
|
291
278
|
workingDir,
|
|
292
|
-
memoryPolicy,
|
|
293
279
|
undefined, // sharedCesClient
|
|
294
280
|
undefined, // speedOverride
|
|
295
281
|
"5m", // cacheTtl — subagents run tight tool-use loops, 5m is always hot
|
package/src/tasks/task-runner.ts
CHANGED
package/src/tasks/task-store.ts
CHANGED
|
@@ -27,7 +27,6 @@ export interface TaskRun {
|
|
|
27
27
|
finishedAt: number | null;
|
|
28
28
|
error: string | null;
|
|
29
29
|
principalId: string | null;
|
|
30
|
-
memoryScopeId: string | null;
|
|
31
30
|
createdAt: number;
|
|
32
31
|
}
|
|
33
32
|
|
|
@@ -109,7 +108,6 @@ export function createTaskRun(taskId: string): TaskRun {
|
|
|
109
108
|
finishedAt: null,
|
|
110
109
|
error: null,
|
|
111
110
|
principalId: null,
|
|
112
|
-
memoryScopeId: null,
|
|
113
111
|
createdAt: now,
|
|
114
112
|
};
|
|
115
113
|
db.insert(taskRuns).values(run).run();
|
|
@@ -125,7 +123,6 @@ export function updateTaskRun(
|
|
|
125
123
|
| "conversationId"
|
|
126
124
|
| "error"
|
|
127
125
|
| "principalId"
|
|
128
|
-
| "memoryScopeId"
|
|
129
126
|
| "startedAt"
|
|
130
127
|
| "finishedAt"
|
|
131
128
|
>
|
|
@@ -19,7 +19,7 @@ export interface BackgroundTool {
|
|
|
19
19
|
command: string;
|
|
20
20
|
startedAt: number;
|
|
21
21
|
/** Kills the process (bash) or aborts the proxy (host_bash). */
|
|
22
|
-
cancel: () => void;
|
|
22
|
+
cancel: (reason?: string) => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Maximum number of concurrent background tools allowed. */
|
|
@@ -61,16 +61,30 @@ export function listBackgroundTools(conversationId?: string): BackgroundTool[] {
|
|
|
61
61
|
* Cancels a background tool by ID: calls `tool.cancel()`, removes the entry,
|
|
62
62
|
* and returns `true`. Returns `false` if the ID is not found.
|
|
63
63
|
*/
|
|
64
|
-
export function cancelBackgroundTool(id: string): boolean {
|
|
64
|
+
export function cancelBackgroundTool(id: string, reason?: string): boolean {
|
|
65
65
|
const tool = registry.get(id);
|
|
66
66
|
if (!tool) {
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
|
-
tool.cancel();
|
|
69
|
+
tool.cancel(reason);
|
|
70
70
|
registry.delete(id);
|
|
71
71
|
return true;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export function cancelBackgroundTools(
|
|
75
|
+
shouldCancel: (tool: BackgroundTool) => boolean,
|
|
76
|
+
reason?: string,
|
|
77
|
+
): BackgroundTool[] {
|
|
78
|
+
const cancelled: BackgroundTool[] = [];
|
|
79
|
+
for (const tool of Array.from(registry.values())) {
|
|
80
|
+
if (!shouldCancel(tool)) continue;
|
|
81
|
+
tool.cancel(reason);
|
|
82
|
+
registry.delete(tool.id);
|
|
83
|
+
cancelled.push(tool);
|
|
84
|
+
}
|
|
85
|
+
return cancelled;
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
/**
|
|
75
89
|
* Generates a short prefixed ID for a background tool.
|
|
76
90
|
* Format: `bg-<8 hex chars>` (e.g. `bg-a1b2c3d4`).
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
saveDocument,
|
|
5
|
+
updateDocumentContent,
|
|
6
|
+
} from "../../documents/document-store.js";
|
|
3
7
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
4
8
|
|
|
5
9
|
// ── Exported execute functions ──────────────────────────────────────
|
|
@@ -12,6 +16,20 @@ export function executeDocumentCreate(
|
|
|
12
16
|
const initialContent = (input.initial_content as string | undefined) || "";
|
|
13
17
|
const surfaceId = `doc-${randomUUID()}`;
|
|
14
18
|
|
|
19
|
+
// Persist the document so any client (web or macOS) can fetch it via
|
|
20
|
+
// GET /v1/documents/:id. The macOS client may later update the row
|
|
21
|
+
// via document_save; ON CONFLICT DO UPDATE handles that.
|
|
22
|
+
const wordCount = initialContent
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.filter((w) => w.length > 0).length;
|
|
25
|
+
saveDocument({
|
|
26
|
+
surfaceId,
|
|
27
|
+
conversationId: context.conversationId,
|
|
28
|
+
title,
|
|
29
|
+
content: initialContent,
|
|
30
|
+
wordCount,
|
|
31
|
+
});
|
|
32
|
+
|
|
15
33
|
// Send document_editor_show message to open the built-in RTE
|
|
16
34
|
if (context.sendToClient) {
|
|
17
35
|
context.sendToClient({
|
|
@@ -67,6 +85,8 @@ export function executeDocumentUpdate(
|
|
|
67
85
|
const content = input.content as string;
|
|
68
86
|
const mode = (input.mode as string | undefined) || "append";
|
|
69
87
|
|
|
88
|
+
updateDocumentContent(surfaceId, content, mode);
|
|
89
|
+
|
|
70
90
|
// Send document_editor_update message to update the built-in RTE
|
|
71
91
|
if (context.sendToClient) {
|
|
72
92
|
context.sendToClient({
|
package/src/tools/executor.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { applyEdit } from "./shared/filesystem/edit-engine.js";
|
|
|
27
27
|
import { sandboxPolicy } from "./shared/filesystem/path-policy.js";
|
|
28
28
|
import { MAX_FILE_SIZE_BYTES } from "./shared/filesystem/size-guard.js";
|
|
29
29
|
import { ToolApprovalHandler } from "./tool-approval-handler.js";
|
|
30
|
+
import { resolveToolNameAlias } from "./tool-name-aliases.js";
|
|
30
31
|
import type {
|
|
31
32
|
ToolContext,
|
|
32
33
|
ToolExecutionResult,
|
|
@@ -76,7 +77,12 @@ export class ToolExecutor {
|
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
const middlewares = getMiddlewaresFor("toolExecute");
|
|
79
|
-
const
|
|
80
|
+
const executionName = resolveToolNameAlias(name, context.allowedToolNames);
|
|
81
|
+
const pipelineArgs: ToolExecuteArgs = {
|
|
82
|
+
name: executionName,
|
|
83
|
+
input,
|
|
84
|
+
context,
|
|
85
|
+
};
|
|
80
86
|
|
|
81
87
|
// No pipeline-level timeout: `executeInternal` already wraps the real
|
|
82
88
|
// tool invocation in `executeWithTimeout`, which is the sole enforcer
|
|
@@ -107,6 +113,11 @@ export class ToolExecutor {
|
|
|
107
113
|
riskLevel: string;
|
|
108
114
|
riskReason: string;
|
|
109
115
|
riskScopeOptions: Array<{ pattern: string; label: string }>;
|
|
116
|
+
riskAllowlistOptions?: Array<{
|
|
117
|
+
label: string;
|
|
118
|
+
description: string;
|
|
119
|
+
pattern: string;
|
|
120
|
+
}>;
|
|
110
121
|
riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
111
122
|
isContainerized?: boolean;
|
|
112
123
|
}
|
|
@@ -205,6 +216,7 @@ export class ToolExecutor {
|
|
|
205
216
|
riskLevel: permRiskMeta?.riskLevel,
|
|
206
217
|
riskReason: permRiskMeta?.riskReason,
|
|
207
218
|
riskScopeOptions: permRiskMeta?.riskScopeOptions,
|
|
219
|
+
riskAllowlistOptions: permRiskMeta?.riskAllowlistOptions,
|
|
208
220
|
riskDirectoryScopeOptions: permRiskMeta?.riskDirectoryScopeOptions,
|
|
209
221
|
isContainerized: permRiskMeta?.isContainerized,
|
|
210
222
|
matchedTrustRuleId: permMatchedTrustRuleId,
|
|
@@ -422,12 +434,16 @@ export class ToolExecutor {
|
|
|
422
434
|
riskLevel: permRiskMeta.riskLevel,
|
|
423
435
|
riskReason: permRiskMeta.riskReason,
|
|
424
436
|
riskScopeOptions: permRiskMeta.riskScopeOptions,
|
|
437
|
+
riskAllowlistOptions: permRiskMeta.riskAllowlistOptions,
|
|
425
438
|
riskDirectoryScopeOptions: permRiskMeta.riskDirectoryScopeOptions,
|
|
426
439
|
isContainerized: permRiskMeta.isContainerized,
|
|
427
440
|
};
|
|
428
441
|
}
|
|
429
442
|
if (permMatchedTrustRuleId) {
|
|
430
|
-
execResult = {
|
|
443
|
+
execResult = {
|
|
444
|
+
...execResult,
|
|
445
|
+
matchedTrustRuleId: permMatchedTrustRuleId,
|
|
446
|
+
};
|
|
431
447
|
}
|
|
432
448
|
if (permApprovalMode) {
|
|
433
449
|
execResult = { ...execResult, approvalMode: permApprovalMode };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import type { ToolContext } from "../types.js";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Singleton mocks — must precede the tool import so bun's module mock applies.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
let mockProxyAvailable = false;
|
|
13
|
+
|
|
14
|
+
mock.module("../../daemon/host-file-proxy.js", () => ({
|
|
15
|
+
HostFileProxy: {
|
|
16
|
+
get instance() {
|
|
17
|
+
return {
|
|
18
|
+
isAvailable: () => mockProxyAvailable,
|
|
19
|
+
request: () => Promise.resolve({ content: "ok", isError: false }),
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
26
|
+
assistantEventHub: {
|
|
27
|
+
listClientsByCapability: () => [],
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const { hostFileEditTool } = await import("./edit.js");
|
|
32
|
+
|
|
33
|
+
const testDirs: string[] = [];
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
mockProxyAvailable = false;
|
|
37
|
+
for (const dir of testDirs.splice(0)) {
|
|
38
|
+
rmSync(dir, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function makeTempDir(): string {
|
|
43
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "host-edit-test-")));
|
|
44
|
+
testDirs.push(dir);
|
|
45
|
+
return dir;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeContext(
|
|
49
|
+
workingDir: string,
|
|
50
|
+
transportInterface: ToolContext["transportInterface"],
|
|
51
|
+
): ToolContext {
|
|
52
|
+
return {
|
|
53
|
+
workingDir,
|
|
54
|
+
conversationId: "test-conv",
|
|
55
|
+
trustClass: "guardian",
|
|
56
|
+
transportInterface,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("host_file_edit cross-client guards", () => {
|
|
61
|
+
test("returns 'no client' error on web transport when proxy unavailable and no targetClientId", async () => {
|
|
62
|
+
const workingDir = makeTempDir();
|
|
63
|
+
const result = await hostFileEditTool.execute(
|
|
64
|
+
{
|
|
65
|
+
path: "/some/host/path.txt",
|
|
66
|
+
old_string: "foo",
|
|
67
|
+
new_string: "bar",
|
|
68
|
+
},
|
|
69
|
+
makeContext(workingDir, "web"),
|
|
70
|
+
);
|
|
71
|
+
expect(result.isError).toBe(true);
|
|
72
|
+
expect(result.content).toContain(
|
|
73
|
+
"no client with host_file capability is connected",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
78
|
+
const workingDir = makeTempDir();
|
|
79
|
+
const result = await hostFileEditTool.execute(
|
|
80
|
+
{
|
|
81
|
+
path: "/some/host/path.txt",
|
|
82
|
+
old_string: "foo",
|
|
83
|
+
new_string: "bar",
|
|
84
|
+
target_client_id: "abc-123",
|
|
85
|
+
},
|
|
86
|
+
makeContext(workingDir, "web"),
|
|
87
|
+
);
|
|
88
|
+
expect(result.isError).toBe(true);
|
|
89
|
+
expect(result.content).toContain(
|
|
90
|
+
'target client "abc-123" is no longer connected',
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("falls through to local fs on macos transport when proxy unavailable", async () => {
|
|
95
|
+
const workingDir = makeTempDir();
|
|
96
|
+
const result = await hostFileEditTool.execute(
|
|
97
|
+
{
|
|
98
|
+
path: "/nonexistent/x.txt",
|
|
99
|
+
old_string: "foo",
|
|
100
|
+
new_string: "bar",
|
|
101
|
+
},
|
|
102
|
+
makeContext(workingDir, "macos"),
|
|
103
|
+
);
|
|
104
|
+
// Proves the guard did NOT fire on macOS — instead we got the
|
|
105
|
+
// local FileSystemOps error path (file not found / IO error).
|
|
106
|
+
expect(result.isError).toBe(true);
|
|
107
|
+
expect(result.content.toLowerCase()).toMatch(/not found|enoent|editing/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("does NOT reject on macos transport with a stale target_client_id when proxy unavailable (regression: P2 fix)", async () => {
|
|
111
|
+
const workingDir = makeTempDir();
|
|
112
|
+
const result = await hostFileEditTool.execute(
|
|
113
|
+
{
|
|
114
|
+
path: "/nonexistent/x.txt",
|
|
115
|
+
old_string: "foo",
|
|
116
|
+
new_string: "bar",
|
|
117
|
+
target_client_id: "stale-mac",
|
|
118
|
+
},
|
|
119
|
+
makeContext(workingDir, "macos"),
|
|
120
|
+
);
|
|
121
|
+
// The disconnected-target guard is scoped to non-host-proxy transports
|
|
122
|
+
// (!supportsHostProxy). On macos, a stale target_client_id auto-filled
|
|
123
|
+
// from a prior cross-client turn must be silently ignored and the call
|
|
124
|
+
// must fall through to local FileSystemOps, NOT reject with "target
|
|
125
|
+
// client ... is no longer connected".
|
|
126
|
+
expect(result.isError).toBe(true);
|
|
127
|
+
expect(result.content).not.toContain("is no longer connected");
|
|
128
|
+
expect(result.content.toLowerCase()).toMatch(/not found|enoent|editing/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("rejects when target_client_id is set but transport metadata is missing (legacy/backwards-compat path)", async () => {
|
|
132
|
+
const workingDir = makeTempDir();
|
|
133
|
+
const result = await hostFileEditTool.execute(
|
|
134
|
+
{
|
|
135
|
+
path: "/some/host/path.txt",
|
|
136
|
+
old_string: "foo",
|
|
137
|
+
new_string: "bar",
|
|
138
|
+
target_client_id: "abc-123",
|
|
139
|
+
},
|
|
140
|
+
// transportInterface intentionally undefined (legacy callers).
|
|
141
|
+
makeContext(workingDir, undefined),
|
|
142
|
+
);
|
|
143
|
+
// Without transport metadata, falling through to local fs would
|
|
144
|
+
// silently target the daemon container. The guard fires for undefined
|
|
145
|
+
// transport AND non-host-proxy transports — only macos turns skip it.
|
|
146
|
+
expect(result.isError).toBe(true);
|
|
147
|
+
expect(result.content).toContain(
|
|
148
|
+
'target client "abc-123" is no longer connected',
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -92,7 +92,8 @@ class HostFileEditTool implements Tool {
|
|
|
92
92
|
const replaceAll = input.replace_all === true;
|
|
93
93
|
|
|
94
94
|
const targetClientId =
|
|
95
|
-
typeof input.target_client_id === "string" &&
|
|
95
|
+
typeof input.target_client_id === "string" &&
|
|
96
|
+
input.target_client_id !== ""
|
|
96
97
|
? input.target_client_id
|
|
97
98
|
: undefined;
|
|
98
99
|
|
|
@@ -109,6 +110,45 @@ class HostFileEditTool implements Tool {
|
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
// Guard: non-host-proxy interfaces with no capable clients connected.
|
|
114
|
+
// Without this guard, the request would fall through to local
|
|
115
|
+
// FileSystemOps below and read the daemon container's filesystem
|
|
116
|
+
// instead of the user's host machine.
|
|
117
|
+
if (
|
|
118
|
+
targetClientId == null &&
|
|
119
|
+
transportInterface != null &&
|
|
120
|
+
!supportsHostProxy(transportInterface) &&
|
|
121
|
+
!HostFileProxy.instance.isAvailable()
|
|
122
|
+
) {
|
|
123
|
+
return {
|
|
124
|
+
content:
|
|
125
|
+
"Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
|
|
126
|
+
isError: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Guard: explicit targetClientId provided but proxy is unavailable.
|
|
131
|
+
// Fires on non-host-proxy transports (web, ios) AND on legacy callers
|
|
132
|
+
// without transport metadata, where falling through to local fs would
|
|
133
|
+
// silently target the daemon container's filesystem instead of the
|
|
134
|
+
// intended host client. Skips only when transport is explicitly
|
|
135
|
+
// host-proxy-capable (macos), where local-fs fallback IS the intended
|
|
136
|
+
// offline behavior — a stale target_client_id auto-filled from a prior
|
|
137
|
+
// cross-client turn is silently ignored on those turns.
|
|
138
|
+
// Note: this scoping deliberately differs from host_bash
|
|
139
|
+
// (host-shell.ts:239-247), which rejects unconditionally for any
|
|
140
|
+
// stale target_client_id regardless of transport.
|
|
141
|
+
if (
|
|
142
|
+
targetClientId != null &&
|
|
143
|
+
!HostFileProxy.instance.isAvailable() &&
|
|
144
|
+
(transportInterface == null || !supportsHostProxy(transportInterface))
|
|
145
|
+
) {
|
|
146
|
+
return {
|
|
147
|
+
content: `Error: target client "${targetClientId}" is no longer connected. The specified client may have disconnected since the tool was called. Run \`assistant clients list --capability host_file\` to see currently connected clients.`,
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
112
152
|
// Proxy to connected client for execution on the user's machine
|
|
113
153
|
// when a capable client is available (managed/cloud-hosted mode).
|
|
114
154
|
if (HostFileProxy.instance.isAvailable()) {
|
|
@@ -123,6 +163,8 @@ class HostFileEditTool implements Tool {
|
|
|
123
163
|
},
|
|
124
164
|
context.conversationId,
|
|
125
165
|
context.signal,
|
|
166
|
+
targetClientId,
|
|
167
|
+
context.sourceActorPrincipalId,
|
|
126
168
|
);
|
|
127
169
|
}
|
|
128
170
|
|