@rallycry/conveyor-agent 10.7.3 → 10.7.5
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/dist/{chunk-UOHROQ6A.js → chunk-CWTQXS34.js} +282 -120
- package/dist/chunk-CWTQXS34.js.map +1 -0
- package/dist/cli.js +376 -130
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-UOHROQ6A.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DEFAULT_LIFECYCLE_CONFIG,
|
|
6
6
|
DEFAULT_SONNET_MODEL,
|
|
7
7
|
Lifecycle,
|
|
8
|
+
PortDiscovery,
|
|
8
9
|
PtyHarness,
|
|
9
10
|
SessionRunner,
|
|
10
11
|
TUI_KINDS,
|
|
@@ -25,8 +26,9 @@ import {
|
|
|
25
26
|
resolveSessionStart,
|
|
26
27
|
runSetupCommand,
|
|
27
28
|
runStartCommand,
|
|
28
|
-
sampleKeyUsage
|
|
29
|
-
|
|
29
|
+
sampleKeyUsage,
|
|
30
|
+
terminateProcessGroup
|
|
31
|
+
} from "./chunk-CWTQXS34.js";
|
|
30
32
|
import "./chunk-7TQO4ZF4.js";
|
|
31
33
|
|
|
32
34
|
// src/cli.ts
|
|
@@ -45,14 +47,18 @@ var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
|
45
47
|
var DEFAULT_PROBE_TIMEOUT_MS = 2e3;
|
|
46
48
|
var POSTGRES_DEFAULT_PORT = 5432;
|
|
47
49
|
var FIREBASE_DEFAULT_PORT = 9099;
|
|
48
|
-
async function startLazySidecars(env, onLog) {
|
|
50
|
+
async function startLazySidecars(env, onLog, signal) {
|
|
51
|
+
throwIfAborted(signal);
|
|
49
52
|
const markerPath = env.CONVEYOR_SIDECAR_START_FILE;
|
|
50
53
|
if (!markerPath) return;
|
|
51
54
|
try {
|
|
52
55
|
await mkdir(dirname(markerPath), { recursive: true });
|
|
56
|
+
throwIfAborted(signal);
|
|
53
57
|
await writeFile(markerPath, "start\n", "utf8");
|
|
58
|
+
throwIfAborted(signal);
|
|
54
59
|
onLog("Started lazy sidecars");
|
|
55
60
|
} catch (err) {
|
|
61
|
+
if (signal?.aborted) throw abortError();
|
|
56
62
|
const message = err instanceof Error ? err.message : String(err);
|
|
57
63
|
onLog(`WARNING: failed to start lazy sidecars: ${message}`);
|
|
58
64
|
}
|
|
@@ -96,7 +102,16 @@ function resolveSidecarTargets(env = process.env) {
|
|
|
96
102
|
}
|
|
97
103
|
return targets;
|
|
98
104
|
}
|
|
99
|
-
function
|
|
105
|
+
function abortError() {
|
|
106
|
+
const error = new Error("Operation aborted");
|
|
107
|
+
error.name = "AbortError";
|
|
108
|
+
return error;
|
|
109
|
+
}
|
|
110
|
+
function throwIfAborted(signal) {
|
|
111
|
+
if (signal?.aborted) throw abortError();
|
|
112
|
+
}
|
|
113
|
+
function defaultProbe(target, signal) {
|
|
114
|
+
throwIfAborted(signal);
|
|
100
115
|
return new Promise((resolve) => {
|
|
101
116
|
let settled = false;
|
|
102
117
|
const socket = net.createConnection({ host: target.host, port: target.port });
|
|
@@ -104,23 +119,56 @@ function defaultProbe(target) {
|
|
|
104
119
|
if (settled) return;
|
|
105
120
|
settled = true;
|
|
106
121
|
socket.destroy();
|
|
122
|
+
signal?.removeEventListener("abort", onAbort);
|
|
107
123
|
resolve(ok);
|
|
108
124
|
};
|
|
125
|
+
const onAbort = () => done(false);
|
|
109
126
|
socket.once("connect", () => done(true));
|
|
110
127
|
socket.once("error", () => done(false));
|
|
111
128
|
socket.setTimeout(DEFAULT_PROBE_TIMEOUT_MS, () => done(false));
|
|
129
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
var delay = (ms, signal) => {
|
|
133
|
+
throwIfAborted(signal);
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const timer = setTimeout(() => {
|
|
136
|
+
signal?.removeEventListener("abort", onAbort);
|
|
137
|
+
resolve();
|
|
138
|
+
}, ms);
|
|
139
|
+
const onAbort = () => {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
reject(abortError());
|
|
142
|
+
};
|
|
143
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
function raceWithAbort(promise, signal) {
|
|
147
|
+
if (!signal) return promise;
|
|
148
|
+
throwIfAborted(signal);
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const onAbort = () => reject(abortError());
|
|
151
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
152
|
+
void promise.then(
|
|
153
|
+
(value) => {
|
|
154
|
+
signal.removeEventListener("abort", onAbort);
|
|
155
|
+
resolve(value);
|
|
156
|
+
},
|
|
157
|
+
(error) => {
|
|
158
|
+
signal.removeEventListener("abort", onAbort);
|
|
159
|
+
reject(error);
|
|
160
|
+
}
|
|
161
|
+
);
|
|
112
162
|
});
|
|
113
163
|
}
|
|
114
|
-
var delay = (ms) => new Promise((resolve) => {
|
|
115
|
-
setTimeout(resolve, ms);
|
|
116
|
-
});
|
|
117
164
|
async function waitForTarget(target, opts) {
|
|
118
|
-
const { onLog, pollIntervalMs, probe } = opts;
|
|
165
|
+
const { onLog, pollIntervalMs, probe, signal } = opts;
|
|
119
166
|
const timeoutMs = opts.timeoutMs ?? target.timeoutMs ?? FALLBACK_TIMEOUT_MS;
|
|
120
167
|
const deadline = Date.now() + timeoutMs;
|
|
121
168
|
onLog(`Waiting for ${target.name} on ${target.host}:${target.port}...`);
|
|
122
169
|
while (true) {
|
|
123
|
-
|
|
170
|
+
throwIfAborted(signal);
|
|
171
|
+
if (await raceWithAbort(probe(target, signal), signal)) {
|
|
124
172
|
onLog(`${target.name} is ready`);
|
|
125
173
|
return;
|
|
126
174
|
}
|
|
@@ -130,7 +178,7 @@ async function waitForTarget(target, opts) {
|
|
|
130
178
|
);
|
|
131
179
|
return;
|
|
132
180
|
}
|
|
133
|
-
await delay(pollIntervalMs);
|
|
181
|
+
await delay(pollIntervalMs, signal);
|
|
134
182
|
}
|
|
135
183
|
}
|
|
136
184
|
async function waitForSidecars(opts = {}) {
|
|
@@ -141,18 +189,282 @@ async function waitForSidecars(opts = {}) {
|
|
|
141
189
|
timeoutMs,
|
|
142
190
|
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
143
191
|
probe = defaultProbe,
|
|
144
|
-
startLazy = true
|
|
192
|
+
startLazy = true,
|
|
193
|
+
signal
|
|
145
194
|
} = opts;
|
|
146
195
|
if (startLazy) {
|
|
147
|
-
await startLazySidecars(env, onLog);
|
|
196
|
+
await startLazySidecars(env, onLog, signal);
|
|
148
197
|
}
|
|
198
|
+
throwIfAborted(signal);
|
|
149
199
|
const targets = resolveSidecarTargets(env);
|
|
150
200
|
if (targets.length === 0) return;
|
|
151
201
|
await Promise.all(
|
|
152
|
-
targets.map(
|
|
202
|
+
targets.map(
|
|
203
|
+
(target) => waitForTarget(target, { onLog, timeoutMs, pollIntervalMs, probe, signal })
|
|
204
|
+
)
|
|
153
205
|
);
|
|
154
206
|
}
|
|
155
207
|
|
|
208
|
+
// src/setup/workspace-command-supervisor.ts
|
|
209
|
+
var defaultWriteOutput = (stream, data) => {
|
|
210
|
+
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
211
|
+
};
|
|
212
|
+
function stopWorkspaceCommands(supervisor) {
|
|
213
|
+
return supervisor?.stop() ?? Promise.resolve();
|
|
214
|
+
}
|
|
215
|
+
async function startWorkspaceCommandsAfterConnect(options) {
|
|
216
|
+
const connected = await options.connect();
|
|
217
|
+
if (!connected || options.isShuttingDown()) return null;
|
|
218
|
+
const supervisor = options.create();
|
|
219
|
+
supervisor.start();
|
|
220
|
+
return supervisor;
|
|
221
|
+
}
|
|
222
|
+
var WorkspaceCommandSupervisor = class {
|
|
223
|
+
abortController = new AbortController();
|
|
224
|
+
expectedStartCommandStops = /* @__PURE__ */ new WeakSet();
|
|
225
|
+
config;
|
|
226
|
+
workspaceDir;
|
|
227
|
+
connection;
|
|
228
|
+
env;
|
|
229
|
+
awaitGitReadyFn;
|
|
230
|
+
startLazySidecarsFn;
|
|
231
|
+
waitForSidecarsFn;
|
|
232
|
+
runSetupCommandFn;
|
|
233
|
+
runStartCommandFn;
|
|
234
|
+
loadForwardPortsFn;
|
|
235
|
+
writeOutput;
|
|
236
|
+
terminateStartCommand;
|
|
237
|
+
startCommandChild = null;
|
|
238
|
+
liveStartCommandChildren = /* @__PURE__ */ new Set();
|
|
239
|
+
startCommandTerminations = /* @__PURE__ */ new WeakMap();
|
|
240
|
+
startCommandEndPromises = /* @__PURE__ */ new WeakMap();
|
|
241
|
+
resolveStartCommandEnd = /* @__PURE__ */ new WeakMap();
|
|
242
|
+
backgroundTasks = /* @__PURE__ */ new Set();
|
|
243
|
+
startCommandQueue = Promise.resolve();
|
|
244
|
+
shutdownPromise = null;
|
|
245
|
+
startCommandLaunchRequested = false;
|
|
246
|
+
started = false;
|
|
247
|
+
stopped = false;
|
|
248
|
+
constructor(options) {
|
|
249
|
+
this.config = options.config;
|
|
250
|
+
this.workspaceDir = options.workspaceDir;
|
|
251
|
+
this.connection = options.connection;
|
|
252
|
+
this.env = options.env ?? process.env;
|
|
253
|
+
this.awaitGitReadyFn = options.awaitGitReady ?? ((opts) => awaitGitReady({
|
|
254
|
+
onLog: opts.onLog,
|
|
255
|
+
signal: opts.signal
|
|
256
|
+
}));
|
|
257
|
+
this.startLazySidecarsFn = options.startLazySidecars ?? ((env, onLog, signal) => startLazySidecars(env, onLog, signal));
|
|
258
|
+
this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, startLazy: opts.startLazy, signal: opts.signal }));
|
|
259
|
+
this.runSetupCommandFn = options.runSetupCommand ?? ((command, cwd, onOutput, signal) => runSetupCommand(command, cwd, onOutput, signal));
|
|
260
|
+
this.runStartCommandFn = options.runStartCommand ?? runStartCommand;
|
|
261
|
+
this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
|
|
262
|
+
this.writeOutput = options.writeOutput ?? defaultWriteOutput;
|
|
263
|
+
this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
|
|
264
|
+
}
|
|
265
|
+
start() {
|
|
266
|
+
if (this.started || this.stopped) return;
|
|
267
|
+
this.started = true;
|
|
268
|
+
this.connection.onRunStartCommand(() => this.restartStartCommand());
|
|
269
|
+
this.trackBackgroundTask(
|
|
270
|
+
this.startLazySidecarsFn(
|
|
271
|
+
this.env,
|
|
272
|
+
(message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
|
|
273
|
+
`),
|
|
274
|
+
this.abortController.signal
|
|
275
|
+
).catch((error) => this.reportUnexpectedError(error))
|
|
276
|
+
);
|
|
277
|
+
if (this.config) {
|
|
278
|
+
this.trackBackgroundTask(
|
|
279
|
+
this.runSetupAndStart().catch((error) => this.reportUnexpectedError(error))
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
stop() {
|
|
284
|
+
if (this.shutdownPromise) return this.shutdownPromise;
|
|
285
|
+
this.stopped = true;
|
|
286
|
+
this.abortController.abort();
|
|
287
|
+
const backgroundTasks = [...this.backgroundTasks];
|
|
288
|
+
const termination = this.terminateAllStartCommands();
|
|
289
|
+
this.shutdownPromise = (async () => {
|
|
290
|
+
await Promise.allSettled([termination, this.startCommandQueue, ...backgroundTasks]);
|
|
291
|
+
await this.terminateAllStartCommands();
|
|
292
|
+
})();
|
|
293
|
+
return this.shutdownPromise;
|
|
294
|
+
}
|
|
295
|
+
async runSetupAndStart() {
|
|
296
|
+
const gitState = await this.awaitGitReadyFn({
|
|
297
|
+
onLog: (message) => this.forwardSetupOutput("stdout", `[git] ${message}
|
|
298
|
+
`),
|
|
299
|
+
signal: this.abortController.signal
|
|
300
|
+
});
|
|
301
|
+
if (this.stopped) return;
|
|
302
|
+
if (gitState === "failed" || gitState === "timeout") {
|
|
303
|
+
this.connection.sendEvent({
|
|
304
|
+
type: "setup_error",
|
|
305
|
+
message: "Workspace not ready \u2014 skipping setup/start"
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
await this.waitForSidecarsFn({
|
|
310
|
+
onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
|
|
311
|
+
`),
|
|
312
|
+
startLazy: false,
|
|
313
|
+
signal: this.abortController.signal
|
|
314
|
+
});
|
|
315
|
+
if (this.stopped) return;
|
|
316
|
+
await this.runConfiguredSetup();
|
|
317
|
+
if (this.stopped) return;
|
|
318
|
+
const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
|
|
319
|
+
if (this.stopped) return;
|
|
320
|
+
const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
|
|
321
|
+
if (this.stopped) return;
|
|
322
|
+
const previewPorts = buildSessionPreviewPorts(forwardPorts);
|
|
323
|
+
this.connection.sendEvent({
|
|
324
|
+
type: "setup_complete",
|
|
325
|
+
startCommandRunning,
|
|
326
|
+
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async runConfiguredSetup() {
|
|
330
|
+
const command = this.config?.setupCommand;
|
|
331
|
+
if (!command) return;
|
|
332
|
+
try {
|
|
333
|
+
await this.runSetupCommandFn(
|
|
334
|
+
command,
|
|
335
|
+
this.workspaceDir,
|
|
336
|
+
(stream, data) => this.forwardSetupOutput(stream, data),
|
|
337
|
+
this.abortController.signal
|
|
338
|
+
);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (this.stopped) return;
|
|
341
|
+
this.connection.sendEvent({
|
|
342
|
+
type: "setup_error",
|
|
343
|
+
message: error instanceof Error ? error.message : "Setup command failed"
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
restartStartCommand() {
|
|
348
|
+
if (this.stopped || !this.config?.startCommand) return;
|
|
349
|
+
this.connection.sendEvent({
|
|
350
|
+
type: "start_command_output",
|
|
351
|
+
stream: "stdout",
|
|
352
|
+
data: "[conveyor-agent] Restarting start command...\n"
|
|
353
|
+
});
|
|
354
|
+
void this.enqueueStartCommandReplacement(this.config.startCommand);
|
|
355
|
+
}
|
|
356
|
+
async ensureStartCommandLaunched(command) {
|
|
357
|
+
if (!this.startCommandLaunchRequested) {
|
|
358
|
+
return this.enqueueStartCommandReplacement(command);
|
|
359
|
+
}
|
|
360
|
+
await this.startCommandQueue;
|
|
361
|
+
return !this.stopped && this.liveStartCommandChildren.size > 0;
|
|
362
|
+
}
|
|
363
|
+
enqueueStartCommandReplacement(command) {
|
|
364
|
+
this.startCommandLaunchRequested = true;
|
|
365
|
+
const operation = this.startCommandQueue.then(async () => {
|
|
366
|
+
if (this.stopped) return false;
|
|
367
|
+
await this.terminateAllStartCommands();
|
|
368
|
+
if (this.stopped) return false;
|
|
369
|
+
return this.launchStartCommand(command);
|
|
370
|
+
});
|
|
371
|
+
this.startCommandQueue = operation.then(
|
|
372
|
+
() => void 0,
|
|
373
|
+
() => void 0
|
|
374
|
+
);
|
|
375
|
+
return operation;
|
|
376
|
+
}
|
|
377
|
+
launchStartCommand(command) {
|
|
378
|
+
if (this.stopped) return false;
|
|
379
|
+
this.connection.sendEvent({ type: "start_command_started" });
|
|
380
|
+
try {
|
|
381
|
+
const child = this.runStartCommandFn(command, this.workspaceDir, (stream, data) => {
|
|
382
|
+
if (this.stopped) return;
|
|
383
|
+
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
384
|
+
this.writeOutput(stream, data);
|
|
385
|
+
});
|
|
386
|
+
this.liveStartCommandChildren.add(child);
|
|
387
|
+
const ended = new Promise((resolve) => {
|
|
388
|
+
this.resolveStartCommandEnd.set(child, resolve);
|
|
389
|
+
});
|
|
390
|
+
this.startCommandEndPromises.set(child, ended);
|
|
391
|
+
if (this.stopped) {
|
|
392
|
+
void this.terminateStartCommandChild(child);
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
this.startCommandChild = child;
|
|
396
|
+
child.on("exit", (code, signal) => {
|
|
397
|
+
if (this.startCommandChild === child) this.startCommandChild = null;
|
|
398
|
+
this.liveStartCommandChildren.delete(child);
|
|
399
|
+
this.settleStartCommandEnd(child);
|
|
400
|
+
if (this.stopped || this.expectedStartCommandStops.has(child)) return;
|
|
401
|
+
const message = `start command exited${code === null ? "" : ` with code ${code}`}${signal ? ` (signal: ${signal})` : ""}`;
|
|
402
|
+
this.connection.sendEvent({ type: "start_command_exited", code, signal, message });
|
|
403
|
+
if (code !== null && code !== 0) {
|
|
404
|
+
this.connection.sendEvent({
|
|
405
|
+
type: "start_command_error",
|
|
406
|
+
message: `start command exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
child.on("error", (error) => {
|
|
411
|
+
if (child.pid === void 0) {
|
|
412
|
+
if (this.startCommandChild === child) this.startCommandChild = null;
|
|
413
|
+
this.liveStartCommandChildren.delete(child);
|
|
414
|
+
this.settleStartCommandEnd(child);
|
|
415
|
+
this.startCommandTerminations.delete(child);
|
|
416
|
+
}
|
|
417
|
+
if (this.stopped) return;
|
|
418
|
+
this.connection.sendEvent({ type: "start_command_error", message: error.message });
|
|
419
|
+
});
|
|
420
|
+
return true;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (!this.stopped) {
|
|
423
|
+
this.connection.sendEvent({
|
|
424
|
+
type: "start_command_error",
|
|
425
|
+
message: error instanceof Error ? error.message : String(error)
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
terminateStartCommandChild(child) {
|
|
432
|
+
const existing = this.startCommandTerminations.get(child);
|
|
433
|
+
if (existing) return existing;
|
|
434
|
+
this.expectedStartCommandStops.add(child);
|
|
435
|
+
const processTermination = Promise.resolve(this.terminateStartCommand(child));
|
|
436
|
+
const ended = this.startCommandEndPromises.get(child);
|
|
437
|
+
const termination = (ended ? Promise.race([processTermination, ended]) : processTermination).then(() => void 0).finally(() => this.startCommandTerminations.delete(child));
|
|
438
|
+
this.startCommandTerminations.set(child, termination);
|
|
439
|
+
return termination;
|
|
440
|
+
}
|
|
441
|
+
async terminateAllStartCommands() {
|
|
442
|
+
const live = [...this.liveStartCommandChildren].filter((child) => child.exitCode === null);
|
|
443
|
+
await Promise.all(live.map((child) => this.terminateStartCommandChild(child)));
|
|
444
|
+
}
|
|
445
|
+
settleStartCommandEnd(child) {
|
|
446
|
+
this.resolveStartCommandEnd.get(child)?.();
|
|
447
|
+
this.resolveStartCommandEnd.delete(child);
|
|
448
|
+
this.startCommandEndPromises.delete(child);
|
|
449
|
+
}
|
|
450
|
+
trackBackgroundTask(task) {
|
|
451
|
+
this.backgroundTasks.add(task);
|
|
452
|
+
void task.finally(() => this.backgroundTasks.delete(task));
|
|
453
|
+
}
|
|
454
|
+
forwardSetupOutput(stream, data) {
|
|
455
|
+
if (this.stopped) return;
|
|
456
|
+
this.connection.sendEvent({ type: "setup_output", stream, data });
|
|
457
|
+
this.writeOutput(stream, data);
|
|
458
|
+
}
|
|
459
|
+
reportUnexpectedError(error) {
|
|
460
|
+
if (this.stopped) return;
|
|
461
|
+
this.connection.sendEvent({
|
|
462
|
+
type: "setup_error",
|
|
463
|
+
message: error instanceof Error ? error.message : String(error)
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
156
468
|
// src/utils/session-identity.ts
|
|
157
469
|
async function checkSessionTaskIdentity(params) {
|
|
158
470
|
const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
|
|
@@ -553,6 +865,8 @@ var AdhocSessionRunner = class {
|
|
|
553
865
|
connection;
|
|
554
866
|
lifecycle;
|
|
555
867
|
harness;
|
|
868
|
+
portDiscovery;
|
|
869
|
+
commandSupervisor;
|
|
556
870
|
config;
|
|
557
871
|
callbacks;
|
|
558
872
|
abortController = new AbortController();
|
|
@@ -567,6 +881,14 @@ var AdhocSessionRunner = class {
|
|
|
567
881
|
buildAdhocPtyBridge(this.connection),
|
|
568
882
|
resolveTuiAdapter(resolveAdhocTui(process.env))
|
|
569
883
|
);
|
|
884
|
+
this.portDiscovery = deps?.portDiscovery ?? new PortDiscovery({
|
|
885
|
+
report: (ports) => this.connection.reportDiscoveredPorts(ports)
|
|
886
|
+
});
|
|
887
|
+
this.commandSupervisor = deps?.commandSupervisor ?? new WorkspaceCommandSupervisor({
|
|
888
|
+
config: loadConveyorConfig(),
|
|
889
|
+
workspaceDir: config.workspaceDir,
|
|
890
|
+
connection: this.connection
|
|
891
|
+
});
|
|
570
892
|
this.lifecycle = new Lifecycle(
|
|
571
893
|
// No git flush: the WIP snapshot machinery is branch/task-shaped; the human
|
|
572
894
|
// commits/pushes explicitly from the interactive shell.
|
|
@@ -605,6 +927,13 @@ var AdhocSessionRunner = class {
|
|
|
605
927
|
await this.connection.call("connectAgent", {
|
|
606
928
|
sessionId: this.config.connection.sessionId
|
|
607
929
|
});
|
|
930
|
+
await this.portDiscovery.start().catch(() => {
|
|
931
|
+
});
|
|
932
|
+
if (this.stopped) {
|
|
933
|
+
this._finalState = "finished";
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
this.commandSupervisor.start();
|
|
608
937
|
await this.connection.emitStatus("running");
|
|
609
938
|
this.callbacks.onEvent?.({ type: "adhoc_runner_started", projectId: this.config.projectId });
|
|
610
939
|
this.lifecycle.startIdleTimer();
|
|
@@ -617,7 +946,7 @@ var AdhocSessionRunner = class {
|
|
|
617
946
|
this.connection.sendEvent({ type: "error", message });
|
|
618
947
|
this._finalState = "error";
|
|
619
948
|
} finally {
|
|
620
|
-
this.shutdown();
|
|
949
|
+
await this.shutdown();
|
|
621
950
|
}
|
|
622
951
|
}
|
|
623
952
|
/**
|
|
@@ -651,6 +980,8 @@ var AdhocSessionRunner = class {
|
|
|
651
980
|
requestStop() {
|
|
652
981
|
if (this.stopped) return;
|
|
653
982
|
this.stopped = true;
|
|
983
|
+
this.portDiscovery.stop();
|
|
984
|
+
void this.commandSupervisor.stop();
|
|
654
985
|
this.abortController.abort();
|
|
655
986
|
if (this.stopResolver) {
|
|
656
987
|
const resolve = this.stopResolver;
|
|
@@ -670,8 +1001,10 @@ var AdhocSessionRunner = class {
|
|
|
670
1001
|
});
|
|
671
1002
|
}
|
|
672
1003
|
}
|
|
673
|
-
shutdown() {
|
|
1004
|
+
async shutdown() {
|
|
674
1005
|
this.stopped = true;
|
|
1006
|
+
this.portDiscovery.stop();
|
|
1007
|
+
await this.commandSupervisor.stop();
|
|
675
1008
|
this.lifecycle.destroy();
|
|
676
1009
|
this.connection.sendEvent({ type: "shutdown", reason: this._finalState ?? "finished" });
|
|
677
1010
|
this.connection.disconnect();
|
|
@@ -1381,10 +1714,13 @@ var runner = new SessionRunner(
|
|
|
1381
1714
|
var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1382
1715
|
var sessionChildren = new SessionChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1383
1716
|
var shutdownSignal;
|
|
1717
|
+
var workspaceCommandSupervisor = null;
|
|
1718
|
+
var shutdownCompletion = null;
|
|
1384
1719
|
var shutdownAgent = (signal) => {
|
|
1385
1720
|
logger5.info(`Received ${signal}, flushing git and stopping agent`);
|
|
1721
|
+
const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
1386
1722
|
shutdownSignal = signal;
|
|
1387
|
-
|
|
1723
|
+
shutdownCompletion ??= (async () => {
|
|
1388
1724
|
try {
|
|
1389
1725
|
await Promise.all([reviewChildren.stopAll(), sessionChildren.stopAll()]);
|
|
1390
1726
|
} catch {
|
|
@@ -1392,9 +1728,11 @@ var shutdownAgent = (signal) => {
|
|
|
1392
1728
|
try {
|
|
1393
1729
|
await runner.flushGitOnShutdown();
|
|
1394
1730
|
} finally {
|
|
1731
|
+
await commandShutdown;
|
|
1395
1732
|
runner.stop();
|
|
1396
1733
|
}
|
|
1397
1734
|
})();
|
|
1735
|
+
void shutdownCompletion;
|
|
1398
1736
|
setTimeout(() => {
|
|
1399
1737
|
logger5.warn(`Forcing exit after ${signal} timeout`);
|
|
1400
1738
|
logger5.warn(
|
|
@@ -1413,63 +1751,25 @@ var shutdownAgent = (signal) => {
|
|
|
1413
1751
|
};
|
|
1414
1752
|
process.on("SIGTERM", () => shutdownAgent("SIGTERM"));
|
|
1415
1753
|
process.on("SIGINT", () => shutdownAgent("SIGINT"));
|
|
1416
|
-
await
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
(
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
child.kill("SIGTERM");
|
|
1433
|
-
} catch {
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
};
|
|
1437
|
-
var launchStartCommand = (command) => {
|
|
1438
|
-
logger5.info("Running start command", { command });
|
|
1439
|
-
runner.connection.sendEvent({ type: "start_command_started" });
|
|
1440
|
-
const child = runStartCommand(command, CONVEYOR_WORKSPACE, (stream, data) => {
|
|
1441
|
-
runner.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
1442
|
-
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
1443
|
-
});
|
|
1444
|
-
startCommandChild = child;
|
|
1445
|
-
child.on("exit", (code, signal) => {
|
|
1446
|
-
if (startCommandChild === child) startCommandChild = null;
|
|
1447
|
-
if (expectedStartCommandStops.has(child)) return;
|
|
1448
|
-
logger5.info("Start command exited", { code, signal });
|
|
1449
|
-
runner.connection.sendEvent({
|
|
1450
|
-
type: "start_command_exited",
|
|
1451
|
-
code,
|
|
1452
|
-
signal,
|
|
1453
|
-
message: `start command exited${code === null ? "" : ` with code ${code}`}${signal ? ` (signal: ${signal})` : ""}`
|
|
1454
|
-
});
|
|
1455
|
-
if (code !== null && code !== 0) {
|
|
1456
|
-
runner.connection.sendEvent({
|
|
1457
|
-
type: "start_command_error",
|
|
1458
|
-
message: `start command exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
child.on("error", (err) => {
|
|
1463
|
-
if (startCommandChild === child) startCommandChild = null;
|
|
1464
|
-
logger5.error("Start command error", { error: err.message });
|
|
1465
|
-
runner.connection.sendEvent({ type: "start_command_error", message: err.message });
|
|
1754
|
+
workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
|
|
1755
|
+
connect: () => runner.connect(),
|
|
1756
|
+
isShuttingDown: () => shutdownSignal !== void 0,
|
|
1757
|
+
create: () => new WorkspaceCommandSupervisor({
|
|
1758
|
+
config: loadConveyorConfig(),
|
|
1759
|
+
workspaceDir: CONVEYOR_WORKSPACE,
|
|
1760
|
+
connection: runner.connection
|
|
1761
|
+
})
|
|
1762
|
+
});
|
|
1763
|
+
if (!workspaceCommandSupervisor) {
|
|
1764
|
+
logger5.info("Agent stopped before workspace commands were started");
|
|
1765
|
+
if (shutdownCompletion) await shutdownCompletion;
|
|
1766
|
+
logAgentExit(shutdownSignal ? "signal" : "clean", {
|
|
1767
|
+
exitCode: 0,
|
|
1768
|
+
signal: shutdownSignal,
|
|
1769
|
+
finalState: runner.finalState ?? void 0
|
|
1466
1770
|
});
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
process.env,
|
|
1470
|
-
(message) => logSetupOutput("stdout", `[sidecars] ${message}
|
|
1471
|
-
`)
|
|
1472
|
-
);
|
|
1771
|
+
process.exit(0);
|
|
1772
|
+
}
|
|
1473
1773
|
void checkSessionTaskIdentity({
|
|
1474
1774
|
sessionId: process.env.CONVEYOR_SESSION_ID,
|
|
1475
1775
|
taskId: CONVEYOR_TASK_ID,
|
|
@@ -1505,63 +1805,8 @@ if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
|
|
|
1505
1805
|
});
|
|
1506
1806
|
});
|
|
1507
1807
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
if (!conveyorConfig?.startCommand) return;
|
|
1511
|
-
runner.connection.sendEvent({
|
|
1512
|
-
type: "start_command_output",
|
|
1513
|
-
stream: "stdout",
|
|
1514
|
-
data: "[conveyor-agent] Restarting start command...\n"
|
|
1515
|
-
});
|
|
1516
|
-
stopStartCommandChild();
|
|
1517
|
-
launchStartCommand(conveyorConfig.startCommand);
|
|
1518
|
-
});
|
|
1519
|
-
if (conveyorConfig) {
|
|
1520
|
-
void (async () => {
|
|
1521
|
-
const gitState = await awaitGitReady({
|
|
1522
|
-
onLog: (m) => logSetupOutput("stdout", `[git] ${m}
|
|
1523
|
-
`)
|
|
1524
|
-
});
|
|
1525
|
-
if (gitState === "failed" || gitState === "timeout") {
|
|
1526
|
-
runner.connection.sendEvent({
|
|
1527
|
-
type: "setup_error",
|
|
1528
|
-
message: "Workspace not ready \u2014 skipping setup/start"
|
|
1529
|
-
});
|
|
1530
|
-
return;
|
|
1531
|
-
}
|
|
1532
|
-
await waitForSidecars({
|
|
1533
|
-
onLog: (message) => logSetupOutput("stdout", `[sidecars] ${message}
|
|
1534
|
-
`),
|
|
1535
|
-
startLazy: false
|
|
1536
|
-
});
|
|
1537
|
-
if (conveyorConfig.setupCommand) {
|
|
1538
|
-
logger5.info("Running setup command (background)", {
|
|
1539
|
-
command: conveyorConfig.setupCommand
|
|
1540
|
-
});
|
|
1541
|
-
try {
|
|
1542
|
-
await runSetupCommand(conveyorConfig.setupCommand, CONVEYOR_WORKSPACE, logSetupOutput);
|
|
1543
|
-
logger5.info("Setup command completed");
|
|
1544
|
-
} catch (error) {
|
|
1545
|
-
const msg = error instanceof Error ? error.message : "Setup command failed";
|
|
1546
|
-
logger5.error("Setup command failed", { error: msg });
|
|
1547
|
-
runner.connection.sendEvent({ type: "setup_error", message: msg });
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
let startCommandRunning = false;
|
|
1551
|
-
if (conveyorConfig.startCommand) {
|
|
1552
|
-
launchStartCommand(conveyorConfig.startCommand);
|
|
1553
|
-
startCommandRunning = true;
|
|
1554
|
-
}
|
|
1555
|
-
const forwardPorts = await loadForwardPorts(CONVEYOR_WORKSPACE);
|
|
1556
|
-
const previewPorts = buildSessionPreviewPorts(forwardPorts);
|
|
1557
|
-
runner.connection.sendEvent({
|
|
1558
|
-
type: "setup_complete",
|
|
1559
|
-
startCommandRunning,
|
|
1560
|
-
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
1561
|
-
});
|
|
1562
|
-
})();
|
|
1563
|
-
}
|
|
1564
|
-
runner.run().then(() => {
|
|
1808
|
+
runner.run().then(async () => {
|
|
1809
|
+
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
1565
1810
|
const errored = runner.finalState === "error";
|
|
1566
1811
|
const reason = shutdownSignal ? "signal" : errored ? "error" : "clean";
|
|
1567
1812
|
logAgentExit(reason, {
|
|
@@ -1570,7 +1815,8 @@ runner.run().then(() => {
|
|
|
1570
1815
|
finalState: runner.finalState ?? void 0
|
|
1571
1816
|
});
|
|
1572
1817
|
process.exit(errored ? 1 : 0);
|
|
1573
|
-
}).catch((error) => {
|
|
1818
|
+
}).catch(async (error) => {
|
|
1819
|
+
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
1574
1820
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1575
1821
|
logger5.error("Agent runner failed", { error: msg });
|
|
1576
1822
|
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|