@aeolus-fleet/trierarch 0.33.1 → 0.35.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/README.md +7 -4
- package/dist/adapters/claude-code-setup.d.ts +11 -0
- package/dist/adapters/claude-code-setup.js +60 -0
- package/dist/adapters/claude-code-setup.js.map +1 -1
- package/dist/adapters/claude-code-usage.d.ts +2 -0
- package/dist/adapters/claude-code-usage.js +16 -12
- package/dist/adapters/claude-code-usage.js.map +1 -1
- package/dist/adapters/claude-code.d.ts +21 -18
- package/dist/adapters/claude-code.js +46 -36
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/clone-workspace.js +4 -0
- package/dist/adapters/clone-workspace.js.map +1 -1
- package/dist/adapters/codex-setup.d.ts +2 -0
- package/dist/adapters/codex-setup.js +6 -0
- package/dist/adapters/codex-setup.js.map +1 -1
- package/dist/adapters/codex-usage.d.ts +2 -0
- package/dist/adapters/codex-usage.js +16 -11
- package/dist/adapters/codex-usage.js.map +1 -1
- package/dist/adapters/codex.d.ts +2 -0
- package/dist/adapters/codex.js +31 -3
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/container.d.ts +36 -15
- package/dist/adapters/container.js +72 -93
- package/dist/adapters/container.js.map +1 -1
- package/dist/adapters/copilot-detector.d.ts +13 -0
- package/dist/adapters/copilot-detector.js +62 -0
- package/dist/adapters/copilot-detector.js.map +1 -0
- package/dist/adapters/copilot.d.ts +79 -0
- package/dist/adapters/copilot.js +187 -0
- package/dist/adapters/copilot.js.map +1 -0
- package/dist/adapters/core-version.d.ts +1 -1
- package/dist/adapters/core-version.js +1 -1
- package/dist/adapters/detectors.js +6 -0
- package/dist/adapters/detectors.js.map +1 -1
- package/dist/adapters/files.d.ts +16 -0
- package/dist/adapters/files.js +5 -0
- package/dist/adapters/files.js.map +1 -1
- package/dist/adapters/harnesses.d.ts +3 -2
- package/dist/adapters/harnesses.js +21 -9
- package/dist/adapters/harnesses.js.map +1 -1
- package/dist/adapters/hold-runtime.d.ts +19 -5
- package/dist/adapters/hold-runtime.js +23 -10
- package/dist/adapters/hold-runtime.js.map +1 -1
- package/dist/adapters/hold.d.ts +12 -0
- package/dist/adapters/hold.js +66 -31
- package/dist/adapters/hold.js.map +1 -1
- package/dist/adapters/json-state.js +5 -0
- package/dist/adapters/json-state.js.map +1 -1
- package/dist/adapters/key-files.d.ts +16 -0
- package/dist/adapters/key-files.js +28 -0
- package/dist/adapters/key-files.js.map +1 -0
- package/dist/adapters/key-states.d.ts +21 -0
- package/dist/adapters/key-states.js +43 -0
- package/dist/adapters/key-states.js.map +1 -0
- package/dist/adapters/model-logins.d.ts +21 -0
- package/dist/adapters/model-logins.js +48 -0
- package/dist/adapters/model-logins.js.map +1 -0
- package/dist/adapters/model-proxy.d.ts +32 -0
- package/dist/adapters/model-proxy.js +171 -0
- package/dist/adapters/model-proxy.js.map +1 -0
- package/dist/adapters/paths.d.ts +4 -0
- package/dist/adapters/paths.js +2 -0
- package/dist/adapters/paths.js.map +1 -1
- package/dist/adapters/plugin.d.ts +11 -8
- package/dist/adapters/plugin.js +47 -13
- package/dist/adapters/plugin.js.map +1 -1
- package/dist/adapters/proxy-runtime.d.ts +31 -0
- package/dist/adapters/proxy-runtime.js +100 -0
- package/dist/adapters/proxy-runtime.js.map +1 -0
- package/dist/adapters/proxy-tokens.d.ts +25 -0
- package/dist/adapters/proxy-tokens.js +67 -0
- package/dist/adapters/proxy-tokens.js.map +1 -0
- package/dist/adapters/rest-fleet.js +3 -5
- package/dist/adapters/rest-fleet.js.map +1 -1
- package/dist/adapters/runtime-adapters.d.ts +11 -6
- package/dist/adapters/runtime-adapters.js +25 -11
- package/dist/adapters/runtime-adapters.js.map +1 -1
- package/dist/adapters/service.d.ts +3 -1
- package/dist/adapters/service.js +6 -2
- package/dist/adapters/service.js.map +1 -1
- package/dist/adapters/ship-sockets.d.ts +1 -1
- package/dist/adapters/ship-sockets.js +2 -2
- package/dist/adapters/ship-sockets.js.map +1 -1
- package/dist/adapters/tokens-per-login.d.ts +8 -0
- package/dist/adapters/tokens-per-login.js +15 -0
- package/dist/adapters/tokens-per-login.js.map +1 -0
- package/dist/adapters/trust.d.ts +2 -1
- package/dist/adapters/trust.js +4 -1
- package/dist/adapters/trust.js.map +1 -1
- package/dist/adapters/usage.d.ts +2 -0
- package/dist/adapters/usage.js +1 -1
- package/dist/adapters/usage.js.map +1 -1
- package/dist/adapters/vendor-answers.d.ts +6 -0
- package/dist/adapters/vendor-answers.js +38 -0
- package/dist/adapters/vendor-answers.js.map +1 -0
- package/dist/adapters/vendor-key-check.d.ts +23 -0
- package/dist/adapters/vendor-key-check.js +36 -0
- package/dist/adapters/vendor-key-check.js.map +1 -0
- package/dist/bin/aeolus-trierarch.js +7 -2
- package/dist/bin/aeolus-trierarch.js.map +1 -1
- package/dist/cli/add.d.ts +2 -2
- package/dist/cli/add.js +1 -1
- package/dist/cli/add.js.map +1 -1
- package/dist/cli/config-check.js +1 -1
- package/dist/cli/config-check.js.map +1 -1
- package/dist/cli/hold.js +13 -4
- package/dist/cli/hold.js.map +1 -1
- package/dist/cli/init.js +19 -2
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/login.d.ts +72 -0
- package/dist/cli/login.js +114 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/main.js +89 -8
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/proxy.d.ts +2 -0
- package/dist/cli/proxy.js +58 -0
- package/dist/cli/proxy.js.map +1 -0
- package/dist/cli/run.js +31 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/upgrade.d.ts +5 -3
- package/dist/cli/upgrade.js +9 -3
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/core/argo-reports.d.ts +10 -0
- package/dist/core/argo-reports.js +17 -0
- package/dist/core/argo-reports.js.map +1 -1
- package/dist/core/check-session-runtime.js +3 -1
- package/dist/core/check-session-runtime.js.map +1 -1
- package/dist/core/check-settings.d.ts +2 -0
- package/dist/core/check-settings.js +6 -1
- package/dist/core/check-settings.js.map +1 -1
- package/dist/core/entry.d.ts +14 -0
- package/dist/core/entry.js.map +1 -1
- package/dist/core/login.d.ts +49 -0
- package/dist/core/login.js +24 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/ports.d.ts +39 -4
- package/dist/core/read-usage.d.ts +2 -1
- package/dist/core/read-usage.js +6 -3
- package/dist/core/read-usage.js.map +1 -1
- package/dist/core/reconciler.d.ts +5 -0
- package/dist/core/reconciler.js +12 -7
- package/dist/core/reconciler.js.map +1 -1
- package/dist/core/report-self.d.ts +6 -2
- package/dist/core/report-self.js +34 -8
- package/dist/core/report-self.js.map +1 -1
- package/dist/core/run-pass.d.ts +6 -1
- package/dist/core/run-pass.js +84 -14
- package/dist/core/run-pass.js.map +1 -1
- package/package.json +3 -4
- package/dist/adapters/claude-code-screen.d.ts +0 -1
- package/dist/adapters/claude-code-screen.js +0 -22
- package/dist/adapters/claude-code-screen.js.map +0 -1
- package/dist/adapters/held-command.d.ts +0 -13
- package/dist/adapters/held-command.js +0 -16
- package/dist/adapters/held-command.js.map +0 -1
- package/dist/adapters/launch-screen.d.ts +0 -14
- package/dist/adapters/launch-screen.js +0 -5
- package/dist/adapters/launch-screen.js.map +0 -1
- package/dist/adapters/stop-turn.d.ts +0 -13
- package/dist/adapters/stop-turn.js +0 -26
- package/dist/adapters/stop-turn.js.map +0 -1
- package/dist/adapters/tmux.d.ts +0 -13
- package/dist/adapters/tmux.js +0 -33
- package/dist/adapters/tmux.js.map +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { mkdir,
|
|
2
|
-
import { tmpdir } from 'node:os';
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import { idSchema } from '@aeolus-fleet/common';
|
|
5
|
-
import {
|
|
4
|
+
import { createHoldRuntime } from './hold-runtime.js';
|
|
6
5
|
import { shipHome } from './paths.js';
|
|
7
6
|
import { runCommand } from './run-command.js';
|
|
8
|
-
import { stopTurn } from './stop-turn.js';
|
|
9
7
|
const PREFIX = 'trierarch-';
|
|
10
8
|
const OWNER_LABEL = 'aeolus.trierarch';
|
|
11
9
|
const CONTAINER_OWNER = 'aeolus-trierarch';
|
|
@@ -15,30 +13,28 @@ export const MODEL_TOKEN_VARIABLE = 'CLAUDE_CODE_OAUTH_TOKEN';
|
|
|
15
13
|
export const SESSION_HOME = '/home/ubuntu';
|
|
16
14
|
/** The image's session user (ubuntu), whose subordinate ids the nested Podman maps. */
|
|
17
15
|
const SESSION_USER = 'uid=1000,gid=1000';
|
|
18
|
-
/** The tmux session inside each container: one per container, so one name serves. */
|
|
19
|
-
const TMUX_TARGET = '=session:';
|
|
20
16
|
/**
|
|
21
17
|
* The most processes and threads a container may hold, a hard cap beside
|
|
22
18
|
* memory and cpus (decision 0039): room for Claude Code, a test run with its
|
|
23
19
|
* workers, a browser and a nested Postgres, and none for a runaway fork.
|
|
24
20
|
*/
|
|
25
21
|
const PIDS_LIMIT = 4096;
|
|
26
|
-
/** How many lines back a screen is read, as on the host. */
|
|
27
|
-
const SCREEN_LINES = 200;
|
|
28
22
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* starts with the shell and is respawned with the command once remain-on-exit
|
|
33
|
-
* is set, so even a command that ends at once leaves its exit to see.
|
|
23
|
+
* Where a container reaches the host's loopback, and so the model proxy
|
|
24
|
+
* (headless H16): the host's address in Podman's slirp4netns network, which
|
|
25
|
+
* maps it to the host's loopback when the network allows that.
|
|
34
26
|
*/
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
export const MODEL_PROXY_HOST = '10.0.2.2';
|
|
28
|
+
/** The network that lets a session on an API key reach the host's loopback, and nothing more of the host than the network gives any container. */
|
|
29
|
+
const HOST_LOOPBACK_NETWORK = 'slirp4netns:allow_host_loopback=true';
|
|
30
|
+
/** Where a session in a container reaches the model proxy the trierarch reaches at the url, on the host's loopback. */
|
|
31
|
+
export function containerProxyUrl(url) {
|
|
32
|
+
const at = new URL(url);
|
|
33
|
+
at.hostname = MODEL_PROXY_HOST;
|
|
34
|
+
return at.origin;
|
|
35
|
+
}
|
|
36
|
+
/** The env file in the session's own folder, beside its hold's files. */
|
|
37
|
+
const ENV_FILE = 'session.env';
|
|
42
38
|
function containerName(shipId) {
|
|
43
39
|
return `${PREFIX}${shipId}`;
|
|
44
40
|
}
|
|
@@ -54,26 +50,32 @@ function envFileOf(env) {
|
|
|
54
50
|
.join('');
|
|
55
51
|
}
|
|
56
52
|
/**
|
|
57
|
-
* The container session runtime (decision 0039): one container
|
|
58
|
-
* named after its ship id,
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
53
|
+
* The container session runtime (decision 0039, headless H18): one container
|
|
54
|
+
* per ship, named after its ship id, run by the ship's hold on the host as
|
|
55
|
+
* `run --rm -i`, attached: the hold holds the harness's input and output
|
|
56
|
+
* through the runtime, so a line sent, an interrupt and the events are the
|
|
57
|
+
* hold's, as on the host, and the container lives as long as the hold, a
|
|
58
|
+
* signal to it reaching the harness through the image's tini. A container an
|
|
59
|
+
* older trierarch started detached is listed as of another version until its
|
|
60
|
+
* ship starts again (H19). Rootless only, with hard memory and cpu caps and no
|
|
61
|
+
* restart policy: the trierarch starts a dead session again itself. The
|
|
62
|
+
* container sees its folder, its ship's own home, as its home, and its ship's
|
|
63
|
+
* socket folder, at its own path, and nothing else of the host, never the
|
|
64
|
+
* runtime's socket. The home is made, for its owner alone, when the ship has
|
|
65
|
+
* none yet.
|
|
67
66
|
*
|
|
68
67
|
* The model token and the session's environment reach the container through
|
|
69
|
-
* an env file
|
|
70
|
-
* command line (#445).
|
|
71
|
-
*
|
|
72
|
-
*
|
|
68
|
+
* an env file in the session's own folder, for its owner alone, never a
|
|
69
|
+
* command line (#445). A session on an API key gets no model token, the
|
|
70
|
+
* subscription's login: its proxy token, which its hold holds in the
|
|
71
|
+
* environment it runs the container with, is passed on by its variable's name
|
|
72
|
+
* alone, and the container reaches the host's loopback, where the model proxy
|
|
73
|
+
* listens (headless H16, api keys K7). It is kept while the session runs, since the hold runs
|
|
74
|
+
* the container again when it starts Claude Code again (H12), and goes with
|
|
75
|
+
* the session's folder when it stops.
|
|
73
76
|
*/
|
|
74
77
|
export function createContainerRuntime(options) {
|
|
75
78
|
const owner = options.owner ?? CONTAINER_OWNER;
|
|
76
|
-
const launch = options.launch ?? [];
|
|
77
79
|
const run = options.run ?? runCommand;
|
|
78
80
|
const runtime = async (args) => run(options.binary, { args });
|
|
79
81
|
const must = async (args) => {
|
|
@@ -83,22 +85,8 @@ export function createContainerRuntime(options) {
|
|
|
83
85
|
}
|
|
84
86
|
return result.stdout;
|
|
85
87
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await runtime(['kill', containerName(shipId)]);
|
|
89
|
-
await runtime(['rm', '--force', containerName(shipId)]);
|
|
90
|
-
};
|
|
91
|
-
const tmux = (shipId, args) => ['exec', containerName(shipId), 'tmux', ...args];
|
|
92
|
-
const screen = async (shipId) => {
|
|
93
|
-
const shown = await runtime(tmux(shipId, ['capture-pane', '-p', '-J', '-S', `-${String(SCREEN_LINES)}`, '-t', TMUX_TARGET]));
|
|
94
|
-
if (shown.status === 0) {
|
|
95
|
-
return shown.stdout;
|
|
96
|
-
}
|
|
97
|
-
// An exited container printed its last screen as it ended.
|
|
98
|
-
const logged = await runtime(['logs', containerName(shipId)]);
|
|
99
|
-
return logged.status === 0 ? logged.stdout : '';
|
|
100
|
-
};
|
|
101
|
-
return {
|
|
88
|
+
/** The ship's container: stopped before its hold, so the hold ends with it, and listed where no hold holds the ship. */
|
|
89
|
+
const containers = {
|
|
102
90
|
list: async () => {
|
|
103
91
|
const listed = await must(['ps', '--all', '--filter', `label=${OWNER_LABEL}=${owner}`, '--format', '{{.Names}} {{.State}}']);
|
|
104
92
|
return listed.split('\n').flatMap((line) => {
|
|
@@ -107,25 +95,36 @@ export function createContainerRuntime(options) {
|
|
|
107
95
|
return name.startsWith(PREFIX) && shipId.success ? [{ shipId: shipId.data, status: state === 'running' ? 'running' : 'exited' }] : [];
|
|
108
96
|
});
|
|
109
97
|
},
|
|
110
|
-
stop
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
stop: async (shipId) => {
|
|
99
|
+
// Kill, then remove: a remove alone waits out the stop timeout on podman.
|
|
100
|
+
await runtime(['kill', containerName(shipId)]);
|
|
101
|
+
await runtime(['rm', '--force', containerName(shipId)]);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const holds = createHoldRuntime({
|
|
105
|
+
sessions: options.sessions,
|
|
106
|
+
program: options.program,
|
|
107
|
+
version: options.version,
|
|
108
|
+
platform: options.platform,
|
|
109
|
+
launch: options.launch ?? [],
|
|
110
|
+
beside: containers,
|
|
111
|
+
heldAs: async ({ shipId, folder, command, env = {}, readOnly = [], proxyToken }, files) => {
|
|
112
|
+
const login = proxyToken === undefined ? { [MODEL_TOKEN_VARIABLE]: (await readFile(options.tokenFile, 'utf8')).trim() } : {};
|
|
113
|
+
const content = envFileOf({ ...env, ...login });
|
|
115
114
|
const home = shipHome(options.homes, shipId);
|
|
116
115
|
const shipSockets = join(options.sockets, shipId);
|
|
117
116
|
// The runtime mounts only a folder that exists, and the ship's socket may not have made it yet.
|
|
118
117
|
await mkdir(shipSockets, { recursive: true, mode: 0o700 });
|
|
119
118
|
await mkdir(home, { recursive: true, mode: 0o700 });
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
...launch,
|
|
119
|
+
await mkdir(files.folder, { recursive: true, mode: 0o700 });
|
|
120
|
+
const envFile = join(files.folder, ENV_FILE);
|
|
121
|
+
await writeFile(envFile, content, { mode: 0o600 });
|
|
122
|
+
return {
|
|
123
|
+
command: [
|
|
126
124
|
options.binary,
|
|
127
125
|
'run',
|
|
128
|
-
'--
|
|
126
|
+
'--rm',
|
|
127
|
+
'-i',
|
|
129
128
|
'--name',
|
|
130
129
|
containerName(shipId),
|
|
131
130
|
'--label',
|
|
@@ -159,42 +158,22 @@ export function createContainerRuntime(options) {
|
|
|
159
158
|
...readOnly.flatMap((path) => ['--volume', `${path}:${path}:ro`]),
|
|
160
159
|
'--env',
|
|
161
160
|
`HOME=${SESSION_HOME}`,
|
|
161
|
+
...(proxyToken === undefined ? [] : ['--env', proxyToken.variable, '--network', HOST_LOOPBACK_NETWORK]),
|
|
162
162
|
'--workdir',
|
|
163
163
|
folder,
|
|
164
164
|
'--env-file',
|
|
165
165
|
envFile,
|
|
166
166
|
options.image,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
];
|
|
173
|
-
const started = await run(program, { args });
|
|
174
|
-
if (started.status !== 0) {
|
|
175
|
-
throw new Error(`${options.binary} run failed: ${started.stderr.trim()}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
finally {
|
|
179
|
-
await rm(secrets, { recursive: true, force: true });
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
// The line typed, then Enter, as a person types it in the session's terminal.
|
|
183
|
-
send: async ({ shipId, line }) => {
|
|
184
|
-
await must(tmux(shipId, ['send-keys', '-t', TMUX_TARGET, '-l', line]));
|
|
185
|
-
await must(tmux(shipId, ['send-keys', '-t', TMUX_TARGET, 'Enter']));
|
|
186
|
-
},
|
|
187
|
-
interrupt: async (shipId) => {
|
|
188
|
-
await stopTurn({ escape: async () => {
|
|
189
|
-
await must(tmux(shipId, ['send-keys', '-t', TMUX_TARGET, 'Escape']));
|
|
190
|
-
}, screen: () => screen(shipId) });
|
|
167
|
+
...command,
|
|
168
|
+
],
|
|
169
|
+
// The session's environment reaches the container through the env file alone.
|
|
170
|
+
env: {},
|
|
171
|
+
};
|
|
191
172
|
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
// The container's first process as the host sees it: every process of the session runs under it; 0 once it runs no more.
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
...holds,
|
|
176
|
+
// The container's first process as the host sees it: every process of the session runs under it; none once it runs no more.
|
|
198
177
|
sessionRootOf: async (shipId) => {
|
|
199
178
|
const inspected = await runtime(['inspect', '--format', '{{.State.Pid}}', containerName(shipId)]);
|
|
200
179
|
const pid = Number(inspected.stdout.trim());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/adapters/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/adapters/container.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HarnessDetector } from '../core/ports.js';
|
|
2
|
+
import type { CommandResult } from './run-command.js';
|
|
3
|
+
export declare const AUTO_MODEL = "auto";
|
|
4
|
+
export declare function createCopilotDetector(deps: {
|
|
5
|
+
run: (command: string, options: {
|
|
6
|
+
args: readonly string[];
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}) => Promise<CommandResult>;
|
|
9
|
+
/** Copilot's own settings, `settings.json` in COPILOT_HOME or `~/.copilot`; undefined when there are none. */
|
|
10
|
+
readSettings: () => Promise<string | undefined>;
|
|
11
|
+
/** The program to run; `copilot` unless a test runs another. */
|
|
12
|
+
program?: string;
|
|
13
|
+
}): HarnessDetector;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { trierarchNameSchema } from '@aeolus-fleet/common';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* Detecting Copilot (docs/trierarch.md, "Detected options"): its version from
|
|
5
|
+
* `copilot --version`, and its models from the model setting `copilot help
|
|
6
|
+
* config` documents, offline, with `auto`, on which Copilot picks the model
|
|
7
|
+
* per request (CC9). No model is probed: every Copilot prompt costs a
|
|
8
|
+
* premium request, so none is confirmed, and a model this login may not use
|
|
9
|
+
* is refused in its session's launch window, as Copilot says it then. The
|
|
10
|
+
* default is the model Copilot's own settings name when it is declared, else
|
|
11
|
+
* `auto`, on which Copilot starts. Copilot has an effort flag, but its levels
|
|
12
|
+
* differ per model, so no effort is declared.
|
|
13
|
+
*/
|
|
14
|
+
/** The longest --version and the help may take: both are read offline. */
|
|
15
|
+
const QUICK_TIMEOUT_MS = 10_000;
|
|
16
|
+
const MODEL_FLAG = '--model';
|
|
17
|
+
export const AUTO_MODEL = 'auto';
|
|
18
|
+
/** The model setting's entry in `copilot help config`, up to the blank line that ends it. */
|
|
19
|
+
const MODEL_SETTING = /^ {2}`model`:[^\n]*\n((?: {4}- "[^"]+"\n?)+)/m;
|
|
20
|
+
const LISTED_MODEL = /- "([^"]+)"/g;
|
|
21
|
+
const settingsSchema = z.object({ model: z.string() });
|
|
22
|
+
/** The models the help lists for the model setting, in its order. */
|
|
23
|
+
function listedModelsOf(result) {
|
|
24
|
+
const listed = result.status === 0 ? MODEL_SETTING.exec(result.stdout)?.[1] : undefined;
|
|
25
|
+
return [...(listed ?? '').matchAll(LISTED_MODEL)].flatMap((match) => (match[1] === undefined ? [] : [match[1]]));
|
|
26
|
+
}
|
|
27
|
+
/** The model Copilot's settings name, when they are JSON naming one. */
|
|
28
|
+
function configuredModelOf(settings) {
|
|
29
|
+
try {
|
|
30
|
+
return settingsSchema.safeParse(JSON.parse(settings ?? '')).data?.model;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Settings that are no JSON name no model.
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function createCopilotDetector(deps) {
|
|
38
|
+
const program = deps.program ?? 'copilot';
|
|
39
|
+
return {
|
|
40
|
+
version: async () => {
|
|
41
|
+
const result = await deps.run(program, { args: ['--version'], timeoutMs: QUICK_TIMEOUT_MS }).catch(() => undefined);
|
|
42
|
+
// "GitHub Copilot CLI 1.0.105." on its first line.
|
|
43
|
+
return result?.status === 0 ? /(\d+\.\d+\.\d+)/.exec(result.stdout.split('\n')[0] ?? '')?.[1] : undefined;
|
|
44
|
+
},
|
|
45
|
+
detect: async ({ version, now }) => {
|
|
46
|
+
const help = await deps.run(program, { args: ['help', 'config'], timeoutMs: QUICK_TIMEOUT_MS }).catch(() => ({ status: 1, stdout: '', stderr: '' }));
|
|
47
|
+
const listed = listedModelsOf(help).filter((model) => trierarchNameSchema.safeParse(model).success && model !== AUTO_MODEL);
|
|
48
|
+
const models = [...listed, AUTO_MODEL];
|
|
49
|
+
const configured = configuredModelOf(await deps.readSettings());
|
|
50
|
+
const defaultModel = configured !== undefined && models.includes(configured) ? configured : AUTO_MODEL;
|
|
51
|
+
const values = Object.fromEntries(models.map((model) => [model, [MODEL_FLAG, model]]));
|
|
52
|
+
return {
|
|
53
|
+
version,
|
|
54
|
+
detectedAt: now,
|
|
55
|
+
confirmedAt: null,
|
|
56
|
+
options: { model: { values, default: defaultModel } },
|
|
57
|
+
...(listed.length === 0 && { problem: `Copilot ${version} names no model in copilot help config, so this machine declares auto only` }),
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=copilot-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-detector.js","sourceRoot":"","sources":["../../src/adapters/copilot-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { TrierarchConfiguration } from '@aeolus-fleet/common';
|
|
2
|
+
import type { AdapterFlag, HarnessPort, SessionRuntime } from '../core/ports.js';
|
|
3
|
+
import { type CommandPart } from './command-line.js';
|
|
4
|
+
import { type AeolusPlugin } from './plugin-identity.js';
|
|
5
|
+
/**
|
|
6
|
+
* The GitHub Copilot CLI as a harness (docs/architecture.md, "First
|
|
7
|
+
* adapters"; design harnesses/cursor-copilot, CC3, CC4, CC6, CC10), headless:
|
|
8
|
+
* one `copilot -p` per turn in the folder, as Codex runs one `codex exec`, its
|
|
9
|
+
* output as JSON lines, every tool allowed and its question tool off, so a
|
|
10
|
+
* turn asks nothing. Every later turn resumes the session by the id the last
|
|
11
|
+
* turn's result named; a restart too, since `--continue` takes the latest
|
|
12
|
+
* session of the machine, whatever its folder (Copilot CLI 1.0.105). Between
|
|
13
|
+
* turns no Copilot process runs. Copilot signs in with the machine's own
|
|
14
|
+
* login, which the hold's clean environment leaves alone: no GitHub token
|
|
15
|
+
* reaches a session, and no API key (CC6). The identity and the turn marker
|
|
16
|
+
* go through the aeolus-fleet plugin Copilot installed, whose hooks run in
|
|
17
|
+
* `copilot -p` as in a terminal (S3).
|
|
18
|
+
*
|
|
19
|
+
* A failure before the model call (no login, a refused model, a session it
|
|
20
|
+
* cannot resume) fires no hook and writes no JSON line, only its error on
|
|
21
|
+
* stderr and exit status 1: the hold keeps Copilot's stderr lines among the
|
|
22
|
+
* events, wrapped as `aeolus.stderr`, and the launch window reads them as it
|
|
23
|
+
* reads `session.error`, which a failed model call writes.
|
|
24
|
+
*/
|
|
25
|
+
/** The Copilot CLI builds whose hook roles the aeolus-fleet plugin is proven on (design harnesses/cursor-copilot, S3). */
|
|
26
|
+
export declare const COPILOT_PROVEN_VERSIONS: readonly string[];
|
|
27
|
+
/** The prompt of a restarted session: Copilot has no wake command, so the start context and the crew-a-ship skill say the rest. */
|
|
28
|
+
export declare const WAKE_PROMPT = "Aeolus started this session again: go on crewing this folder's Aeolus ship as the crew-a-ship skill says.";
|
|
29
|
+
/** The line the hold writes for each line Copilot writes on stderr, so the launch window reads what Copilot says before any event. */
|
|
30
|
+
export declare const STDERR_EVENT = "aeolus.stderr";
|
|
31
|
+
export declare const COPILOT_ADAPTER_FLAGS: readonly AdapterFlag[];
|
|
32
|
+
/** The flags of Copilot that are risky: beyond every tool, which every launch allows, they open every path and every URL (#326). */
|
|
33
|
+
export declare const COPILOT_RISKY_FLAGS: readonly string[];
|
|
34
|
+
/** `copilot`, the configured flags, JSON output, every tool allowed and no question tool, then the session to resume when there is one, and the prompt last as the value of --prompt, so it never reads as a flag. */
|
|
35
|
+
export declare function copilotCommandLine(at: {
|
|
36
|
+
flags: readonly string[];
|
|
37
|
+
prompt: string;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
program?: string;
|
|
40
|
+
}): CommandPart[];
|
|
41
|
+
/** The command of a later turn (CC4): the session's start command, resuming the session by the id given, with the line as its prompt. */
|
|
42
|
+
export declare function copilotTurnCommand(at: {
|
|
43
|
+
start: readonly string[];
|
|
44
|
+
sessionId: string;
|
|
45
|
+
line: string;
|
|
46
|
+
}): string[];
|
|
47
|
+
/** The line the hold keeps for a line Copilot wrote on stderr. */
|
|
48
|
+
export declare function copilotStderrEvent(line: string): string;
|
|
49
|
+
/** The session id a line of Copilot's output names: its result's; none for any other line. */
|
|
50
|
+
export declare function copilotSessionIdOf(line: string): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Why Copilot's start failed, from its last error (headless H13): the model it
|
|
53
|
+
* refused when that is the model it launched with (or any, when it launched
|
|
54
|
+
* with none), or else the error's message. Tested on output captured from
|
|
55
|
+
* Copilot CLI 1.0.105 (test/logs).
|
|
56
|
+
*/
|
|
57
|
+
export declare function copilotErrorIn(at: {
|
|
58
|
+
events: readonly string[];
|
|
59
|
+
model?: string;
|
|
60
|
+
}): {
|
|
61
|
+
kind: 'refused';
|
|
62
|
+
model: string;
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'cause';
|
|
65
|
+
cause: string;
|
|
66
|
+
} | undefined;
|
|
67
|
+
export declare function createCopilotHarness(options: {
|
|
68
|
+
configuration: TrierarchConfiguration;
|
|
69
|
+
plugin: AeolusPlugin;
|
|
70
|
+
sessions: Pick<SessionRuntime, 'start' | 'send' | 'interrupt' | 'events'>;
|
|
71
|
+
/** Copilot's version as detection last found it, read each time: a build its plugin is not proven on is not offered (CC3). */
|
|
72
|
+
detectedVersion: () => string | undefined;
|
|
73
|
+
/** Whether sessions run in containers, where Copilot is not proven (CC10). */
|
|
74
|
+
isInContainer: boolean;
|
|
75
|
+
/** The trierarch's COPILOT_HOME, which its sessions get so they use the login and plugin it found; none when it has none. */
|
|
76
|
+
copilotHome?: string;
|
|
77
|
+
/** The program to run; `copilot` unless a test runs another. */
|
|
78
|
+
command?: string;
|
|
79
|
+
}): HarnessPort;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { effectiveFlags } from '../core/effective-flags.js';
|
|
3
|
+
import { firstPromptOf, wakePromptOf } from '../core/no-terminal-questions.js';
|
|
4
|
+
import { LAUNCH_VARIABLE } from './claude-code.js';
|
|
5
|
+
import { partsWithWords, wordsOf } from './command-line.js';
|
|
6
|
+
import { parsedLine } from './hold-protocol.js';
|
|
7
|
+
import { createLaunchMarks } from './launch-marker.js';
|
|
8
|
+
import { createPluginIdentity } from './plugin-identity.js';
|
|
9
|
+
/**
|
|
10
|
+
* The GitHub Copilot CLI as a harness (docs/architecture.md, "First
|
|
11
|
+
* adapters"; design harnesses/cursor-copilot, CC3, CC4, CC6, CC10), headless:
|
|
12
|
+
* one `copilot -p` per turn in the folder, as Codex runs one `codex exec`, its
|
|
13
|
+
* output as JSON lines, every tool allowed and its question tool off, so a
|
|
14
|
+
* turn asks nothing. Every later turn resumes the session by the id the last
|
|
15
|
+
* turn's result named; a restart too, since `--continue` takes the latest
|
|
16
|
+
* session of the machine, whatever its folder (Copilot CLI 1.0.105). Between
|
|
17
|
+
* turns no Copilot process runs. Copilot signs in with the machine's own
|
|
18
|
+
* login, which the hold's clean environment leaves alone: no GitHub token
|
|
19
|
+
* reaches a session, and no API key (CC6). The identity and the turn marker
|
|
20
|
+
* go through the aeolus-fleet plugin Copilot installed, whose hooks run in
|
|
21
|
+
* `copilot -p` as in a terminal (S3).
|
|
22
|
+
*
|
|
23
|
+
* A failure before the model call (no login, a refused model, a session it
|
|
24
|
+
* cannot resume) fires no hook and writes no JSON line, only its error on
|
|
25
|
+
* stderr and exit status 1: the hold keeps Copilot's stderr lines among the
|
|
26
|
+
* events, wrapped as `aeolus.stderr`, and the launch window reads them as it
|
|
27
|
+
* reads `session.error`, which a failed model call writes.
|
|
28
|
+
*/
|
|
29
|
+
/** The Copilot CLI builds whose hook roles the aeolus-fleet plugin is proven on (design harnesses/cursor-copilot, S3). */
|
|
30
|
+
export const COPILOT_PROVEN_VERSIONS = ['1.0.105'];
|
|
31
|
+
/** The prompt of a restarted session: Copilot has no wake command, so the start context and the crew-a-ship skill say the rest. */
|
|
32
|
+
export const WAKE_PROMPT = "Aeolus started this session again: go on crewing this folder's Aeolus ship as the crew-a-ship skill says.";
|
|
33
|
+
/** Added to every launch, as mechanism: the turn's events as JSON lines, which the hold keeps (headless H13). */
|
|
34
|
+
const JSON_OUTPUT = '--output-format=json';
|
|
35
|
+
/** Added to every launch: `copilot -p` runs no tool without it. Running every tool without asking is the mode a session the trierarch runs is in (decision 0016). */
|
|
36
|
+
const ALLOW_ALL_TOOLS = '--allow-all-tools';
|
|
37
|
+
/** Added to every launch: Copilot's question tool waits for an answer nobody gives; the session asks over the fleet instead (#460). */
|
|
38
|
+
const NO_ASK_USER = '--no-ask-user';
|
|
39
|
+
const PROMPT = '--prompt=';
|
|
40
|
+
const RESUME = '--resume=';
|
|
41
|
+
/** The variable Copilot reads its configuration folder from, which the trierarch found its plugin in. */
|
|
42
|
+
const COPILOT_HOME_VARIABLE = 'COPILOT_HOME';
|
|
43
|
+
/** The line the hold writes for each line Copilot writes on stderr, so the launch window reads what Copilot says before any event. */
|
|
44
|
+
export const STDERR_EVENT = 'aeolus.stderr';
|
|
45
|
+
export const COPILOT_ADAPTER_FLAGS = [
|
|
46
|
+
{ flag: JSON_OUTPUT, when: 'always' },
|
|
47
|
+
{ flag: ALLOW_ALL_TOOLS, when: 'always' },
|
|
48
|
+
{ flag: NO_ASK_USER, when: 'always' },
|
|
49
|
+
];
|
|
50
|
+
/** The flags of Copilot that are risky: beyond every tool, which every launch allows, they open every path and every URL (#326). */
|
|
51
|
+
export const COPILOT_RISKY_FLAGS = ['--allow-all', '--yolo', '--allow-all-paths', '--allow-all-urls'];
|
|
52
|
+
/** `copilot`, the configured flags, JSON output, every tool allowed and no question tool, then the session to resume when there is one, and the prompt last as the value of --prompt, so it never reads as a flag. */
|
|
53
|
+
export function copilotCommandLine(at) {
|
|
54
|
+
return partsWithWords([
|
|
55
|
+
{ words: [at.program ?? 'copilot'] },
|
|
56
|
+
{ words: at.flags, source: 'configuration' },
|
|
57
|
+
{ words: [JSON_OUTPUT], source: 'adapter' },
|
|
58
|
+
{ words: [ALLOW_ALL_TOOLS], source: 'adapter' },
|
|
59
|
+
{ words: [NO_ASK_USER], source: 'adapter' },
|
|
60
|
+
{ words: [...(at.sessionId === undefined ? [] : [`${RESUME}${at.sessionId}`]), `${PROMPT}${at.prompt}`] },
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
/** The command of a later turn (CC4): the session's start command, resuming the session by the id given, with the line as its prompt. */
|
|
64
|
+
export function copilotTurnCommand(at) {
|
|
65
|
+
const words = at.start.filter((word) => !word.startsWith(RESUME) && !word.startsWith(PROMPT));
|
|
66
|
+
return [...words, `${RESUME}${at.sessionId}`, `${PROMPT}${at.line}`];
|
|
67
|
+
}
|
|
68
|
+
/** The line the hold keeps for a line Copilot wrote on stderr. */
|
|
69
|
+
export function copilotStderrEvent(line) {
|
|
70
|
+
return JSON.stringify({ type: STDERR_EVENT, line });
|
|
71
|
+
}
|
|
72
|
+
/** The result that ends each `copilot -p`, the only event naming the session. */
|
|
73
|
+
const resultSchema = z.object({ type: z.literal('result'), sessionId: z.string().min(1) });
|
|
74
|
+
/** The session id a line of Copilot's output names: its result's; none for any other line. */
|
|
75
|
+
export function copilotSessionIdOf(line) {
|
|
76
|
+
return resultSchema.safeParse(parsedLine(line)).data?.sessionId;
|
|
77
|
+
}
|
|
78
|
+
const sessionErrorSchema = z.object({ type: z.literal('session.error'), data: z.object({ message: z.string() }) });
|
|
79
|
+
const stderrSchema = z.object({ type: z.literal(STDERR_EVENT), line: z.string() });
|
|
80
|
+
/** How Copilot CLI 1.0.105 refuses a model this login may not use, before any turn. */
|
|
81
|
+
const REFUSED_MODEL = /^Model "([^"]+)" from --model flag is not available\.$/u;
|
|
82
|
+
/** How Copilot starts the error it writes on stderr. */
|
|
83
|
+
const ERROR_PREFIX = 'Error: ';
|
|
84
|
+
/** The last error in Copilot's events: a session error, or the error it wrote on stderr; none without one. */
|
|
85
|
+
function lastErrorIn(events) {
|
|
86
|
+
for (const line of [...events].reverse()) {
|
|
87
|
+
const event = parsedLine(line);
|
|
88
|
+
const sessionError = sessionErrorSchema.safeParse(event);
|
|
89
|
+
if (sessionError.success) {
|
|
90
|
+
return sessionError.data.data.message;
|
|
91
|
+
}
|
|
92
|
+
const stderr = stderrSchema.safeParse(event).data?.line;
|
|
93
|
+
if (stderr?.startsWith(ERROR_PREFIX) === true) {
|
|
94
|
+
return stderr.slice(ERROR_PREFIX.length);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Why Copilot's start failed, from its last error (headless H13): the model it
|
|
101
|
+
* refused when that is the model it launched with (or any, when it launched
|
|
102
|
+
* with none), or else the error's message. Tested on output captured from
|
|
103
|
+
* Copilot CLI 1.0.105 (test/logs).
|
|
104
|
+
*/
|
|
105
|
+
export function copilotErrorIn(at) {
|
|
106
|
+
const error = lastErrorIn(at.events);
|
|
107
|
+
if (error === undefined) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const refused = REFUSED_MODEL.exec(error)?.[1];
|
|
111
|
+
return refused !== undefined && (at.model === undefined || refused === at.model) ? { kind: 'refused', model: refused } : { kind: 'cause', cause: error };
|
|
112
|
+
}
|
|
113
|
+
/** The session the last turn in the events named, which a restart resumes; none when no turn named one. */
|
|
114
|
+
function lastSessionIn(events) {
|
|
115
|
+
return [...events].reverse().map(copilotSessionIdOf).find((id) => id !== undefined);
|
|
116
|
+
}
|
|
117
|
+
export function createCopilotHarness(options) {
|
|
118
|
+
const { configuration, plugin, sessions } = options;
|
|
119
|
+
const marks = createLaunchMarks(plugin);
|
|
120
|
+
const { handOverLine, forceOverLine, ...identity } = createPluginIdentity(plugin);
|
|
121
|
+
const proven = COPILOT_PROVEN_VERSIONS.map((version) => `Copilot CLI ${version}`).join(', ');
|
|
122
|
+
return {
|
|
123
|
+
...identity,
|
|
124
|
+
launch: async ({ shipId, folder, harness, options: picked, isFirstStart, launchId, login, firstPrompt }) => {
|
|
125
|
+
const settings = configuration.harnesses[harness];
|
|
126
|
+
if (settings === undefined) {
|
|
127
|
+
throw new Error(`The configuration has no harness ${harness}`);
|
|
128
|
+
}
|
|
129
|
+
if (login !== 'subscription') {
|
|
130
|
+
throw new Error("Copilot signs in with this computer's own login (copilot login): it runs on no API key");
|
|
131
|
+
}
|
|
132
|
+
const prompt = isFirstStart && firstPrompt !== undefined ? firstPromptOf(firstPrompt) : wakePromptOf(WAKE_PROMPT);
|
|
133
|
+
// Read before the start, which clears the session's events.
|
|
134
|
+
const sessionId = isFirstStart ? undefined : lastSessionIn(await sessions.events(shipId));
|
|
135
|
+
const command = copilotCommandLine({ flags: effectiveFlags(settings, picked), prompt, ...(sessionId !== undefined && { sessionId }), ...(options.command !== undefined && { program: options.command }) });
|
|
136
|
+
await marks.clear(folder);
|
|
137
|
+
const env = { [LAUNCH_VARIABLE]: launchId, ...(options.copilotHome !== undefined && { [COPILOT_HOME_VARIABLE]: options.copilotHome }) };
|
|
138
|
+
await sessions.start({ shipId, folder, command: wordsOf(command), harness, env });
|
|
139
|
+
},
|
|
140
|
+
handOver: async (session) => {
|
|
141
|
+
const line = await handOverLine(session);
|
|
142
|
+
if (line === undefined) {
|
|
143
|
+
return 'nothing';
|
|
144
|
+
}
|
|
145
|
+
await sessions.send({ shipId: session.shipId, line: wakePromptOf(line) });
|
|
146
|
+
return 'handed';
|
|
147
|
+
},
|
|
148
|
+
forceOver: async ({ shipId, folder, isTurnBusy }) => {
|
|
149
|
+
const line = await forceOverLine({ shipId, folder });
|
|
150
|
+
if (line === undefined) {
|
|
151
|
+
return 'nothing';
|
|
152
|
+
}
|
|
153
|
+
if (isTurnBusy) {
|
|
154
|
+
await sessions.interrupt(shipId);
|
|
155
|
+
}
|
|
156
|
+
await sessions.send({ shipId, line: wakePromptOf(line) });
|
|
157
|
+
return 'handed';
|
|
158
|
+
},
|
|
159
|
+
marksLaunches: () => marks.isMarking(),
|
|
160
|
+
notOfferedReason: async () => {
|
|
161
|
+
// The trierarch's upgrade installs no Copilot plugin: the reason says Copilot's own command.
|
|
162
|
+
if ((await plugin.root().catch(() => undefined)) === undefined) {
|
|
163
|
+
return 'its aeolus-fleet plugin is not installed for Copilot: copilot plugin install aeolus-fleet@aeolus-fleet';
|
|
164
|
+
}
|
|
165
|
+
if (options.isInContainer) {
|
|
166
|
+
return 'Copilot is not proven in a container: the container runtime runs claude-code only';
|
|
167
|
+
}
|
|
168
|
+
const version = options.detectedVersion();
|
|
169
|
+
if (version === undefined) {
|
|
170
|
+
return `its Copilot CLI version is not detected yet: the hooks of its aeolus-fleet plugin are proven on ${proven}`;
|
|
171
|
+
}
|
|
172
|
+
return COPILOT_PROVEN_VERSIONS.includes(version) ? undefined : `Copilot CLI ${version} is not proven: the hooks of its aeolus-fleet plugin are proven on ${proven}`;
|
|
173
|
+
},
|
|
174
|
+
// The marker says what the session did (#490); what Copilot says before any hook runs, a refused model or login, is read from its output (headless H13).
|
|
175
|
+
launchSeen: async ({ shipId, folder, launchId, model }) => {
|
|
176
|
+
const error = copilotErrorIn({ events: await sessions.events(shipId), ...(model !== undefined && { model }) });
|
|
177
|
+
if (error?.kind === 'refused') {
|
|
178
|
+
return { kind: 'refused', model: error.model };
|
|
179
|
+
}
|
|
180
|
+
if ((await marks.read({ folder, launchId }))?.isActive === true) {
|
|
181
|
+
return { kind: 'active' };
|
|
182
|
+
}
|
|
183
|
+
return { kind: 'none', ...(error !== undefined && { cause: error.cause }) };
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=copilot.js.map
|