@aria-cli/tools 1.0.9 → 1.0.11
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/package.json +9 -5
- package/src/__tests__/web-fetch-download.test.ts +0 -433
- package/src/__tests__/web-tools.test.ts +0 -619
- package/src/ask-user-interaction.ts +0 -33
- package/src/cache/web-cache.ts +0 -110
- package/src/definitions/arion.ts +0 -118
- package/src/definitions/browser/browser.ts +0 -502
- package/src/definitions/browser/index.ts +0 -5
- package/src/definitions/browser/pw-downloads.ts +0 -142
- package/src/definitions/browser/pw-interactions.ts +0 -282
- package/src/definitions/browser/pw-responses.ts +0 -98
- package/src/definitions/browser/pw-session.ts +0 -405
- package/src/definitions/browser/pw-shared.ts +0 -85
- package/src/definitions/browser/pw-snapshot.ts +0 -383
- package/src/definitions/browser/pw-state.ts +0 -101
- package/src/definitions/browser/types.ts +0 -203
- package/src/definitions/code-intelligence.ts +0 -526
- package/src/definitions/core.ts +0 -118
- package/src/definitions/delegation.ts +0 -567
- package/src/definitions/deploy.ts +0 -73
- package/src/definitions/filesystem.ts +0 -217
- package/src/definitions/frg.ts +0 -67
- package/src/definitions/index.ts +0 -28
- package/src/definitions/memory.ts +0 -150
- package/src/definitions/messaging.ts +0 -734
- package/src/definitions/meta.ts +0 -392
- package/src/definitions/network.ts +0 -179
- package/src/definitions/outlook.ts +0 -318
- package/src/definitions/patch/apply-patch.ts +0 -235
- package/src/definitions/patch/fuzzy-match.ts +0 -217
- package/src/definitions/patch/index.ts +0 -1
- package/src/definitions/patch/patch-parser.ts +0 -297
- package/src/definitions/patch/sandbox-paths.ts +0 -129
- package/src/definitions/process/index.ts +0 -5
- package/src/definitions/process/process-registry.ts +0 -303
- package/src/definitions/process/process.ts +0 -456
- package/src/definitions/process/pty-keys.ts +0 -298
- package/src/definitions/process/session-slug.ts +0 -147
- package/src/definitions/quip.ts +0 -225
- package/src/definitions/search.ts +0 -67
- package/src/definitions/session-history.ts +0 -79
- package/src/definitions/shell.ts +0 -202
- package/src/definitions/slack.ts +0 -211
- package/src/definitions/web.ts +0 -119
- package/src/executors/apply-patch.ts +0 -1035
- package/src/executors/arion.ts +0 -199
- package/src/executors/code-intelligence.ts +0 -1179
- package/src/executors/deploy.ts +0 -1066
- package/src/executors/filesystem.ts +0 -1428
- package/src/executors/frg-freshness.ts +0 -743
- package/src/executors/frg.ts +0 -394
- package/src/executors/index.ts +0 -280
- package/src/executors/learning-meta.ts +0 -1367
- package/src/executors/lsp-client.ts +0 -355
- package/src/executors/memory.ts +0 -978
- package/src/executors/meta.ts +0 -293
- package/src/executors/process-registry.ts +0 -570
- package/src/executors/pty-session-store.ts +0 -43
- package/src/executors/pty.ts +0 -342
- package/src/executors/restart.ts +0 -133
- package/src/executors/search-freshness.ts +0 -249
- package/src/executors/search-types.ts +0 -98
- package/src/executors/search.ts +0 -89
- package/src/executors/self-diagnose.ts +0 -552
- package/src/executors/session-history.ts +0 -435
- package/src/executors/shell-safety.ts +0 -519
- package/src/executors/shell.ts +0 -1243
- package/src/executors/utils.ts +0 -40
- package/src/executors/web.ts +0 -786
- package/src/extraction/content-extraction.ts +0 -281
- package/src/extraction/index.ts +0 -5
- package/src/headless-control-contract.ts +0 -1149
- package/src/index.ts +0 -788
- package/src/local-control-http-auth.ts +0 -2
- package/src/mcp/client.ts +0 -218
- package/src/mcp/connection.ts +0 -568
- package/src/mcp/index.ts +0 -11
- package/src/mcp/jsonrpc.ts +0 -195
- package/src/mcp/types.ts +0 -199
- package/src/network-control-adapter.ts +0 -88
- package/src/network-runtime/address-types.ts +0 -218
- package/src/network-runtime/db-owner-fencing.ts +0 -91
- package/src/network-runtime/delivery-receipts.ts +0 -372
- package/src/network-runtime/direct-endpoint-authority.ts +0 -35
- package/src/network-runtime/index.ts +0 -316
- package/src/network-runtime/local-control-contract.ts +0 -784
- package/src/network-runtime/node-store-contract.ts +0 -46
- package/src/network-runtime/pair-route-contract.ts +0 -97
- package/src/network-runtime/peer-capabilities.ts +0 -48
- package/src/network-runtime/peer-principal-ref.ts +0 -20
- package/src/network-runtime/peer-state-machine.ts +0 -160
- package/src/network-runtime/protocol-schemas.ts +0 -265
- package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
- package/src/outlook/desktop-session.ts +0 -409
- package/src/policy.ts +0 -171
- package/src/providers/brave.ts +0 -80
- package/src/providers/duckduckgo.ts +0 -199
- package/src/providers/exa.ts +0 -85
- package/src/providers/firecrawl.ts +0 -77
- package/src/providers/index.ts +0 -8
- package/src/providers/jina.ts +0 -70
- package/src/providers/router.ts +0 -121
- package/src/providers/search-provider.ts +0 -74
- package/src/providers/tavily.ts +0 -74
- package/src/quip/desktop-session.ts +0 -435
- package/src/registry/index.ts +0 -1
- package/src/registry/registry.ts +0 -905
- package/src/runtime-socket-local-control-client.ts +0 -632
- package/src/security/dns-normalization.ts +0 -34
- package/src/security/dns-pinning.ts +0 -138
- package/src/security/external-content.ts +0 -129
- package/src/security/ssrf.ts +0 -207
- package/src/slack/desktop-session.ts +0 -493
- package/src/tool-factory.ts +0 -91
- package/src/types.ts +0 -1341
- package/src/utils/retry.ts +0 -163
- package/src/utils/safe-parse-json.ts +0 -176
- package/src/utils/url.ts +0 -20
- package/tests/benchmarks/registry.bench.ts +0 -57
- package/tests/cache/web-cache.test.ts +0 -147
- package/tests/critical-integration.test.ts +0 -1465
- package/tests/definitions/apply-patch.test.ts +0 -586
- package/tests/definitions/browser.test.ts +0 -495
- package/tests/definitions/delegation-pause-resume.test.ts +0 -758
- package/tests/definitions/execution.test.ts +0 -671
- package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
- package/tests/definitions/messaging.test.ts +0 -1468
- package/tests/definitions/outlook.test.ts +0 -30
- package/tests/definitions/process.test.ts +0 -469
- package/tests/definitions/slack.test.ts +0 -28
- package/tests/definitions/tool-inventory.test.ts +0 -218
- package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
- package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
- package/tests/executors/apply-patch.test.ts +0 -538
- package/tests/executors/arion.test.ts +0 -309
- package/tests/executors/conversation-primitives.test.ts +0 -250
- package/tests/executors/deploy.test.ts +0 -746
- package/tests/executors/filesystem-tools.test.ts +0 -357
- package/tests/executors/filesystem.test.ts +0 -959
- package/tests/executors/frg-freshness.test.ts +0 -136
- package/tests/executors/frg-merge.test.ts +0 -70
- package/tests/executors/frg-session-content.test.ts +0 -40
- package/tests/executors/frg.test.ts +0 -56
- package/tests/executors/memory-bugfixes.test.ts +0 -257
- package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
- package/tests/executors/memory.test.ts +0 -853
- package/tests/executors/meta-tools.test.ts +0 -411
- package/tests/executors/meta.test.ts +0 -683
- package/tests/executors/path-containment.test.ts +0 -51
- package/tests/executors/process-registry.test.ts +0 -505
- package/tests/executors/pty.test.ts +0 -664
- package/tests/executors/quest-security.test.ts +0 -249
- package/tests/executors/read-file-media.test.ts +0 -230
- package/tests/executors/recall-knowledge-schema.test.ts +0 -209
- package/tests/executors/recall-tags.test.ts +0 -278
- package/tests/executors/remember-null-safety.contract.test.ts +0 -41
- package/tests/executors/restart.test.ts +0 -67
- package/tests/executors/search-unified.test.ts +0 -381
- package/tests/executors/session-history.test.ts +0 -340
- package/tests/executors/session-transcript.test.ts +0 -561
- package/tests/executors/shell-abort.test.ts +0 -416
- package/tests/executors/shell-env-blocklist.test.ts +0 -648
- package/tests/executors/shell-env-process.test.ts +0 -245
- package/tests/executors/shell-process-registry.test.ts +0 -334
- package/tests/executors/shell-tools.test.ts +0 -393
- package/tests/executors/shell.test.ts +0 -690
- package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
- package/tests/executors/web-integration.test.ts +0 -633
- package/tests/executors/web-symlink.test.ts +0 -18
- package/tests/executors/web.test.ts +0 -1400
- package/tests/executors/write-stdin.test.ts +0 -145
- package/tests/extraction/content-extraction.test.ts +0 -153
- package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
- package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
- package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
- package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
- package/tests/helpers/async-waits.ts +0 -53
- package/tests/integration/headless-control-contract.integration.test.ts +0 -153
- package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
- package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
- package/tests/integration/quest-round-trip.test.ts +0 -303
- package/tests/integration/registry-executor-flow.test.ts +0 -85
- package/tests/integration.test.ts +0 -177
- package/tests/loading-tier.test.ts +0 -126
- package/tests/mcp/client-reconnect.test.ts +0 -267
- package/tests/mcp/connection.test.ts +0 -846
- package/tests/mcp/injectable-logger.test.ts +0 -83
- package/tests/mcp/jsonrpc.test.ts +0 -109
- package/tests/mcp/lifecycle.test.ts +0 -879
- package/tests/network-runtime/address-types.contract.test.ts +0 -143
- package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
- package/tests/network-runtime/local-control-contract.test.ts +0 -869
- package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
- package/tests/network-runtime/node-store-contract.test.ts +0 -11
- package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
- package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
- package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
- package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
- package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
- package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
- package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
- package/tests/parallel.test.ts +0 -71
- package/tests/policy.test.ts +0 -184
- package/tests/print-default-test-lane.ts +0 -14
- package/tests/print-test-lane-manifest.ts +0 -22
- package/tests/providers/brave.test.ts +0 -159
- package/tests/providers/duckduckgo.test.ts +0 -207
- package/tests/providers/exa.test.ts +0 -175
- package/tests/providers/firecrawl.test.ts +0 -168
- package/tests/providers/jina.test.ts +0 -144
- package/tests/providers/router.test.ts +0 -328
- package/tests/providers/tavily.test.ts +0 -165
- package/tests/registry/discovery.test.ts +0 -154
- package/tests/registry/injectable-logger.test.ts +0 -230
- package/tests/registry/input-validation.test.ts +0 -361
- package/tests/registry/interface-completeness.test.ts +0 -85
- package/tests/registry/mcp-integration.test.ts +0 -103
- package/tests/registry/mcp-read-only-hint.test.ts +0 -60
- package/tests/registry/memoria-discovery.test.ts +0 -390
- package/tests/registry/nested-validation.test.ts +0 -283
- package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
- package/tests/registry/registration-lifecycle.test.ts +0 -133
- package/tests/registry-validation.test.ts +0 -424
- package/tests/registry.test.ts +0 -460
- package/tests/security/dns-pinning.test.ts +0 -162
- package/tests/security/external-content.test.ts +0 -144
- package/tests/security/ssrf.test.ts +0 -118
- package/tests/shell-safety-integration.test.ts +0 -32
- package/tests/shell-safety.test.ts +0 -365
- package/tests/slack/desktop-session.test.ts +0 -50
- package/tests/test-lane-manifest.ts +0 -440
- package/tests/test-utils.ts +0 -27
- package/tests/tool-factory.test.ts +0 -188
- package/tests/utils/retry.test.ts +0 -231
- package/tests/utils/url.test.ts +0 -63
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -55
- package/vitest.e2e.config.ts +0 -24
- package/vitest.integration.config.ts +0 -24
- package/vitest.native.config.ts +0 -24
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aria/tools - Shell executor abort signal tests
|
|
3
|
-
*
|
|
4
|
-
* Verifies that executeBash, executeExec, and executeSpawn respect
|
|
5
|
-
* AbortSignal for cancellation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
-
import * as fs from "node:fs/promises";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import * as os from "node:os";
|
|
12
|
-
import type { ToolContext } from "../../src/types.js";
|
|
13
|
-
import { executeBash, executeExec, executeSpawn } from "../../src/executors/shell.js";
|
|
14
|
-
|
|
15
|
-
const createTempDir = async (): Promise<string> => {
|
|
16
|
-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "aria-shell-abort-"));
|
|
17
|
-
return fs.realpath(tempDir);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const cleanupTempDir = async (dir: string): Promise<void> => {
|
|
21
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const createContext = (workingDir: string, overrides?: Partial<ToolContext>): ToolContext => ({
|
|
25
|
-
workingDir,
|
|
26
|
-
env: {},
|
|
27
|
-
confirm: async () => true,
|
|
28
|
-
...overrides,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
|
-
|
|
33
|
-
const waitForFile = async (filePath: string, timeoutMs = 5000): Promise<void> => {
|
|
34
|
-
const deadline = Date.now() + timeoutMs;
|
|
35
|
-
while (Date.now() < deadline) {
|
|
36
|
-
try {
|
|
37
|
-
await fs.access(filePath);
|
|
38
|
-
return;
|
|
39
|
-
} catch {
|
|
40
|
-
await sleep(25);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
throw new Error(`Timed out waiting for file: ${filePath}`);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
describe("Shell Executors - AbortSignal", () => {
|
|
47
|
-
let tempDir: string;
|
|
48
|
-
let ctx: ToolContext;
|
|
49
|
-
|
|
50
|
-
beforeEach(async () => {
|
|
51
|
-
tempDir = await createTempDir();
|
|
52
|
-
ctx = createContext(tempDir);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
afterEach(async () => {
|
|
56
|
-
await cleanupTempDir(tempDir);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// =========================================================================
|
|
60
|
-
// executeBash
|
|
61
|
-
// =========================================================================
|
|
62
|
-
|
|
63
|
-
describe("executeBash", () => {
|
|
64
|
-
it("should not execute when signal is already aborted", async () => {
|
|
65
|
-
const ac = new AbortController();
|
|
66
|
-
ac.abort();
|
|
67
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
68
|
-
|
|
69
|
-
const result = await executeBash({ command: 'echo "should not run"' }, abortCtx);
|
|
70
|
-
|
|
71
|
-
expect(result.success).toBe(false);
|
|
72
|
-
expect(result.message).toBe("Command cancelled");
|
|
73
|
-
// No data should be present since command never ran
|
|
74
|
-
expect(result.data).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should abort a running bash command when signal fires", async () => {
|
|
78
|
-
const ac = new AbortController();
|
|
79
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
80
|
-
|
|
81
|
-
// Start a command that will block indefinitely
|
|
82
|
-
const resultPromise = executeBash({ command: "sleep 300" }, abortCtx);
|
|
83
|
-
|
|
84
|
-
// Let the process start, then abort
|
|
85
|
-
await sleep(100);
|
|
86
|
-
ac.abort();
|
|
87
|
-
|
|
88
|
-
const result = await resultPromise;
|
|
89
|
-
|
|
90
|
-
expect(result.success).toBe(false);
|
|
91
|
-
expect(result.message).toBe("Command cancelled");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should distinguish abort from timeout", async () => {
|
|
95
|
-
// First: timeout case (no abort signal)
|
|
96
|
-
const timeoutResult = await executeBash({ command: "sleep 10", timeout: 100 }, ctx);
|
|
97
|
-
expect(timeoutResult.success).toBe(false);
|
|
98
|
-
expect(timeoutResult.message?.toLowerCase()).toContain("time");
|
|
99
|
-
|
|
100
|
-
// Second: abort case (with abort signal, long timeout)
|
|
101
|
-
const ac = new AbortController();
|
|
102
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
103
|
-
const abortPromise = executeBash({ command: "sleep 300", timeout: 60_000 }, abortCtx);
|
|
104
|
-
await sleep(100);
|
|
105
|
-
ac.abort();
|
|
106
|
-
|
|
107
|
-
const abortResult = await abortPromise;
|
|
108
|
-
expect(abortResult.success).toBe(false);
|
|
109
|
-
expect(abortResult.message).toBe("Command cancelled");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// =========================================================================
|
|
114
|
-
// executeExec
|
|
115
|
-
// =========================================================================
|
|
116
|
-
|
|
117
|
-
describe("executeExec", () => {
|
|
118
|
-
it("should not execute when signal is already aborted", async () => {
|
|
119
|
-
const ac = new AbortController();
|
|
120
|
-
ac.abort();
|
|
121
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
122
|
-
|
|
123
|
-
const result = await executeExec({ program: "echo", args: ["should not run"] }, abortCtx);
|
|
124
|
-
|
|
125
|
-
expect(result.success).toBe(false);
|
|
126
|
-
expect(result.message).toBe("Command cancelled");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("should kill running process when signal fires", async () => {
|
|
130
|
-
const ac = new AbortController();
|
|
131
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
132
|
-
|
|
133
|
-
const resultPromise = executeExec({ program: "sleep", args: ["300"] }, abortCtx);
|
|
134
|
-
|
|
135
|
-
// Let the process start, then abort
|
|
136
|
-
await sleep(100);
|
|
137
|
-
ac.abort();
|
|
138
|
-
|
|
139
|
-
const result = await resultPromise;
|
|
140
|
-
|
|
141
|
-
expect(result.success).toBe(false);
|
|
142
|
-
expect(result.message).toBe("Command cancelled");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("should preserve partial stdout captured before abort", async () => {
|
|
146
|
-
const ac = new AbortController();
|
|
147
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
148
|
-
|
|
149
|
-
// Write a script that outputs then blocks. Using a file-based approach
|
|
150
|
-
// avoids pipe buffering issues under load.
|
|
151
|
-
const scriptPath = path.join(tempDir, "partial.sh");
|
|
152
|
-
await fs.writeFile(scriptPath, '#!/bin/bash\necho "partial output"\nexec sleep 300\n', {
|
|
153
|
-
mode: 0o755,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const resultPromise = executeExec({ program: "/bin/bash", args: [scriptPath] }, abortCtx);
|
|
157
|
-
|
|
158
|
-
// Wait long enough for echo to flush, then abort
|
|
159
|
-
await sleep(500);
|
|
160
|
-
ac.abort();
|
|
161
|
-
|
|
162
|
-
const result = await resultPromise;
|
|
163
|
-
|
|
164
|
-
expect(result.success).toBe(false);
|
|
165
|
-
expect(result.message).toBe("Command cancelled");
|
|
166
|
-
const data = result.data as { stdout: string };
|
|
167
|
-
expect(data.stdout).toContain("partial output");
|
|
168
|
-
}, 15000);
|
|
169
|
-
|
|
170
|
-
it("should abort the whole exec process tree without dropping prior stdout", async () => {
|
|
171
|
-
const ac = new AbortController();
|
|
172
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
173
|
-
|
|
174
|
-
const readyFile = path.join(tempDir, "tree-ready");
|
|
175
|
-
const childPidFile = path.join(tempDir, "tree-child.pid");
|
|
176
|
-
const scriptPath = path.join(tempDir, "tree.sh");
|
|
177
|
-
await fs.writeFile(
|
|
178
|
-
scriptPath,
|
|
179
|
-
`#!/bin/bash
|
|
180
|
-
sleep 300 &
|
|
181
|
-
child_pid=$!
|
|
182
|
-
echo "$child_pid" > "${childPidFile}"
|
|
183
|
-
echo "partial output"
|
|
184
|
-
touch "${readyFile}"
|
|
185
|
-
wait "$child_pid"
|
|
186
|
-
`,
|
|
187
|
-
{ mode: 0o755 },
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
const resultPromise = executeExec({ program: "/bin/bash", args: [scriptPath] }, abortCtx);
|
|
191
|
-
|
|
192
|
-
await waitForFile(readyFile);
|
|
193
|
-
const childPid = Number.parseInt((await fs.readFile(childPidFile, "utf8")).trim(), 10);
|
|
194
|
-
expect(Number.isFinite(childPid)).toBe(true);
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
ac.abort();
|
|
198
|
-
|
|
199
|
-
const result = await resultPromise;
|
|
200
|
-
|
|
201
|
-
expect(result.success).toBe(false);
|
|
202
|
-
expect(result.message).toBe("Command cancelled");
|
|
203
|
-
const data = result.data as { stdout: string };
|
|
204
|
-
expect(data.stdout).toContain("partial output");
|
|
205
|
-
expect(() => process.kill(childPid, 0)).toThrow();
|
|
206
|
-
} finally {
|
|
207
|
-
try {
|
|
208
|
-
process.kill(childPid, "SIGKILL");
|
|
209
|
-
} catch {
|
|
210
|
-
// child already terminated
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}, 15000);
|
|
214
|
-
|
|
215
|
-
it("should timeout the whole exec process tree without dropping prior stdout", async () => {
|
|
216
|
-
const readyFile = path.join(tempDir, "timeout-tree-ready");
|
|
217
|
-
const childPidFile = path.join(tempDir, "timeout-tree-child.pid");
|
|
218
|
-
const childScriptPath = path.join(tempDir, "timeout-tree-child.sh");
|
|
219
|
-
const scriptPath = path.join(tempDir, "timeout-tree.sh");
|
|
220
|
-
await fs.writeFile(
|
|
221
|
-
childScriptPath,
|
|
222
|
-
`#!/bin/bash
|
|
223
|
-
trap '' TERM
|
|
224
|
-
while true; do
|
|
225
|
-
sleep 1
|
|
226
|
-
done
|
|
227
|
-
`,
|
|
228
|
-
{ mode: 0o755 },
|
|
229
|
-
);
|
|
230
|
-
await fs.writeFile(
|
|
231
|
-
scriptPath,
|
|
232
|
-
`#!/bin/bash
|
|
233
|
-
"${childScriptPath}" &
|
|
234
|
-
child_pid=$!
|
|
235
|
-
echo "$child_pid" > "${childPidFile}"
|
|
236
|
-
echo "partial output"
|
|
237
|
-
touch "${readyFile}"
|
|
238
|
-
wait "$child_pid"
|
|
239
|
-
`,
|
|
240
|
-
{ mode: 0o755 },
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
// executeExec timeout starts at spawn(), not after script readiness.
|
|
244
|
-
// Give the script enough startup slack to publish the child PID and
|
|
245
|
-
// initial stdout before we assert timeout-driven tree teardown.
|
|
246
|
-
const resultPromise = executeExec(
|
|
247
|
-
{
|
|
248
|
-
program: "/bin/bash",
|
|
249
|
-
args: [scriptPath],
|
|
250
|
-
timeout: 2_000,
|
|
251
|
-
},
|
|
252
|
-
ctx,
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
await waitForFile(readyFile);
|
|
256
|
-
const childPid = Number.parseInt((await fs.readFile(childPidFile, "utf8")).trim(), 10);
|
|
257
|
-
expect(Number.isFinite(childPid)).toBe(true);
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const result = await resultPromise;
|
|
261
|
-
|
|
262
|
-
expect(result.success).toBe(false);
|
|
263
|
-
expect(result.message).toBe("Command timed out");
|
|
264
|
-
const data = result.data as { stdout: string };
|
|
265
|
-
expect(data.stdout).toContain("partial output");
|
|
266
|
-
expect(() => process.kill(childPid, 0)).toThrow();
|
|
267
|
-
} finally {
|
|
268
|
-
try {
|
|
269
|
-
process.kill(childPid, "SIGKILL");
|
|
270
|
-
} catch {
|
|
271
|
-
// child already terminated
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}, 15000);
|
|
275
|
-
|
|
276
|
-
it("should include exit code in abort result data", async () => {
|
|
277
|
-
const ac = new AbortController();
|
|
278
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
279
|
-
|
|
280
|
-
const resultPromise = executeExec({ program: "sleep", args: ["300"] }, abortCtx);
|
|
281
|
-
|
|
282
|
-
await sleep(100);
|
|
283
|
-
ac.abort();
|
|
284
|
-
|
|
285
|
-
const result = await resultPromise;
|
|
286
|
-
|
|
287
|
-
expect(result.success).toBe(false);
|
|
288
|
-
const data = result.data as { exitCode: number; stdout: string; stderr: string };
|
|
289
|
-
expect(data).toBeDefined();
|
|
290
|
-
expect(typeof data.exitCode).toBe("number");
|
|
291
|
-
expect(typeof data.stdout).toBe("string");
|
|
292
|
-
expect(typeof data.stderr).toBe("string");
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// =========================================================================
|
|
297
|
-
// executeSpawn
|
|
298
|
-
// =========================================================================
|
|
299
|
-
|
|
300
|
-
describe("executeSpawn", () => {
|
|
301
|
-
it("should not spawn when signal is already aborted", async () => {
|
|
302
|
-
const ac = new AbortController();
|
|
303
|
-
ac.abort();
|
|
304
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
305
|
-
|
|
306
|
-
const result = await executeSpawn({ program: "sleep", args: ["300"] }, abortCtx);
|
|
307
|
-
|
|
308
|
-
expect(result.success).toBe(false);
|
|
309
|
-
expect(result.message).toBe("Command cancelled");
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it("should kill spawned process when signal fires after spawn", async () => {
|
|
313
|
-
const ac = new AbortController();
|
|
314
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
315
|
-
|
|
316
|
-
const result = await executeSpawn({ program: "sleep", args: ["300"] }, abortCtx);
|
|
317
|
-
|
|
318
|
-
expect(result.success).toBe(true);
|
|
319
|
-
const data = result.data as { pid: number };
|
|
320
|
-
const pid = data.pid;
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
// Process should be running right after spawn
|
|
324
|
-
expect(() => process.kill(pid, 0)).not.toThrow();
|
|
325
|
-
|
|
326
|
-
// Fire the abort signal
|
|
327
|
-
ac.abort();
|
|
328
|
-
|
|
329
|
-
// Poll until the process is dead (SIGTERM + SIGKILL fallback)
|
|
330
|
-
let isDead = false;
|
|
331
|
-
for (let i = 0; i < 20; i++) {
|
|
332
|
-
await sleep(100);
|
|
333
|
-
try {
|
|
334
|
-
process.kill(pid, 0);
|
|
335
|
-
} catch {
|
|
336
|
-
isDead = true;
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
expect(isDead).toBe(true);
|
|
342
|
-
} finally {
|
|
343
|
-
try {
|
|
344
|
-
process.kill(pid, "SIGKILL");
|
|
345
|
-
} catch {
|
|
346
|
-
// Process may already be dead
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}, 15000);
|
|
350
|
-
|
|
351
|
-
it("should succeed initially then abort handler kills the background process", async () => {
|
|
352
|
-
// executeSpawn returns success (with PID) immediately, then the abort
|
|
353
|
-
// handler kills it afterwards. This verifies the two-phase behavior.
|
|
354
|
-
const ac = new AbortController();
|
|
355
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
356
|
-
|
|
357
|
-
const result = await executeSpawn({ program: "sleep", args: ["300"] }, abortCtx);
|
|
358
|
-
|
|
359
|
-
// Spawn should succeed
|
|
360
|
-
expect(result.success).toBe(true);
|
|
361
|
-
expect(result.message).toContain("PID");
|
|
362
|
-
|
|
363
|
-
const pid = (result.data as { pid: number }).pid;
|
|
364
|
-
try {
|
|
365
|
-
// After abort, process should eventually die
|
|
366
|
-
ac.abort();
|
|
367
|
-
await sleep(500);
|
|
368
|
-
|
|
369
|
-
let alive = true;
|
|
370
|
-
try {
|
|
371
|
-
process.kill(pid, 0);
|
|
372
|
-
} catch {
|
|
373
|
-
alive = false;
|
|
374
|
-
}
|
|
375
|
-
expect(alive).toBe(false);
|
|
376
|
-
} finally {
|
|
377
|
-
try {
|
|
378
|
-
process.kill(pid, "SIGKILL");
|
|
379
|
-
} catch {
|
|
380
|
-
// already dead
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
// =========================================================================
|
|
387
|
-
// Edge cases
|
|
388
|
-
// =========================================================================
|
|
389
|
-
|
|
390
|
-
describe("edge cases", () => {
|
|
391
|
-
it("should handle abort signal that is undefined (no signal)", async () => {
|
|
392
|
-
// Ensure executors work fine without any abort signal
|
|
393
|
-
const noSignalCtx = createContext(tempDir);
|
|
394
|
-
// abortSignal should be undefined by default
|
|
395
|
-
expect(noSignalCtx.abortSignal).toBeUndefined();
|
|
396
|
-
|
|
397
|
-
const result = await executeBash({ command: 'echo "works"' }, noSignalCtx);
|
|
398
|
-
expect(result.success).toBe(true);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("should handle abort after command completes naturally (no-op)", async () => {
|
|
402
|
-
const ac = new AbortController();
|
|
403
|
-
const abortCtx = createContext(tempDir, { abortSignal: ac.signal });
|
|
404
|
-
|
|
405
|
-
// Run a quick command that finishes before abort
|
|
406
|
-
const result = await executeBash({ command: 'echo "done"' }, abortCtx);
|
|
407
|
-
expect(result.success).toBe(true);
|
|
408
|
-
|
|
409
|
-
// Abort after completion should be a no-op (no crash)
|
|
410
|
-
ac.abort();
|
|
411
|
-
|
|
412
|
-
// Verify context is still usable info-wise
|
|
413
|
-
expect(abortCtx.abortSignal?.aborted).toBe(true);
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
});
|