@zgeoff/atc 3.4.1 → 3.5.0
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 +1 -1
- package/src/agents/agent-adapter.ts +4 -0
- package/src/agents/build-claude-query-options.ts +1 -0
- package/src/agents/build-headless-env.ts +4 -0
- package/src/agents/claude-adapter.ts +4 -3
- package/src/agents/gateway-adapter.ts +2 -2
- package/src/agents/plan-claude-line-input.ts +44 -0
- package/src/client/daemon-client.ts +6 -1
- package/src/collect-restart-env.ts +6 -3
- package/src/daemon/build-scoped-context.ts +2 -0
- package/src/daemon/check-session-scope.ts +235 -0
- package/src/daemon/daemon-connection.ts +74 -0
- package/src/daemon/daemon-context.ts +21 -0
- package/src/daemon/daemon.ts +21 -0
- package/src/daemon/find-host-branch.ts +28 -0
- package/src/daemon/materialize-workspace.ts +6 -1
- package/src/daemon/merge-recorded-scope.ts +50 -0
- package/src/daemon/published-records.ts +269 -0
- package/src/daemon/request-access-classes.ts +1 -0
- package/src/daemon/run-host-git.ts +52 -0
- package/src/daemon/sessions.ts +185 -53
- package/src/daemon/start-headless-turn.ts +3 -0
- package/src/federation/id-rules.ts +11 -2
- package/src/mcp/mcp-tools.ts +51 -1
- package/src/mcp/require-daemon-features.ts +1 -0
- package/src/mcp/run-tool.ts +14 -0
- package/src/protocol/daemon-features.ts +5 -0
- package/src/protocol/parse-declared-scope.ts +158 -0
- package/src/protocol/protocol.ts +1 -0
- package/src/protocol/published-record.ts +62 -0
- package/src/protocol/request-param-schemas.ts +16 -0
- package/src/store/parse-published-record.ts +59 -0
- package/src/store/run-migrations.ts +23 -0
- package/src/store/state-store.ts +32 -0
- package/src/test-utils/build-mock-published-record.ts +39 -0
- package/src/test-utils/build-stub-brokered-agent-adapter.ts +3 -1
- package/src/test-utils/build-stub-daemon-context.ts +2 -0
- package/src/test-utils/build-stub-daemon-requests.ts +3 -3
- package/src/test-utils/build-stub-harness-relay.ts +2 -2
- package/src/test-utils/build-stub-held-gh.ts +5 -7
- package/src/test-utils/build-stub-held-inbox-source.ts +2 -2
- package/src/test-utils/build-stub-held-zoxide.ts +4 -6
- package/src/test-utils/build-stub-host-hold.ts +7 -6
- package/src/test-utils/build-stub-launch-admission.ts +20 -0
- package/src/test-utils/build-stub-pregranted-broker-host.ts +22 -0
- package/src/test-utils/build-stub-terminal.ts +3 -3
- package/src/test-utils/create-git-fixture.ts +2 -2
- package/src/test-utils/create-stub-claude.ts +3 -1
- package/src/test-utils/create-stub-imp-port.ts +46 -37
- package/src/test-utils/create-stub-systemd.ts +6 -6
- package/src/test-utils/isolate-home.ts +8 -5
- package/src/test-utils/read-query-plan.ts +10 -6
- package/src/test-utils/register-test-cleanup.ts +10 -10
- package/src/test-utils/{run-legacy-daemon.ts → run-stub-legacy-daemon.ts} +1 -1
- package/src/test-utils/setup-mcp-home.ts +7 -6
- package/src/test-utils/setup-mcp-http.ts +6 -6
- package/src/test-utils/setup-temp-dir.ts +4 -10
- package/src/test-utils/start-cut-proxy.ts +2 -4
- package/src/test-utils/start-daemon-process.ts +13 -13
- package/src/test-utils/start-git-http-server.ts +2 -4
- package/src/test-utils/start-mcp-stdio.ts +5 -5
- package/src/test-utils/start-stub-answering-listener.ts +3 -3
- package/src/test-utils/start-stub-closing-listener.ts +3 -3
- package/src/test-utils/start-stub-dropping-daemon.ts +2 -3
- package/src/test-utils/start-stub-impd-info.ts +3 -7
- package/src/test-utils/start-stub-impd.ts +3 -5
- package/src/test-utils/start-stub-legacy-daemon.ts +2 -3
- package/src/test-utils/start-stub-mcp-server.ts +3 -5
- package/src/test-utils/start-stub-recording-listener.ts +3 -3
- package/src/test-utils/start-stub-refusing-listener.ts +3 -3
- package/src/test-utils/start-stub-reporter-socket.ts +3 -9
- package/src/test-utils/start-stub-restore-daemon.ts +2 -7
- package/src/test-utils/start-stub-session-bridge.ts +3 -9
- package/src/test-utils/start-stub-silent-server.ts +3 -6
- package/src/test-utils/start-stub-source-daemon.ts +3 -3
- package/src/test-utils/start-stub-stalled-client.ts +3 -3
- package/src/test-utils/start-stub-stalled-listener.ts +3 -3
- package/src/test-utils/start-stub-tcp-daemon.ts +3 -3
- package/src/test-utils/start-stub-telemetry-collector.ts +2 -2
- package/src/test-utils/start-stub-unanswering-listener.ts +3 -3
- package/src/test-utils/start-stub-unanswering-unix-listener.ts +3 -3
- package/src/test-utils/start-test-daemon.ts +62 -18
- package/src/test-utils/start-tui-harness.ts +17 -10
- package/src/test-utils/subscribe-to-socket-lines.ts +5 -9
- package/src/test-utils/{can-bind-addresses.ts → try-bind-addresses.ts} +4 -3
- /package/src/test-utils/{run-source-daemon.ts → run-stub-source-daemon.ts} +0 -0
|
@@ -42,31 +42,67 @@ interface TestDaemonConfig {
|
|
|
42
42
|
// The temp directory's name prefix.
|
|
43
43
|
readonly prefix?: string;
|
|
44
44
|
readonly options?: TestDaemonOptionsBuilder;
|
|
45
|
+
|
|
46
|
+
// Whether each boot opens the main client; it does unless this is false.
|
|
47
|
+
readonly mainClient?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A running test daemon and the calls that drive it.
|
|
52
|
+
*/
|
|
53
|
+
interface TestDaemon extends TestDaemonPaths {
|
|
54
|
+
readonly build: string;
|
|
55
|
+
readonly logs: string[];
|
|
56
|
+
readonly events: EventMsg[];
|
|
57
|
+
readonly daemon: DaemonHandle;
|
|
58
|
+
readonly client: DaemonClient;
|
|
59
|
+
readonly openClient: (hello?: Readonly<Record<string, unknown>>) => Promise<DaemonClient>;
|
|
60
|
+
readonly openTCPClient: () => Promise<DaemonClient>;
|
|
61
|
+
readonly sendHookLines: (...lines: readonly Readonly<Record<string, unknown>>[]) => Promise<void>;
|
|
62
|
+
readonly stop: () => Promise<void>;
|
|
63
|
+
readonly restart: (options?: TestDaemonOptionsBuilder) => Promise<void>;
|
|
64
|
+
readonly dispose: () => Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The harness a config starts: one that opens no main client has neither
|
|
69
|
+
* that client nor the events it would collect.
|
|
70
|
+
*/
|
|
71
|
+
type StartedTestDaemon<Config extends TestDaemonConfig> = Config extends {
|
|
72
|
+
readonly mainClient: false;
|
|
45
73
|
}
|
|
74
|
+
? Omit<TestDaemon, 'client' | 'events'>
|
|
75
|
+
: TestDaemon;
|
|
46
76
|
|
|
47
77
|
// The build string the daemon and every client the harness opens send in
|
|
48
78
|
// their handshake.
|
|
49
79
|
const BUILD = 'atc/test-build';
|
|
50
80
|
|
|
51
81
|
/**
|
|
52
|
-
* A real daemon in a fresh temp directory, with a client that has
|
|
53
|
-
* sent its handshake
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
82
|
+
* A real daemon in a fresh temp directory, with a main client that has
|
|
83
|
+
* already sent its handshake unless the config turns that client off, which
|
|
84
|
+
* leaves every connection the daemon counts to the test. The options
|
|
85
|
+
* builder chooses what the daemon wires; the harness sets the paths and the
|
|
86
|
+
* build, and collects the daemon's log lines unless the options set their
|
|
87
|
+
* own log. `events` collects every event the main client receives, across
|
|
88
|
+
* restarts. `openClient` opens another client over the unix socket and
|
|
89
|
+
* sends its handshake with the given params; `openTCPClient` connects to
|
|
90
|
+
* the TCP listener and sends nothing, so the test drives that handshake
|
|
91
|
+
* itself. `sendHookLines` writes reporter lines to the reporter socket and
|
|
92
|
+
* resolves once the daemon has closed the connection. `stop` closes every
|
|
93
|
+
* client and stops the daemon; `restart` does the same, then boots on the
|
|
94
|
+
* same paths and state with the options given or the last ones, and opens
|
|
95
|
+
* a new main client unless the config turns it off. `dispose` stops what is
|
|
96
|
+
* running and removes the directory. It runs once the current test
|
|
97
|
+
* finishes, so the harness must start inside a test; calling it sooner runs
|
|
98
|
+
* it then, and a second call does nothing. A first boot that fails runs it
|
|
67
99
|
* before the start rejects.
|
|
68
100
|
*/
|
|
69
|
-
export
|
|
101
|
+
export function startTestDaemon<const Config extends TestDaemonConfig = TestDaemonConfig>(
|
|
102
|
+
config?: Config,
|
|
103
|
+
): Promise<StartedTestDaemon<Config>>;
|
|
104
|
+
|
|
105
|
+
export async function startTestDaemon(config: TestDaemonConfig = {}): Promise<TestDaemon> {
|
|
70
106
|
const tmp = setupTempDir(config.prefix ?? 'atc-test-daemon-');
|
|
71
107
|
|
|
72
108
|
// Registered after the directory, so it releases first: the daemon stops
|
|
@@ -75,7 +111,7 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
75
111
|
|
|
76
112
|
const dispose = registerTestCleanup(() => stack.disposeAsync());
|
|
77
113
|
|
|
78
|
-
stack.
|
|
114
|
+
stack.defer(tmp.teardown);
|
|
79
115
|
|
|
80
116
|
const paths: TestDaemonPaths = {
|
|
81
117
|
dir: tmp.dir,
|
|
@@ -142,6 +178,10 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
142
178
|
|
|
143
179
|
live = daemon;
|
|
144
180
|
|
|
181
|
+
if (config.mainClient === false) {
|
|
182
|
+
return { daemon, client: null };
|
|
183
|
+
}
|
|
184
|
+
|
|
145
185
|
const client = await openClient();
|
|
146
186
|
|
|
147
187
|
client.onEvent = (event) => {
|
|
@@ -168,6 +208,10 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
168
208
|
return current.daemon;
|
|
169
209
|
},
|
|
170
210
|
get client(): DaemonClient {
|
|
211
|
+
if (current.client === null) {
|
|
212
|
+
throw new Error('the test daemon started without a main client');
|
|
213
|
+
}
|
|
214
|
+
|
|
171
215
|
return current.client;
|
|
172
216
|
},
|
|
173
217
|
openClient,
|
|
@@ -224,6 +268,6 @@ export async function startTestDaemon(config: TestDaemonConfig = {}) {
|
|
|
224
268
|
|
|
225
269
|
current = await boot();
|
|
226
270
|
},
|
|
227
|
-
|
|
271
|
+
dispose,
|
|
228
272
|
};
|
|
229
273
|
}
|
|
@@ -32,16 +32,17 @@ interface TUIHarnessOptions {
|
|
|
32
32
|
* starts a new client on the same home and stops capturing the old one.
|
|
33
33
|
* `read` returns every byte the client drew since the last `reset`, and
|
|
34
34
|
* `waitFor` polls that capture for a needle. The client appends what it
|
|
35
|
-
* decides without drawing to a log: `
|
|
36
|
-
* it, and `waitForClientLog` polls for a line written after
|
|
35
|
+
* decides without drawing to a log: `countClientLogLines` returns how many
|
|
36
|
+
* lines it holds, and `waitForClientLog` polls for a line written after
|
|
37
|
+
* that many.
|
|
37
38
|
* `writeConfig` writes the home's config: the stand-in binaries and the git
|
|
38
39
|
* transports the fixture repositories need, with the fields given laid over
|
|
39
40
|
* them. `env` is the environment the client runs with, so a daemon started
|
|
40
41
|
* with it serves the client. A wait made before the client draws anything
|
|
41
|
-
* gets `bootMs` for that first byte.
|
|
42
|
-
* daemon in the home and removes
|
|
43
|
-
*
|
|
44
|
-
* then, and a second
|
|
42
|
+
* gets `bootMs` for that first byte. `stop` kills every client a boot
|
|
43
|
+
* started, stops the daemon in the home, and removes the home. That stop runs once the current test
|
|
44
|
+
* finishes, so it must run inside a test; calling `stop` sooner runs it
|
|
45
|
+
* then, and a second stop does nothing.
|
|
45
46
|
*/
|
|
46
47
|
export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
47
48
|
const tmp = setupTempDir('atc-tui-');
|
|
@@ -50,9 +51,9 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
50
51
|
// daemon stop before their home is removed.
|
|
51
52
|
const owned = new AsyncDisposableStack();
|
|
52
53
|
|
|
53
|
-
const
|
|
54
|
+
const stop = registerTestCleanup(() => owned.disposeAsync());
|
|
54
55
|
|
|
55
|
-
owned.
|
|
56
|
+
owned.defer(tmp.teardown);
|
|
56
57
|
|
|
57
58
|
// The client boots with this home as its cwd and lists it first in the
|
|
58
59
|
// picker, so the path is resolved the way the client reports it.
|
|
@@ -129,6 +130,12 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
129
130
|
env,
|
|
130
131
|
});
|
|
131
132
|
|
|
133
|
+
// Every client a boot starts is killed before the daemon stops, so a
|
|
134
|
+
// client a later boot replaced never outlives the test.
|
|
135
|
+
owned.defer(() => {
|
|
136
|
+
booted.kill();
|
|
137
|
+
});
|
|
138
|
+
|
|
132
139
|
const exit = Promise.withResolvers<number>();
|
|
133
140
|
|
|
134
141
|
booted.onExit((event) => {
|
|
@@ -206,7 +213,7 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
206
213
|
return exited;
|
|
207
214
|
},
|
|
208
215
|
|
|
209
|
-
|
|
216
|
+
countClientLogLines(): number {
|
|
210
217
|
return readClientLog(clientLogPath).length;
|
|
211
218
|
},
|
|
212
219
|
|
|
@@ -225,7 +232,7 @@ export function startTUIHarness(options: TUIHarnessOptions = {}) {
|
|
|
225
232
|
);
|
|
226
233
|
},
|
|
227
234
|
|
|
228
|
-
|
|
235
|
+
stop,
|
|
229
236
|
};
|
|
230
237
|
}
|
|
231
238
|
|
|
@@ -11,7 +11,7 @@ interface SocketLines {
|
|
|
11
11
|
|
|
12
12
|
readonly write: (data: string) => void;
|
|
13
13
|
readonly waitForLine: (count?: number, timeoutMs?: number) => Promise<string[]>;
|
|
14
|
-
readonly
|
|
14
|
+
readonly unsubscribe: () => void;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
interface SocketLinesOptions {
|
|
@@ -35,8 +35,8 @@ interface SocketLinesOptions {
|
|
|
35
35
|
* the lines, throwing when `timeoutMs` passes first or the connection
|
|
36
36
|
* closes short of the count; a stub clock passed as `now` and `wait` steps
|
|
37
37
|
* that timeout without waiting it out. The connection ends once the current
|
|
38
|
-
* test finishes, so it must run inside a test;
|
|
39
|
-
* a second end does nothing.
|
|
38
|
+
* test finishes, so it must run inside a test; `unsubscribe` ends it
|
|
39
|
+
* sooner, and a second end does nothing.
|
|
40
40
|
*/
|
|
41
41
|
export async function subscribeToSocketLines(
|
|
42
42
|
path: string,
|
|
@@ -72,7 +72,7 @@ export async function subscribeToSocketLines(
|
|
|
72
72
|
},
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
const
|
|
75
|
+
const unsubscribe = registerTestCleanup(() => {
|
|
76
76
|
socket.end();
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -118,10 +118,6 @@ export async function subscribeToSocketLines(
|
|
|
118
118
|
|
|
119
119
|
return requireLines(count);
|
|
120
120
|
},
|
|
121
|
-
|
|
122
|
-
stop();
|
|
123
|
-
|
|
124
|
-
return Promise.resolve();
|
|
125
|
-
},
|
|
121
|
+
unsubscribe,
|
|
126
122
|
};
|
|
127
123
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Tries to bind a socket to every given address on this host, by listening
|
|
3
|
+
* on a kernel-chosen port of each and closing it at once, and returns whether
|
|
4
|
+
* every bind succeeded. A host
|
|
4
5
|
* without an address, such as stock macOS for any loopback address past
|
|
5
6
|
* 127.0.0.1, refuses the bind with EADDRNOTAVAIL.
|
|
6
7
|
*/
|
|
7
|
-
export function
|
|
8
|
+
export function tryBindAddresses(hostnames: readonly string[]): boolean {
|
|
8
9
|
for (const hostname of hostnames) {
|
|
9
10
|
try {
|
|
10
11
|
const listener = Bun.listen({ hostname, port: 0, socket: { data() {} } });
|
|
File without changes
|