@zgeoff/atc 2.12.0 → 2.13.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 +10 -10
- package/package.json +2 -1
- package/src/agents/agent-adapter.ts +48 -11
- package/src/agents/build-atc-bridge-files.ts +13 -0
- package/src/agents/build-claude-query-options.ts +72 -0
- package/src/agents/build-cli-command.ts +7 -5
- package/src/{daemon → agents}/build-headless-env.ts +12 -6
- package/src/agents/build-hook-settings.ts +8 -2
- package/src/agents/build-restore-mode-args.ts +23 -0
- package/src/agents/claude-adapter.ts +64 -17
- package/src/agents/codex-adapter.ts +5 -0
- package/src/agents/find-claude-permission-mode.ts +24 -0
- package/src/agents/gateway-adapter.ts +38 -11
- package/src/agents/grok-adapter.ts +5 -0
- package/src/agents/make-claude-headless-runner.ts +46 -0
- package/src/agents/plan-pasted-line-input.ts +22 -0
- package/src/agents/plan-typed-line-input.ts +8 -0
- package/src/agents/resolve-claude-permission-mode.ts +13 -0
- package/src/{daemon/start-headless-run.ts → agents/start-claude-headless-run.ts} +6 -53
- package/src/agents/write-atc-bridge.ts +2 -6
- package/src/cli.ts +8 -12
- package/src/client/index.ts +50 -1
- package/src/client/ui.ts +8 -0
- package/src/daemon/build-execution-targets.ts +49 -10
- package/src/daemon/build-imp-provider.ts +68 -0
- package/src/daemon/build-report-trail-entry.ts +4 -2
- package/src/daemon/build-scoped-context.ts +4 -1
- package/src/daemon/build-session-lifecycle.ts +52 -0
- package/src/daemon/build-tar-archive.ts +85 -0
- package/src/daemon/daemon-connection.ts +97 -4
- package/src/daemon/daemon.ts +298 -41
- package/src/daemon/execution-provider.ts +109 -4
- package/src/daemon/hooks.ts +5 -17
- package/src/daemon/imp-client-port.ts +343 -0
- package/src/daemon/imp-harness.ts +618 -0
- package/src/daemon/imp-port-error.ts +18 -0
- package/src/daemon/imp-port.ts +246 -0
- package/src/daemon/imp-provider.ts +601 -0
- package/src/daemon/is-binding-current.ts +43 -0
- package/src/daemon/local-pty-provider.ts +62 -3
- package/src/daemon/materialize-workspace.ts +571 -0
- package/src/daemon/parse-hook-line.ts +31 -0
- package/src/daemon/pick-session-state.ts +15 -0
- package/src/daemon/restore-fleet.ts +41 -23
- package/src/daemon/screen-model.ts +7 -0
- package/src/daemon/sessions.ts +574 -61
- package/src/daemon/start-headless-turn.ts +1 -1
- package/src/daemon/start-session-bridge.ts +294 -0
- package/src/mcp/mcp-tools.ts +9 -2
- package/src/mcp/require-daemon-features.ts +3 -0
- package/src/mcp/run-tool.ts +27 -8
- package/src/protocol/daemon-features.ts +11 -0
- package/src/protocol/protocol.ts +23 -0
- package/src/protocol/request-param-schemas.ts +66 -0
- package/src/report.ts +37 -2
- package/src/run-bridge-tap.ts +241 -0
- package/src/shared/collect-clean-env.ts +9 -1
- package/src/shared/open-bridge-socket.ts +86 -0
- package/src/shared/send-bridge-request.ts +47 -0
- package/src/statusline.ts +34 -3
- package/src/store/fleet-entry.ts +19 -0
- package/src/store/run-migrations.ts +76 -0
- package/src/store/state-store.ts +182 -21
- package/src/store/trail-entry.ts +4 -0
- package/src/store/workspace-materialization.ts +70 -0
- package/src/tap.ts +11 -0
- package/src/workspace/check-url-credentials.ts +59 -0
- package/src/workspace/create-workspace-clone.ts +13 -1
- package/src/workspace/repository-env-vars.ts +22 -0
- package/src/workspace/run-git.ts +2 -18
- /package/src/{daemon → agents}/resolve-headless-executable.ts +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isRecord } from '../shared/report';
|
|
2
|
+
import { toSessionID } from '../shared/to-session-id';
|
|
3
|
+
import type { HookEvent } from './hooks';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One reporter line as a hook event, or null for a line that is not one.
|
|
7
|
+
*/
|
|
8
|
+
export function parseHookLine(line: string): HookEvent | null {
|
|
9
|
+
let parsed: unknown;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(line);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
!isRecord(parsed) ||
|
|
19
|
+
typeof parsed['atcId'] !== 'string' ||
|
|
20
|
+
typeof parsed['event'] !== 'string' ||
|
|
21
|
+
!isRecord(parsed['payload'])
|
|
22
|
+
) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
atcId: toSessionID(parsed['atcId']),
|
|
28
|
+
event: parsed['event'],
|
|
29
|
+
payload: parsed['payload'],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SessionLifecycle } from './build-session-lifecycle';
|
|
2
|
+
|
|
3
|
+
type SessionState = 'running' | 'needs_you' | 'done' | 'exited';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The state a session lists with: `exited` whenever its harness is not
|
|
7
|
+
* running, suspended inside a sleeping host included, and otherwise the
|
|
8
|
+
* attention its hooks last reported.
|
|
9
|
+
*/
|
|
10
|
+
export function pickSessionState(
|
|
11
|
+
lifecycle: Readonly<SessionLifecycle>,
|
|
12
|
+
attention: SessionState,
|
|
13
|
+
): SessionState {
|
|
14
|
+
return lifecycle.harness === 'running' ? attention : 'exited';
|
|
15
|
+
}
|
|
@@ -2,7 +2,6 @@ import { DaemonError } from '../protocol/daemon-error';
|
|
|
2
2
|
import type { SessionID } from '../shared/session-id';
|
|
3
3
|
import type { FleetEntry } from '../store/fleet-entry';
|
|
4
4
|
import type { StateStore } from '../store/state-store';
|
|
5
|
-
import { ScreenModel } from './screen-model';
|
|
6
5
|
import type { SessionRuntime } from './session-runtime';
|
|
7
6
|
import type { Session, SessionManager } from './sessions';
|
|
8
7
|
|
|
@@ -86,26 +85,24 @@ export async function restoreFleet(params: RestoreFleetParams): Promise<number>
|
|
|
86
85
|
|
|
87
86
|
// A session whose target refuses to start a terminal stays listed without
|
|
88
87
|
// one, and the restore moves on to the next.
|
|
89
|
-
const adoptQueued = (s: Session): boolean => {
|
|
90
|
-
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const runtime = findRuntime(s.id);
|
|
95
|
-
|
|
96
|
-
if (runtime !== undefined) {
|
|
97
|
-
runtime.resetBoot({ cols, rows });
|
|
88
|
+
const adoptQueued = async (s: Session): Promise<boolean> => {
|
|
89
|
+
const adopted = await tryAdoptTerminal(mgr, s.id, cols, rows);
|
|
98
90
|
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return true;
|
|
91
|
+
return adopted !== null;
|
|
103
92
|
};
|
|
104
93
|
|
|
94
|
+
// A session that announced itself, or whose terminal died, before the
|
|
95
|
+
// wait began has nothing left to wait for.
|
|
105
96
|
const waitForBoot = (sessionID: SessionID): Promise<void> => {
|
|
106
97
|
const runtime = findRuntime(sessionID);
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
const session = mgr.sessions.find((s) => s.id === sessionID);
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
runtime === undefined ||
|
|
102
|
+
runtime.startedAt !== null ||
|
|
103
|
+
session === undefined ||
|
|
104
|
+
session.pty === null
|
|
105
|
+
) {
|
|
109
106
|
return Promise.resolve();
|
|
110
107
|
}
|
|
111
108
|
|
|
@@ -134,11 +131,26 @@ export async function restoreFleet(params: RestoreFleetParams): Promise<number>
|
|
|
134
131
|
return registered.length;
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
// The first terminal attaches
|
|
138
|
-
// once; each later one waits for the previous session to report it has
|
|
134
|
+
// The first terminal attaches before the restore answers so a caller can
|
|
135
|
+
// attach at once; each later one waits for the previous session to report it has
|
|
139
136
|
// booted, so a heavy fleet comes up one process at a time instead of all
|
|
140
137
|
// at once. A per-session cap keeps a session that never reports from
|
|
141
138
|
// stalling the rest.
|
|
139
|
+
// A session whose revive fails is logged and left without a terminal,
|
|
140
|
+
// and the restore moves on: one host's failure never keeps the sessions on
|
|
141
|
+
// other hosts down, and a later one's runs where nothing awaits it.
|
|
142
|
+
const tryAdoptQueued = async (s: Session): Promise<boolean> => {
|
|
143
|
+
try {
|
|
144
|
+
return await adoptQueued(s);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
mgr.log(
|
|
147
|
+
`atc could not revive session ${s.id} (${error instanceof Error ? error.message : String(error)})`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
142
154
|
const adoptRest = async (previous: Session | null) => {
|
|
143
155
|
let prev = previous;
|
|
144
156
|
|
|
@@ -147,11 +159,15 @@ export async function restoreFleet(params: RestoreFleetParams): Promise<number>
|
|
|
147
159
|
await waitForBoot(prev.id);
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
|
|
162
|
+
const booted = await tryAdoptQueued(s);
|
|
163
|
+
|
|
164
|
+
prev = booted ? s : null;
|
|
151
165
|
}
|
|
152
166
|
};
|
|
153
167
|
|
|
154
|
-
const
|
|
168
|
+
const firstAdopted = await tryAdoptQueued(first);
|
|
169
|
+
|
|
170
|
+
const firstBooted = firstAdopted ? first : null;
|
|
155
171
|
|
|
156
172
|
void adoptRest(firstBooted);
|
|
157
173
|
|
|
@@ -176,16 +192,18 @@ const REFUSED_ADOPT_CODES: ReadonlySet<string> = new Set([
|
|
|
176
192
|
'target_unavailable',
|
|
177
193
|
'target_changed',
|
|
178
194
|
'target_config_invalid',
|
|
195
|
+
'host_unavailable',
|
|
196
|
+
'auth_not_configured',
|
|
179
197
|
]);
|
|
180
198
|
|
|
181
|
-
function tryAdoptTerminal(
|
|
199
|
+
async function tryAdoptTerminal(
|
|
182
200
|
mgr: SessionManager,
|
|
183
201
|
id: SessionID,
|
|
184
202
|
cols: number,
|
|
185
203
|
rows: number,
|
|
186
|
-
): Session | null {
|
|
204
|
+
): Promise<Session | null> {
|
|
187
205
|
try {
|
|
188
|
-
return mgr.adoptTerminal(id, cols, rows);
|
|
206
|
+
return await mgr.adoptTerminal(id, cols, rows);
|
|
189
207
|
} catch (error) {
|
|
190
208
|
if (error instanceof DaemonError && REFUSED_ADOPT_CODES.has(error.code)) {
|
|
191
209
|
return null;
|
|
@@ -99,6 +99,13 @@ export class ScreenModel {
|
|
|
99
99
|
return RESET_INPUT_MODES + this.renderVisibleScreen() + this.renderInputModes();
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Whether the TUI has turned bracketed paste on, as of the output parsed
|
|
103
|
+
// so far. It reads the mode without waiting, so output still queued for
|
|
104
|
+
// parsing never delays the caller; a TUI turns the mode on once, at start.
|
|
105
|
+
hasBracketedPaste(): boolean {
|
|
106
|
+
return this.term.modes.bracketedPasteMode;
|
|
107
|
+
}
|
|
108
|
+
|
|
102
109
|
// The visible rows of whichever buffer the session is showing, as plain
|
|
103
110
|
// text with no escape sequences: one line per row, trailing blanks
|
|
104
111
|
// trimmed from each row, trailing blank rows dropped. Drains pending
|