@zgeoff/atc 3.3.1 → 3.4.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/package.json +5 -3
- package/src/agents/agent-adapter.ts +10 -0
- package/src/agents/build-agent-adapters.ts +21 -3
- package/src/agents/build-cli-argv.ts +2 -4
- package/src/agents/claude-adapter.ts +13 -3
- package/src/agents/codex-adapter.ts +31 -2
- package/src/agents/gateway-adapter.ts +20 -8
- package/src/agents/grok-adapter.ts +16 -13
- package/src/agents/load-claude-config-bundle.ts +6 -6
- package/src/agents/print-codex-hook-file.ts +7 -3
- package/src/agents/print-grok-hook-file.ts +8 -3
- package/src/agents/render-sdk-message.ts +57 -0
- package/src/agents/resolve-agent-home.ts +7 -3
- package/src/agents/resolve-claude-global-config-path.ts +4 -4
- package/src/agents/start-claude-headless-run.ts +2 -57
- package/src/agents/update-claude-project-trust.ts +41 -30
- package/src/agents/with-claude-config-lock.ts +37 -12
- package/src/agents/write-atc-bridge.ts +10 -7
- package/src/agents/write-hook-settings.ts +13 -7
- package/src/client/boot-daemon.ts +122 -39
- package/src/client/dirs.ts +4 -2
- package/src/client/index.ts +17 -1
- package/src/client/log-client-event.ts +20 -0
- package/src/client/resolve-workspace-root.ts +10 -5
- package/src/client/spawn-picker.ts +200 -121
- package/src/client/ui.ts +7 -7
- package/src/clients.ts +41 -11
- package/src/daemon/daemon.ts +143 -33
- package/src/daemon/event-signal.ts +19 -3
- package/src/daemon/make-hook-runner.ts +82 -22
- package/src/daemon/permission-registry.ts +11 -6
- package/src/daemon/restore-fleet.ts +62 -17
- package/src/daemon/run-eject-handoff.ts +20 -2
- package/src/daemon/session-runtime.ts +0 -4
- package/src/daemon/sessions.ts +6 -0
- package/src/daemon/start-tcp-listener.ts +12 -10
- package/src/federation/daemon-caller.ts +25 -4
- package/src/federation/daemon-pool.ts +3 -1
- package/src/federation/open-gateway-caller.ts +6 -1
- package/src/gateway.ts +2 -179
- package/src/grants.ts +36 -11
- package/src/hook-report.ts +17 -3
- package/src/mcp/read-report-texts.ts +11 -7
- package/src/mcp/start-mcp-http-server.ts +5 -1
- package/src/mcp-http-server.ts +88 -32
- package/src/report.ts +21 -3
- package/src/run-bridge-tap.ts +36 -7
- package/src/run-config-migrate.ts +35 -11
- package/src/run-gateway-cli.ts +327 -0
- package/src/run-gateway.ts +51 -18
- package/src/shared/build-atc-command.ts +7 -3
- package/src/shared/bun-sqlite-driver.ts +10 -0
- package/src/shared/claim-daemon-lock.ts +10 -4
- package/src/shared/collect-dir-roots.ts +6 -6
- package/src/shared/collect-hooks.ts +7 -6
- package/src/shared/config.ts +19 -9
- package/src/shared/system-clock.ts +24 -0
- package/src/sources/github/collect-github-repos.ts +1 -1
- package/src/sources/github/read-git-protocol.ts +1 -1
- package/src/sources/github/run-gh.ts +24 -9
- package/src/store/state-store.ts +57 -7
- package/src/tap.ts +44 -13
- package/src/test-utils/build-mock-agent-adapter.ts +25 -0
- package/src/test-utils/build-mock-agent-entry.ts +32 -0
- package/src/test-utils/build-mock-auth-binding.ts +28 -0
- package/src/test-utils/build-mock-auth-profile.ts +62 -0
- package/src/test-utils/build-mock-daemon-record.ts +26 -0
- package/src/test-utils/build-mock-execution-target.ts +25 -0
- package/src/test-utils/build-mock-fleet-entry.ts +33 -0
- package/src/test-utils/build-mock-gateway-config.ts +32 -0
- package/src/test-utils/build-mock-imp-identity.ts +18 -0
- package/src/test-utils/build-mock-imp-session-request.ts +53 -0
- package/src/test-utils/build-mock-key-binding-claim.ts +34 -0
- package/src/test-utils/build-mock-message-record.ts +26 -0
- package/src/test-utils/build-mock-mirror-session.ts +42 -0
- package/src/test-utils/build-mock-registry-daemon.ts +28 -0
- package/src/test-utils/build-mock-session-descriptor.ts +66 -0
- package/src/test-utils/build-mock-session.ts +43 -0
- package/src/test-utils/build-mock-sortable-session-view.ts +20 -0
- 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-attention-adapter.ts +22 -0
- package/src/test-utils/build-stub-bridge-context.ts +20 -0
- package/src/test-utils/build-stub-brokered-agent-adapter.ts +96 -0
- package/src/test-utils/build-stub-brokered-gateway-adapter.ts +50 -0
- package/src/test-utils/build-stub-channel-opener.ts +45 -0
- package/src/test-utils/build-stub-claude-headless-run.ts +13 -0
- package/src/test-utils/build-stub-client-screens.ts +33 -0
- package/src/test-utils/build-stub-clock.ts +67 -0
- package/src/test-utils/build-stub-daemon-context.ts +81 -0
- package/src/test-utils/build-stub-daemon-requests.ts +103 -0
- package/src/test-utils/{fixture-dir-provider.ts → build-stub-dir-provider.ts} +20 -12
- package/src/test-utils/build-stub-execution-provider.ts +112 -0
- package/src/test-utils/build-stub-fleet-caller.ts +60 -0
- package/src/test-utils/build-stub-forking-gh.ts +10 -0
- package/src/test-utils/build-stub-gh.ts +50 -0
- package/src/test-utils/build-stub-handoff-daemon.ts +11 -0
- package/src/test-utils/build-stub-harness-relay.ts +50 -0
- package/src/test-utils/build-stub-headless-runner.ts +60 -0
- package/src/test-utils/build-stub-held-gh.ts +19 -0
- package/src/test-utils/build-stub-held-inbox-source.ts +36 -0
- package/src/test-utils/build-stub-held-provider.ts +58 -0
- package/src/test-utils/build-stub-held-zoxide.ts +13 -0
- package/src/test-utils/build-stub-host-hold.ts +31 -0
- package/src/test-utils/build-stub-host-provider.ts +26 -0
- package/src/test-utils/build-stub-log.ts +15 -0
- package/src/test-utils/build-stub-mcp-claude.ts +26 -0
- package/src/test-utils/build-stub-mcp-grok.ts +19 -0
- package/src/test-utils/build-stub-mcp-stdio-server.ts +20 -0
- package/src/test-utils/build-stub-peer-socket.ts +81 -0
- package/src/test-utils/build-stub-picker-host.ts +36 -0
- package/src/test-utils/build-stub-proxied-agent-adapter.ts +29 -0
- package/src/test-utils/build-stub-pty-provider.ts +53 -0
- package/src/test-utils/build-stub-recording-atc.ts +10 -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/build-stub-signed-in-gh.ts +18 -0
- package/src/test-utils/build-stub-signed-out-gh.ts +8 -0
- package/src/test-utils/build-stub-soft-kill-provider.ts +29 -0
- package/src/test-utils/build-stub-targets.ts +58 -0
- package/src/test-utils/build-stub-terminal.ts +19 -0
- package/src/test-utils/build-stub-timeout-scheduler.ts +48 -0
- package/src/test-utils/build-stub-tui-claude.ts +47 -0
- package/src/test-utils/build-stub-tui-grok.ts +53 -0
- package/src/test-utils/build-stub-wait-clock.ts +23 -0
- package/src/test-utils/build-target-options-from-config.ts +37 -0
- package/src/test-utils/collect-process-tree.ts +75 -0
- package/src/test-utils/create-git-fixture.ts +117 -0
- package/src/test-utils/create-migrated-state-db.ts +12 -10
- package/src/test-utils/create-stub-bin.ts +19 -0
- package/src/test-utils/create-stub-claude.ts +75 -0
- package/src/test-utils/create-stub-codex.ts +31 -0
- package/src/test-utils/create-stub-composer.ts +65 -0
- package/src/test-utils/create-stub-echo-claude.ts +21 -0
- package/src/test-utils/create-stub-failing-agent-adapter.ts +111 -0
- package/src/test-utils/create-stub-grok.ts +44 -0
- package/src/test-utils/create-stub-guest-clis.ts +55 -0
- package/src/test-utils/create-stub-harness-guest.ts +35 -0
- package/src/test-utils/{fixture-imp-port.ts → create-stub-imp-port.ts} +144 -63
- package/src/test-utils/create-stub-recording-claude.ts +20 -0
- 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/{setup-fake-systemd.ts → create-stub-systemd.ts} +15 -9
- package/src/test-utils/get-gateway-config.ts +3 -3
- package/src/test-utils/get-only-effect-ref.ts +21 -0
- package/src/test-utils/get-only-imp-name.ts +19 -0
- package/src/test-utils/get-records.ts +20 -0
- package/src/test-utils/get-string.ts +14 -0
- package/src/test-utils/keys.ts +26 -0
- package/src/test-utils/merge-deep.ts +36 -0
- package/src/test-utils/mock-overrides.ts +13 -0
- package/src/test-utils/open-repo-step.ts +24 -0
- package/src/test-utils/read-query-plan.ts +22 -0
- package/src/test-utils/register-test-cleanup.ts +63 -0
- package/src/test-utils/resolve-atc-command.ts +13 -0
- package/src/test-utils/resolve-gateway-command.ts +13 -0
- package/src/test-utils/run-atc.ts +56 -0
- package/src/test-utils/run-legacy-daemon.ts +27 -5
- package/src/test-utils/run-listener-daemon.ts +5 -10
- package/src/test-utils/run-mcp-authorization.ts +7 -4
- package/src/test-utils/run-source-daemon.ts +2 -2
- package/src/test-utils/seed-faker.ts +7 -0
- package/src/test-utils/send-lines-before-handshake.ts +35 -0
- package/src/test-utils/send-mcp-request.ts +25 -0
- package/src/test-utils/setup-mcp-home.ts +110 -0
- package/src/test-utils/setup-mcp-http.ts +26 -30
- package/src/test-utils/setup-temp-dir.ts +7 -6
- package/src/test-utils/spawn-claude-session.ts +34 -0
- package/src/test-utils/spawn-grok-session.ts +39 -0
- package/src/test-utils/start-cut-proxy.ts +29 -8
- package/src/test-utils/start-daemon-process.ts +193 -0
- package/src/test-utils/start-git-http-server.ts +28 -4
- package/src/test-utils/start-mcp-stdio.ts +190 -0
- package/src/test-utils/start-output-capture.ts +20 -0
- 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 +105 -0
- package/src/test-utils/start-stub-impd-info.ts +38 -0
- package/src/test-utils/start-stub-impd.ts +143 -0
- package/src/test-utils/start-stub-legacy-daemon.ts +171 -0
- package/src/test-utils/start-stub-mcp-server.ts +55 -0
- package/src/test-utils/start-stub-recording-listener.ts +47 -0
- package/src/test-utils/start-stub-refusing-listener.ts +43 -0
- package/src/test-utils/start-stub-reporter-socket.ts +79 -0
- package/src/test-utils/start-stub-restore-daemon.ts +59 -0
- package/src/test-utils/start-stub-session-bridge.ts +100 -0
- package/src/test-utils/start-stub-silent-server.ts +38 -0
- package/src/test-utils/start-stub-source-daemon.ts +41 -0
- package/src/test-utils/start-stub-stalled-client.ts +61 -0
- package/src/test-utils/start-stub-stalled-listener.ts +44 -0
- package/src/test-utils/start-stub-tap.ts +38 -0
- package/src/test-utils/start-stub-tcp-daemon.ts +75 -0
- package/src/test-utils/start-stub-telemetry-collector.ts +32 -0
- 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 +229 -0
- package/src/test-utils/start-tui-harness.ts +291 -0
- package/src/test-utils/subscribe-to-socket-lines.ts +81 -14
- package/src/test-utils/try-send-request.ts +29 -0
- package/src/test-utils/wait-for-event.ts +41 -0
- package/src/test-utils/wait-for.ts +15 -3
- package/src/verify-restored-fleet.ts +30 -17
- package/src/workspace/check-repository-access.ts +4 -0
- package/src/workspace/collect-remote-refs.ts +4 -2
- package/src/workspace/run-git.ts +6 -0
- package/src/test-utils/start-legacy-daemon.ts +0 -111
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { FleetCaller, FleetFeature } from '../mcp/types';
|
|
2
|
+
import type { DaemonFeature } from '../protocol/daemon-features';
|
|
3
|
+
import { DAEMON_FEATURES } from '../protocol/daemon-features';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One request a stub fleet caller received: the method and each further
|
|
7
|
+
* argument the sender passed, with an argument left undefined left out.
|
|
8
|
+
*/
|
|
9
|
+
interface StubFleetRequest {
|
|
10
|
+
readonly m: string;
|
|
11
|
+
readonly p?: Readonly<Record<string, unknown>>;
|
|
12
|
+
readonly required?: readonly DaemonFeature[];
|
|
13
|
+
readonly principal?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type StubFleetAnswer = (
|
|
17
|
+
request: StubFleetRequest,
|
|
18
|
+
) => Readonly<Record<string, unknown>> | Promise<Readonly<Record<string, unknown>>>;
|
|
19
|
+
|
|
20
|
+
interface StubFleetCallerConfig {
|
|
21
|
+
// Gives the reply to one request: a value, a promise, or a throw, which
|
|
22
|
+
// the caller turns into a rejection, as a daemon refusal arrives.
|
|
23
|
+
readonly answer?: StubFleetAnswer;
|
|
24
|
+
|
|
25
|
+
// The features the caller reports its daemon announced.
|
|
26
|
+
readonly features?: readonly FleetFeature[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A fleet caller that stands in for a daemon connection: it records every
|
|
31
|
+
* request in `requests`, in the order sent, and replies through the
|
|
32
|
+
* config's `answer`, by default an empty record. It reports the config's
|
|
33
|
+
* `features`, by default every feature a current daemon announces, and it
|
|
34
|
+
* checks no request's required features against them, so a test that needs
|
|
35
|
+
* that refusal uses a real connection.
|
|
36
|
+
*/
|
|
37
|
+
export function buildStubFleetCaller(
|
|
38
|
+
config: StubFleetCallerConfig = {},
|
|
39
|
+
): FleetCaller & { readonly requests: readonly StubFleetRequest[] } {
|
|
40
|
+
const answer: StubFleetAnswer = config.answer ?? (() => ({}));
|
|
41
|
+
const features: readonly FleetFeature[] = config.features ?? DAEMON_FEATURES;
|
|
42
|
+
const requests: StubFleetRequest[] = [];
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
requests,
|
|
46
|
+
sendRequest: (m, p, required, principal) => {
|
|
47
|
+
const request: StubFleetRequest = {
|
|
48
|
+
m,
|
|
49
|
+
...(p === undefined ? {} : { p }),
|
|
50
|
+
...(required === undefined ? {} : { required }),
|
|
51
|
+
...(principal === undefined ? {} : { principal }),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
requests.push(request);
|
|
55
|
+
|
|
56
|
+
return Promise.try(() => answer(request));
|
|
57
|
+
},
|
|
58
|
+
readFeatures: () => Promise.resolve(new Set(features)),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script of a stand-in `gh` that starts a child of its own and waits on
|
|
3
|
+
* it, as a gh extension might. It writes its own process ID, which is also
|
|
4
|
+
* the ID of the process group it leads when started detached, and its
|
|
5
|
+
* child's ID to `pidsFile`, one per line, moving the file into place so it
|
|
6
|
+
* appears whole. The child runs until it is killed.
|
|
7
|
+
*/
|
|
8
|
+
export function buildStubForkingGH(pidsFile: string): string {
|
|
9
|
+
return `#!/bin/sh\nsh -c 'sleep 30' &\nprintf '%s\\n%s\\n' $$ $! > '${pidsFile}.tmp'\nmv '${pidsFile}.tmp' '${pidsFile}'\nwait\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface StubGHReply {
|
|
2
|
+
readonly stdout?: string;
|
|
3
|
+
readonly stderr?: string;
|
|
4
|
+
readonly exitCode?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface StubGHOptions {
|
|
8
|
+
// What each command prints and exits with, keyed by its first argument,
|
|
9
|
+
// such as `repo` or `config`; `hang` waits until the command is killed.
|
|
10
|
+
// A command without a reply prints nothing and exits 0.
|
|
11
|
+
readonly replies: Readonly<Record<string, StubGHReply | 'hang'>>;
|
|
12
|
+
|
|
13
|
+
// A file each run appends its arguments to, as one line.
|
|
14
|
+
readonly argvFile?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The script of a stand-in `gh` that answers each command with the reply
|
|
19
|
+
* the test gave for its first argument: the stdout and stderr verbatim,
|
|
20
|
+
* then the exit code, 0 when unset. A `hang` reply waits until it is
|
|
21
|
+
* killed, as a gh that never answers does.
|
|
22
|
+
*/
|
|
23
|
+
export function buildStubGH(options: StubGHOptions): string {
|
|
24
|
+
const record =
|
|
25
|
+
options.argvFile === undefined
|
|
26
|
+
? ''
|
|
27
|
+
: `printf '%s\\n' "$*" >> ${toShellLiteral(options.argvFile)}\n`;
|
|
28
|
+
|
|
29
|
+
const cases = Object.entries(options.replies)
|
|
30
|
+
.map(([command, reply]) => ` ${toShellLiteral(command)}) ${renderReply(reply)} ;;\n`)
|
|
31
|
+
.join('');
|
|
32
|
+
|
|
33
|
+
return `#!/bin/sh\n${record}case "$1" in\n${cases}esac\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toShellLiteral(value: string): string {
|
|
37
|
+
return `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderReply(reply: StubGHReply | 'hang'): string {
|
|
41
|
+
if (reply === 'hang') {
|
|
42
|
+
return 'exec sleep 30';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
`printf '%s' ${toShellLiteral(reply.stdout ?? '')}`,
|
|
47
|
+
`printf '%s' ${toShellLiteral(reply.stderr ?? '')} >&2`,
|
|
48
|
+
`exit ${reply.exitCode ?? 0}`,
|
|
49
|
+
].join('; ');
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script of a stand-in `atc` whose daemon hands its socket to a
|
|
3
|
+
* replacement: it moves the socket listening at `listeningPath` to
|
|
4
|
+
* `$HOME/atc-daemon.sock`, where a client of a daemon on that home dials,
|
|
5
|
+
* and exits 0. Write it with `createStubBin`.
|
|
6
|
+
*/
|
|
7
|
+
export function buildStubHandoffDaemon(listeningPath: string): string {
|
|
8
|
+
return `#!/usr/bin/env bash
|
|
9
|
+
mv '${listeningPath}' "$HOME/atc-daemon.sock"
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { HarnessRelay } from '../daemon/execution-provider';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A harness relay held in memory, as a provider hands one to the daemon for
|
|
5
|
+
* a guest's connection. `sendLine` delivers a value as one JSON line from
|
|
6
|
+
* the guest to every line listener, `written` holds every line written back
|
|
7
|
+
* to the guest, parsed, in order, and `isClosed` turns true once the relay
|
|
8
|
+
* is closed. A write settles at once, as a relay with room does. `hangUp`
|
|
9
|
+
* runs every close listener, as a guest that hangs up does; closing the
|
|
10
|
+
* relay from the daemon's end runs none.
|
|
11
|
+
*/
|
|
12
|
+
export function buildStubHarnessRelay() {
|
|
13
|
+
const lineListeners: ((line: string) => void)[] = [];
|
|
14
|
+
const closeListeners: (() => void)[] = [];
|
|
15
|
+
const written: unknown[] = [];
|
|
16
|
+
let closed = false;
|
|
17
|
+
|
|
18
|
+
const relay: HarnessRelay = {
|
|
19
|
+
onLine: (listener) => {
|
|
20
|
+
lineListeners.push(listener);
|
|
21
|
+
},
|
|
22
|
+
onClose: (listener) => {
|
|
23
|
+
closeListeners.push(listener);
|
|
24
|
+
},
|
|
25
|
+
writeLine: (line) => {
|
|
26
|
+
written.push(JSON.parse(line));
|
|
27
|
+
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
},
|
|
30
|
+
close: () => {
|
|
31
|
+
closed = true;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
relay,
|
|
37
|
+
written,
|
|
38
|
+
isClosed: () => closed,
|
|
39
|
+
sendLine: (value: Readonly<Record<string, unknown>>) => {
|
|
40
|
+
for (const listener of lineListeners) {
|
|
41
|
+
listener(JSON.stringify(value));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
hangUp: () => {
|
|
45
|
+
for (const listener of closeListeners) {
|
|
46
|
+
listener();
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HeadlessRunEvents,
|
|
3
|
+
HeadlessRunRequest,
|
|
4
|
+
HeadlessRunner,
|
|
5
|
+
} from '../agents/agent-adapter';
|
|
6
|
+
|
|
7
|
+
interface StubHeadlessRun {
|
|
8
|
+
readonly request: HeadlessRunRequest;
|
|
9
|
+
|
|
10
|
+
// The events the daemon listens on, which the test calls to play the run.
|
|
11
|
+
readonly events: HeadlessRunEvents;
|
|
12
|
+
stopped: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface StubHeadlessRunner {
|
|
16
|
+
readonly runner: HeadlessRunner;
|
|
17
|
+
|
|
18
|
+
// Every run the daemon started, in order, and whether it stopped it.
|
|
19
|
+
readonly runs: readonly StubHeadlessRun[];
|
|
20
|
+
|
|
21
|
+
// Resolves with the run at the index, counting from zero, once the
|
|
22
|
+
// daemon starts it.
|
|
23
|
+
readonly waitForRun: (index: number) => Promise<StubHeadlessRun>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A headless runner for daemon tests that starts no process. Each run the
|
|
28
|
+
* daemon starts is recorded in `runs`, in order, and does nothing until
|
|
29
|
+
* the test plays it by calling the run's events: output, a finished turn,
|
|
30
|
+
* or a turn that needs the user. A stop marks the run stopped and plays
|
|
31
|
+
* nothing, as a killed turn reports nothing more.
|
|
32
|
+
*/
|
|
33
|
+
export function buildStubHeadlessRunner(): StubHeadlessRunner {
|
|
34
|
+
const runs: StubHeadlessRun[] = [];
|
|
35
|
+
const started: PromiseWithResolvers<StubHeadlessRun>[] = [];
|
|
36
|
+
|
|
37
|
+
const getStarted = (index: number) => {
|
|
38
|
+
started[index] ??= Promise.withResolvers<StubHeadlessRun>();
|
|
39
|
+
|
|
40
|
+
return started[index];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
runner: (request, events) => {
|
|
45
|
+
const run: StubHeadlessRun = { request, events, stopped: false };
|
|
46
|
+
|
|
47
|
+
runs.push(run);
|
|
48
|
+
|
|
49
|
+
getStarted(runs.length - 1).resolve(run);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
stop: () => {
|
|
53
|
+
run.stopped = true;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
runs,
|
|
58
|
+
waitForRun: (index) => getStarted(index).promise,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script of a stand-in `gh` whose repository listing waits for the
|
|
3
|
+
* test. `gh config …` prints `https`. Any other command touches
|
|
4
|
+
* `$HOME/gh-held` and waits while `$HOME/gh-hold` exists, then prints one
|
|
5
|
+
* repository, `me/dots`, in the JSON shape `gh repo list` prints.
|
|
6
|
+
*/
|
|
7
|
+
export function buildStubHeldGH(): string {
|
|
8
|
+
return `#!/bin/sh
|
|
9
|
+
case "$1" in
|
|
10
|
+
config) echo https ;;
|
|
11
|
+
*)
|
|
12
|
+
if [ -f "$HOME/gh-hold" ]; then
|
|
13
|
+
touch "$HOME/gh-held"
|
|
14
|
+
while [ -f "$HOME/gh-hold" ]; do sleep 0.05; done
|
|
15
|
+
fi
|
|
16
|
+
echo '[{"nameWithOwner":"me/dots","description":"dotfiles","isPrivate":false,"url":"https://github.com/me/dots","sshUrl":"git@github.com:me/dots.git"}]' ;;
|
|
17
|
+
esac
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TapClient } from '../daemon/daemon-context';
|
|
2
|
+
import type { InboxSource } from '../daemon/drain-inbox';
|
|
3
|
+
import { TapRegistry } from '../daemon/tap-registry';
|
|
4
|
+
import type { AgentSessionID } from '../shared/agent-session-id';
|
|
5
|
+
import type { SessionID } from '../shared/session-id';
|
|
6
|
+
import type { MessageRecord } from '../store/message-record';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An inbox source over a real, empty tap registry, standing in for the
|
|
10
|
+
* daemon's store. It holds every session under the given agent session id,
|
|
11
|
+
* and its pending-message read waits until the test answers it: `reading`
|
|
12
|
+
* settles once a read starts, `read` answers the read with the given
|
|
13
|
+
* messages whatever owner it asked for.
|
|
14
|
+
*/
|
|
15
|
+
export function buildStubHeldInboxSource(agentSessionID: AgentSessionID) {
|
|
16
|
+
const pending = Promise.withResolvers<MessageRecord[]>();
|
|
17
|
+
const reading = Promise.withResolvers<void>();
|
|
18
|
+
|
|
19
|
+
const source: InboxSource = {
|
|
20
|
+
taps: new TapRegistry<TapClient>(),
|
|
21
|
+
findLinkedOwner: (sessionID: SessionID) => ({ atcID: sessionID, agentSessionID }),
|
|
22
|
+
collectPendingMessages: () => {
|
|
23
|
+
reading.resolve();
|
|
24
|
+
|
|
25
|
+
return pending.promise;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
source,
|
|
31
|
+
reading: reading.promise,
|
|
32
|
+
read: (records: readonly MessageRecord[]) => {
|
|
33
|
+
pending.resolve([...records]);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ExecutionProvider, HarnessSpec, HostRequest } from '../daemon/execution-provider';
|
|
2
|
+
import { LocalPTYProvider } from '../daemon/local-pty-provider';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An execution provider that runs on this machine as the local
|
|
6
|
+
* pseudo-terminal provider does, except that each host preparation holds
|
|
7
|
+
* until the test calls `release` with that host, so a test can act while a
|
|
8
|
+
* harness is still starting. `prepares` holds the host of each preparation
|
|
9
|
+
* in the order it began, and `harnesses` each harness spec in the order it
|
|
10
|
+
* started.
|
|
11
|
+
*/
|
|
12
|
+
export function buildStubHeldProvider() {
|
|
13
|
+
const local = new LocalPTYProvider();
|
|
14
|
+
const gates = new Map<string, PromiseWithResolvers<void>>();
|
|
15
|
+
|
|
16
|
+
const prepares: string[] = [];
|
|
17
|
+
const harnesses: HarnessSpec[] = [];
|
|
18
|
+
|
|
19
|
+
const getGate = (host: string): PromiseWithResolvers<void> => {
|
|
20
|
+
const gate = gates.get(host) ?? Promise.withResolvers<void>();
|
|
21
|
+
|
|
22
|
+
gates.set(host, gate);
|
|
23
|
+
|
|
24
|
+
return gate;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const provider: ExecutionProvider = {
|
|
28
|
+
kind: local.kind,
|
|
29
|
+
remote: local.remote,
|
|
30
|
+
capabilities: local.capabilities,
|
|
31
|
+
prepareHost: async (request: HostRequest) => {
|
|
32
|
+
prepares.push(request.host);
|
|
33
|
+
|
|
34
|
+
await getGate(request.host).promise;
|
|
35
|
+
|
|
36
|
+
await local.prepareHost();
|
|
37
|
+
},
|
|
38
|
+
spawnHarness: (spec) => {
|
|
39
|
+
harnesses.push(spec);
|
|
40
|
+
|
|
41
|
+
return local.spawnHarness(spec);
|
|
42
|
+
},
|
|
43
|
+
transferArchive: local.transferArchive,
|
|
44
|
+
runCommand: local.runCommand,
|
|
45
|
+
suspendHost: local.suspendHost,
|
|
46
|
+
destroyHost: local.destroyHost,
|
|
47
|
+
dispose: local.dispose,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
provider,
|
|
52
|
+
prepares,
|
|
53
|
+
harnesses,
|
|
54
|
+
release: (host: string) => {
|
|
55
|
+
getGate(host).resolve();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script of a stand-in `zoxide` that lists no directories, and waits
|
|
3
|
+
* for the test first: while `$HOME/zoxide-hold` exists, it touches
|
|
4
|
+
* `$HOME/zoxide-held` and waits for the file to go.
|
|
5
|
+
*/
|
|
6
|
+
export function buildStubHeldZoxide(): string {
|
|
7
|
+
return `#!/bin/sh
|
|
8
|
+
if [ -f "$HOME/zoxide-hold" ]; then
|
|
9
|
+
touch "$HOME/zoxide-held"
|
|
10
|
+
while [ -f "$HOME/zoxide-hold" ]; do sleep 0.05; done
|
|
11
|
+
fi
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A host operation a test can hold open, for a host preparation or sleep
|
|
3
|
+
* that must stay in flight while the test changes something. Unarmed, the
|
|
4
|
+
* operation resolves at once, so the arrange steps before the hold pass
|
|
5
|
+
* through. Once armed, the next call resolves `entered` and every call
|
|
6
|
+
* waits until `release`; after the release, calls resolve at once again.
|
|
7
|
+
*/
|
|
8
|
+
export function buildStubHostHold() {
|
|
9
|
+
const entered = Promise.withResolvers<void>();
|
|
10
|
+
const released = Promise.withResolvers<void>();
|
|
11
|
+
let armed = false;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
hold: (): Promise<void> => {
|
|
15
|
+
if (!armed) {
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
entered.resolve();
|
|
20
|
+
|
|
21
|
+
return released.promise;
|
|
22
|
+
},
|
|
23
|
+
arm: () => {
|
|
24
|
+
armed = true;
|
|
25
|
+
},
|
|
26
|
+
entered: entered.promise,
|
|
27
|
+
release: () => {
|
|
28
|
+
released.resolve();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExecutionProvider } from '../daemon/execution-provider';
|
|
2
|
+
import { LocalPTYProvider } from '../daemon/local-pty-provider';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An execution provider of kind `imp-like` whose hosts can be put to sleep
|
|
6
|
+
* and destroyed, as an imp's can, while every harness, archive transfer, and
|
|
7
|
+
* command runs on this machine through the local terminal provider.
|
|
8
|
+
* Readying, suspending, and destroying a host each do nothing and settle at
|
|
9
|
+
* once.
|
|
10
|
+
*/
|
|
11
|
+
export function buildStubHostProvider(): ExecutionProvider {
|
|
12
|
+
const local = new LocalPTYProvider();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
kind: 'imp-like',
|
|
16
|
+
remote: false,
|
|
17
|
+
capabilities: { ...local.capabilities, suspend: true, destroy: true },
|
|
18
|
+
prepareHost: () => Promise.resolve(),
|
|
19
|
+
spawnHarness: local.spawnHarness,
|
|
20
|
+
transferArchive: local.transferArchive,
|
|
21
|
+
runCommand: local.runCommand,
|
|
22
|
+
suspendHost: () => Promise.resolve(),
|
|
23
|
+
destroyHost: () => Promise.resolve(),
|
|
24
|
+
dispose: local.dispose,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A log sink that records each line it is given, in order, in place of the
|
|
3
|
+
* daemon's stderr log: `log` takes a line and `lines` holds every line taken
|
|
4
|
+
* so far.
|
|
5
|
+
*/
|
|
6
|
+
export function buildStubLog() {
|
|
7
|
+
const lines: string[] = [];
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
lines,
|
|
11
|
+
log: (line: string): void => {
|
|
12
|
+
lines.push(line);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const CLI_PATH = join(import.meta.dir, '..', 'cli.ts');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The script of a stand-in `claude` for the stdio MCP suites. It appends its
|
|
7
|
+
* pid to `$HOME/stub-pids`, prints `FAKE_CLAUDE_UP args: ` and its
|
|
8
|
+
* arguments, and ends by echoing its input back until that input closes.
|
|
9
|
+
* Unless the file
|
|
10
|
+
* `$HOME/fake-claude-hold-start` exists, it first reports `SessionStart`
|
|
11
|
+
* through the real reporter with session id `fake-1` and transcript
|
|
12
|
+
* `$HOME/fake-transcript.jsonl`, and when `$HOME/fake-claude-note` exists it
|
|
13
|
+
* files that file's text as a report labelled `decision`.
|
|
14
|
+
*/
|
|
15
|
+
export function buildStubMCPClaude(): string {
|
|
16
|
+
const cli = `"${process.execPath}" "${CLI_PATH}"`;
|
|
17
|
+
|
|
18
|
+
return `#!/usr/bin/env bash
|
|
19
|
+
echo $$ >> "$HOME/stub-pids"
|
|
20
|
+
echo "FAKE_CLAUDE_UP args: $@"
|
|
21
|
+
if [ -f "$HOME/fake-claude-hold-start" ]; then exec cat; fi
|
|
22
|
+
printf '{"hook_event_name":"SessionStart","session_id":"fake-1","transcript_path":"'"$HOME"'/fake-transcript.jsonl"}' | ${cli} hook-report
|
|
23
|
+
if [ -f "$HOME/fake-claude-note" ]; then ${cli} report note --label decision < "$HOME/fake-claude-note"; fi
|
|
24
|
+
exec cat
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
|
|
3
|
+
const CLI_PATH = join(import.meta.dir, '..', 'cli.ts');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The script of a stand-in `grok` for the stdio MCP suites. It appends its
|
|
7
|
+
* pid to `$HOME/stub-pids`, prints `FAKE_GROK_UP args: ` and its arguments,
|
|
8
|
+
* reports `session_start` through the real reporter with session id
|
|
9
|
+
* `fake-grok-1` and its working directory, then echoes its input back until
|
|
10
|
+
* that input closes.
|
|
11
|
+
*/
|
|
12
|
+
export function buildStubMCPGrok(): string {
|
|
13
|
+
return `#!/usr/bin/env bash
|
|
14
|
+
echo $$ >> "$HOME/stub-pids"
|
|
15
|
+
echo "FAKE_GROK_UP args: $@"
|
|
16
|
+
printf '{"hookEventName":"session_start","sessionId":"fake-grok-1","cwd":"%s"}' "$PWD" | "${process.execPath}" "${CLI_PATH}" hook-report
|
|
17
|
+
exec cat
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The script of a stand-in `atc mcp` server on stdio that answers each line
|
|
3
|
+
* it reads with the next of `replies`, printed as given, whether JSON or
|
|
4
|
+
* not, and reads stdin without answering once the replies run out. It
|
|
5
|
+
* exits without a reply when stdin ends before a line arrives. Before
|
|
6
|
+
* it reads anything, it records its pid in the file at its own path with
|
|
7
|
+
* `.pid` appended, moving the file into place so it appears whole.
|
|
8
|
+
*/
|
|
9
|
+
export function buildStubMCPStdioServer(replies: readonly string[]): string {
|
|
10
|
+
const answers = replies
|
|
11
|
+
.map((reply) => `read -r _ || exit 0\nprintf '%s\\n' ${toShellLiteral(reply)}\n`)
|
|
12
|
+
.join('');
|
|
13
|
+
|
|
14
|
+
return `#!/usr/bin/env bash\necho $$ > "$0.pid.tmp"\nmv "$0.pid.tmp" "$0.pid"\n${answers}exec cat > /dev/null\n`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Quotes a value for the shell as one word, whatever it holds.
|
|
18
|
+
function toShellLiteral(value: string): string {
|
|
19
|
+
return `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A peer socket for tests that drive a daemon connection without a socket:
|
|
3
|
+
* while accepting, it takes every byte at once, as a peer with room does,
|
|
4
|
+
* and keeps what the daemon wrote; while not, it takes none and returns 0,
|
|
5
|
+
* as a socket whose kernel buffer is full does, so backpressure is a switch
|
|
6
|
+
* instead of a buffer race. It starts accepting, and `setAccepting` flips
|
|
7
|
+
* it. It ignores the end of the connection. `collectFrames`
|
|
8
|
+
* parses each complete line written so far as one protocol frame, in order.
|
|
9
|
+
* `waitForAnswer` resolves with the frame answering the request id once the
|
|
10
|
+
* daemon has written it, that id left out, so two answers compare whole.
|
|
11
|
+
*/
|
|
12
|
+
export function buildStubPeerSocket() {
|
|
13
|
+
const decoder = new TextDecoder();
|
|
14
|
+
const waiters = new Map<number, PromiseWithResolvers<Record<string, unknown>>>();
|
|
15
|
+
|
|
16
|
+
let accepting = true;
|
|
17
|
+
let written = '';
|
|
18
|
+
|
|
19
|
+
const collectFrames = (): Record<string, unknown>[] =>
|
|
20
|
+
written
|
|
21
|
+
.split('\n')
|
|
22
|
+
.slice(0, -1)
|
|
23
|
+
.map((line): Record<string, unknown> => toFrame(JSON.parse(line)));
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
socket: {
|
|
27
|
+
// oxlint-disable-next-line prefer-readonly-parameter-types -- a readonly view cannot satisfy the writer contract; the stub never mutates chunks
|
|
28
|
+
write: (data: Uint8Array): number => {
|
|
29
|
+
if (!accepting) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
written += decoder.decode(data, { stream: true });
|
|
34
|
+
|
|
35
|
+
// Frames are parsed only for a pending wait, so a stray line fails
|
|
36
|
+
// the read that looks at it rather than the daemon's write.
|
|
37
|
+
for (const frame of waiters.size === 0 ? [] : collectFrames()) {
|
|
38
|
+
const id = frame['id'];
|
|
39
|
+
const waiter = typeof id === 'number' ? waiters.get(id) : undefined;
|
|
40
|
+
|
|
41
|
+
if (waiter !== undefined) {
|
|
42
|
+
waiters.delete(Number(id));
|
|
43
|
+
waiter.resolve(buildAnswer(frame));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return data.length;
|
|
48
|
+
},
|
|
49
|
+
end: () => {},
|
|
50
|
+
},
|
|
51
|
+
setAccepting: (value: boolean) => {
|
|
52
|
+
accepting = value;
|
|
53
|
+
},
|
|
54
|
+
collectFrames,
|
|
55
|
+
waitForAnswer: (id: number): Promise<Record<string, unknown>> => {
|
|
56
|
+
const found = collectFrames().find((frame) => frame['id'] === id);
|
|
57
|
+
|
|
58
|
+
if (found !== undefined) {
|
|
59
|
+
return Promise.resolve(buildAnswer(found));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const waiter = Promise.withResolvers<Record<string, unknown>>();
|
|
63
|
+
|
|
64
|
+
waiters.set(id, waiter);
|
|
65
|
+
|
|
66
|
+
return waiter.promise;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toFrame(value: unknown): Record<string, unknown> {
|
|
72
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
73
|
+
throw new TypeError(`a protocol frame is a JSON object, not ${JSON.stringify(value)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Object.fromEntries(Object.entries(value));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildAnswer(frame: Readonly<Record<string, unknown>>): Record<string, unknown> {
|
|
80
|
+
return Object.fromEntries(Object.entries(frame).filter(([key]) => key !== 'id'));
|
|
81
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DroppedAnswer } from '../client/spawn-picker';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The client a spawn picker runs inside, reduced to the reactions it
|
|
5
|
+
* records: `counts` holds how many times the picker asked for a status
|
|
6
|
+
* draw, returned to the screen the flow came from, attached a session, and
|
|
7
|
+
* dropped an answer, and `dropped` holds the kind of each dropped answer
|
|
8
|
+
* in order. `countReactions` returns the sum of every count, for a daemon
|
|
9
|
+
* stand-in that waits on the picker to react. Attaching resolves at once.
|
|
10
|
+
*/
|
|
11
|
+
export function buildStubPickerHost() {
|
|
12
|
+
const counts = { renders: 0, exits: 0, attached: 0, drops: 0 };
|
|
13
|
+
const dropped: DroppedAnswer[] = [];
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
counts,
|
|
17
|
+
dropped,
|
|
18
|
+
countReactions: (): number => counts.renders + counts.exits + counts.attached + counts.drops,
|
|
19
|
+
scheduleStatus: (): void => {
|
|
20
|
+
counts.renders += 1;
|
|
21
|
+
},
|
|
22
|
+
toBase: (): void => {
|
|
23
|
+
counts.exits += 1;
|
|
24
|
+
},
|
|
25
|
+
attach: (): Promise<void> => {
|
|
26
|
+
counts.attached += 1;
|
|
27
|
+
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
},
|
|
30
|
+
onDropAnswer: (kind: DroppedAnswer): void => {
|
|
31
|
+
counts.drops += 1;
|
|
32
|
+
|
|
33
|
+
dropped.push(kind);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentAdapter } from '../agents/agent-adapter';
|
|
2
|
+
import { buildStubBrokeredAgentAdapter } from './build-stub-brokered-agent-adapter';
|
|
3
|
+
|
|
4
|
+
interface StubProxiedAgentAdapterConfig {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* An agent adapter for daemon tests that takes the GLM credential from
|
|
10
|
+
* impd's broker, as the brokered stand-in does, and that always starts
|
|
11
|
+
* behind the broker, but whose guest spawn plans a proxy variable of its
|
|
12
|
+
* own: `https_proxy` set to http://proxy.example:3128, beside a `sleep 30`.
|
|
13
|
+
* Every other member is the brokered stand-in's.
|
|
14
|
+
*/
|
|
15
|
+
export function buildStubProxiedAgentAdapter(config: StubProxiedAgentAdapterConfig): AgentAdapter {
|
|
16
|
+
return {
|
|
17
|
+
...buildStubBrokeredAgentAdapter({
|
|
18
|
+
id: config.id,
|
|
19
|
+
brokerRequired: true,
|
|
20
|
+
isSelected: () => true,
|
|
21
|
+
}),
|
|
22
|
+
planGuestSpawn: () => ({
|
|
23
|
+
bin: 'sleep',
|
|
24
|
+
args: ['30'],
|
|
25
|
+
files: {},
|
|
26
|
+
env: { https_proxy: 'http://proxy.example:3128' },
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|