@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.
Files changed (71) hide show
  1. package/README.md +10 -10
  2. package/package.json +2 -1
  3. package/src/agents/agent-adapter.ts +48 -11
  4. package/src/agents/build-atc-bridge-files.ts +13 -0
  5. package/src/agents/build-claude-query-options.ts +72 -0
  6. package/src/agents/build-cli-command.ts +7 -5
  7. package/src/{daemon → agents}/build-headless-env.ts +12 -6
  8. package/src/agents/build-hook-settings.ts +8 -2
  9. package/src/agents/build-restore-mode-args.ts +23 -0
  10. package/src/agents/claude-adapter.ts +64 -17
  11. package/src/agents/codex-adapter.ts +5 -0
  12. package/src/agents/find-claude-permission-mode.ts +24 -0
  13. package/src/agents/gateway-adapter.ts +38 -11
  14. package/src/agents/grok-adapter.ts +5 -0
  15. package/src/agents/make-claude-headless-runner.ts +46 -0
  16. package/src/agents/plan-pasted-line-input.ts +22 -0
  17. package/src/agents/plan-typed-line-input.ts +8 -0
  18. package/src/agents/resolve-claude-permission-mode.ts +13 -0
  19. package/src/{daemon/start-headless-run.ts → agents/start-claude-headless-run.ts} +6 -53
  20. package/src/agents/write-atc-bridge.ts +2 -6
  21. package/src/cli.ts +8 -12
  22. package/src/client/index.ts +50 -1
  23. package/src/client/ui.ts +8 -0
  24. package/src/daemon/build-execution-targets.ts +49 -10
  25. package/src/daemon/build-imp-provider.ts +68 -0
  26. package/src/daemon/build-report-trail-entry.ts +4 -2
  27. package/src/daemon/build-scoped-context.ts +4 -1
  28. package/src/daemon/build-session-lifecycle.ts +52 -0
  29. package/src/daemon/build-tar-archive.ts +85 -0
  30. package/src/daemon/daemon-connection.ts +97 -4
  31. package/src/daemon/daemon.ts +298 -41
  32. package/src/daemon/execution-provider.ts +109 -4
  33. package/src/daemon/hooks.ts +5 -17
  34. package/src/daemon/imp-client-port.ts +343 -0
  35. package/src/daemon/imp-harness.ts +618 -0
  36. package/src/daemon/imp-port-error.ts +18 -0
  37. package/src/daemon/imp-port.ts +246 -0
  38. package/src/daemon/imp-provider.ts +601 -0
  39. package/src/daemon/is-binding-current.ts +43 -0
  40. package/src/daemon/local-pty-provider.ts +62 -3
  41. package/src/daemon/materialize-workspace.ts +571 -0
  42. package/src/daemon/parse-hook-line.ts +31 -0
  43. package/src/daemon/pick-session-state.ts +15 -0
  44. package/src/daemon/restore-fleet.ts +41 -23
  45. package/src/daemon/screen-model.ts +7 -0
  46. package/src/daemon/sessions.ts +574 -61
  47. package/src/daemon/start-headless-turn.ts +1 -1
  48. package/src/daemon/start-session-bridge.ts +294 -0
  49. package/src/mcp/mcp-tools.ts +9 -2
  50. package/src/mcp/require-daemon-features.ts +3 -0
  51. package/src/mcp/run-tool.ts +27 -8
  52. package/src/protocol/daemon-features.ts +11 -0
  53. package/src/protocol/protocol.ts +23 -0
  54. package/src/protocol/request-param-schemas.ts +66 -0
  55. package/src/report.ts +37 -2
  56. package/src/run-bridge-tap.ts +241 -0
  57. package/src/shared/collect-clean-env.ts +9 -1
  58. package/src/shared/open-bridge-socket.ts +86 -0
  59. package/src/shared/send-bridge-request.ts +47 -0
  60. package/src/statusline.ts +34 -3
  61. package/src/store/fleet-entry.ts +19 -0
  62. package/src/store/run-migrations.ts +76 -0
  63. package/src/store/state-store.ts +182 -21
  64. package/src/store/trail-entry.ts +4 -0
  65. package/src/store/workspace-materialization.ts +70 -0
  66. package/src/tap.ts +11 -0
  67. package/src/workspace/check-url-credentials.ts +59 -0
  68. package/src/workspace/create-workspace-clone.ts +13 -1
  69. package/src/workspace/repository-env-vars.ts +22 -0
  70. package/src/workspace/run-git.ts +2 -18
  71. /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
- if (tryAdoptTerminal(mgr, s.id, cols, rows) === null) {
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
- runtime.screen ??= new ScreenModel(cols, rows);
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
- if (runtime === undefined) {
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 synchronously so a caller can attach at
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
- prev = adoptQueued(s) ? s : null;
162
+ const booted = await tryAdoptQueued(s);
163
+
164
+ prev = booted ? s : null;
151
165
  }
152
166
  };
153
167
 
154
- const firstBooted = adoptQueued(first) ? first : null;
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