Files
nexu-io__open-design/apps/daemon/tests/acp-timeout-env.test.ts
Leon Aburime 91f22f301d refactor(daemon): split acp.ts + pi-rpc.ts into an agent-protocol/ capability-barrel (#5200)
* refactor(daemon): split acp.ts + pi-rpc.ts into agent-protocol/ capability-barrel

Move acp.ts (1744) and pi-rpc.ts (684) into a single agent-protocol/
capability-barrel module: a core/ foundation kernel (shared JSON-line-stream
transport) plus per-protocol concern subdirs (acp/, pi-rpc/), each with a
barrel. The shared createJsonLineStream transport moves into core/, dissolving
the former pi-rpc -> acp edge into a clean star topology (allowedEdges: []).

Pure structural move: every function body byte-identical (verified via
line-multiset, 1960==1960, 0 added/0 dropped); public surface reproduced
exactly (10 names) through the root barrel. All importers repointed to
./agent-protocol/index.js (server.ts, connectionTest.ts, runtimes/defs/shared.ts
+ 5 test files).

Guard registration deferred (check-barrel-imports infra not on main yet).

* docs(daemon): @module docblocks, per-export JSDoc, and README for agent-protocol/

Documentation only — zero code lines changed (verified by line-multiset,
1960==1960). Every file gets a @module docblock, every exported symbol gets
JSDoc, and README.md mirrors design-systems/README.md (What changed / Why this
shape / Import conventions / Directory structure / Consumers).

* fix(daemon): repoint verify-amr-real-vela script to the agent-protocol barrel

The acp.ts -> agent-protocol/ split stops emitting dist/acp.js, so the
real-vela smoke verifier's dynamic import of ../dist/acp.js would fail with
ERR_MODULE_NOT_FOUND after a clean build. Repoint it to
../dist/agent-protocol/index.js, which re-exports attachAcpSession. Verified:
daemon build emits dist/agent-protocol/index.js and the script's import
resolves attachAcpSession as a function.

---------

Co-authored-by: LA <la@LAs-MacBook-Pro.lan>
2026-07-08 07:51:12 +00:00

129 lines
3.9 KiB
TypeScript

import assert from 'node:assert/strict';
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { test, vi } from 'vitest';
import { detectAcpModels } from '../src/agent-protocol/index.js';
function writeStallingProbe(): { dir: string; bin: string } {
const dir = mkdtempSync(join(tmpdir(), 'od-acp-timeout-'));
const bin = join(dir, 'stall-acp-probe.mjs');
writeFileSync(
bin,
'process.stdin.resume();\nsetTimeout(() => {}, 60_000);\n',
'utf8',
);
chmodSync(bin, 0o755);
return { dir, bin };
}
function writeDelayedSuccessProbe(delayMs: number): { dir: string; bin: string } {
const dir = mkdtempSync(join(tmpdir(), 'od-acp-timeout-'));
const bin = join(dir, 'delayed-acp-probe.mjs');
writeFileSync(
bin,
[
'process.stdin.setEncoding("utf8");',
'let buffer = "";',
'process.stdin.on("data", (chunk) => {',
' buffer += chunk;',
' for (;;) {',
' const idx = buffer.indexOf("\\n");',
' if (idx === -1) break;',
' const line = buffer.slice(0, idx).trim();',
' buffer = buffer.slice(idx + 1);',
' if (!line) continue;',
' const message = JSON.parse(line);',
' setTimeout(() => {',
' process.stdout.write(JSON.stringify({ id: message.id, result: message.method === "session/new" ? { sessionId: "s1" } : {} }) + "\\n");',
` }, ${delayMs});`,
' }',
'});',
'process.stdin.resume();',
].join('\n'),
'utf8',
);
chmodSync(bin, 0o755);
return { dir, bin };
}
test('detectAcpModels uses OD_ACP_TIMEOUT_MS from the ACP probe environment', async () => {
const { dir, bin } = writeStallingProbe();
try {
const started = Date.now();
await assert.rejects(
detectAcpModels({
bin: process.execPath,
args: [bin],
env: { OD_ACP_TIMEOUT_MS: '123' },
timeoutMs: 15_000,
}),
/ACP model detection timed out after 123ms/,
);
assert.ok(
Date.now() - started < 5_000,
'expected OD_ACP_TIMEOUT_MS to bound the probe instead of waiting for the 15s caller timeout',
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('detectAcpModels ignores invalid OD_ACP_TIMEOUT_MS values', async () => {
const { dir, bin } = writeStallingProbe();
try {
await assert.rejects(
detectAcpModels({
bin: process.execPath,
args: [bin],
env: { OD_ACP_TIMEOUT_MS: 'not-a-number' },
timeoutMs: 50,
}),
/ACP model detection timed out after 50ms/,
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('detectAcpModels caps oversized OD_ACP_TIMEOUT_MS values before scheduling timers', async () => {
vi.useFakeTimers();
const timeoutSpy = vi.spyOn(globalThis, 'setTimeout');
try {
const probe = detectAcpModels({
bin: process.execPath,
args: ['-e', 'process.stdin.resume()'],
env: { OD_ACP_TIMEOUT_MS: '10000000000' },
timeoutMs: 15_000,
});
probe.catch(() => {});
const scheduledDelay = timeoutSpy.mock.calls
.map((call) => call[1])
.find((delay) => delay === 24 * 60 * 60 * 1000);
assert.equal(scheduledDelay, 24 * 60 * 60 * 1000);
await vi.advanceTimersByTimeAsync(24 * 60 * 60 * 1000);
await assert.rejects(probe, /ACP model detection timed out after 86400000ms/);
} finally {
timeoutSpy.mockRestore();
vi.useRealTimers();
}
});
test('detectAcpModels treats OD_ACP_TIMEOUT_MS=0 as disabling the ACP probe timeout', async () => {
const { dir, bin } = writeDelayedSuccessProbe(120);
try {
const models = await detectAcpModels({
bin: process.execPath,
args: [bin],
env: { OD_ACP_TIMEOUT_MS: '0' },
timeoutMs: 50,
});
assert.deepEqual(models, [{ id: 'default', label: 'Default (CLI config)' }]);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});