@vellumai/assistant 0.12.2 → 0.12.3-staging.1
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/.env.example +2 -0
- package/AGENTS.md +1 -1
- package/docs/architecture/integrations.md +7 -0
- package/docs/architecture/memory.md +17 -2
- package/docs/credential-execution-service.md +1 -1
- package/docs/desktop-browser-cli.md +7 -3
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +21 -0
- package/node_modules/@vellumai/environments/src/shell.ts +24 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +4 -1
- package/openapi.yaml +54 -5
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +1 -0
- package/scripts/sync-llm-catalog.ts +3 -0
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/agent-loop.test.ts +259 -0
- package/src/__tests__/agent-wake-delegation-prompt.test.ts +64 -2
- package/src/__tests__/anthropic-provider.test.ts +55 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/attachments-store.test.ts +22 -3
- package/src/__tests__/channel-approval.test.ts +9 -14
- package/src/__tests__/channel-reply-delivery.test.ts +56 -0
- package/src/__tests__/chat-credential-redaction.test.ts +23 -0
- package/src/__tests__/computer-use-screenshot-attachments.test.ts +411 -0
- package/src/__tests__/computer-use-screenshot-selection.test.ts +50 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +257 -6
- package/src/__tests__/conversation-attachments.test.ts +106 -0
- package/src/__tests__/conversation-fork-crud.test.ts +191 -0
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +276 -0
- package/src/__tests__/credential-execution-client.test.ts +70 -53
- package/src/__tests__/db-conversation-tool-surface.test.ts +221 -0
- package/src/__tests__/events-tail-route.test.ts +33 -0
- package/src/__tests__/history-repair.test.ts +53 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +26 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +85 -1
- package/src/__tests__/llm-catalog-parity.test.ts +27 -4
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-commands-routes.test.ts +114 -101
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/persist-media-references.test.ts +50 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/run-conversation-turn-persistence.test.ts +138 -1
- package/src/__tests__/schedule-routes.test.ts +20 -0
- package/src/__tests__/scheduler-result-notification.test.ts +23 -4
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +86 -31
- package/src/__tests__/secret-routes-scrub.test.ts +22 -27
- package/src/__tests__/secret-scanner.test.ts +20 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +8 -0
- package/src/__tests__/secure-keys.test.ts +7 -3
- package/src/__tests__/server-tool-pairing.test.ts +107 -0
- package/src/__tests__/skills.test.ts +5 -4
- package/src/__tests__/subagent-tool-gate-mode.test.ts +226 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/tool-result-follow-up.test.ts +99 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +63 -0
- package/src/__tests__/unicode.test.ts +36 -0
- package/src/agent/loop.ts +37 -12
- package/src/agent/tool-result-follow-up.ts +70 -0
- package/src/api/attachment-provenance.test.ts +68 -0
- package/src/api/computer-use-tool.test.ts +55 -0
- package/src/api/computer-use-tool.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +3 -0
- package/src/api/events/desktop-activity-changed.ts +10 -0
- package/src/api/events/question-request.ts +1 -0
- package/src/api/index.ts +11 -0
- package/src/api/responses/conversation-message.ts +3 -0
- package/src/approvals/approval-primitive.ts +5 -2
- package/src/approvals/scoped-approval-grants.ts +6 -2
- package/src/browser/virtual-desktop-target.ts +1 -2
- package/src/cli/commands/__tests__/cli-test-harness.ts +21 -3
- package/src/cli/commands/__tests__/plugins.test.ts +20 -1
- package/src/cli/commands/__tests__/schedules.test.ts +14 -0
- package/src/cli/commands/bash.help.ts +4 -3
- package/src/cli/commands/browser.help.ts +5 -1
- package/src/cli/commands/credentials.help.ts +3 -3
- package/src/cli/commands/oauth/index.help.ts +10 -0
- package/src/cli/commands/oauth/providers.ts +12 -0
- package/src/cli/commands/oauth/request.test.ts +179 -2
- package/src/cli/commands/oauth/request.ts +41 -10
- package/src/cli/commands/plugins.ts +12 -5
- package/src/cli/commands/schedules.ts +2 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +41 -0
- package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +133 -51
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +8 -2
- package/src/cli/lib/bundled-marketplace.json +855 -0
- package/src/cli/lib/bundled-plugin-packages.json +783 -1
- package/src/cli/lib/inspect-plugin.ts +11 -4
- package/src/cli/lib/upgrade-plugin.ts +19 -11
- package/src/cli/output.ts +18 -4
- package/src/config/__tests__/default-provider.test.ts +4 -0
- package/src/config/bundled-skills/schedule/SKILL.md +6 -6
- package/src/config/loader.ts +9 -17
- package/src/config/platform-identity.ts +8 -8
- package/src/config/profile-text-generation.test.ts +51 -0
- package/src/config/profile-text-generation.ts +51 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +12 -0
- package/src/config/schemas/llm.ts +7 -2
- package/src/config/schemas/mcp.ts +5 -1
- package/src/config/schemas/memory-v3.ts +12 -0
- package/src/credential-execution/ces-runtime.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +36 -134
- package/src/credential-execution/process-manager.test.ts +20 -24
- package/src/credential-execution/process-manager.ts +24 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +43 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +36 -0
- package/src/daemon/assistant-attachments.ts +20 -12
- package/src/daemon/chat-credential-redaction.ts +16 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +74 -3
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-attachments.ts +93 -12
- package/src/daemon/conversation-tool-setup.ts +71 -3
- package/src/daemon/conversation-turn-finalize.ts +26 -13
- package/src/daemon/conversation.ts +72 -17
- package/src/daemon/daemon-control.ts +3 -7
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/message-types/shared.ts +1 -0
- package/src/daemon/orphan-reaper.ts +4 -3
- package/src/daemon/persist-media-references.ts +20 -3
- package/src/daemon/tool-setup-types.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +50 -15
- package/src/desktop/desktop-automation-lease.test.ts +230 -0
- package/src/desktop/desktop-automation-lease.ts +90 -6
- package/src/desktop/desktop-help.ts +15 -0
- package/src/desktop/desktop-stream-bridge.test.ts +65 -10
- package/src/desktop/desktop-stream-bridge.ts +3 -3
- package/src/desktop/desktop-wallpaper-renderer.ts +132 -0
- package/src/desktop/desktop-wallpaper-worker.ts +17 -0
- package/src/desktop/desktop-wallpaper.test.ts +48 -7
- package/src/desktop/desktop-wallpaper.ts +34 -102
- package/src/desktop/virtual-desktop-feature.ts +1 -1
- package/src/mcp/__tests__/manager-tool-caps.test.ts +111 -0
- package/src/mcp/__tests__/startup.test.ts +30 -7
- package/src/mcp/__tests__/tool-caps.test.ts +107 -0
- package/src/mcp/manager.ts +168 -101
- package/src/mcp/startup.ts +33 -13
- package/src/mcp/tool-caps.ts +176 -0
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +70 -0
- package/src/notifications/copy-composer.ts +11 -3
- package/src/notifications/schedule-result-producer.ts +4 -6
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +36 -1
- package/src/oauth/identity-verifier.ts +24 -0
- package/src/oauth/oauth-store.ts +11 -1
- package/src/oauth/provider-serializer.ts +1 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/permissions/confirmation-guardian-request.test.ts +16 -0
- package/src/permissions/confirmation-guardian-request.ts +2 -3
- package/src/permissions/question-prompter.test.ts +31 -0
- package/src/permissions/question-prompter.ts +2 -0
- package/src/persistence/attachments-store.ts +84 -44
- package/src/persistence/conversation-crud.ts +111 -20
- package/src/persistence/conversation-plugin-facade.ts +30 -0
- package/src/persistence/conversation-tool-surface.ts +135 -0
- package/src/persistence/conversation-types.test.ts +32 -0
- package/src/persistence/conversation-types.ts +26 -4
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +78 -0
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +29 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.test.ts +92 -0
- package/src/persistence/migrations/379-oauth-providers-response-ok-field.ts +32 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.test.ts +90 -0
- package/src/persistence/migrations/380-conversation-tool-surfaces-delegate-independent-tasks.ts +34 -0
- package/src/persistence/schema/conversation-tool-surfaces.ts +32 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/oauth.ts +1 -0
- package/src/persistence/steps.ts +24 -3
- package/src/plugin-api/conversation-turn.ts +31 -7
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +133 -0
- package/src/plugin-api/plugin-channel-turn-trust.ts +71 -0
- package/src/plugins/defaults/memory/AGENTS.md +39 -5
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +365 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +43 -0
- package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +1 -0
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +17 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +74 -0
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +161 -0
- package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +10 -0
- package/src/plugins/defaults/memory/buffer-file.ts +354 -0
- package/src/plugins/defaults/memory/buffer-format.ts +40 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -2
- package/src/plugins/defaults/memory/context-search/format.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -1
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +2 -1
- package/src/plugins/defaults/memory/conversation-memory-purge.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +1 -2
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -42
- package/src/plugins/defaults/memory/host-utils.ts +0 -10
- package/src/plugins/defaults/memory/injectors.ts +4 -3
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +72 -183
- package/src/plugins/defaults/memory/memory-run-evidence.ts +213 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +407 -99
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +10 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +107 -7
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +307 -86
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +34 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +89 -49
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +2 -1
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +5 -0
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +5 -2
- package/src/plugins/defaults/memory/v2/reranker.ts +2 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +81 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +87 -0
- package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +183 -4
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +103 -2
- package/src/plugins/defaults/memory/v3/card.ts +2 -1
- package/src/plugins/defaults/memory/v3/injector.ts +212 -178
- package/src/plugins/defaults/memory/v3/orchestrate.ts +112 -25
- package/src/plugins/defaults/memory/v3/plugin-schema.ts +54 -4
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +253 -1
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +35 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +31 -19
- package/src/plugins/defaults/memory/v3/sections.ts +2 -1
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +41 -14
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +4 -1
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +1 -46
- package/src/prompts/__tests__/parallel-tasks-section.test.ts +25 -0
- package/src/prompts/delegation-gate.ts +57 -0
- package/src/prompts/system-prompt.ts +14 -32
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/jev/client.test.ts +260 -0
- package/src/providers/jev/client.ts +518 -0
- package/src/providers/model-catalog.ts +58 -3
- package/src/providers/server-tool-pairing.ts +16 -7
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +94 -1
- package/src/runtime/agent-wake.ts +31 -4
- package/src/runtime/guardian-action-service.ts +2 -17
- package/src/runtime/guardian-reply-router.ts +1 -8
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +89 -0
- package/src/runtime/migrations/vbundle-import-policy.ts +33 -11
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +24 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -9
- package/src/runtime/routes/conversation-routes.ts +29 -4
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +8 -5
- package/src/runtime/routes/guardian-approval-interception.ts +24 -0
- package/src/runtime/routes/inbound-message-handler.ts +2 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -1
- package/src/runtime/routes/inference-profiles-routes.ts +9 -0
- package/src/runtime/routes/oauth-commands-routes.ts +57 -54
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/oauth-request-hints.test.ts +246 -0
- package/src/runtime/routes/oauth-request-hints.ts +149 -0
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/secret-routes.ts +99 -119
- package/src/schedule/__tests__/worker-mcp-bootstrap.test.ts +107 -0
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +173 -0
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +13 -47
- package/src/schedule/run-script.ts +2 -2
- package/src/schedule/scheduler.ts +47 -11
- package/src/schedule/tool-surface-readiness.ts +65 -0
- package/src/schedule/worker-mcp.ts +85 -0
- package/src/schedule/worker.ts +9 -31
- package/src/security/secure-keys.ts +60 -48
- package/src/tools/ask-question/ask-question-tool.test.ts +167 -5
- package/src/tools/ask-question/ask-question-tool.ts +119 -23
- package/src/tools/browser/browser-execution.ts +7 -1
- package/src/tools/host-terminal/host-shell.ts +12 -6
- package/src/tools/shared/filesystem/file-ops-service.ts +1 -31
- package/src/tools/shared/shell-output.test.ts +10 -0
- package/src/tools/shared/shell-output.ts +14 -2
- package/src/tools/skills/sandbox-runner.ts +13 -2
- package/src/tools/skills/scaffold-managed.ts +2 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +33 -4
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -14
- package/src/tools/terminal/safe-env.ts +35 -24
- package/src/tools/terminal/sanitized-bash.ts +15 -2
- package/src/tools/terminal/shell-launch.test.ts +162 -0
- package/src/tools/terminal/shell.test.ts +29 -0
- package/src/tools/terminal/shell.ts +13 -7
- package/src/util/browser-human-verification.ts +14 -0
- package/src/util/host-process.test.ts +17 -1
- package/src/util/host-process.ts +24 -0
- package/src/util/unicode.ts +29 -0
|
@@ -51,19 +51,19 @@ describe("safe-env Qdrant forwarding", () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
describe("safe-env CES child forwarding", () => {
|
|
54
|
-
test("forwards CES
|
|
54
|
+
test("forwards CES IPC discovery env and CES HTTP credentials", () => {
|
|
55
|
+
expect(SAFE_ENV_VARS).toContain("CES_BOOTSTRAP_SOCKET_DIR");
|
|
55
56
|
expect(SAFE_ENV_VARS).toContain("CES_SERVICE_TOKEN");
|
|
56
57
|
expect(SAFE_ENV_VARS).toContain("CES_CREDENTIAL_URL");
|
|
57
|
-
expect(SAFE_ENV_VARS).toContain("CES_LOCAL_SOCKET");
|
|
58
58
|
|
|
59
59
|
const env = buildSanitizedEnv("linux", {
|
|
60
|
+
CES_BOOTSTRAP_SOCKET_DIR: "/run/ces-bootstrap",
|
|
60
61
|
CES_SERVICE_TOKEN: "vault-bearer",
|
|
61
62
|
CES_CREDENTIAL_URL: "http://127.0.0.1:8090",
|
|
62
|
-
CES_LOCAL_SOCKET: "/tmp/ces.sock",
|
|
63
63
|
});
|
|
64
|
+
expect(env.CES_BOOTSTRAP_SOCKET_DIR).toBe("/run/ces-bootstrap");
|
|
64
65
|
expect(env.CES_SERVICE_TOKEN).toBe("vault-bearer");
|
|
65
66
|
expect(env.CES_CREDENTIAL_URL).toBe("http://127.0.0.1:8090");
|
|
66
|
-
expect(env.CES_LOCAL_SOCKET).toBe("/tmp/ces.sock");
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
|
|
@@ -111,6 +111,35 @@ describe("safe-env Windows forwarding", () => {
|
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
test("prepends the Windows assistant binary directory when it sits beside the runtime", () => {
|
|
115
|
+
const dir = mkdtempSync(join(tmpdir(), "win-assistant-"));
|
|
116
|
+
writeFileSync(join(dir, "assistant.exe"), "");
|
|
117
|
+
try {
|
|
118
|
+
const env = buildSanitizedEnv(
|
|
119
|
+
"win32",
|
|
120
|
+
{ Path: "C:\\Windows\\System32" },
|
|
121
|
+
{ execPath: join(dir, "vellum-daemon.exe") },
|
|
122
|
+
);
|
|
123
|
+
expect(env.PATH).toBe(`${dir};C:\\Windows\\System32`);
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(dir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("does not invent a Windows assistant path when the sibling binary is absent", () => {
|
|
130
|
+
const dir = mkdtempSync(join(tmpdir(), "win-no-assistant-"));
|
|
131
|
+
try {
|
|
132
|
+
const env = buildSanitizedEnv(
|
|
133
|
+
"win32",
|
|
134
|
+
{ Path: "C:\\Windows\\System32" },
|
|
135
|
+
{ execPath: join(dir, "vellum-daemon.exe") },
|
|
136
|
+
);
|
|
137
|
+
expect(env.PATH).toBe("C:\\Windows\\System32");
|
|
138
|
+
} finally {
|
|
139
|
+
rmSync(dir, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
114
143
|
test("matches Windows environment names case-insensitively", () => {
|
|
115
144
|
const windowsEnv = buildSanitizedEnv("win32", {
|
|
116
145
|
Path: "C:\\Windows\\System32;C:\\Program Files\\Vellum",
|
|
@@ -4,7 +4,6 @@ import { runSanitizedBash } from "../sanitized-bash.js";
|
|
|
4
4
|
|
|
5
5
|
describe("runSanitizedBash", () => {
|
|
6
6
|
const priorToken = process.env.CES_SERVICE_TOKEN;
|
|
7
|
-
const priorSocket = process.env.CES_LOCAL_SOCKET;
|
|
8
7
|
|
|
9
8
|
afterEach(() => {
|
|
10
9
|
if (priorToken == null) {
|
|
@@ -12,11 +11,6 @@ describe("runSanitizedBash", () => {
|
|
|
12
11
|
} else {
|
|
13
12
|
process.env.CES_SERVICE_TOKEN = priorToken;
|
|
14
13
|
}
|
|
15
|
-
if (priorSocket == null) {
|
|
16
|
-
delete process.env.CES_LOCAL_SOCKET;
|
|
17
|
-
} else {
|
|
18
|
-
process.env.CES_LOCAL_SOCKET = priorSocket;
|
|
19
|
-
}
|
|
20
14
|
});
|
|
21
15
|
|
|
22
16
|
test("runs a command and returns stdout", async () => {
|
|
@@ -35,14 +29,6 @@ describe("runSanitizedBash", () => {
|
|
|
35
29
|
expect(result.stdout.trim()).toBe("vault-bearer");
|
|
36
30
|
});
|
|
37
31
|
|
|
38
|
-
test("forwards CES_LOCAL_SOCKET to the child", async () => {
|
|
39
|
-
process.env.CES_LOCAL_SOCKET = "/tmp/ces.sock";
|
|
40
|
-
const result = await runSanitizedBash("printenv CES_LOCAL_SOCKET", 5_000);
|
|
41
|
-
expect(result.error).toBeUndefined();
|
|
42
|
-
expect(result.exitCode).toBe(0);
|
|
43
|
-
expect(result.stdout.trim()).toBe("/tmp/ces.sock");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
32
|
test("times out a hung command", async () => {
|
|
47
33
|
const result = await runSanitizedBash("sleep 5", 200);
|
|
48
34
|
expect(result.error).toBeUndefined();
|
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { readdirSync, statSync } from "node:fs";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
pathListDelimiter,
|
|
14
|
+
prependUniquePathEntries,
|
|
15
|
+
} from "@vellumai/environments/shell";
|
|
12
16
|
|
|
13
17
|
import { getGatewayInternalBaseUrl } from "../../config/env.js";
|
|
14
18
|
import { getDataDir, getWorkspaceDir } from "../../util/platform.js";
|
|
@@ -55,34 +59,16 @@ export const SAFE_ENV_VARS = [
|
|
|
55
59
|
"VELLUM_MIGRATION_EXPORT_ALLOWED_HOSTS",
|
|
56
60
|
"VELLUM_MIGRATION_IMPORT_ALLOWED_HOSTS",
|
|
57
61
|
"CES_MANAGED_MODE",
|
|
58
|
-
"CES_LOCAL_SOCKET",
|
|
59
|
-
// Child processes (bash, skill sandbox, scheduled scripts) run
|
|
60
|
-
// `assistant oauth request` in-process and resolve managed connections
|
|
61
|
-
// through CES HTTP (`getSecureKeyAsync` / platform client). They need
|
|
62
|
-
// both the credential URL and the service token. CES_LOCAL_SOCKET alone
|
|
63
|
-
// is not enough on managed pods: the socket is often absent or does not
|
|
64
|
-
// complete the lazy RPC path those children use, so a connected account
|
|
65
|
-
// looks disconnected from sanitized children while the daemon still
|
|
66
|
-
// reports healthy.
|
|
67
|
-
//
|
|
68
|
-
// To drop these later, children must resolve the same ACTIVE managed
|
|
69
|
-
// connections without inheriting a reusable vault bearer. That means
|
|
70
|
-
// CES RPC over CES_LOCAL_SOCKET works on managed pods (present,
|
|
71
|
-
// reachable, and sufficient for secure-key / platform-client reads), or
|
|
72
|
-
// a narrower grant protocol issues a one-shot provider token.
|
|
73
|
-
//
|
|
74
|
-
// Before removing them again, reproduce from a sanitized child on a
|
|
75
|
-
// platform-managed pod. After `buildSanitizedEnv()`,
|
|
76
|
-
// `assistant oauth request --provider <key> --json <provider-url>` must
|
|
77
|
-
// succeed for an ACTIVE connection and match a direct terminal
|
|
78
|
-
// invocation. Unit tests that only assert the vars are absent are not
|
|
79
|
-
// enough: also cover skill sandbox and scheduled-script children.
|
|
80
62
|
"CES_CREDENTIAL_URL",
|
|
81
63
|
"CES_SERVICE_TOKEN",
|
|
64
|
+
// Child processes (bash, skill sandbox, scheduled scripts) reach CES
|
|
65
|
+
// over IPC via `CES_BOOTSTRAP_SOCKET_DIR`. CES HTTP credentials are
|
|
66
|
+
// forwarded so children can fail over to CES HTTP while that transport
|
|
67
|
+
// still exists.
|
|
82
68
|
// Per-instance port of the assistant-managed Qdrant sidecar, so skill and
|
|
83
69
|
// bash-tool subprocesses that use the vector helpers (e.g. embed/search over
|
|
84
70
|
// `@vellumai/plugin-api`) resolve the same local sidecar as the daemon
|
|
85
|
-
// (127.0.0.1:<port>). `QDRANT_URL` is intentionally excluded
|
|
71
|
+
// (127.0.0.1:<port>). `QDRANT_URL` is intentionally excluded: it flips
|
|
86
72
|
// QdrantManager into external mode and bypasses the local managed lifecycle.
|
|
87
73
|
"QDRANT_HTTP_PORT",
|
|
88
74
|
"IS_CONTAINERIZED",
|
|
@@ -284,6 +270,7 @@ function appendUniquePathEntries(
|
|
|
284
270
|
export function buildSanitizedEnv(
|
|
285
271
|
hostPlatform: NodeJS.Platform = process.platform,
|
|
286
272
|
sourceEnv: NodeJS.ProcessEnv = process.env,
|
|
273
|
+
options?: { execPath?: string },
|
|
287
274
|
): Record<string, string> {
|
|
288
275
|
const env: Record<string, string> = {};
|
|
289
276
|
const isKataRuntime = isKataFamilyRuntime(sourceEnv.VELLUM_SANDBOX_RUNTIME);
|
|
@@ -360,5 +347,29 @@ export function buildSanitizedEnv(
|
|
|
360
347
|
if (!env.LC_ALL) {
|
|
361
348
|
env.LC_ALL = utf8Locale;
|
|
362
349
|
}
|
|
350
|
+
prependWindowsAssistantDir(
|
|
351
|
+
env,
|
|
352
|
+
hostPlatform,
|
|
353
|
+
options?.execPath ?? process.execPath,
|
|
354
|
+
);
|
|
363
355
|
return env;
|
|
364
356
|
}
|
|
357
|
+
|
|
358
|
+
function prependWindowsAssistantDir(
|
|
359
|
+
env: Record<string, string>,
|
|
360
|
+
hostPlatform: NodeJS.Platform,
|
|
361
|
+
execPath: string,
|
|
362
|
+
): void {
|
|
363
|
+
if (hostPlatform !== "win32") {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const execDir = dirname(execPath);
|
|
367
|
+
try {
|
|
368
|
+
if (!statSync(join(execDir, "assistant.exe")).isFile()) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
} catch {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
env.PATH = prependUniquePathEntries(env.PATH, [execDir], hostPlatform);
|
|
375
|
+
}
|
|
@@ -11,9 +11,12 @@ import { spawn } from "node:child_process";
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
buildShellInvocation,
|
|
14
|
+
buildShellSpawnFlags,
|
|
14
15
|
terminateProcessTree,
|
|
16
|
+
watchShellProcessStart,
|
|
15
17
|
} from "../../util/host-process.js";
|
|
16
18
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
19
|
+
import { SHELL_DID_NOT_START_MESSAGE } from "../shared/shell-output.js";
|
|
17
20
|
import { buildSanitizedEnv } from "./safe-env.js";
|
|
18
21
|
|
|
19
22
|
export const DEFAULT_SANITIZED_BASH_TIMEOUT_MS = 30_000;
|
|
@@ -63,10 +66,10 @@ export function runSanitizedBash(
|
|
|
63
66
|
const child = spawn(wrapped.command, wrapped.args, {
|
|
64
67
|
cwd: getWorkspaceDir(),
|
|
65
68
|
stdio: ["ignore", "pipe", "pipe"],
|
|
66
|
-
detached: true,
|
|
67
69
|
env: buildSanitizedEnv(),
|
|
68
|
-
|
|
70
|
+
...buildShellSpawnFlags(),
|
|
69
71
|
});
|
|
72
|
+
const launch = watchShellProcessStart(child);
|
|
70
73
|
|
|
71
74
|
const timer = setTimeout(() => {
|
|
72
75
|
timedOut = true;
|
|
@@ -83,6 +86,16 @@ export function runSanitizedBash(
|
|
|
83
86
|
|
|
84
87
|
child.on("close", (code) => {
|
|
85
88
|
clearTimeout(timer);
|
|
89
|
+
if (!launch.didStart()) {
|
|
90
|
+
finish({
|
|
91
|
+
stdout: "",
|
|
92
|
+
stderr: "",
|
|
93
|
+
exitCode: null,
|
|
94
|
+
timedOut: false,
|
|
95
|
+
error: SHELL_DID_NOT_START_MESSAGE,
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
86
99
|
finish({
|
|
87
100
|
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
88
101
|
stderr: Buffer.concat(stderrChunks).toString(),
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as realChildProcess from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
9
|
+
import { SHELL_DID_NOT_START_MESSAGE } from "../shared/shell-output.js";
|
|
10
|
+
import type { ToolContext } from "../types.js";
|
|
11
|
+
|
|
12
|
+
const originalSpawn = realChildProcess.spawn;
|
|
13
|
+
|
|
14
|
+
type FakeChild = EventEmitter & {
|
|
15
|
+
pid?: number;
|
|
16
|
+
stdout: EventEmitter;
|
|
17
|
+
stderr: EventEmitter;
|
|
18
|
+
kill: () => boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type SpawnImpl = (
|
|
22
|
+
...args: Parameters<typeof realChildProcess.spawn>
|
|
23
|
+
) => ReturnType<typeof realChildProcess.spawn>;
|
|
24
|
+
|
|
25
|
+
const spawnCalls: {
|
|
26
|
+
command: string;
|
|
27
|
+
args: unknown;
|
|
28
|
+
options: realChildProcess.SpawnOptions;
|
|
29
|
+
}[] = [];
|
|
30
|
+
|
|
31
|
+
let spawnImpl: SpawnImpl = originalSpawn as SpawnImpl;
|
|
32
|
+
|
|
33
|
+
const spawnSpy = mock((...args: Parameters<typeof realChildProcess.spawn>) => {
|
|
34
|
+
spawnCalls.push({
|
|
35
|
+
command: args[0] as string,
|
|
36
|
+
args: args[1],
|
|
37
|
+
options: (args[2] ?? {}) as realChildProcess.SpawnOptions,
|
|
38
|
+
});
|
|
39
|
+
return spawnImpl(...args);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
mock.module("node:child_process", () => ({
|
|
43
|
+
...realChildProcess,
|
|
44
|
+
spawn: spawnSpy,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
const realLogger = await import("../../util/logger.js");
|
|
48
|
+
mock.module("../../util/logger.js", () => ({
|
|
49
|
+
...realLogger,
|
|
50
|
+
getLogger: () =>
|
|
51
|
+
new Proxy({} as Record<string, unknown>, {
|
|
52
|
+
get: () => () => {},
|
|
53
|
+
}),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
setConfig("timeouts", { shellDefaultTimeoutSec: 30, shellMaxTimeoutSec: 60 });
|
|
57
|
+
|
|
58
|
+
const realHub = await import("../../runtime/assistant-event-hub.js");
|
|
59
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
60
|
+
...realHub,
|
|
61
|
+
broadcastMessage: () => {},
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const realWake = await import("../../runtime/agent-wake.js");
|
|
65
|
+
mock.module("../../runtime/agent-wake.js", () => ({
|
|
66
|
+
...realWake,
|
|
67
|
+
wakeAgentForOpportunity: async () => ({}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
const { shellTool } = await import("./shell.js");
|
|
71
|
+
|
|
72
|
+
function makeContext(workingDir: string): ToolContext {
|
|
73
|
+
return {
|
|
74
|
+
workingDir,
|
|
75
|
+
conversationId: "conv-xyz",
|
|
76
|
+
trustClass: "guardian",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fakeChild(): FakeChild {
|
|
81
|
+
const child = new EventEmitter() as FakeChild;
|
|
82
|
+
child.stdout = new EventEmitter();
|
|
83
|
+
child.stderr = new EventEmitter();
|
|
84
|
+
child.kill = () => true;
|
|
85
|
+
return child;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function closeWithoutStart(): ReturnType<typeof realChildProcess.spawn> {
|
|
89
|
+
const child = fakeChild();
|
|
90
|
+
queueMicrotask(() => {
|
|
91
|
+
child.emit("close", 0, null);
|
|
92
|
+
});
|
|
93
|
+
return child as unknown as ReturnType<typeof realChildProcess.spawn>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function spawnError(
|
|
97
|
+
err: NodeJS.ErrnoException,
|
|
98
|
+
): ReturnType<typeof realChildProcess.spawn> {
|
|
99
|
+
const child = fakeChild();
|
|
100
|
+
queueMicrotask(() => {
|
|
101
|
+
child.emit("error", err);
|
|
102
|
+
});
|
|
103
|
+
return child as unknown as ReturnType<typeof realChildProcess.spawn>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe("bash launch failures are not success", () => {
|
|
107
|
+
let workingDir = "";
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
spawnCalls.length = 0;
|
|
111
|
+
spawnImpl = originalSpawn as SpawnImpl;
|
|
112
|
+
workingDir = mkdtempSync(join(tmpdir(), "bash-launch-"));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
spawnImpl = originalSpawn as SpawnImpl;
|
|
117
|
+
rmSync(workingDir, { recursive: true, force: true });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a close with exit 0 and no process start is an error, not command_completed", async () => {
|
|
121
|
+
spawnImpl = () => closeWithoutStart();
|
|
122
|
+
const proof = join(workingDir, "proof.txt");
|
|
123
|
+
|
|
124
|
+
const result = await shellTool.execute(
|
|
125
|
+
{
|
|
126
|
+
command: "printf ran > proof.txt",
|
|
127
|
+
activity: "test",
|
|
128
|
+
},
|
|
129
|
+
makeContext(workingDir),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(result.isError).toBe(true);
|
|
133
|
+
expect(result.content).toBe(SHELL_DID_NOT_START_MESSAGE);
|
|
134
|
+
expect(result.content).not.toContain("<command_completed />");
|
|
135
|
+
expect(existsSync(proof)).toBe(false);
|
|
136
|
+
expect(spawnCalls).toHaveLength(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("a failed spawn of the shell is an error", async () => {
|
|
140
|
+
spawnImpl = () => {
|
|
141
|
+
const err = new Error(
|
|
142
|
+
"spawn powershell.exe ENOENT",
|
|
143
|
+
) as NodeJS.ErrnoException;
|
|
144
|
+
err.code = "ENOENT";
|
|
145
|
+
return spawnError(err);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const result = await shellTool.execute(
|
|
149
|
+
{
|
|
150
|
+
command: "assistant browser --help",
|
|
151
|
+
activity: "test",
|
|
152
|
+
},
|
|
153
|
+
makeContext(workingDir),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(result.isError).toBe(true);
|
|
157
|
+
expect(result.content).toContain("Error spawning command");
|
|
158
|
+
expect(result.content).toContain("The command was not found");
|
|
159
|
+
expect(result.content).not.toContain("<command_completed />");
|
|
160
|
+
expect(existsSync(join(workingDir, "help.txt"))).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -7,6 +7,7 @@ import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
|
7
7
|
import {
|
|
8
8
|
MAX_OUTPUT_LENGTH,
|
|
9
9
|
OUTPUT_TRUNCATED_TAG,
|
|
10
|
+
SHELL_DID_NOT_START_MESSAGE,
|
|
10
11
|
} from "../shared/shell-output.js";
|
|
11
12
|
import type { ToolContext } from "../types.js";
|
|
12
13
|
|
|
@@ -207,6 +208,34 @@ describe("foreground stdin handling", () => {
|
|
|
207
208
|
});
|
|
208
209
|
});
|
|
209
210
|
|
|
211
|
+
describe("foreground command execution", () => {
|
|
212
|
+
let tmpDir = "";
|
|
213
|
+
|
|
214
|
+
beforeEach(async () => {
|
|
215
|
+
tmpDir = await mkdtemp(join(tmpdir(), "bash-exec-"));
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
afterEach(async () => {
|
|
219
|
+
if (tmpDir) {
|
|
220
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("a command that writes a file must actually create it", async () => {
|
|
225
|
+
const result = await shellTool.execute(
|
|
226
|
+
{
|
|
227
|
+
command: "printf ran > proof.txt",
|
|
228
|
+
activity: "test",
|
|
229
|
+
},
|
|
230
|
+
{ ...makeContext(), workingDir: tmpDir },
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect(result.isError).toBeFalsy();
|
|
234
|
+
expect(result.content).not.toBe(SHELL_DID_NOT_START_MESSAGE);
|
|
235
|
+
expect(await readFile(join(tmpDir, "proof.txt"), "utf-8")).toBe("ran");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
210
239
|
describe("streaming stdout cap", () => {
|
|
211
240
|
let tmpDir = "";
|
|
212
241
|
|
|
@@ -11,7 +11,9 @@ import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
|
|
|
11
11
|
import { redactSecrets } from "../../security/secret-scanner.js";
|
|
12
12
|
import {
|
|
13
13
|
buildShellInvocation,
|
|
14
|
+
buildShellSpawnFlags,
|
|
14
15
|
terminateProcessTree,
|
|
16
|
+
watchShellProcessStart,
|
|
15
17
|
} from "../../util/host-process.js";
|
|
16
18
|
import { getLogger } from "../../util/logger.js";
|
|
17
19
|
import { getDataDir } from "../../util/platform.js";
|
|
@@ -324,9 +326,9 @@ export const shellTool = {
|
|
|
324
326
|
cwd: context.workingDir,
|
|
325
327
|
env,
|
|
326
328
|
stdio: ["ignore", "pipe", "pipe"],
|
|
327
|
-
|
|
328
|
-
windowsHide: true,
|
|
329
|
+
...buildShellSpawnFlags(),
|
|
329
330
|
});
|
|
331
|
+
const launch = watchShellProcessStart(child);
|
|
330
332
|
const collector = attachBoundedStdio(child);
|
|
331
333
|
|
|
332
334
|
const killTree = buildKillTree(child, {
|
|
@@ -367,11 +369,13 @@ export const shellTool = {
|
|
|
367
369
|
timedOut,
|
|
368
370
|
});
|
|
369
371
|
|
|
370
|
-
const fmtResult = collector.format(code, timedOut, timeoutSec
|
|
372
|
+
const fmtResult = collector.format(code, timedOut, timeoutSec, {
|
|
373
|
+
started: launch.didStart(),
|
|
374
|
+
});
|
|
371
375
|
|
|
372
376
|
const status: BackgroundToolCompletedEvent["status"] = aborted
|
|
373
377
|
? "cancelled"
|
|
374
|
-
: timedOut
|
|
378
|
+
: timedOut || fmtResult.isError
|
|
375
379
|
? "failed"
|
|
376
380
|
: code === 0
|
|
377
381
|
? "completed"
|
|
@@ -529,9 +533,9 @@ export const shellTool = {
|
|
|
529
533
|
cwd: context.workingDir,
|
|
530
534
|
env,
|
|
531
535
|
stdio: ["ignore", "pipe", "pipe"],
|
|
532
|
-
|
|
533
|
-
windowsHide: true,
|
|
536
|
+
...buildShellSpawnFlags(),
|
|
534
537
|
});
|
|
538
|
+
const launch = watchShellProcessStart(child);
|
|
535
539
|
const collector = attachBoundedStdio(child, {
|
|
536
540
|
onOutput: context.onOutput,
|
|
537
541
|
});
|
|
@@ -573,7 +577,9 @@ export const shellTool = {
|
|
|
573
577
|
timedOut,
|
|
574
578
|
});
|
|
575
579
|
|
|
576
|
-
const fmtResult = collector.format(code, timedOut, timeoutSec
|
|
580
|
+
const fmtResult = collector.format(code, timedOut, timeoutSec, {
|
|
581
|
+
started: launch.didStart(),
|
|
582
|
+
});
|
|
577
583
|
|
|
578
584
|
resolve({
|
|
579
585
|
content: fmtResult.content,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const HUMAN_VERIFICATION_GUIDANCE =
|
|
2
|
+
"CAPTCHAs and bot-detection challenges must be completed by the user. " +
|
|
3
|
+
"This includes drag-to-verify sliders, press-and-hold checks, verification checkboxes and image puzzles. " +
|
|
4
|
+
"Stop before attempting the challenge, even when its controls look easy to automate. " +
|
|
5
|
+
"Do not click, drag, hold, script or retry the verification yourself.";
|
|
6
|
+
|
|
7
|
+
export const DESKTOP_HELP_GUIDANCE =
|
|
8
|
+
HUMAN_VERIFICATION_GUIDANCE +
|
|
9
|
+
" In the virtual desktop, immediately call ask_question with " +
|
|
10
|
+
"desktopHelp: { message, doneLabel, skipLabel } in the user's language. " +
|
|
11
|
+
"Keep message to one short sentence explaining the needed human action. " +
|
|
12
|
+
"This shows an inline preview with Step In, Done and Skip. Wait for the response. " +
|
|
13
|
+
"After Done, inspect a fresh snapshot. If verification remains, request help again. " +
|
|
14
|
+
"Skip leaves the challenge unresolved. Use the same desktopHelp request for other steps that require direct user interaction.";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
1
2
|
import { expect, mock, test } from "bun:test";
|
|
2
3
|
|
|
3
|
-
import { terminateProcessTree } from "./host-process.js";
|
|
4
|
+
import { terminateProcessTree, watchShellProcessStart } from "./host-process.js";
|
|
4
5
|
|
|
5
6
|
test("Windows process trees use taskkill and fall back to the direct child", () => {
|
|
6
7
|
const kill = mock(() => true);
|
|
@@ -15,3 +16,18 @@ test("Windows process trees use taskkill and fall back to the direct child", ()
|
|
|
15
16
|
expect(kill).toHaveBeenCalledTimes(1);
|
|
16
17
|
expect(kill).toHaveBeenCalledWith();
|
|
17
18
|
});
|
|
19
|
+
|
|
20
|
+
test("watchShellProcessStart is false until spawn or a pid appears", () => {
|
|
21
|
+
const child = new EventEmitter() as EventEmitter & { pid?: number };
|
|
22
|
+
const launch = watchShellProcessStart(child);
|
|
23
|
+
expect(launch.didStart()).toBe(false);
|
|
24
|
+
|
|
25
|
+
child.emit("spawn");
|
|
26
|
+
expect(launch.didStart()).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("watchShellProcessStart treats a pid assigned at construction as started", () => {
|
|
30
|
+
const child = new EventEmitter() as EventEmitter & { pid?: number };
|
|
31
|
+
child.pid = 4242;
|
|
32
|
+
expect(watchShellProcessStart(child).didStart()).toBe(true);
|
|
33
|
+
});
|
package/src/util/host-process.ts
CHANGED
|
@@ -2,10 +2,34 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
|
|
3
3
|
export {
|
|
4
4
|
buildShellInvocation,
|
|
5
|
+
buildShellSpawnFlags,
|
|
5
6
|
pathListDelimiter,
|
|
6
7
|
prependUniquePathEntries,
|
|
7
8
|
} from "@vellumai/environments/shell";
|
|
8
9
|
|
|
10
|
+
export interface ShellProcessStartWatch {
|
|
11
|
+
didStart(): boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Record whether a spawned child actually started. A `close` with exit 0 and
|
|
16
|
+
* empty stdio is not proof of execution: Windows can emit that when the
|
|
17
|
+
* process was never created. Callers must treat `didStart() === false` as a
|
|
18
|
+
* launch failure.
|
|
19
|
+
*/
|
|
20
|
+
export function watchShellProcessStart(child: {
|
|
21
|
+
pid?: number;
|
|
22
|
+
once(event: "spawn", listener: () => void): unknown;
|
|
23
|
+
}): ShellProcessStartWatch {
|
|
24
|
+
let spawnSeen = child.pid != null;
|
|
25
|
+
child.once("spawn", () => {
|
|
26
|
+
spawnSeen = true;
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
didStart: () => spawnSeen || child.pid != null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
9
33
|
export interface KillableProcess {
|
|
10
34
|
pid?: number;
|
|
11
35
|
kill: unknown;
|
package/src/util/unicode.ts
CHANGED
|
@@ -67,6 +67,35 @@ export function safeStringSlice(
|
|
|
67
67
|
return str.slice(safeStart, safeEnd);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* A `[start, end)` character window of at most `maxChars` code units that
|
|
72
|
+
* never splits a surrogate pair, for paging through text by offset. A split
|
|
73
|
+
* leaves a lone half at each edge, and each encodes to U+FFFD, so the
|
|
74
|
+
* character is lost from both this window and the next one paged in after
|
|
75
|
+
* it. `charCodeAt` reads the underlying text so a caller need not hold it as
|
|
76
|
+
* one string.
|
|
77
|
+
*/
|
|
78
|
+
export function surrogateSafeWindow(
|
|
79
|
+
total: number,
|
|
80
|
+
charCodeAt: (index: number) => number,
|
|
81
|
+
requestedStart: number,
|
|
82
|
+
maxChars: number,
|
|
83
|
+
): { start: number; end: number } {
|
|
84
|
+
let start = Math.max(0, Math.min(requestedStart, total));
|
|
85
|
+
if (start > 0 && start < total && isLowSurrogate(charCodeAt(start))) {
|
|
86
|
+
start -= 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let end = Math.min(total, start + maxChars);
|
|
90
|
+
if (end > start && end < total && isHighSurrogate(charCodeAt(end - 1))) {
|
|
91
|
+
// Backing off would empty a one-character window, which stalls paging on
|
|
92
|
+
// the same offset, so take the whole pair instead.
|
|
93
|
+
end = end - 1 > start ? end - 1 : Math.min(total, end + 1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { start, end };
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
/**
|
|
71
100
|
* Replace every orphaned UTF-16 surrogate in `str` with U+FFFD
|
|
72
101
|
* (REPLACEMENT CHARACTER).
|