@zgeoff/atc 3.4.0 → 3.4.2
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 +2 -2
- package/src/agents/build-agent-adapters.ts +21 -3
- package/src/agents/claude-adapter.ts +4 -3
- package/src/agents/codex-adapter.ts +20 -0
- package/src/agents/gateway-adapter.ts +2 -2
- package/src/agents/plan-claude-line-input.ts +44 -0
- package/src/agents/render-sdk-message.ts +57 -0
- package/src/agents/start-claude-headless-run.ts +2 -57
- package/src/agents/with-claude-config-lock.ts +28 -15
- package/src/client/boot-daemon.ts +75 -25
- package/src/client/resolve-workspace-root.ts +10 -5
- package/src/daemon/session-runtime.ts +0 -4
- package/src/gateway.ts +2 -179
- package/src/mcp/mcp-tools.ts +1 -1
- package/src/mcp-http-server.ts +13 -10
- package/src/run-gateway-cli.ts +327 -0
- package/src/run-gateway.ts +49 -18
- package/src/sources/github/run-gh.ts +9 -0
- package/src/test-utils/build-mock-execution-target.ts +25 -0
- package/src/test-utils/build-mock-imp-session-request.ts +53 -0
- package/src/test-utils/build-mock-message-record.ts +7 -3
- package/src/test-utils/build-mock-stored-event.ts +20 -0
- package/src/test-utils/build-mock-stored-row.ts +23 -0
- package/src/test-utils/build-stub-brokered-gateway-adapter.ts +50 -0
- package/src/test-utils/build-stub-held-gh.ts +5 -7
- package/src/test-utils/build-stub-held-zoxide.ts +4 -6
- package/src/test-utils/build-stub-mcp-stdio-server.ts +20 -0
- package/src/test-utils/build-stub-recording-filter.ts +9 -0
- package/src/test-utils/build-stub-recording-git.ts +8 -0
- package/src/test-utils/create-git-fixture.ts +5 -8
- package/src/test-utils/create-stub-claude.ts +3 -1
- package/src/test-utils/create-stub-echo-claude.ts +21 -0
- package/src/test-utils/{build-stub-imp-port.ts → create-stub-imp-port.ts} +39 -5
- package/src/test-utils/create-stub-recording-codex.ts +20 -0
- package/src/test-utils/create-stub-shared-server-codex.ts +56 -0
- package/src/test-utils/create-stub-systemd.ts +10 -4
- package/src/test-utils/get-only-imp-name.ts +2 -2
- package/src/test-utils/register-test-cleanup.ts +63 -0
- package/src/test-utils/run-mcp-authorization.ts +7 -4
- package/src/test-utils/{run-legacy-daemon.ts → run-stub-legacy-daemon.ts} +2 -2
- package/src/test-utils/setup-mcp-home.ts +24 -13
- package/src/test-utils/setup-mcp-http.ts +15 -7
- package/src/test-utils/setup-temp-dir.ts +7 -6
- package/src/test-utils/start-cut-proxy.ts +6 -2
- package/src/test-utils/start-daemon-process.ts +25 -24
- package/src/test-utils/start-git-http-server.ts +9 -3
- package/src/test-utils/start-mcp-stdio.ts +17 -22
- package/src/test-utils/start-stub-answering-listener.ts +65 -0
- package/src/test-utils/start-stub-closing-listener.ts +31 -0
- package/src/test-utils/start-stub-dropping-daemon.ts +10 -8
- package/src/test-utils/start-stub-impd-info.ts +10 -4
- package/src/test-utils/start-stub-impd.ts +8 -3
- package/src/test-utils/start-stub-legacy-daemon.ts +13 -8
- package/src/test-utils/start-stub-mcp-server.ts +7 -2
- package/src/test-utils/start-stub-recording-listener.ts +47 -0
- package/src/test-utils/start-stub-refusing-listener.ts +9 -4
- package/src/test-utils/start-stub-reporter-socket.ts +6 -4
- package/src/test-utils/start-stub-restore-daemon.ts +5 -4
- package/src/test-utils/start-stub-session-bridge.ts +6 -4
- package/src/test-utils/start-stub-silent-server.ts +10 -5
- package/src/test-utils/start-stub-source-daemon.ts +6 -4
- package/src/test-utils/start-stub-stalled-client.ts +61 -0
- package/src/test-utils/start-stub-stalled-listener.ts +13 -9
- package/src/test-utils/start-stub-tcp-daemon.ts +9 -4
- package/src/test-utils/start-stub-telemetry-collector.ts +9 -3
- package/src/test-utils/start-stub-unanswering-listener.ts +41 -0
- package/src/test-utils/start-stub-unanswering-unix-listener.ts +38 -0
- package/src/test-utils/start-test-daemon.ts +20 -6
- package/src/test-utils/start-tui-harness.ts +15 -8
- package/src/test-utils/subscribe-to-socket-lines.ts +25 -5
- /package/src/test-utils/{run-source-daemon.ts → run-stub-source-daemon.ts} +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { registerTestCleanup } from './register-test-cleanup';
|
|
2
|
+
|
|
3
|
+
interface StubUnansweringUnixListener {
|
|
4
|
+
// What each read took from any connection, decoded, in arrival order.
|
|
5
|
+
readonly received: readonly string[];
|
|
6
|
+
|
|
7
|
+
readonly [Symbol.dispose]: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A stand-in for a daemon socket that takes a connection and never answers
|
|
12
|
+
* it: it listens on the unix socket path, records what each read takes from
|
|
13
|
+
* a connection, sends nothing back, and keeps the connection open. It
|
|
14
|
+
* stops, dropping every connection it holds, once the current test
|
|
15
|
+
* finishes, so it must run inside a test; disposal stops it sooner, and a
|
|
16
|
+
* second stop does nothing.
|
|
17
|
+
*/
|
|
18
|
+
export function startStubUnansweringUnixListener(path: string): StubUnansweringUnixListener {
|
|
19
|
+
const received: string[] = [];
|
|
20
|
+
|
|
21
|
+
const server = Bun.listen({
|
|
22
|
+
unix: path,
|
|
23
|
+
socket: {
|
|
24
|
+
data(_socket, data) {
|
|
25
|
+
received.push(data.toString());
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const stop = registerTestCleanup(() => {
|
|
31
|
+
server.stop(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
received,
|
|
36
|
+
[Symbol.dispose]: stop,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -4,6 +4,7 @@ import { DaemonClient } from '../client/daemon-client';
|
|
|
4
4
|
import { startDaemon } from '../daemon/daemon';
|
|
5
5
|
import type { DaemonHandle, DaemonOptions } from '../daemon/daemon';
|
|
6
6
|
import type { EventMsg } from '../protocol/protocol';
|
|
7
|
+
import { registerTestCleanup } from './register-test-cleanup';
|
|
7
8
|
import { setupTempDir } from './setup-temp-dir';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -60,12 +61,21 @@ const BUILD = 'atc/test-build';
|
|
|
60
61
|
* connection. `stop` closes every client and stops the daemon; `restart`
|
|
61
62
|
* does the same, then boots on the same paths and state with the options
|
|
62
63
|
* given or the last ones, and opens a new main client. Disposal stops what
|
|
63
|
-
* is running and removes the directory
|
|
64
|
+
* is running and removes the directory. That disposal runs once the current
|
|
65
|
+
* test finishes, so it must run inside a test; disposing sooner runs it
|
|
66
|
+
* then, and a second disposal does nothing. A first boot that fails runs it
|
|
67
|
+
* before the start rejects.
|
|
64
68
|
*/
|
|
65
69
|
export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
66
|
-
|
|
70
|
+
const tmp = setupTempDir(config.prefix ?? 'atc-test-daemon-');
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
// Registered after the directory, so it releases first: the daemon stops
|
|
73
|
+
// before its directory is removed.
|
|
74
|
+
const stack = new AsyncDisposableStack();
|
|
75
|
+
|
|
76
|
+
const dispose = registerTestCleanup(() => stack.disposeAsync());
|
|
77
|
+
|
|
78
|
+
stack.use(tmp);
|
|
69
79
|
|
|
70
80
|
const paths: TestDaemonPaths = {
|
|
71
81
|
dir: tmp.dir,
|
|
@@ -141,9 +151,13 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
141
151
|
return { daemon, client };
|
|
142
152
|
};
|
|
143
153
|
|
|
144
|
-
|
|
154
|
+
// A boot that fails stops what it started and removes the directory
|
|
155
|
+
// before the start rejects.
|
|
156
|
+
let current = await boot().catch(async (error: unknown) => {
|
|
157
|
+
await dispose();
|
|
145
158
|
|
|
146
|
-
|
|
159
|
+
throw error;
|
|
160
|
+
});
|
|
147
161
|
|
|
148
162
|
return {
|
|
149
163
|
...paths,
|
|
@@ -210,6 +224,6 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
210
224
|
|
|
211
225
|
current = await boot();
|
|
212
226
|
},
|
|
213
|
-
[Symbol.asyncDispose]:
|
|
227
|
+
[Symbol.asyncDispose]: dispose,
|
|
214
228
|
};
|
|
215
229
|
}
|
|
@@ -6,6 +6,7 @@ import { buildStubTUIClaude } from './build-stub-tui-claude';
|
|
|
6
6
|
import { buildStubTUIGrok } from './build-stub-tui-grok';
|
|
7
7
|
import { createStubBin } from './create-stub-bin';
|
|
8
8
|
import { mergeDeep } from './merge-deep';
|
|
9
|
+
import { registerTestCleanup } from './register-test-cleanup';
|
|
9
10
|
import { setupTempDir } from './setup-temp-dir';
|
|
10
11
|
import { waitFor } from './wait-for';
|
|
11
12
|
|
|
@@ -37,15 +38,24 @@ interface TUIHarnessOptions {
|
|
|
37
38
|
* transports the fixture repositories need, with the fields given laid over
|
|
38
39
|
* them. `env` is the environment the client runs with, so a daemon started
|
|
39
40
|
* with it serves the client. A wait made before the client draws anything
|
|
40
|
-
* gets `bootMs` for that first byte. Disposal stops the client and the
|
|
41
|
-
* the home and removes it
|
|
41
|
+
* gets `bootMs` for that first byte. Disposal stops the client and the
|
|
42
|
+
* daemon in the home and removes it. That disposal runs once the current
|
|
43
|
+
* test finishes, so it must run inside a test; disposing sooner runs it
|
|
44
|
+
* then, and a second disposal does nothing.
|
|
42
45
|
*/
|
|
43
46
|
export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
44
|
-
|
|
47
|
+
const tmp = setupTempDir('atc-tui-');
|
|
48
|
+
|
|
49
|
+
// Registered after the home, so it releases first: the client and its
|
|
50
|
+
// daemon stop before their home is removed.
|
|
51
|
+
const owned = new AsyncDisposableStack();
|
|
52
|
+
|
|
53
|
+
const dispose = registerTestCleanup(() => owned.disposeAsync());
|
|
54
|
+
|
|
55
|
+
owned.use(tmp);
|
|
45
56
|
|
|
46
57
|
// The client boots with this home as its cwd and lists it first in the
|
|
47
58
|
// picker, so the path is resolved the way the client reports it.
|
|
48
|
-
const tmp = setup.use(setupTempDir('atc-tui-'));
|
|
49
59
|
const home = realpathSync(tmp.dir);
|
|
50
60
|
const configPath = join(home, '.config', 'atc', 'config.json');
|
|
51
61
|
const clientLogPath = join(home, 'client.log');
|
|
@@ -97,9 +107,6 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
97
107
|
// the interaction budget.
|
|
98
108
|
let firstOutputAt: number | null = null;
|
|
99
109
|
|
|
100
|
-
const owned = new AsyncDisposableStack();
|
|
101
|
-
|
|
102
|
-
owned.use(setup.move());
|
|
103
110
|
owned.defer(() => stopClientAndDaemon(home, pty, firstOutputAt === null));
|
|
104
111
|
|
|
105
112
|
return {
|
|
@@ -218,7 +225,7 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
218
225
|
);
|
|
219
226
|
},
|
|
220
227
|
|
|
221
|
-
[Symbol.asyncDispose]:
|
|
228
|
+
[Symbol.asyncDispose]: dispose,
|
|
222
229
|
};
|
|
223
230
|
}
|
|
224
231
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OutboundQueue } from '../protocol/outbound-queue';
|
|
2
|
+
import { registerTestCleanup } from './register-test-cleanup';
|
|
2
3
|
import { waitFor } from './wait-for';
|
|
3
4
|
|
|
4
5
|
interface SocketLines {
|
|
@@ -16,15 +17,26 @@ interface SocketLines {
|
|
|
16
17
|
interface SocketLinesOptions {
|
|
17
18
|
// How many unsent bytes the connection holds before it refuses a write.
|
|
18
19
|
readonly queueBytes?: number;
|
|
20
|
+
|
|
21
|
+
// The clock a line wait reads its deadline from; the wall clock when absent.
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
|
|
24
|
+
// Waits out the interval between a line wait's polls; a real sleep when
|
|
25
|
+
// absent.
|
|
26
|
+
readonly wait?: (ms: number) => Promise<void>;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* Connects to a unix socket and collects every complete newline-terminated
|
|
23
31
|
* line it sends, buffering a partial line across reads. `write` sends the
|
|
24
32
|
* whole of what it is given, the rest going out as the socket drains,
|
|
25
|
-
* and throws instead when the unsent bytes already fill the queue.
|
|
26
|
-
* `
|
|
27
|
-
* the
|
|
33
|
+
* and throws instead when the unsent bytes already fill the queue.
|
|
34
|
+
* `waitForLine` polls until the collected count reaches `count` and returns
|
|
35
|
+
* the lines, throwing when `timeoutMs` passes first or the connection
|
|
36
|
+
* closes short of the count; a stub clock passed as `now` and `wait` steps
|
|
37
|
+
* that timeout without waiting it out. The connection ends once the current
|
|
38
|
+
* test finishes, so it must run inside a test; disposal ends it sooner, and
|
|
39
|
+
* a second end does nothing.
|
|
28
40
|
*/
|
|
29
41
|
export async function subscribeToSocketLines(
|
|
30
42
|
path: string,
|
|
@@ -60,6 +72,10 @@ export async function subscribeToSocketLines(
|
|
|
60
72
|
},
|
|
61
73
|
});
|
|
62
74
|
|
|
75
|
+
const stop = registerTestCleanup(() => {
|
|
76
|
+
socket.end();
|
|
77
|
+
});
|
|
78
|
+
|
|
63
79
|
// The default leaves room for the largest payload a test sends in one
|
|
64
80
|
// piece.
|
|
65
81
|
queue = new OutboundQueue(socket, options.queueBytes ?? 8 * 1024 * 1024);
|
|
@@ -93,13 +109,17 @@ export async function subscribeToSocketLines(
|
|
|
93
109
|
requireLines(count);
|
|
94
110
|
}
|
|
95
111
|
},
|
|
96
|
-
{
|
|
112
|
+
{
|
|
113
|
+
timeoutMs,
|
|
114
|
+
now: options.now ?? Date.now,
|
|
115
|
+
wait: options.wait ?? Bun.sleep,
|
|
116
|
+
},
|
|
97
117
|
);
|
|
98
118
|
|
|
99
119
|
return requireLines(count);
|
|
100
120
|
},
|
|
101
121
|
[Symbol.asyncDispose]: () => {
|
|
102
|
-
|
|
122
|
+
stop();
|
|
103
123
|
|
|
104
124
|
return Promise.resolve();
|
|
105
125
|
},
|
|
File without changes
|