@vellumai/assistant 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +27 -3
- package/src/config/loader.ts +127 -8
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +75 -11
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +0 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -31,6 +31,15 @@ mock.module("../../daemon/host-transfer-proxy.js", () => ({
|
|
|
31
31
|
},
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
+
// Mirror read/write/edit test files: stub the event hub so the multi-client
|
|
35
|
+
// guard at line ~100 of transfer.ts is exercised against an isolated stub
|
|
36
|
+
// rather than the live process-wide singleton.
|
|
37
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
38
|
+
assistantEventHub: {
|
|
39
|
+
listClientsByCapability: () => [],
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
34
43
|
const { hostFileTransferTool } = await import("./transfer.js");
|
|
35
44
|
|
|
36
45
|
const testDirs: string[] = [];
|
|
@@ -50,8 +59,16 @@ function makeTempDir(): string {
|
|
|
50
59
|
return dir;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
function makeContext(
|
|
54
|
-
|
|
62
|
+
function makeContext(
|
|
63
|
+
workingDir: string,
|
|
64
|
+
transportInterface?: ToolContext["transportInterface"],
|
|
65
|
+
): ToolContext {
|
|
66
|
+
return {
|
|
67
|
+
workingDir,
|
|
68
|
+
conversationId: "test-conv",
|
|
69
|
+
trustClass: "guardian",
|
|
70
|
+
transportInterface,
|
|
71
|
+
};
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
// ---------------------------------------------------------------------------
|
|
@@ -269,3 +286,111 @@ describe("host_file_transfer managed mode", () => {
|
|
|
269
286
|
expect(toSandboxCalls.length).toBe(0);
|
|
270
287
|
});
|
|
271
288
|
});
|
|
289
|
+
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Cross-client guard tests
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
describe("host_file_transfer cross-client guards", () => {
|
|
295
|
+
test("returns 'no client' error on web transport when proxy unavailable and no targetClientId", async () => {
|
|
296
|
+
// mockProxyAvailable defaults to false.
|
|
297
|
+
const workingDir = makeTempDir();
|
|
298
|
+
const srcDir = makeTempDir();
|
|
299
|
+
const srcFile = join(srcDir, "source.txt");
|
|
300
|
+
writeFileSync(srcFile, "content");
|
|
301
|
+
|
|
302
|
+
const result = await hostFileTransferTool.execute(
|
|
303
|
+
{
|
|
304
|
+
source_path: srcFile,
|
|
305
|
+
dest_path: "out.txt",
|
|
306
|
+
direction: "to_sandbox",
|
|
307
|
+
},
|
|
308
|
+
makeContext(workingDir, "web"),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(result.isError).toBe(true);
|
|
312
|
+
expect(result.content).toContain(
|
|
313
|
+
"no client with host_file capability is connected",
|
|
314
|
+
);
|
|
315
|
+
expect(toSandboxCalls.length).toBe(0);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
319
|
+
const workingDir = makeTempDir();
|
|
320
|
+
const srcDir = makeTempDir();
|
|
321
|
+
const srcFile = join(srcDir, "source.txt");
|
|
322
|
+
writeFileSync(srcFile, "content");
|
|
323
|
+
|
|
324
|
+
const result = await hostFileTransferTool.execute(
|
|
325
|
+
{
|
|
326
|
+
source_path: srcFile,
|
|
327
|
+
dest_path: "out.txt",
|
|
328
|
+
direction: "to_sandbox",
|
|
329
|
+
target_client_id: "abc-123",
|
|
330
|
+
},
|
|
331
|
+
makeContext(workingDir, "web"),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
expect(result.isError).toBe(true);
|
|
335
|
+
expect(result.content).toContain(
|
|
336
|
+
'target client "abc-123" is no longer connected',
|
|
337
|
+
);
|
|
338
|
+
expect(toSandboxCalls.length).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("rejects when target_client_id is set but transport metadata is missing (legacy/backwards-compat path)", async () => {
|
|
342
|
+
const workingDir = makeTempDir();
|
|
343
|
+
const srcDir = makeTempDir();
|
|
344
|
+
const srcFile = join(srcDir, "source.txt");
|
|
345
|
+
writeFileSync(srcFile, "content");
|
|
346
|
+
const destFile = join(workingDir, "should-not-exist.txt");
|
|
347
|
+
|
|
348
|
+
const result = await hostFileTransferTool.execute(
|
|
349
|
+
{
|
|
350
|
+
source_path: srcFile,
|
|
351
|
+
dest_path: destFile,
|
|
352
|
+
direction: "to_sandbox",
|
|
353
|
+
target_client_id: "abc-123",
|
|
354
|
+
},
|
|
355
|
+
// transportInterface intentionally omitted (legacy callers).
|
|
356
|
+
makeContext(workingDir),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// Without transport metadata, falling through to executeLocal would
|
|
360
|
+
// silently target the daemon container. The guard fires for undefined
|
|
361
|
+
// transport AND non-host-proxy transports — only macos turns skip it.
|
|
362
|
+
expect(result.isError).toBe(true);
|
|
363
|
+
expect(result.content).toContain(
|
|
364
|
+
'target client "abc-123" is no longer connected',
|
|
365
|
+
);
|
|
366
|
+
expect(existsSync(destFile)).toBe(false);
|
|
367
|
+
expect(toSandboxCalls.length).toBe(0);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("does NOT reject on macos transport with a stale target_client_id when proxy unavailable (regression: Devin-flagged scope drift fix)", async () => {
|
|
371
|
+
const workingDir = makeTempDir();
|
|
372
|
+
const srcDir = makeTempDir();
|
|
373
|
+
const srcFile = join(srcDir, "source.txt");
|
|
374
|
+
writeFileSync(srcFile, "content");
|
|
375
|
+
const destFile = join(workingDir, "stale-target.txt");
|
|
376
|
+
|
|
377
|
+
const result = await hostFileTransferTool.execute(
|
|
378
|
+
{
|
|
379
|
+
source_path: srcFile,
|
|
380
|
+
dest_path: destFile,
|
|
381
|
+
direction: "to_sandbox",
|
|
382
|
+
target_client_id: "stale-mac",
|
|
383
|
+
},
|
|
384
|
+
makeContext(workingDir, "macos"),
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// The disconnected-target guard is scoped to non-host-proxy transports
|
|
388
|
+
// (!supportsHostProxy). On macos, a stale target_client_id auto-filled
|
|
389
|
+
// from a prior cross-client turn must be silently ignored and the local
|
|
390
|
+
// copy must succeed, NOT reject with "target client ... is no longer
|
|
391
|
+
// connected" or the older "target_client_id was specified but no host
|
|
392
|
+
// client is available" message.
|
|
393
|
+
expect(result.isError).toBe(false);
|
|
394
|
+
expect(existsSync(destFile)).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
@@ -93,7 +93,8 @@ class HostFileTransferTool implements Tool {
|
|
|
93
93
|
const overwrite = input.overwrite === true;
|
|
94
94
|
|
|
95
95
|
const targetClientId =
|
|
96
|
-
typeof input.target_client_id === "string" &&
|
|
96
|
+
typeof input.target_client_id === "string" &&
|
|
97
|
+
input.target_client_id !== ""
|
|
97
98
|
? input.target_client_id
|
|
98
99
|
: undefined;
|
|
99
100
|
|
|
@@ -103,7 +104,51 @@ class HostFileTransferTool implements Tool {
|
|
|
103
104
|
!supportsHostProxy(context.transportInterface) &&
|
|
104
105
|
assistantEventHub.listClientsByCapability("host_file").length > 1
|
|
105
106
|
) {
|
|
106
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
content: `Error: multiple clients support host_file. Specify which client to use with \`target_client_id\`. Run \`assistant clients list --capability host_file\` to see client IDs and labels.`,
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Guard: non-host-proxy interfaces with no capable clients connected.
|
|
114
|
+
// Without this guard, a web/ios turn whose host_file client has
|
|
115
|
+
// disconnected since projection would fall through to executeLocal
|
|
116
|
+
// below and act on the daemon container's filesystem instead of
|
|
117
|
+
// the user's host machine.
|
|
118
|
+
if (
|
|
119
|
+
targetClientId == null &&
|
|
120
|
+
context.transportInterface != null &&
|
|
121
|
+
!supportsHostProxy(context.transportInterface) &&
|
|
122
|
+
!HostTransferProxy.instance.isAvailable()
|
|
123
|
+
) {
|
|
124
|
+
return {
|
|
125
|
+
content:
|
|
126
|
+
"Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Guard: explicit targetClientId provided but proxy is unavailable.
|
|
132
|
+
// Fires on non-host-proxy transports (web, ios) AND on legacy callers
|
|
133
|
+
// without transport metadata, where falling through to executeLocal
|
|
134
|
+
// would silently target the daemon container's filesystem instead of
|
|
135
|
+
// the intended host client. Skips only when transport is explicitly
|
|
136
|
+
// host-proxy-capable (macos), where local-fs fallback IS the intended
|
|
137
|
+
// offline behavior — a stale target_client_id auto-filled from a prior
|
|
138
|
+
// cross-client turn is silently ignored on those turns.
|
|
139
|
+
// Note: this scoping deliberately differs from host_bash
|
|
140
|
+
// (host-shell.ts:239-247), which rejects unconditionally for any
|
|
141
|
+
// stale target_client_id regardless of transport.
|
|
142
|
+
if (
|
|
143
|
+
targetClientId != null &&
|
|
144
|
+
!HostTransferProxy.instance.isAvailable() &&
|
|
145
|
+
(context.transportInterface == null ||
|
|
146
|
+
!supportsHostProxy(context.transportInterface))
|
|
147
|
+
) {
|
|
148
|
+
return {
|
|
149
|
+
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.`,
|
|
150
|
+
isError: true,
|
|
151
|
+
};
|
|
107
152
|
}
|
|
108
153
|
|
|
109
154
|
// Validate that host-side paths are absolute.
|
|
@@ -136,7 +181,9 @@ class HostFileTransferTool implements Tool {
|
|
|
136
181
|
|
|
137
182
|
let resolvedDestPath = destPath;
|
|
138
183
|
if (direction === "to_sandbox") {
|
|
139
|
-
const pathCheck = sandboxPolicy(destPath, context.workingDir, {
|
|
184
|
+
const pathCheck = sandboxPolicy(destPath, context.workingDir, {
|
|
185
|
+
mustExist: false,
|
|
186
|
+
});
|
|
140
187
|
if (!pathCheck.ok) {
|
|
141
188
|
return {
|
|
142
189
|
content: `Invalid destination path: ${pathCheck.error}`,
|
|
@@ -158,6 +205,7 @@ class HostFileTransferTool implements Tool {
|
|
|
158
205
|
targetClientId,
|
|
159
206
|
},
|
|
160
207
|
context.signal,
|
|
208
|
+
context.sourceActorPrincipalId,
|
|
161
209
|
);
|
|
162
210
|
}
|
|
163
211
|
return HostTransferProxy.instance.requestToSandbox(
|
|
@@ -169,17 +217,14 @@ class HostFileTransferTool implements Tool {
|
|
|
169
217
|
targetClientId,
|
|
170
218
|
},
|
|
171
219
|
context.signal,
|
|
220
|
+
context.sourceActorPrincipalId,
|
|
172
221
|
);
|
|
173
222
|
}
|
|
174
223
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Local mode: direct filesystem copy.
|
|
224
|
+
// Local mode: direct filesystem copy. The non-host-proxy + stale
|
|
225
|
+
// target_client_id case is caught by the scoped guard at the top of
|
|
226
|
+
// execute(); on macos a stale target_client_id is silently ignored
|
|
227
|
+
// here, matching the read/write/edit pattern.
|
|
183
228
|
return this.executeLocal(resolvedSourcePath, resolvedDestPath, overwrite);
|
|
184
229
|
}
|
|
185
230
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { existsSync, 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 { hostFileWriteTool } = await import("./write.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-write-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_write 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 hostFileWriteTool.execute(
|
|
64
|
+
{ path: "/some/host/path.txt", content: "hello" },
|
|
65
|
+
makeContext(workingDir, "web"),
|
|
66
|
+
);
|
|
67
|
+
expect(result.isError).toBe(true);
|
|
68
|
+
expect(result.content).toContain(
|
|
69
|
+
"no client with host_file capability is connected",
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
|
|
74
|
+
const workingDir = makeTempDir();
|
|
75
|
+
const result = await hostFileWriteTool.execute(
|
|
76
|
+
{
|
|
77
|
+
path: "/some/host/path.txt",
|
|
78
|
+
content: "hello",
|
|
79
|
+
target_client_id: "abc-123",
|
|
80
|
+
},
|
|
81
|
+
makeContext(workingDir, "web"),
|
|
82
|
+
);
|
|
83
|
+
expect(result.isError).toBe(true);
|
|
84
|
+
expect(result.content).toContain(
|
|
85
|
+
'target client "abc-123" is no longer connected',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("falls through to local fs on macos transport when proxy unavailable", async () => {
|
|
90
|
+
const workingDir = makeTempDir();
|
|
91
|
+
const destFile = join(workingDir, "out.txt");
|
|
92
|
+
const result = await hostFileWriteTool.execute(
|
|
93
|
+
{ path: destFile, content: "hello" },
|
|
94
|
+
makeContext(workingDir, "macos"),
|
|
95
|
+
);
|
|
96
|
+
// Proves the guard did NOT fire on macOS — local write succeeded.
|
|
97
|
+
expect(result.isError).toBe(false);
|
|
98
|
+
expect(existsSync(destFile)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("does NOT reject on macos transport with a stale target_client_id when proxy unavailable (regression: P2 fix)", async () => {
|
|
102
|
+
const workingDir = makeTempDir();
|
|
103
|
+
const destFile = join(workingDir, "stale-target.txt");
|
|
104
|
+
const result = await hostFileWriteTool.execute(
|
|
105
|
+
{ path: destFile, content: "hello", target_client_id: "stale-mac" },
|
|
106
|
+
makeContext(workingDir, "macos"),
|
|
107
|
+
);
|
|
108
|
+
// The disconnected-target guard is scoped to non-host-proxy transports
|
|
109
|
+
// (!supportsHostProxy). On macos, a stale target_client_id auto-filled
|
|
110
|
+
// from a prior cross-client turn must be silently ignored and the local
|
|
111
|
+
// write must succeed, NOT reject with "target client ... is no longer
|
|
112
|
+
// connected".
|
|
113
|
+
expect(result.isError).toBe(false);
|
|
114
|
+
expect(existsSync(destFile)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("rejects when target_client_id is set but transport metadata is missing (legacy/backwards-compat path)", async () => {
|
|
118
|
+
const workingDir = makeTempDir();
|
|
119
|
+
const destFile = join(workingDir, "should-not-exist.txt");
|
|
120
|
+
const result = await hostFileWriteTool.execute(
|
|
121
|
+
{ path: destFile, content: "hello", target_client_id: "abc-123" },
|
|
122
|
+
// transportInterface intentionally undefined (legacy callers).
|
|
123
|
+
makeContext(workingDir, undefined),
|
|
124
|
+
);
|
|
125
|
+
// Without transport metadata, falling through to local fs would
|
|
126
|
+
// silently target the daemon container. The guard fires for undefined
|
|
127
|
+
// transport AND non-host-proxy transports — only macos turns skip it.
|
|
128
|
+
expect(result.isError).toBe(true);
|
|
129
|
+
expect(result.content).toContain(
|
|
130
|
+
'target client "abc-123" is no longer connected',
|
|
131
|
+
);
|
|
132
|
+
expect(existsSync(destFile)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -62,7 +62,8 @@ class HostFileWriteTool implements Tool {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const targetClientId =
|
|
65
|
-
typeof input.target_client_id === "string" &&
|
|
65
|
+
typeof input.target_client_id === "string" &&
|
|
66
|
+
input.target_client_id !== ""
|
|
66
67
|
? input.target_client_id
|
|
67
68
|
: undefined;
|
|
68
69
|
|
|
@@ -79,6 +80,45 @@ class HostFileWriteTool implements Tool {
|
|
|
79
80
|
};
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
// Guard: non-host-proxy interfaces with no capable clients connected.
|
|
84
|
+
// Without this guard, the request would fall through to local
|
|
85
|
+
// FileSystemOps below and read the daemon container's filesystem
|
|
86
|
+
// instead of the user's host machine.
|
|
87
|
+
if (
|
|
88
|
+
targetClientId == null &&
|
|
89
|
+
transportInterface != null &&
|
|
90
|
+
!supportsHostProxy(transportInterface) &&
|
|
91
|
+
!HostFileProxy.instance.isAvailable()
|
|
92
|
+
) {
|
|
93
|
+
return {
|
|
94
|
+
content:
|
|
95
|
+
"Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Guard: explicit targetClientId provided but proxy is unavailable.
|
|
101
|
+
// Fires on non-host-proxy transports (web, ios) AND on legacy callers
|
|
102
|
+
// without transport metadata, where falling through to local fs would
|
|
103
|
+
// silently target the daemon container's filesystem instead of the
|
|
104
|
+
// intended host client. Skips only when transport is explicitly
|
|
105
|
+
// host-proxy-capable (macos), where local-fs fallback IS the intended
|
|
106
|
+
// offline behavior — a stale target_client_id auto-filled from a prior
|
|
107
|
+
// cross-client turn is silently ignored on those turns.
|
|
108
|
+
// Note: this scoping deliberately differs from host_bash
|
|
109
|
+
// (host-shell.ts:239-247), which rejects unconditionally for any
|
|
110
|
+
// stale target_client_id regardless of transport.
|
|
111
|
+
if (
|
|
112
|
+
targetClientId != null &&
|
|
113
|
+
!HostFileProxy.instance.isAvailable() &&
|
|
114
|
+
(transportInterface == null || !supportsHostProxy(transportInterface))
|
|
115
|
+
) {
|
|
116
|
+
return {
|
|
117
|
+
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.`,
|
|
118
|
+
isError: true,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
82
122
|
// Proxy to connected client for execution on the user's machine
|
|
83
123
|
// when a capable client is available (managed/cloud-hosted mode).
|
|
84
124
|
if (HostFileProxy.instance.isAvailable()) {
|
|
@@ -91,6 +131,8 @@ class HostFileWriteTool implements Tool {
|
|
|
91
131
|
},
|
|
92
132
|
context.conversationId,
|
|
93
133
|
context.signal,
|
|
134
|
+
targetClientId,
|
|
135
|
+
context.sourceActorPrincipalId,
|
|
94
136
|
);
|
|
95
137
|
}
|
|
96
138
|
|
|
@@ -179,9 +179,17 @@ class HostShellTool implements Tool {
|
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
181
|
const background = input.background === true;
|
|
182
|
+
if (background && context.diskPressureCleanupModeActive === true) {
|
|
183
|
+
return {
|
|
184
|
+
content:
|
|
185
|
+
"Error: background host shell commands are not available during disk pressure cleanup mode.",
|
|
186
|
+
isError: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
182
189
|
|
|
183
190
|
const targetClientId =
|
|
184
|
-
typeof input.target_client_id === "string" &&
|
|
191
|
+
typeof input.target_client_id === "string" &&
|
|
192
|
+
input.target_client_id !== ""
|
|
185
193
|
? input.target_client_id
|
|
186
194
|
: undefined;
|
|
187
195
|
|
|
@@ -236,10 +244,7 @@ class HostShellTool implements Tool {
|
|
|
236
244
|
// guard both targetClientId != null guards above are bypassed, and the
|
|
237
245
|
// code falls through to local daemon execution — silently running commands
|
|
238
246
|
// inside the Docker container instead of on the intended host machine.
|
|
239
|
-
if (
|
|
240
|
-
targetClientId != null &&
|
|
241
|
-
!HostBashProxy.instance.isAvailable()
|
|
242
|
-
) {
|
|
247
|
+
if (targetClientId != null && !HostBashProxy.instance.isAvailable()) {
|
|
243
248
|
return {
|
|
244
249
|
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_bash\` to see currently connected clients.`,
|
|
245
250
|
isError: true,
|
|
@@ -287,6 +292,7 @@ class HostShellTool implements Tool {
|
|
|
287
292
|
},
|
|
288
293
|
context.conversationId,
|
|
289
294
|
abortController.signal,
|
|
295
|
+
context.sourceActorPrincipalId,
|
|
290
296
|
);
|
|
291
297
|
|
|
292
298
|
proxyPromise
|
|
@@ -315,7 +321,7 @@ class HostShellTool implements Tool {
|
|
|
315
321
|
conversationId: context.conversationId,
|
|
316
322
|
command,
|
|
317
323
|
startedAt: Date.now(),
|
|
318
|
-
cancel: () => abortController.abort(),
|
|
324
|
+
cancel: (reason?: string) => abortController.abort(reason),
|
|
319
325
|
});
|
|
320
326
|
|
|
321
327
|
return {
|
|
@@ -334,6 +340,7 @@ class HostShellTool implements Tool {
|
|
|
334
340
|
},
|
|
335
341
|
context.conversationId,
|
|
336
342
|
context.signal,
|
|
343
|
+
context.sourceActorPrincipalId,
|
|
337
344
|
);
|
|
338
345
|
}
|
|
339
346
|
|
|
@@ -2,6 +2,7 @@ import type { McpServerConfig } from "../../config/schemas/mcp.js";
|
|
|
2
2
|
import type { McpServerManager } from "../../mcp/manager.js";
|
|
3
3
|
import { RiskLevel } from "../../permissions/types.js";
|
|
4
4
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
5
|
+
import { toProviderSafeToolName } from "../provider-tool-name.js";
|
|
5
6
|
import { schemaDefinesProperty } from "../schema-transforms.js";
|
|
6
7
|
import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ const riskMap: Record<string, RiskLevel> = {
|
|
|
16
17
|
* and with core/skill tools.
|
|
17
18
|
*/
|
|
18
19
|
function mcpToolName(serverId: string, toolName: string): string {
|
|
19
|
-
return `mcp__${serverId}__${toolName}
|
|
20
|
+
return toProviderSafeToolName(`mcp__${serverId}__${toolName}`);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface McpToolMetadata {
|
|
@@ -11,9 +11,15 @@ import {
|
|
|
11
11
|
test,
|
|
12
12
|
} from "bun:test";
|
|
13
13
|
|
|
14
|
+
import { _setOverridesForTesting } from "../../config/assistant-feature-flags.js";
|
|
14
15
|
import { PKB_WORKSPACE_SCOPE } from "../../memory/pkb/types.js";
|
|
15
16
|
import type { ToolContext } from "../types.js";
|
|
16
17
|
|
|
18
|
+
// This test exercises v1 PKB re-index enqueue. The `memory-v2-enabled` flag
|
|
19
|
+
// (registry default `true`) makes the enqueue path skipped — disable it so
|
|
20
|
+
// the v1 PKB index path stays under test.
|
|
21
|
+
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
22
|
+
|
|
17
23
|
let tmpWorkspace: string;
|
|
18
24
|
let previousWorkspaceEnv: string | undefined;
|
|
19
25
|
|
|
@@ -175,7 +181,7 @@ describe("recallTool.execute", () => {
|
|
|
175
181
|
max_results: 4,
|
|
176
182
|
depth: "deep",
|
|
177
183
|
},
|
|
178
|
-
makeContext(
|
|
184
|
+
makeContext(),
|
|
179
185
|
);
|
|
180
186
|
|
|
181
187
|
expect(result).toEqual({
|
|
@@ -196,7 +202,7 @@ describe("recallTool.execute", () => {
|
|
|
196
202
|
|
|
197
203
|
const result = await recallTool.execute(
|
|
198
204
|
{ query: "fallback search", sources: ["workspace"], depth: "fast" },
|
|
199
|
-
makeContext(
|
|
205
|
+
makeContext(),
|
|
200
206
|
);
|
|
201
207
|
|
|
202
208
|
expect(result).toEqual({
|
|
@@ -205,7 +211,7 @@ describe("recallTool.execute", () => {
|
|
|
205
211
|
});
|
|
206
212
|
});
|
|
207
213
|
|
|
208
|
-
test("propagates tool context
|
|
214
|
+
test("propagates tool context", async () => {
|
|
209
215
|
const controller = new AbortController();
|
|
210
216
|
|
|
211
217
|
await recallTool.execute(
|
|
@@ -221,7 +227,6 @@ describe("recallTool.execute", () => {
|
|
|
221
227
|
expect(recallCalls[0]?.context).toEqual({
|
|
222
228
|
workingDir: "/workspace/project",
|
|
223
229
|
conversationId: "conv-context",
|
|
224
|
-
memoryScopeId: "default",
|
|
225
230
|
config: getConfig(),
|
|
226
231
|
signal: controller.signal,
|
|
227
232
|
});
|
|
@@ -275,9 +280,7 @@ describe("rememberTool.execute — PKB re-index enqueue", () => {
|
|
|
275
280
|
test("enqueues re-index jobs for both buffer and daily archive paths", async () => {
|
|
276
281
|
const result = await rememberTool.execute(
|
|
277
282
|
{ content: "index me please" },
|
|
278
|
-
|
|
279
|
-
// enqueue ignores it and pins to PKB_WORKSPACE_SCOPE instead.
|
|
280
|
-
makeContext({ memoryScopeId: "scope-enqueue" }),
|
|
283
|
+
makeContext(),
|
|
281
284
|
);
|
|
282
285
|
expect(result.isError).toBe(false);
|
|
283
286
|
|
|
@@ -308,7 +311,7 @@ describe("rememberTool.execute — PKB re-index enqueue", () => {
|
|
|
308
311
|
test("does not enqueue when content is empty (write was skipped)", async () => {
|
|
309
312
|
const result = await rememberTool.execute(
|
|
310
313
|
{ content: " " },
|
|
311
|
-
makeContext(
|
|
314
|
+
makeContext(),
|
|
312
315
|
);
|
|
313
316
|
expect(result.isError).toBe(true);
|
|
314
317
|
expect(enqueueCalls).toHaveLength(0);
|
|
@@ -319,7 +322,7 @@ describe("rememberTool.execute — PKB re-index enqueue", () => {
|
|
|
319
322
|
|
|
320
323
|
const result = await rememberTool.execute(
|
|
321
324
|
{ content: "enqueue will throw" },
|
|
322
|
-
makeContext(
|
|
325
|
+
makeContext(),
|
|
323
326
|
);
|
|
324
327
|
|
|
325
328
|
// Remember call succeeded despite enqueue throwing for each write.
|
|
@@ -34,7 +34,7 @@ class RememberTool implements Tool {
|
|
|
34
34
|
const result = handleRemember(
|
|
35
35
|
typedInput,
|
|
36
36
|
context.conversationId,
|
|
37
|
-
|
|
37
|
+
"default",
|
|
38
38
|
getConfig(),
|
|
39
39
|
);
|
|
40
40
|
return {
|
|
@@ -73,7 +73,6 @@ class RecallTool implements Tool {
|
|
|
73
73
|
const result = await runAgenticRecall(input as unknown as RecallInput, {
|
|
74
74
|
workingDir: context.workingDir,
|
|
75
75
|
conversationId: context.conversationId,
|
|
76
|
-
memoryScopeId: context.memoryScopeId ?? "default",
|
|
77
76
|
config,
|
|
78
77
|
signal: context.signal,
|
|
79
78
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const PROVIDER_TOOL_NAME_MAX_LENGTH = 64;
|
|
4
|
+
const PROVIDER_TOOL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
5
|
+
const HASH_LENGTH = 12;
|
|
6
|
+
|
|
7
|
+
export function isProviderSafeToolName(name: string): boolean {
|
|
8
|
+
return PROVIDER_TOOL_NAME_RE.test(name);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toProviderSafeToolName(rawName: string): string {
|
|
12
|
+
const trimmed = rawName.trim();
|
|
13
|
+
if (isProviderSafeToolName(rawName)) {
|
|
14
|
+
return rawName;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const hash = createHash("sha256")
|
|
18
|
+
.update(rawName)
|
|
19
|
+
.digest("hex")
|
|
20
|
+
.slice(0, HASH_LENGTH);
|
|
21
|
+
const suffix = `__${hash}`;
|
|
22
|
+
const maxBaseLength = PROVIDER_TOOL_NAME_MAX_LENGTH - suffix.length;
|
|
23
|
+
const sanitized =
|
|
24
|
+
trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "tool";
|
|
25
|
+
const base = sanitized.slice(0, maxBaseLength).replace(/_+$/g, "") || "tool";
|
|
26
|
+
|
|
27
|
+
return `${base}${suffix}`;
|
|
28
|
+
}
|