@rallycry/conveyor-agent 10.13.71 → 11.0.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/dist/{boot-PKHZUDAC.js → boot-PKQ2I66D.js} +56 -5
- package/dist/chunk-3F4ZZKCA.js +291 -0
- package/dist/{chunk-QU53HND5.js → chunk-GL2DIQEQ.js} +116 -47
- package/dist/chunk-JQVAWRVL.js +697 -0
- package/dist/chunk-N4WSUTGV.js +2564 -0
- package/dist/{chunk-7MMECTTJ.js → chunk-PEEGCZAR.js} +2257 -3701
- package/dist/{chunk-DOB2XE2I.js → chunk-UBDSLM44.js} +4 -2
- package/dist/{chunk-GJXAAPJ6.js → chunk-W4LZ7R6Z.js} +117 -400
- package/dist/chunk-XORJ6SII.js +46 -0
- package/dist/cli.js +304 -804
- package/dist/index.d.ts +170 -3
- package/dist/index.js +9 -5
- package/dist/serve-boot-YPAUKENG.js +225 -0
- package/dist/{server-CC7KUJOK.js → server-7XH7RYUX.js} +3 -2
- package/package.json +5 -4
- package/skills/conveyor-build/SKILL.md +262 -0
- package/skills/conveyor-build/references/pack-path.md +224 -0
- package/skills/conveyor-build/references/task-path.md +67 -0
- package/skills/conveyor-consensus/SKILL.md +99 -0
- package/skills/conveyor-consensus/references/doc-template.html +204 -0
- package/skills/conveyor-local-loop/SKILL.md +258 -0
- package/skills/conveyor-meeting-review/SKILL.md +72 -0
- package/skills/conveyor-plan/SKILL.md +177 -0
- package/skills/conveyor-plan/references/plan-format.md +134 -0
- package/skills/conveyor-review/SKILL.md +161 -0
- package/skills/conveyor-start/SKILL.md +106 -0
- package/skills/conveyor-triage/SKILL.md +174 -0
- package/skills/conveyor-workflows/SKILL.md +195 -0
- package/skills/conveyor-workflows/references/mcp-setup.md +43 -0
package/dist/cli.js
CHANGED
|
@@ -4,750 +4,77 @@ import {
|
|
|
4
4
|
isLegacyEntrypointLaunch
|
|
5
5
|
} from "./chunk-R3FDJQL6.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
WorkspaceCommandSupervisor,
|
|
8
|
+
startWorkspaceCommandsAfterConnect,
|
|
9
|
+
stopWorkspaceCommands
|
|
10
|
+
} from "./chunk-JQVAWRVL.js";
|
|
11
|
+
import {
|
|
10
12
|
DEFAULT_SONNET_MODEL,
|
|
11
|
-
Lifecycle,
|
|
12
|
-
PortDiscovery,
|
|
13
13
|
PtyHarness,
|
|
14
|
+
RUNNER_MODES,
|
|
14
15
|
SessionRunner,
|
|
15
|
-
applyBootstrapToEnv,
|
|
16
|
-
awaitGitReady,
|
|
17
16
|
buildProjectTools,
|
|
18
17
|
buildRateLimitEvents,
|
|
19
|
-
buildSessionPreviewPorts,
|
|
20
18
|
buildSynthesizedCredentials,
|
|
21
19
|
buildUnmeasurableEvent,
|
|
22
20
|
claudeJsonPath,
|
|
23
|
-
createServiceLogger,
|
|
24
|
-
fetchBootstrap,
|
|
25
21
|
isPermissionDeniedError,
|
|
26
|
-
|
|
27
|
-
loadForwardPorts,
|
|
22
|
+
parsePackExecution,
|
|
28
23
|
parseUsageGauges,
|
|
29
|
-
readWorkspaceBytes,
|
|
30
24
|
resolvePlaywrightMcpServer,
|
|
31
25
|
resolveSessionStart,
|
|
32
26
|
resolveTuiAdapter,
|
|
33
27
|
resolveTuiKindFromEnv,
|
|
34
28
|
runUsageProbe,
|
|
35
|
-
sampleKeyUsage
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} from "./chunk-7MMECTTJ.js";
|
|
29
|
+
sampleKeyUsage
|
|
30
|
+
} from "./chunk-PEEGCZAR.js";
|
|
31
|
+
import "./chunk-XORJ6SII.js";
|
|
39
32
|
import {
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
AgentConnection,
|
|
34
|
+
CodespacePortVisibility,
|
|
35
|
+
DEFAULT_LIFECYCLE_CONFIG,
|
|
36
|
+
Lifecycle,
|
|
37
|
+
PortDiscovery,
|
|
38
|
+
applyBootstrapToEnv,
|
|
39
|
+
createServiceLogger,
|
|
40
|
+
fetchBootstrap,
|
|
41
|
+
loadConveyorConfig
|
|
42
|
+
} from "./chunk-N4WSUTGV.js";
|
|
43
|
+
import "./chunk-GL2DIQEQ.js";
|
|
42
44
|
import "./chunk-IA45XHOA.js";
|
|
43
|
-
import
|
|
44
|
-
|
|
45
|
-
} from "./chunk-EXQ6AHOY.js";
|
|
46
|
-
import {
|
|
47
|
-
workbenchEnabled
|
|
48
|
-
} from "./chunk-KMB3BU4S.js";
|
|
45
|
+
import "./chunk-EXQ6AHOY.js";
|
|
46
|
+
import "./chunk-KMB3BU4S.js";
|
|
49
47
|
import {
|
|
50
48
|
inheritedEnv,
|
|
51
49
|
resolvePtySpawn,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
terminateProcessGroup
|
|
56
|
-
} from "./chunk-GJXAAPJ6.js";
|
|
50
|
+
sessionTempBase
|
|
51
|
+
} from "./chunk-3F4ZZKCA.js";
|
|
52
|
+
import "./chunk-W4LZ7R6Z.js";
|
|
57
53
|
import "./chunk-6Q6LQBWO.js";
|
|
58
54
|
|
|
59
55
|
// src/cli.ts
|
|
60
56
|
import { readFileSync } from "fs";
|
|
61
|
-
import { join as
|
|
57
|
+
import { join as join2, dirname } from "path";
|
|
62
58
|
import { fileURLToPath } from "url";
|
|
63
59
|
|
|
64
|
-
// src/setup/sidecars.ts
|
|
65
|
-
import net from "net";
|
|
66
|
-
var POSTGRES_TIMEOUT_MS = 12e4;
|
|
67
|
-
var FIREBASE_TIMEOUT_MS = 6e4;
|
|
68
|
-
var FALLBACK_TIMEOUT_MS = 3e4;
|
|
69
|
-
var DEFAULT_SIDECAR_POLL_INTERVAL_MS = 1e3;
|
|
70
|
-
var DEFAULT_PROBE_TIMEOUT_MS = 2e3;
|
|
71
|
-
var POSTGRES_DEFAULT_PORT = 5432;
|
|
72
|
-
var FIREBASE_DEFAULT_PORT = 9099;
|
|
73
|
-
function parseHostPort(value, defaultPort) {
|
|
74
|
-
const trimmed = value.trim().replace(/^[a-z]+:\/\//i, "");
|
|
75
|
-
if (!trimmed) return null;
|
|
76
|
-
const idx = trimmed.lastIndexOf(":");
|
|
77
|
-
if (idx === -1) {
|
|
78
|
-
return { host: trimmed, port: defaultPort };
|
|
79
|
-
}
|
|
80
|
-
const host = trimmed.slice(0, idx) || "localhost";
|
|
81
|
-
const port = Number(trimmed.slice(idx + 1));
|
|
82
|
-
return { host, port: Number.isFinite(port) ? port : defaultPort };
|
|
83
|
-
}
|
|
84
|
-
function resolveSidecarTargets(env = process.env) {
|
|
85
|
-
const targets = [];
|
|
86
|
-
const databaseUrl = env.DATABASE_URL;
|
|
87
|
-
if (databaseUrl) {
|
|
88
|
-
try {
|
|
89
|
-
const url = new URL(databaseUrl);
|
|
90
|
-
const host = url.hostname || "localhost";
|
|
91
|
-
const port = url.port ? Number(url.port) : POSTGRES_DEFAULT_PORT;
|
|
92
|
-
if (Number.isFinite(port)) {
|
|
93
|
-
targets.push({ name: "postgres", host, port, timeoutMs: POSTGRES_TIMEOUT_MS });
|
|
94
|
-
}
|
|
95
|
-
} catch {
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const firebaseHost = env.FIREBASE_AUTH_EMULATOR_HOST;
|
|
99
|
-
if (firebaseHost) {
|
|
100
|
-
const parsed = parseHostPort(firebaseHost, FIREBASE_DEFAULT_PORT);
|
|
101
|
-
if (parsed) {
|
|
102
|
-
targets.push({
|
|
103
|
-
name: "firebase auth emulator",
|
|
104
|
-
host: parsed.host,
|
|
105
|
-
port: parsed.port,
|
|
106
|
-
timeoutMs: FIREBASE_TIMEOUT_MS
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return targets;
|
|
111
|
-
}
|
|
112
|
-
function abortError() {
|
|
113
|
-
const error = new Error("Operation aborted");
|
|
114
|
-
error.name = "AbortError";
|
|
115
|
-
return error;
|
|
116
|
-
}
|
|
117
|
-
function throwIfAborted(signal) {
|
|
118
|
-
if (signal?.aborted) throw abortError();
|
|
119
|
-
}
|
|
120
|
-
function defaultProbe(target, signal) {
|
|
121
|
-
throwIfAborted(signal);
|
|
122
|
-
return new Promise((resolve) => {
|
|
123
|
-
let settled = false;
|
|
124
|
-
const socket = net.createConnection({ host: target.host, port: target.port });
|
|
125
|
-
const done = (ok) => {
|
|
126
|
-
if (settled) return;
|
|
127
|
-
settled = true;
|
|
128
|
-
socket.destroy();
|
|
129
|
-
signal?.removeEventListener("abort", onAbort);
|
|
130
|
-
resolve(ok);
|
|
131
|
-
};
|
|
132
|
-
const onAbort = () => done(false);
|
|
133
|
-
socket.once("connect", () => done(true));
|
|
134
|
-
socket.once("error", () => done(false));
|
|
135
|
-
socket.setTimeout(DEFAULT_PROBE_TIMEOUT_MS, () => done(false));
|
|
136
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
var delay = (ms, signal) => {
|
|
140
|
-
throwIfAborted(signal);
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
const timer = setTimeout(() => {
|
|
143
|
-
signal?.removeEventListener("abort", onAbort);
|
|
144
|
-
resolve();
|
|
145
|
-
}, ms);
|
|
146
|
-
const onAbort = () => {
|
|
147
|
-
clearTimeout(timer);
|
|
148
|
-
reject(abortError());
|
|
149
|
-
};
|
|
150
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
function raceWithAbort(promise, signal) {
|
|
154
|
-
if (!signal) return promise;
|
|
155
|
-
throwIfAborted(signal);
|
|
156
|
-
return new Promise((resolve, reject) => {
|
|
157
|
-
const onAbort = () => reject(abortError());
|
|
158
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
159
|
-
void promise.then(
|
|
160
|
-
(value) => {
|
|
161
|
-
signal.removeEventListener("abort", onAbort);
|
|
162
|
-
resolve(value);
|
|
163
|
-
},
|
|
164
|
-
(error) => {
|
|
165
|
-
signal.removeEventListener("abort", onAbort);
|
|
166
|
-
reject(error);
|
|
167
|
-
}
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
async function waitForTarget(target, opts) {
|
|
172
|
-
const { onLog, pollIntervalMs, probe, signal } = opts;
|
|
173
|
-
const timeoutMs = opts.timeoutMs ?? target.timeoutMs ?? FALLBACK_TIMEOUT_MS;
|
|
174
|
-
const deadline = Date.now() + timeoutMs;
|
|
175
|
-
onLog(`Waiting for ${target.name} on ${target.host}:${target.port}...`);
|
|
176
|
-
while (true) {
|
|
177
|
-
throwIfAborted(signal);
|
|
178
|
-
if (await raceWithAbort(probe(target, signal), signal)) {
|
|
179
|
-
onLog(`${target.name} is ready`);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (Date.now() >= deadline) {
|
|
183
|
-
onLog(
|
|
184
|
-
`WARNING: ${target.name} not ready after ${Math.round(timeoutMs / 1e3)}s, continuing anyway`
|
|
185
|
-
);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
await delay(pollIntervalMs, signal);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async function waitForSidecars(opts = {}) {
|
|
192
|
-
const {
|
|
193
|
-
env = process.env,
|
|
194
|
-
onLog = () => {
|
|
195
|
-
},
|
|
196
|
-
timeoutMs,
|
|
197
|
-
pollIntervalMs = DEFAULT_SIDECAR_POLL_INTERVAL_MS,
|
|
198
|
-
probe = defaultProbe,
|
|
199
|
-
signal
|
|
200
|
-
} = opts;
|
|
201
|
-
throwIfAborted(signal);
|
|
202
|
-
const targets = resolveSidecarTargets(env);
|
|
203
|
-
if (targets.length === 0) return;
|
|
204
|
-
await Promise.all(
|
|
205
|
-
targets.map(
|
|
206
|
-
(target) => waitForTarget(target, { onLog, timeoutMs, pollIntervalMs, probe, signal })
|
|
207
|
-
)
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/setup/db-schema-sync.ts
|
|
212
|
-
import { basename, dirname, join } from "path";
|
|
213
|
-
var PRISMA_SCHEMA_CANDIDATES = [
|
|
214
|
-
"packages/db/prisma/schema.prisma",
|
|
215
|
-
"prisma/schema.prisma",
|
|
216
|
-
"apps/api/prisma/schema.prisma",
|
|
217
|
-
"packages/db/schema.prisma"
|
|
218
|
-
];
|
|
219
|
-
var PRISMA_DB_PUSH = "bunx prisma db push";
|
|
220
|
-
var SCHEMA_SYNC_TIMEOUT_MS = 18e4;
|
|
221
|
-
function depsInclude(rawDeps, dep) {
|
|
222
|
-
return (rawDeps ?? "").split(",").map((entry) => entry.trim()).includes(dep);
|
|
223
|
-
}
|
|
224
|
-
function isDisabled(env) {
|
|
225
|
-
const flag = env.CONVEYOR_DB_SCHEMA_SYNC?.trim().toLowerCase();
|
|
226
|
-
return flag === "0" || flag === "false";
|
|
227
|
-
}
|
|
228
|
-
function packageRootForSchema(schemaPath) {
|
|
229
|
-
const dir = dirname(schemaPath);
|
|
230
|
-
return basename(dir) === "prisma" ? dirname(dir) : dir;
|
|
231
|
-
}
|
|
232
|
-
async function resolveSchemaSyncPlan(options) {
|
|
233
|
-
const { workspaceDir, env } = options;
|
|
234
|
-
const pathExists = options.pathExists ?? workspacePathExists;
|
|
235
|
-
if (isDisabled(env)) return null;
|
|
236
|
-
const override = env.CONVEYOR_DB_SCHEMA_SYNC_COMMAND?.trim();
|
|
237
|
-
if (override) return { command: override, cwd: workspaceDir, source: "override" };
|
|
238
|
-
if (!depsInclude(env.CONVEYOR_DEPS, "postgresql")) return null;
|
|
239
|
-
if (!env.DATABASE_URL?.trim()) return null;
|
|
240
|
-
for (const relative of PRISMA_SCHEMA_CANDIDATES) {
|
|
241
|
-
const schemaPath = join(workspaceDir, relative);
|
|
242
|
-
if (await pathExists(schemaPath)) {
|
|
243
|
-
return { command: PRISMA_DB_PUSH, cwd: packageRootForSchema(schemaPath), source: "prisma" };
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
async function runDbSchemaSync(options) {
|
|
249
|
-
let plan = null;
|
|
250
|
-
try {
|
|
251
|
-
plan = await resolveSchemaSyncPlan(options);
|
|
252
|
-
} catch {
|
|
253
|
-
return "skipped";
|
|
254
|
-
}
|
|
255
|
-
if (!plan) return "skipped";
|
|
256
|
-
const { onOutput, runCommand } = options;
|
|
257
|
-
onOutput("stdout", `[db] syncing schema (${plan.source}): ${plan.command}
|
|
258
|
-
`);
|
|
259
|
-
const controller = new AbortController();
|
|
260
|
-
const abort = () => controller.abort();
|
|
261
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
262
|
-
const timer = setTimeout(abort, options.timeoutMs ?? SCHEMA_SYNC_TIMEOUT_MS);
|
|
263
|
-
timer.unref();
|
|
264
|
-
try {
|
|
265
|
-
await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
|
|
266
|
-
onOutput("stdout", "[db] schema in sync\n");
|
|
267
|
-
return "synced";
|
|
268
|
-
} catch (error) {
|
|
269
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
270
|
-
onOutput("stderr", `[db] schema sync failed: ${message}
|
|
271
|
-
`);
|
|
272
|
-
return "failed";
|
|
273
|
-
} finally {
|
|
274
|
-
clearTimeout(timer);
|
|
275
|
-
options.signal?.removeEventListener("abort", abort);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/setup/deps-sync.ts
|
|
280
|
-
import { join as join2 } from "path";
|
|
281
|
-
var INSTALLER_SCRIPT_CANDIDATES = ["scripts/install-deps.sh"];
|
|
282
|
-
var BUN_INSTALL = "bun install --frozen-lockfile";
|
|
283
|
-
var DEPS_SYNC_TIMEOUT_MS = 9e5;
|
|
284
|
-
var REGISTRY_KEY_ENV = "NPM_CACHE_READER_KEY";
|
|
285
|
-
function isDisabled2(env) {
|
|
286
|
-
const flag = env.CONVEYOR_DEPS_SYNC?.trim().toLowerCase();
|
|
287
|
-
return flag === "0" || flag === "false";
|
|
288
|
-
}
|
|
289
|
-
var CKSUM_POLYNOMIAL = 79764919;
|
|
290
|
-
var CKSUM_TABLE = (() => {
|
|
291
|
-
const table = new Uint32Array(256);
|
|
292
|
-
for (let index = 0; index < 256; index++) {
|
|
293
|
-
let value = index << 24;
|
|
294
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
295
|
-
value = value & 2147483648 ? (value << 1 ^ CKSUM_POLYNOMIAL) >>> 0 : value << 1 >>> 0;
|
|
296
|
-
}
|
|
297
|
-
table[index] = value >>> 0;
|
|
298
|
-
}
|
|
299
|
-
return table;
|
|
300
|
-
})();
|
|
301
|
-
function posixCksum(bytes) {
|
|
302
|
-
let crc = 0;
|
|
303
|
-
for (const byte of bytes) {
|
|
304
|
-
crc = (crc << 8 ^ CKSUM_TABLE[(crc >>> 24 ^ byte) & 255]) >>> 0;
|
|
305
|
-
}
|
|
306
|
-
for (let length = bytes.length; length > 0; length >>>= 8) {
|
|
307
|
-
crc = (crc << 8 ^ CKSUM_TABLE[(crc >>> 24 ^ length & 255) & 255]) >>> 0;
|
|
308
|
-
}
|
|
309
|
-
return ~crc >>> 0;
|
|
310
|
-
}
|
|
311
|
-
function bunLockFingerprint(bytes) {
|
|
312
|
-
return `${posixCksum(bytes)}:${bytes.length}`;
|
|
313
|
-
}
|
|
314
|
-
async function defaultDirExists(path) {
|
|
315
|
-
return (await statWorkspacePath(path)).isDirectory;
|
|
316
|
-
}
|
|
317
|
-
async function readLockDrift(options) {
|
|
318
|
-
const { workspaceDir, readBytes } = options;
|
|
319
|
-
let baked;
|
|
320
|
-
try {
|
|
321
|
-
const raw = await readBytes(join2(workspaceDir, ".conveyor/prebake-cache.json"));
|
|
322
|
-
const parsed = JSON.parse(Buffer.from(raw).toString("utf8"));
|
|
323
|
-
const value = parsed?.bunLockCrc;
|
|
324
|
-
if (typeof value !== "string" || !value) return null;
|
|
325
|
-
baked = value;
|
|
326
|
-
} catch {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
const actual = bunLockFingerprint(await readBytes(join2(workspaceDir, "bun.lock")));
|
|
331
|
-
return actual === baked ? null : { baked, actual };
|
|
332
|
-
} catch {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
async function resolveDepsSyncPlan(options) {
|
|
337
|
-
const { workspaceDir, env } = options;
|
|
338
|
-
const pathExists = options.pathExists ?? workspacePathExists;
|
|
339
|
-
const dirExists = options.dirExists ?? defaultDirExists;
|
|
340
|
-
const readBytes = options.readBytes ?? readWorkspaceBytes;
|
|
341
|
-
if (isDisabled2(env)) return null;
|
|
342
|
-
if (!await pathExists(join2(workspaceDir, "package.json"))) return null;
|
|
343
|
-
const override = env.CONVEYOR_DEPS_SYNC_COMMAND?.trim();
|
|
344
|
-
const resolveCommand = async () => {
|
|
345
|
-
if (override) return override;
|
|
346
|
-
for (const relative of INSTALLER_SCRIPT_CANDIDATES) {
|
|
347
|
-
if (await pathExists(join2(workspaceDir, relative))) return `bash ${relative}`;
|
|
348
|
-
}
|
|
349
|
-
return BUN_INSTALL;
|
|
350
|
-
};
|
|
351
|
-
if (!await dirExists(join2(workspaceDir, "node_modules"))) {
|
|
352
|
-
return {
|
|
353
|
-
command: await resolveCommand(),
|
|
354
|
-
cwd: workspaceDir,
|
|
355
|
-
reason: "missing_node_modules",
|
|
356
|
-
detail: "the pod booted with no node_modules \u2014 the baked dependency tree is absent"
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
const drift = await readLockDrift({ workspaceDir, readBytes });
|
|
360
|
-
if (drift) {
|
|
361
|
-
return {
|
|
362
|
-
command: await resolveCommand(),
|
|
363
|
-
cwd: workspaceDir,
|
|
364
|
-
reason: "lockfile_drift",
|
|
365
|
-
detail: `bun.lock changed since the image bake (baked ${drift.baked}, checked out ${drift.actual})`
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
function describeFailure(plan, error, env) {
|
|
371
|
-
const cause = error instanceof Error ? error.message : String(error);
|
|
372
|
-
const parts = [
|
|
373
|
-
`Dependency install failed (${plan.reason}): ${plan.detail}.`,
|
|
374
|
-
`Ran \`${plan.command}\` in ${plan.cwd} \u2014 ${cause}.`
|
|
375
|
-
];
|
|
376
|
-
if (!env[REGISTRY_KEY_ENV]?.trim()) {
|
|
377
|
-
parts.push(
|
|
378
|
-
`${REGISTRY_KEY_ENV} is not set on this pod, so packages on the private registry cache cannot be fetched \u2014 set it as a project secret.`
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
return parts.join(" ");
|
|
382
|
-
}
|
|
383
|
-
async function runDepsSync(options) {
|
|
384
|
-
let plan = null;
|
|
385
|
-
try {
|
|
386
|
-
plan = await resolveDepsSyncPlan(options);
|
|
387
|
-
} catch {
|
|
388
|
-
return { outcome: "skipped" };
|
|
389
|
-
}
|
|
390
|
-
if (!plan) return { outcome: "skipped" };
|
|
391
|
-
const { onOutput, runCommand, env } = options;
|
|
392
|
-
onOutput("stdout", `[deps] ${plan.detail}
|
|
393
|
-
`);
|
|
394
|
-
onOutput("stdout", `[deps] installing (${plan.reason}): ${plan.command}
|
|
395
|
-
`);
|
|
396
|
-
const controller = new AbortController();
|
|
397
|
-
const abort = () => controller.abort();
|
|
398
|
-
options.signal?.addEventListener("abort", abort, { once: true });
|
|
399
|
-
const timer = setTimeout(abort, options.timeoutMs ?? DEPS_SYNC_TIMEOUT_MS);
|
|
400
|
-
timer.unref();
|
|
401
|
-
try {
|
|
402
|
-
await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
|
|
403
|
-
onOutput("stdout", "[deps] dependencies installed\n");
|
|
404
|
-
return { outcome: "installed" };
|
|
405
|
-
} catch (error) {
|
|
406
|
-
const message = describeFailure(plan, error, env);
|
|
407
|
-
onOutput("stderr", `[deps] ${message}
|
|
408
|
-
`);
|
|
409
|
-
return { outcome: "failed", message };
|
|
410
|
-
} finally {
|
|
411
|
-
clearTimeout(timer);
|
|
412
|
-
options.signal?.removeEventListener("abort", abort);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// src/setup/workspace-command-supervisor.ts
|
|
417
|
-
function defaultCommandExecutors() {
|
|
418
|
-
if (workbenchEnabled()) {
|
|
419
|
-
const client = getWorkbenchClient();
|
|
420
|
-
return {
|
|
421
|
-
runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput),
|
|
422
|
-
runShellCommand: (cmd, cwd, onOutput, signal) => client.runSetupCommand(cmd, cwd, onOutput, signal)
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
return { runStartCommand, runShellCommand: runSetupCommand };
|
|
426
|
-
}
|
|
427
|
-
var defaultWriteOutput = (stream, data) => {
|
|
428
|
-
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
429
|
-
};
|
|
430
|
-
function stopWorkspaceCommands(supervisor) {
|
|
431
|
-
return supervisor?.stop() ?? Promise.resolve();
|
|
432
|
-
}
|
|
433
|
-
async function startWorkspaceCommandsAfterConnect(options) {
|
|
434
|
-
const connected = await options.connect();
|
|
435
|
-
if (!connected || options.isShuttingDown()) return null;
|
|
436
|
-
const supervisor = options.create();
|
|
437
|
-
supervisor.start();
|
|
438
|
-
return supervisor;
|
|
439
|
-
}
|
|
440
|
-
var WorkspaceCommandSupervisor = class {
|
|
441
|
-
abortController = new AbortController();
|
|
442
|
-
expectedStartCommandStops = /* @__PURE__ */ new WeakSet();
|
|
443
|
-
config;
|
|
444
|
-
workspaceDir;
|
|
445
|
-
connection;
|
|
446
|
-
env;
|
|
447
|
-
awaitGitReadyFn;
|
|
448
|
-
waitForSidecarsFn;
|
|
449
|
-
runStartCommandFn;
|
|
450
|
-
syncDbSchemaFn;
|
|
451
|
-
syncDepsFn;
|
|
452
|
-
loadForwardPortsFn;
|
|
453
|
-
writeOutput;
|
|
454
|
-
terminateStartCommand;
|
|
455
|
-
reportBootMilestoneFn;
|
|
456
|
-
startCommandChild = null;
|
|
457
|
-
liveStartCommandChildren = /* @__PURE__ */ new Set();
|
|
458
|
-
startCommandTerminations = /* @__PURE__ */ new WeakMap();
|
|
459
|
-
startCommandEndPromises = /* @__PURE__ */ new WeakMap();
|
|
460
|
-
resolveStartCommandEnd = /* @__PURE__ */ new WeakMap();
|
|
461
|
-
backgroundTasks = /* @__PURE__ */ new Set();
|
|
462
|
-
startCommandQueue = Promise.resolve();
|
|
463
|
-
shutdownPromise = null;
|
|
464
|
-
startCommandLaunchRequested = false;
|
|
465
|
-
started = false;
|
|
466
|
-
stopped = false;
|
|
467
|
-
/** Resolved by notifyLoopReady() once the PTY is live — the runner fires it
|
|
468
|
-
* on the first harness event of the initial query (or when a prefill
|
|
469
|
-
* parks), NOT after the first turn completes, so the app's start command
|
|
470
|
-
* isn't held behind a long working turn. Also resolved by stop() if
|
|
471
|
-
* shutdown arrives first — either way unblocks the awaiter in
|
|
472
|
-
* runSetupAndStart so a supervisor stopped before the loop ever signals
|
|
473
|
-
* doesn't hang its background task (and therefore stop()) forever. */
|
|
474
|
-
resolveLoopReady;
|
|
475
|
-
loopReady = new Promise((resolve) => {
|
|
476
|
-
this.resolveLoopReady = resolve;
|
|
477
|
-
});
|
|
478
|
-
constructor(options) {
|
|
479
|
-
this.config = options.config;
|
|
480
|
-
this.workspaceDir = options.workspaceDir;
|
|
481
|
-
this.connection = options.connection;
|
|
482
|
-
this.env = options.env ?? process.env;
|
|
483
|
-
this.awaitGitReadyFn = options.awaitGitReady ?? ((opts) => awaitGitReady({
|
|
484
|
-
onLog: opts.onLog,
|
|
485
|
-
signal: opts.signal
|
|
486
|
-
}));
|
|
487
|
-
this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
|
|
488
|
-
const executors = defaultCommandExecutors();
|
|
489
|
-
this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
|
|
490
|
-
this.syncDbSchemaFn = options.syncDbSchema ?? ((opts) => runDbSchemaSync({
|
|
491
|
-
workspaceDir: this.workspaceDir,
|
|
492
|
-
env: this.env,
|
|
493
|
-
runCommand: executors.runShellCommand,
|
|
494
|
-
onOutput: opts.onOutput,
|
|
495
|
-
signal: opts.signal
|
|
496
|
-
}));
|
|
497
|
-
this.syncDepsFn = options.syncDeps ?? ((opts) => runDepsSync({
|
|
498
|
-
workspaceDir: this.workspaceDir,
|
|
499
|
-
env: this.env,
|
|
500
|
-
runCommand: executors.runShellCommand,
|
|
501
|
-
onOutput: opts.onOutput,
|
|
502
|
-
signal: opts.signal
|
|
503
|
-
}));
|
|
504
|
-
this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
|
|
505
|
-
this.writeOutput = options.writeOutput ?? defaultWriteOutput;
|
|
506
|
-
this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
|
|
507
|
-
this.reportBootMilestoneFn = options.reportBootMilestone ?? ((key) => {
|
|
508
|
-
void reportBootMilestone({ key, env: this.env });
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
start() {
|
|
512
|
-
if (this.started || this.stopped) return;
|
|
513
|
-
this.started = true;
|
|
514
|
-
this.connection.onRunStartCommand(() => this.restartStartCommand());
|
|
515
|
-
if (this.config) {
|
|
516
|
-
this.trackBackgroundTask(
|
|
517
|
-
this.runSetupAndStart().catch((error) => this.reportUnexpectedError(error))
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
/** Release the start-command launch — called once the runner's core loop
|
|
522
|
-
* (the PTY) is live. Idempotent; resolving an already-settled promise is a
|
|
523
|
-
* no-op, so a late/duplicate call (or one after stop() already resolved
|
|
524
|
-
* it) is harmless. */
|
|
525
|
-
notifyLoopReady() {
|
|
526
|
-
this.resolveLoopReady();
|
|
527
|
-
}
|
|
528
|
-
stop() {
|
|
529
|
-
if (this.shutdownPromise) return this.shutdownPromise;
|
|
530
|
-
this.stopped = true;
|
|
531
|
-
this.resolveLoopReady();
|
|
532
|
-
this.abortController.abort();
|
|
533
|
-
const backgroundTasks = [...this.backgroundTasks];
|
|
534
|
-
const termination = this.terminateAllStartCommands();
|
|
535
|
-
this.shutdownPromise = (async () => {
|
|
536
|
-
await Promise.allSettled([termination, this.startCommandQueue, ...backgroundTasks]);
|
|
537
|
-
await this.terminateAllStartCommands();
|
|
538
|
-
})();
|
|
539
|
-
return this.shutdownPromise;
|
|
540
|
-
}
|
|
541
|
-
async runSetupAndStart() {
|
|
542
|
-
const gitState = await this.awaitGitReadyFn({
|
|
543
|
-
onLog: (message) => this.forwardSetupOutput("stdout", `[git] ${message}
|
|
544
|
-
`),
|
|
545
|
-
signal: this.abortController.signal
|
|
546
|
-
});
|
|
547
|
-
if (this.stopped) return;
|
|
548
|
-
if (gitState === "failed" || gitState === "timeout") {
|
|
549
|
-
this.connection.sendEvent({
|
|
550
|
-
type: "setup_error",
|
|
551
|
-
message: "Workspace not ready \u2014 skipping setup/start"
|
|
552
|
-
});
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
await this.waitForSidecarsFn({
|
|
556
|
-
onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
|
|
557
|
-
`),
|
|
558
|
-
signal: this.abortController.signal
|
|
559
|
-
});
|
|
560
|
-
if (this.stopped) return;
|
|
561
|
-
this.reportBootMilestoneFn("sidecars_ready");
|
|
562
|
-
await this.loopReady;
|
|
563
|
-
if (this.stopped) return;
|
|
564
|
-
await this.syncDeps();
|
|
565
|
-
if (this.stopped) return;
|
|
566
|
-
await this.syncDbSchema();
|
|
567
|
-
if (this.stopped) return;
|
|
568
|
-
const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
|
|
569
|
-
if (this.stopped) return;
|
|
570
|
-
this.reportBootMilestoneFn("start_command_launched");
|
|
571
|
-
const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
|
|
572
|
-
if (this.stopped) return;
|
|
573
|
-
const previewPorts = buildSessionPreviewPorts(forwardPorts);
|
|
574
|
-
this.connection.sendEvent({
|
|
575
|
-
type: "setup_complete",
|
|
576
|
-
startCommandRunning,
|
|
577
|
-
// Reported separately so the server can tell "nothing to run" apart from
|
|
578
|
-
// "the dev server failed to launch" — both report startCommandRunning:false.
|
|
579
|
-
startCommandConfigured: Boolean(this.config?.startCommand),
|
|
580
|
-
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
/** Run the dependency sync, forwarding its output as setup output so it lands
|
|
584
|
-
* in the card's setup log. A failure is announced with its own cause rather
|
|
585
|
-
* than thrown: the start command still launches, but the card now carries
|
|
586
|
-
* the reason instead of a generic `start command exited with code 1`. */
|
|
587
|
-
async syncDeps() {
|
|
588
|
-
const result = await this.syncDepsFn({
|
|
589
|
-
onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
|
|
590
|
-
signal: this.abortController.signal
|
|
591
|
-
});
|
|
592
|
-
if (result.outcome !== "failed" || this.stopped) return;
|
|
593
|
-
this.connection.sendEvent({
|
|
594
|
-
type: "setup_error",
|
|
595
|
-
message: result.message ?? "Dependency install failed \u2014 the start command will fail until dependencies are installed (see pod logs)"
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
/** Run the schema sync, forwarding its output as setup output so it lands in
|
|
599
|
-
* the card's setup log. A failure is announced rather than thrown: it is the
|
|
600
|
-
* exact condition that used to render as a silent "Displaying 0 results". */
|
|
601
|
-
async syncDbSchema() {
|
|
602
|
-
const outcome = await this.syncDbSchemaFn({
|
|
603
|
-
onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
|
|
604
|
-
signal: this.abortController.signal
|
|
605
|
-
});
|
|
606
|
-
if (outcome !== "failed" || this.stopped) return;
|
|
607
|
-
this.connection.sendEvent({
|
|
608
|
-
type: "setup_error",
|
|
609
|
-
message: "Database schema sync failed \u2014 the app may error on queries touching recently added columns (see pod logs)"
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
restartStartCommand() {
|
|
613
|
-
if (this.stopped || !this.config?.startCommand) return;
|
|
614
|
-
this.connection.sendEvent({
|
|
615
|
-
type: "start_command_output",
|
|
616
|
-
stream: "stdout",
|
|
617
|
-
data: "[conveyor-agent] Restarting start command...\n"
|
|
618
|
-
});
|
|
619
|
-
void this.enqueueStartCommandReplacement(this.config.startCommand);
|
|
620
|
-
}
|
|
621
|
-
async ensureStartCommandLaunched(command) {
|
|
622
|
-
if (!this.startCommandLaunchRequested) {
|
|
623
|
-
return this.enqueueStartCommandReplacement(command);
|
|
624
|
-
}
|
|
625
|
-
await this.startCommandQueue;
|
|
626
|
-
return !this.stopped && this.liveStartCommandChildren.size > 0;
|
|
627
|
-
}
|
|
628
|
-
enqueueStartCommandReplacement(command) {
|
|
629
|
-
this.startCommandLaunchRequested = true;
|
|
630
|
-
const operation = this.startCommandQueue.then(async () => {
|
|
631
|
-
if (this.stopped) return false;
|
|
632
|
-
await this.terminateAllStartCommands();
|
|
633
|
-
if (this.stopped) return false;
|
|
634
|
-
return this.launchStartCommand(command);
|
|
635
|
-
});
|
|
636
|
-
this.startCommandQueue = operation.then(
|
|
637
|
-
() => void 0,
|
|
638
|
-
() => void 0
|
|
639
|
-
);
|
|
640
|
-
return operation;
|
|
641
|
-
}
|
|
642
|
-
launchStartCommand(command) {
|
|
643
|
-
if (this.stopped) return false;
|
|
644
|
-
this.connection.sendEvent({ type: "start_command_started" });
|
|
645
|
-
try {
|
|
646
|
-
const child = this.runStartCommandFn(command, this.workspaceDir, (stream, data) => {
|
|
647
|
-
if (this.stopped) return;
|
|
648
|
-
this.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
649
|
-
this.writeOutput(stream, data);
|
|
650
|
-
});
|
|
651
|
-
this.liveStartCommandChildren.add(child);
|
|
652
|
-
const ended = new Promise((resolve) => {
|
|
653
|
-
this.resolveStartCommandEnd.set(child, resolve);
|
|
654
|
-
});
|
|
655
|
-
this.startCommandEndPromises.set(child, ended);
|
|
656
|
-
if (this.stopped) {
|
|
657
|
-
void this.terminateStartCommandChild(child);
|
|
658
|
-
return false;
|
|
659
|
-
}
|
|
660
|
-
this.startCommandChild = child;
|
|
661
|
-
child.on("exit", (code, signal) => {
|
|
662
|
-
if (this.startCommandChild === child) this.startCommandChild = null;
|
|
663
|
-
this.liveStartCommandChildren.delete(child);
|
|
664
|
-
this.settleStartCommandEnd(child);
|
|
665
|
-
if (this.stopped || this.expectedStartCommandStops.has(child)) return;
|
|
666
|
-
const message = `start command exited${code === null ? "" : ` with code ${code}`}${signal ? ` (signal: ${signal})` : ""}`;
|
|
667
|
-
this.connection.sendEvent({ type: "start_command_exited", code, signal, message });
|
|
668
|
-
if (code !== null && code !== 0) {
|
|
669
|
-
this.connection.sendEvent({
|
|
670
|
-
type: "start_command_error",
|
|
671
|
-
message: `start command exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
child.on("error", (error) => {
|
|
676
|
-
if (child.pid === void 0) {
|
|
677
|
-
if (this.startCommandChild === child) this.startCommandChild = null;
|
|
678
|
-
this.liveStartCommandChildren.delete(child);
|
|
679
|
-
this.settleStartCommandEnd(child);
|
|
680
|
-
this.startCommandTerminations.delete(child);
|
|
681
|
-
}
|
|
682
|
-
if (this.stopped) return;
|
|
683
|
-
this.connection.sendEvent({ type: "start_command_error", message: error.message });
|
|
684
|
-
});
|
|
685
|
-
return true;
|
|
686
|
-
} catch (error) {
|
|
687
|
-
if (!this.stopped) {
|
|
688
|
-
this.connection.sendEvent({
|
|
689
|
-
type: "start_command_error",
|
|
690
|
-
message: error instanceof Error ? error.message : String(error)
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
terminateStartCommandChild(child) {
|
|
697
|
-
const existing = this.startCommandTerminations.get(child);
|
|
698
|
-
if (existing) return existing;
|
|
699
|
-
this.expectedStartCommandStops.add(child);
|
|
700
|
-
const processTermination = Promise.resolve(this.terminateStartCommand(child));
|
|
701
|
-
const ended = this.startCommandEndPromises.get(child);
|
|
702
|
-
const termination = (ended ? Promise.race([processTermination, ended]) : processTermination).then(() => void 0).finally(() => this.startCommandTerminations.delete(child));
|
|
703
|
-
this.startCommandTerminations.set(child, termination);
|
|
704
|
-
return termination;
|
|
705
|
-
}
|
|
706
|
-
async terminateAllStartCommands() {
|
|
707
|
-
const live = [...this.liveStartCommandChildren].filter((child) => child.exitCode === null);
|
|
708
|
-
await Promise.all(live.map((child) => this.terminateStartCommandChild(child)));
|
|
709
|
-
}
|
|
710
|
-
settleStartCommandEnd(child) {
|
|
711
|
-
this.resolveStartCommandEnd.get(child)?.();
|
|
712
|
-
this.resolveStartCommandEnd.delete(child);
|
|
713
|
-
this.startCommandEndPromises.delete(child);
|
|
714
|
-
}
|
|
715
|
-
trackBackgroundTask(task) {
|
|
716
|
-
this.backgroundTasks.add(task);
|
|
717
|
-
void task.finally(() => this.backgroundTasks.delete(task));
|
|
718
|
-
}
|
|
719
|
-
forwardSetupOutput(stream, data) {
|
|
720
|
-
if (this.stopped) return;
|
|
721
|
-
this.connection.sendEvent({ type: "setup_output", stream, data });
|
|
722
|
-
this.writeOutput(stream, data);
|
|
723
|
-
}
|
|
724
|
-
reportUnexpectedError(error) {
|
|
725
|
-
if (this.stopped) return;
|
|
726
|
-
this.connection.sendEvent({
|
|
727
|
-
type: "setup_error",
|
|
728
|
-
message: error instanceof Error ? error.message : String(error)
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
|
|
733
60
|
// src/utils/session-identity.ts
|
|
734
61
|
async function checkSessionTaskIdentity(params) {
|
|
735
|
-
const { sessionId, taskId, fetchSessionTaskId, logger:
|
|
62
|
+
const { sessionId, taskId, fetchSessionTaskId, logger: logger7 } = params;
|
|
736
63
|
if (!sessionId || sessionId === taskId) return "skipped";
|
|
737
64
|
let sessionTaskId;
|
|
738
65
|
try {
|
|
739
66
|
sessionTaskId = await fetchSessionTaskId(sessionId);
|
|
740
67
|
} catch (err) {
|
|
741
|
-
const
|
|
742
|
-
|
|
68
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
69
|
+
logger7.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
|
|
743
70
|
sessionId,
|
|
744
71
|
taskId,
|
|
745
|
-
error:
|
|
72
|
+
error: message2
|
|
746
73
|
});
|
|
747
74
|
return "error";
|
|
748
75
|
}
|
|
749
76
|
if (sessionTaskId !== taskId) {
|
|
750
|
-
|
|
77
|
+
logger7.warn(
|
|
751
78
|
"!!! CONVEYOR_SESSION_ID is bound to a different task than CONVEYOR_TASK_ID \u2014 server-side guards should still block mutations, but this indicates a misconfiguration or replayed token.",
|
|
752
79
|
{ sessionId, envTaskId: taskId, sessionTaskId }
|
|
753
80
|
);
|
|
@@ -863,10 +190,10 @@ var ProjectSessionRunner = class {
|
|
|
863
190
|
await this.waitUntilStopped();
|
|
864
191
|
this._finalState = "finished";
|
|
865
192
|
} catch (error) {
|
|
866
|
-
const
|
|
867
|
-
process.stderr.write(`[conveyor-agent] Project runner failed: ${
|
|
193
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
194
|
+
process.stderr.write(`[conveyor-agent] Project runner failed: ${message2}
|
|
868
195
|
`);
|
|
869
|
-
this.connection.sendEvent({ type: "error", message });
|
|
196
|
+
this.connection.sendEvent({ type: "error", message: message2 });
|
|
870
197
|
this._finalState = "error";
|
|
871
198
|
} finally {
|
|
872
199
|
this.shutdown();
|
|
@@ -929,7 +256,7 @@ var ProjectSessionRunner = class {
|
|
|
929
256
|
|
|
930
257
|
// src/usage/multi-key-probe.ts
|
|
931
258
|
import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
|
|
932
|
-
import { join
|
|
259
|
+
import { join } from "path";
|
|
933
260
|
var logger = createServiceLogger("multi-key-probe");
|
|
934
261
|
function gaugesToSamples(stdout) {
|
|
935
262
|
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
@@ -957,12 +284,12 @@ function gaugesToSamples(stdout) {
|
|
|
957
284
|
async function isolatedProbe(token, now) {
|
|
958
285
|
let dir = null;
|
|
959
286
|
try {
|
|
960
|
-
dir = await mkdtemp(
|
|
961
|
-
await writeFile(
|
|
287
|
+
dir = await mkdtemp(join(sessionTempBase(), "conveyor-usage-"));
|
|
288
|
+
await writeFile(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
|
|
962
289
|
encoding: "utf8",
|
|
963
290
|
mode: 384
|
|
964
291
|
});
|
|
965
|
-
await copyFile(claudeJsonPath(),
|
|
292
|
+
await copyFile(claudeJsonPath(), join(dir, ".claude.json")).catch(() => {
|
|
966
293
|
});
|
|
967
294
|
return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
|
|
968
295
|
} catch (error) {
|
|
@@ -1138,10 +465,10 @@ var AdhocSessionRunner = class {
|
|
|
1138
465
|
await this.runInteractiveTui();
|
|
1139
466
|
this._finalState = "finished";
|
|
1140
467
|
} catch (error) {
|
|
1141
|
-
const
|
|
1142
|
-
process.stderr.write(`[conveyor-agent] Ad-hoc runner failed: ${
|
|
468
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
469
|
+
process.stderr.write(`[conveyor-agent] Ad-hoc runner failed: ${message2}
|
|
1143
470
|
`);
|
|
1144
|
-
this.connection.sendEvent({ type: "error", message });
|
|
471
|
+
this.connection.sendEvent({ type: "error", message: message2 });
|
|
1145
472
|
this._finalState = "error";
|
|
1146
473
|
} finally {
|
|
1147
474
|
await this.shutdown();
|
|
@@ -1295,6 +622,10 @@ var ReviewChildSupervisor = class {
|
|
|
1295
622
|
spawnFn;
|
|
1296
623
|
child = null;
|
|
1297
624
|
childSessionId = null;
|
|
625
|
+
/** The review session this supervisor currently hosts, if any. */
|
|
626
|
+
get activeSessionId() {
|
|
627
|
+
return this.childSessionId;
|
|
628
|
+
}
|
|
1298
629
|
/** Spawn (or replace) the review child for a `session:spawnReview` push. */
|
|
1299
630
|
async spawn(data) {
|
|
1300
631
|
logger2.info("Spawning same-pod review child", {
|
|
@@ -1328,10 +659,10 @@ var ReviewChildSupervisor = class {
|
|
|
1328
659
|
this.childSessionId = data.sessionId;
|
|
1329
660
|
const spawnedAt = Date.now();
|
|
1330
661
|
let reported = false;
|
|
1331
|
-
const reportOnce = (
|
|
662
|
+
const reportOnce = (message2) => {
|
|
1332
663
|
if (reported) return;
|
|
1333
664
|
reported = true;
|
|
1334
|
-
this.connection.reportReviewSpawnFailure(data.sessionId,
|
|
665
|
+
this.connection.reportReviewSpawnFailure(data.sessionId, message2);
|
|
1335
666
|
};
|
|
1336
667
|
child.stdout?.on("data", (chunk) => {
|
|
1337
668
|
process.stdout.write(`[review-child] ${chunk.toString()}`);
|
|
@@ -1451,6 +782,10 @@ var SessionChildSupervisor = class {
|
|
|
1451
782
|
get size() {
|
|
1452
783
|
return this.children.size;
|
|
1453
784
|
}
|
|
785
|
+
/** Sessions this supervisor currently hosts. Drives the shutdown deferral. */
|
|
786
|
+
get activeSessionIds() {
|
|
787
|
+
return [...this.children.keys()];
|
|
788
|
+
}
|
|
1454
789
|
/** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
|
|
1455
790
|
async spawn(data) {
|
|
1456
791
|
logger3.info("Spawning same-pod session child", {
|
|
@@ -1481,10 +816,10 @@ var SessionChildSupervisor = class {
|
|
|
1481
816
|
this.children.set(data.sessionId, child);
|
|
1482
817
|
const spawnedAt = Date.now();
|
|
1483
818
|
let reported = false;
|
|
1484
|
-
const reportOnce = (
|
|
819
|
+
const reportOnce = (message2) => {
|
|
1485
820
|
if (reported) return;
|
|
1486
821
|
reported = true;
|
|
1487
|
-
this.connection.reportSessionSpawnFailure(data.sessionId,
|
|
822
|
+
this.connection.reportSessionSpawnFailure(data.sessionId, message2);
|
|
1488
823
|
};
|
|
1489
824
|
const prefix = `[${data.mode}-child]`;
|
|
1490
825
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -1611,10 +946,10 @@ var ShellSessionRunner = class {
|
|
|
1611
946
|
await this.runShell();
|
|
1612
947
|
this._finalState = "finished";
|
|
1613
948
|
} catch (error) {
|
|
1614
|
-
const
|
|
1615
|
-
process.stderr.write(`[conveyor-agent] Shell runner failed: ${
|
|
949
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
950
|
+
process.stderr.write(`[conveyor-agent] Shell runner failed: ${message2}
|
|
1616
951
|
`);
|
|
1617
|
-
this.connection.sendEvent({ type: "error", message });
|
|
952
|
+
this.connection.sendEvent({ type: "error", message: message2 });
|
|
1618
953
|
this._finalState = "error";
|
|
1619
954
|
} finally {
|
|
1620
955
|
this.shutdown();
|
|
@@ -1708,14 +1043,158 @@ function createSpawnedChildRunner(inputs) {
|
|
|
1708
1043
|
);
|
|
1709
1044
|
}
|
|
1710
1045
|
|
|
1046
|
+
// src/runner/builder-child.ts
|
|
1047
|
+
import { spawn as nodeSpawn3 } from "child_process";
|
|
1048
|
+
var logger5 = createServiceLogger("BuilderChild");
|
|
1049
|
+
function buildBuilderChildEnv(baseEnv, data) {
|
|
1050
|
+
const env = { ...baseEnv };
|
|
1051
|
+
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
1052
|
+
env.CONVEYOR_SESSION_ID = data.sessionId;
|
|
1053
|
+
env.CONVEYOR_MODE = data.mode ?? "task";
|
|
1054
|
+
env.CONVEYOR_WORKBENCH_TOKEN ??= baseEnv.POD_BOOTSTRAP_TOKEN;
|
|
1055
|
+
delete env.POD_BOOTSTRAP_TOKEN;
|
|
1056
|
+
delete env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1057
|
+
delete env.CONVEYOR_START_COMMAND;
|
|
1058
|
+
delete env.CONVEYOR_GIT_READY;
|
|
1059
|
+
delete env.CONVEYOR_GIT_READY_MARKER;
|
|
1060
|
+
delete env.CONVEYOR_AGENT_MODE;
|
|
1061
|
+
delete env.CONVEYOR_IS_AUTO;
|
|
1062
|
+
return env;
|
|
1063
|
+
}
|
|
1064
|
+
var BuilderChildSupervisor = class {
|
|
1065
|
+
constructor(connection, workspaceDir, spawnFn = nodeSpawn3) {
|
|
1066
|
+
this.connection = connection;
|
|
1067
|
+
this.workspaceDir = workspaceDir;
|
|
1068
|
+
this.spawnFn = spawnFn;
|
|
1069
|
+
}
|
|
1070
|
+
connection;
|
|
1071
|
+
workspaceDir;
|
|
1072
|
+
spawnFn;
|
|
1073
|
+
child = null;
|
|
1074
|
+
childSessionId = null;
|
|
1075
|
+
/** The build session this supervisor currently hosts, if any. */
|
|
1076
|
+
get activeSessionId() {
|
|
1077
|
+
return this.childSessionId;
|
|
1078
|
+
}
|
|
1079
|
+
/** Spawn (or replace) the builder child for a `session:spawnBuilder` push. */
|
|
1080
|
+
async spawn(data) {
|
|
1081
|
+
if (this.childSessionId === data.sessionId && this.child && this.child.exitCode === null) {
|
|
1082
|
+
logger5.info("Builder child already running for this session; ignoring re-push", {
|
|
1083
|
+
buildSessionId: data.sessionId
|
|
1084
|
+
});
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
logger5.info("Spawning same-pod builder child", {
|
|
1088
|
+
buildSessionId: data.sessionId,
|
|
1089
|
+
branch: data.branch ?? void 0,
|
|
1090
|
+
agentName: data.agentName ?? void 0
|
|
1091
|
+
});
|
|
1092
|
+
await this.stopCurrent("superseded by new builder spawn");
|
|
1093
|
+
const cliPath = process.argv[1];
|
|
1094
|
+
if (!cliPath) {
|
|
1095
|
+
this.connection.reportBuilderSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
let child;
|
|
1099
|
+
try {
|
|
1100
|
+
child = this.spawnFn(process.execPath, [cliPath], {
|
|
1101
|
+
cwd: this.workspaceDir,
|
|
1102
|
+
env: buildBuilderChildEnv(process.env, data),
|
|
1103
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1104
|
+
});
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
this.connection.reportBuilderSpawnFailure(
|
|
1107
|
+
data.sessionId,
|
|
1108
|
+
err instanceof Error ? err.message : String(err)
|
|
1109
|
+
);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
this.child = child;
|
|
1113
|
+
this.childSessionId = data.sessionId;
|
|
1114
|
+
this.watchForEarlyExit(child, data.sessionId);
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* A non-zero exit inside the grace window is a spawn failure, not a finished
|
|
1118
|
+
* build — the child never got far enough to connect. Reporting it lets the
|
|
1119
|
+
* server End the orphaned session and fall back to a cold boot instead of
|
|
1120
|
+
* leaving the card parked with a Builder tab that never appears.
|
|
1121
|
+
*/
|
|
1122
|
+
watchForEarlyExit(child, sessionId) {
|
|
1123
|
+
const spawnedAt = Date.now();
|
|
1124
|
+
child.on("exit", (code) => {
|
|
1125
|
+
if (this.childSessionId === sessionId) {
|
|
1126
|
+
this.child = null;
|
|
1127
|
+
this.childSessionId = null;
|
|
1128
|
+
}
|
|
1129
|
+
const early = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
|
|
1130
|
+
if (early && code !== 0) {
|
|
1131
|
+
logger5.warn("Builder child exited inside the spawn grace window", { sessionId, code });
|
|
1132
|
+
this.connection.reportBuilderSpawnFailure(sessionId, `child exited with code ${code}`);
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
/** SIGTERM the current child, escalating to SIGKILL after KILL_WAIT_MS. */
|
|
1137
|
+
async stopCurrent(reason) {
|
|
1138
|
+
const child = this.child;
|
|
1139
|
+
if (!child || child.exitCode !== null) {
|
|
1140
|
+
this.child = null;
|
|
1141
|
+
this.childSessionId = null;
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
logger5.info("Stopping builder child", { reason, buildSessionId: this.childSessionId });
|
|
1145
|
+
child.kill("SIGTERM");
|
|
1146
|
+
await new Promise((resolve) => {
|
|
1147
|
+
const timer = setTimeout(() => {
|
|
1148
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1149
|
+
resolve();
|
|
1150
|
+
}, KILL_WAIT_MS);
|
|
1151
|
+
child.once("exit", () => {
|
|
1152
|
+
clearTimeout(timer);
|
|
1153
|
+
resolve();
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
this.child = null;
|
|
1157
|
+
this.childSessionId = null;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1711
1161
|
// src/runner/host-modes.ts
|
|
1712
1162
|
function hostsSpawnedChildren(mode) {
|
|
1713
|
-
return mode === "task" || mode === "pack";
|
|
1163
|
+
return mode === "task" || mode === "plan" || mode === "pack";
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// src/runner/wire-spawn-children.ts
|
|
1167
|
+
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
1168
|
+
function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
1169
|
+
if (hostsSpawnedChildren(mode)) {
|
|
1170
|
+
connection.onSpawnReview((data) => {
|
|
1171
|
+
void supervisors.review.spawn(data).catch((err) => {
|
|
1172
|
+
logger7.error("Review child spawn threw", { error: message(err) });
|
|
1173
|
+
connection.reportReviewSpawnFailure(data.sessionId, message(err));
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
if (mode === "plan") {
|
|
1178
|
+
connection.onSpawnBuilder((data) => {
|
|
1179
|
+
void supervisors.builder.spawn(data).catch((err) => {
|
|
1180
|
+
logger7.error("Builder child spawn threw", { error: message(err) });
|
|
1181
|
+
connection.reportBuilderSpawnFailure(data.sessionId, message(err));
|
|
1182
|
+
});
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
if (hostsSpawnedChildren(mode)) {
|
|
1186
|
+
connection.onSpawnTui((data) => {
|
|
1187
|
+
void supervisors.session.spawn(data).catch((err) => {
|
|
1188
|
+
logger7.error("Session child spawn threw", { error: message(err) });
|
|
1189
|
+
connection.reportSessionSpawnFailure(data.sessionId, message(err));
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1714
1193
|
}
|
|
1715
1194
|
|
|
1716
1195
|
// src/cli.ts
|
|
1717
1196
|
if (process.argv[2] === "boot") {
|
|
1718
|
-
const { runBoot } = await import("./boot-
|
|
1197
|
+
const { runBoot } = await import("./boot-PKQ2I66D.js");
|
|
1719
1198
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1720
1199
|
}
|
|
1721
1200
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1724,13 +1203,13 @@ if (isLegacyEntrypointLaunch(process.env)) {
|
|
|
1724
1203
|
process.exit(1);
|
|
1725
1204
|
}
|
|
1726
1205
|
if (process.argv.includes("--version")) {
|
|
1727
|
-
const __dirname =
|
|
1728
|
-
const pkgPath =
|
|
1206
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1207
|
+
const pkgPath = join2(__dirname, "..", "package.json");
|
|
1729
1208
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1730
1209
|
process.stdout.write(pkg.version + "\n");
|
|
1731
1210
|
process.exit(0);
|
|
1732
1211
|
}
|
|
1733
|
-
var
|
|
1212
|
+
var logger6 = createServiceLogger("CLI");
|
|
1734
1213
|
var exitContext = {};
|
|
1735
1214
|
var exitLogged = false;
|
|
1736
1215
|
var shuttingDown = false;
|
|
@@ -1745,12 +1224,12 @@ function logAgentExit(reason, opts) {
|
|
|
1745
1224
|
uptimeSec: process.uptime(),
|
|
1746
1225
|
identity: exitContext
|
|
1747
1226
|
});
|
|
1748
|
-
|
|
1227
|
+
logger6[exitLogLevel(reason)]("agent_exit", payload);
|
|
1749
1228
|
}
|
|
1750
1229
|
async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
1751
1230
|
const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1752
1231
|
const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
|
|
1753
|
-
|
|
1232
|
+
logger6.info("Bootstrapping from codespace", {
|
|
1754
1233
|
codespace: instanceName,
|
|
1755
1234
|
apiUrl,
|
|
1756
1235
|
apiUrlFromEnv,
|
|
@@ -1762,7 +1241,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1762
1241
|
bootstrapToken
|
|
1763
1242
|
});
|
|
1764
1243
|
if (!result.ok) {
|
|
1765
|
-
|
|
1244
|
+
logger6.error("Bootstrap failed after retries", {
|
|
1766
1245
|
reason: result.reason,
|
|
1767
1246
|
attempts: result.attempts,
|
|
1768
1247
|
status: result.status,
|
|
@@ -1775,20 +1254,20 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1775
1254
|
process.exit(1);
|
|
1776
1255
|
}
|
|
1777
1256
|
applyBootstrapToEnv(result.config);
|
|
1778
|
-
|
|
1257
|
+
logger6.info("Bootstrap complete", {
|
|
1779
1258
|
taskId: result.config.taskId,
|
|
1780
1259
|
attempts: result.attempts
|
|
1781
1260
|
});
|
|
1782
1261
|
}
|
|
1783
1262
|
function isExpectedAbortError(error) {
|
|
1784
|
-
const
|
|
1785
|
-
const hasAbortMessage = /operation aborted/i.test(
|
|
1263
|
+
const message2 = error.message || "";
|
|
1264
|
+
const hasAbortMessage = /operation aborted/i.test(message2) || /abort/i.test(message2);
|
|
1786
1265
|
const hasAbortCode = !("code" in error) || error.code === void 0 || error.code === "ABORT_ERR";
|
|
1787
1266
|
return hasAbortMessage && hasAbortCode;
|
|
1788
1267
|
}
|
|
1789
1268
|
function exitOnPermissionDenial(err) {
|
|
1790
1269
|
if (!isPermissionDeniedError(err)) return false;
|
|
1791
|
-
|
|
1270
|
+
logger6.error("Not authorized for this project \u2014 parking instead of crash-looping", {
|
|
1792
1271
|
error: err.message
|
|
1793
1272
|
});
|
|
1794
1273
|
logAgentExit("unauthorized", { exitCode: 0 });
|
|
@@ -1797,48 +1276,48 @@ function exitOnPermissionDenial(err) {
|
|
|
1797
1276
|
process.on("uncaughtException", (err) => {
|
|
1798
1277
|
if (err.code === "EPIPE") return;
|
|
1799
1278
|
if (shuttingDown && isExpectedAbortError(err)) {
|
|
1800
|
-
|
|
1279
|
+
logger6.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
|
|
1801
1280
|
return;
|
|
1802
1281
|
}
|
|
1803
1282
|
if (exitOnPermissionDenial(err)) return;
|
|
1804
|
-
|
|
1283
|
+
logger6.error("Uncaught exception", { error: err.message, code: err.code });
|
|
1805
1284
|
logAgentExit("uncaught_exception", { exitCode: 1 });
|
|
1806
1285
|
process.exit(1);
|
|
1807
1286
|
});
|
|
1808
1287
|
process.on("unhandledRejection", (reason) => {
|
|
1809
1288
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
1810
1289
|
if (shuttingDown && isExpectedAbortError(err)) {
|
|
1811
|
-
|
|
1290
|
+
logger6.info("Ignored expected abort rejection after shutdown", { error: err.message });
|
|
1812
1291
|
return;
|
|
1813
1292
|
}
|
|
1814
1293
|
if (exitOnPermissionDenial(err)) return;
|
|
1815
|
-
|
|
1294
|
+
logger6.error("Unhandled rejection", { error: err.message });
|
|
1816
1295
|
logAgentExit("unhandled_rejection", { exitCode: 1 });
|
|
1817
1296
|
process.exit(1);
|
|
1818
1297
|
});
|
|
1819
1298
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1820
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1299
|
+
const { startWorkbenchServer } = await import("./server-7XH7RYUX.js");
|
|
1821
1300
|
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-PAC5OJJG.js");
|
|
1822
1301
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QBCYO4GI.js");
|
|
1823
1302
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
1824
1303
|
const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
|
|
1825
1304
|
if (!token) {
|
|
1826
|
-
|
|
1305
|
+
logger6.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
|
|
1827
1306
|
process.exit(1);
|
|
1828
1307
|
}
|
|
1829
|
-
const pkgDir =
|
|
1830
|
-
const pkg = JSON.parse(readFileSync(
|
|
1308
|
+
const pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
1309
|
+
const pkg = JSON.parse(readFileSync(join2(pkgDir, "..", "package.json"), "utf-8"));
|
|
1831
1310
|
const handle = await startWorkbenchServer({
|
|
1832
1311
|
port,
|
|
1833
1312
|
token,
|
|
1834
1313
|
version: pkg.version ?? "unknown",
|
|
1835
1314
|
oomWatchdog: oomWatchdogOptionsFromEnv({
|
|
1836
|
-
info: (m) =>
|
|
1837
|
-
warn: (m) =>
|
|
1838
|
-
error: (m) =>
|
|
1315
|
+
info: (m) => logger6.info(m),
|
|
1316
|
+
warn: (m) => logger6.warn(m),
|
|
1317
|
+
error: (m) => logger6.error(m)
|
|
1839
1318
|
})
|
|
1840
1319
|
});
|
|
1841
|
-
|
|
1320
|
+
logger6.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
|
|
1842
1321
|
process.on("SIGTERM", () => process.exit(0));
|
|
1843
1322
|
await new Promise(() => {
|
|
1844
1323
|
});
|
|
@@ -1848,7 +1327,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
|
|
|
1848
1327
|
var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
|
|
1849
1328
|
if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
|
|
1850
1329
|
if (!conveyorApiUrl) {
|
|
1851
|
-
|
|
1330
|
+
logger6.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
|
|
1852
1331
|
process.exit(1);
|
|
1853
1332
|
}
|
|
1854
1333
|
await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
|
|
@@ -1866,7 +1345,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
|
|
|
1866
1345
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
|
|
1867
1346
|
var projectIdentity = resolveProjectRunnerIdentity(process.env);
|
|
1868
1347
|
if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
1869
|
-
|
|
1348
|
+
logger6.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
|
|
1870
1349
|
const adhocRunner = new AdhocSessionRunner(
|
|
1871
1350
|
{
|
|
1872
1351
|
connection: {
|
|
@@ -1885,7 +1364,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
1885
1364
|
},
|
|
1886
1365
|
{
|
|
1887
1366
|
onEvent: (event) => {
|
|
1888
|
-
|
|
1367
|
+
logger6.info("Ad-hoc runner event", { eventType: event.type });
|
|
1889
1368
|
}
|
|
1890
1369
|
}
|
|
1891
1370
|
);
|
|
@@ -1909,7 +1388,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
1909
1388
|
process.exit(adhocError ? 1 : 0);
|
|
1910
1389
|
}
|
|
1911
1390
|
if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
1912
|
-
|
|
1391
|
+
logger6.info("Starting project agent", { projectId: projectIdentity.projectId });
|
|
1913
1392
|
const projectRunner = new ProjectSessionRunner(
|
|
1914
1393
|
{
|
|
1915
1394
|
connection: {
|
|
@@ -1924,7 +1403,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
1924
1403
|
},
|
|
1925
1404
|
{
|
|
1926
1405
|
onEvent: (event) => {
|
|
1927
|
-
|
|
1406
|
+
logger6.info("Project runner event", { eventType: event.type });
|
|
1928
1407
|
}
|
|
1929
1408
|
}
|
|
1930
1409
|
);
|
|
@@ -1948,31 +1427,46 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
1948
1427
|
process.exit(projectError ? 1 : 0);
|
|
1949
1428
|
}
|
|
1950
1429
|
if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1430
|
+
logger6.error("Missing required environment variables");
|
|
1431
|
+
logger6.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
|
|
1432
|
+
logger6.error(" CONVEYOR_TASK_ID - ID of the task to execute");
|
|
1433
|
+
logger6.error("");
|
|
1434
|
+
logger6.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
|
|
1435
|
+
logger6.error("");
|
|
1436
|
+
logger6.error("Optional:");
|
|
1437
|
+
logger6.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
|
|
1438
|
+
logger6.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
|
|
1439
|
+
logger6.error(
|
|
1961
1440
|
" Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
|
|
1962
1441
|
);
|
|
1963
1442
|
process.exit(1);
|
|
1964
1443
|
}
|
|
1965
|
-
if (CONVEYOR_MODE
|
|
1966
|
-
|
|
1967
|
-
mode: CONVEYOR_MODE,
|
|
1968
|
-
expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
|
|
1969
|
-
});
|
|
1444
|
+
if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
1445
|
+
logger6.error("Invalid CONVEYOR_MODE", { mode: CONVEYOR_MODE, expected: RUNNER_MODES });
|
|
1970
1446
|
process.exit(1);
|
|
1971
1447
|
}
|
|
1448
|
+
if (CONVEYOR_MODE === "serving") {
|
|
1449
|
+
const { runServingSession } = await import("./serve-boot-YPAUKENG.js");
|
|
1450
|
+
exitContext.runnerMode = "serving";
|
|
1451
|
+
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1452
|
+
const outcome = await runServingSession({
|
|
1453
|
+
apiUrl: conveyorApiUrl ?? "",
|
|
1454
|
+
taskToken: CONVEYOR_TASK_TOKEN,
|
|
1455
|
+
sessionId: process.env.CONVEYOR_SESSION_ID,
|
|
1456
|
+
taskId: CONVEYOR_TASK_ID,
|
|
1457
|
+
workspaceDir: CONVEYOR_WORKSPACE,
|
|
1458
|
+
// BRANCH is the bundle gitPlan's branch (boot/bundle.ts buildChildEnv):
|
|
1459
|
+
// the branch the preview must check out and serve.
|
|
1460
|
+
branch: process.env.BRANCH,
|
|
1461
|
+
onSignal: () => void (shuttingDown = true)
|
|
1462
|
+
});
|
|
1463
|
+
logAgentExit(outcome.reason, { exitCode: outcome.exitCode, finalState: outcome.finalState });
|
|
1464
|
+
process.exit(outcome.exitCode);
|
|
1465
|
+
}
|
|
1972
1466
|
if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
1973
1467
|
const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
|
|
1974
1468
|
if (!spawnedSessionId) {
|
|
1975
|
-
|
|
1469
|
+
logger6.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
|
|
1976
1470
|
process.exit(1);
|
|
1977
1471
|
}
|
|
1978
1472
|
exitContext.sessionId = spawnedSessionId;
|
|
@@ -1985,7 +1479,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
1985
1479
|
workspaceDir: CONVEYOR_WORKSPACE,
|
|
1986
1480
|
projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
|
|
1987
1481
|
});
|
|
1988
|
-
|
|
1482
|
+
logger6.info("Starting spawned session child", {
|
|
1989
1483
|
mode: CONVEYOR_MODE,
|
|
1990
1484
|
sessionId: spawnedSessionId
|
|
1991
1485
|
});
|
|
@@ -2005,7 +1499,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
2005
1499
|
});
|
|
2006
1500
|
process.exit(childError ? 1 : 0);
|
|
2007
1501
|
}
|
|
2008
|
-
|
|
1502
|
+
logger6.info("Starting agent", { mode: CONVEYOR_MODE });
|
|
2009
1503
|
var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
|
|
2010
1504
|
var runner = new SessionRunner(
|
|
2011
1505
|
{
|
|
@@ -2018,6 +1512,10 @@ var runner = new SessionRunner(
|
|
|
2018
1512
|
runnerMode: CONVEYOR_MODE
|
|
2019
1513
|
},
|
|
2020
1514
|
runnerMode: CONVEYOR_MODE,
|
|
1515
|
+
// Only read when the mode is "pack". Parsed rather than cast so an unset or
|
|
1516
|
+
// malformed value falls back to single-pod instead of selecting a third,
|
|
1517
|
+
// non-existent behavior.
|
|
1518
|
+
packExecution: parsePackExecution(process.env.CONVEYOR_PACK_EXECUTION),
|
|
2021
1519
|
isAuto: CONVEYOR_IS_AUTO,
|
|
2022
1520
|
workspaceDir: CONVEYOR_WORKSPACE,
|
|
2023
1521
|
agentMode: CONVEYOR_AGENT_MODE,
|
|
@@ -2025,29 +1523,51 @@ var runner = new SessionRunner(
|
|
|
2025
1523
|
},
|
|
2026
1524
|
{
|
|
2027
1525
|
onStatusChange: (status) => {
|
|
2028
|
-
|
|
1526
|
+
logger6.info("Status changed", { status });
|
|
2029
1527
|
},
|
|
2030
1528
|
onEvent: (event) => {
|
|
2031
1529
|
const detail = event.message ?? event.content ?? event.summary ?? "";
|
|
2032
1530
|
if (detail) {
|
|
2033
|
-
|
|
1531
|
+
logger6.info(detail, { eventType: event.type });
|
|
2034
1532
|
}
|
|
2035
1533
|
}
|
|
2036
1534
|
}
|
|
2037
1535
|
);
|
|
2038
1536
|
var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
2039
1537
|
var sessionChildren = new SessionChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1538
|
+
var builderChildren = new BuilderChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1539
|
+
runner.setLiveChildSources([
|
|
1540
|
+
{
|
|
1541
|
+
kind: "builder",
|
|
1542
|
+
activeSessionIds: () => {
|
|
1543
|
+
const id = builderChildren.activeSessionId;
|
|
1544
|
+
return id ? [id] : [];
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
kind: "review",
|
|
1549
|
+
activeSessionIds: () => {
|
|
1550
|
+
const id = reviewChildren.activeSessionId;
|
|
1551
|
+
return id ? [id] : [];
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
{ kind: "tui", activeSessionIds: () => sessionChildren.activeSessionIds }
|
|
1555
|
+
]);
|
|
2040
1556
|
var shutdownSignal;
|
|
2041
1557
|
var workspaceCommandSupervisor = null;
|
|
2042
1558
|
var shutdownCompletion = null;
|
|
2043
1559
|
var shutdownAgent = (signal) => {
|
|
2044
1560
|
shuttingDown = true;
|
|
2045
|
-
|
|
1561
|
+
logger6.info(`Received ${signal}, flushing git and stopping agent`);
|
|
2046
1562
|
const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2047
1563
|
shutdownSignal = signal;
|
|
2048
1564
|
shutdownCompletion ??= (async () => {
|
|
2049
1565
|
try {
|
|
2050
|
-
await Promise.all([
|
|
1566
|
+
await Promise.all([
|
|
1567
|
+
reviewChildren.stopAll(),
|
|
1568
|
+
sessionChildren.stopAll(),
|
|
1569
|
+
builderChildren.stopCurrent("agent shutdown")
|
|
1570
|
+
]);
|
|
2051
1571
|
} catch {
|
|
2052
1572
|
}
|
|
2053
1573
|
try {
|
|
@@ -2059,8 +1579,8 @@ var shutdownAgent = (signal) => {
|
|
|
2059
1579
|
})();
|
|
2060
1580
|
void shutdownCompletion;
|
|
2061
1581
|
setTimeout(() => {
|
|
2062
|
-
|
|
2063
|
-
|
|
1582
|
+
logger6.warn(`Forcing exit after ${signal} timeout`);
|
|
1583
|
+
logger6.warn(
|
|
2064
1584
|
"agent_exit",
|
|
2065
1585
|
buildAgentExitLog({
|
|
2066
1586
|
reason: "force_timeout",
|
|
@@ -2086,7 +1606,7 @@ workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
|
|
|
2086
1606
|
})
|
|
2087
1607
|
});
|
|
2088
1608
|
if (!workspaceCommandSupervisor) {
|
|
2089
|
-
|
|
1609
|
+
logger6.info("Agent stopped before workspace commands were started");
|
|
2090
1610
|
if (shutdownCompletion) await shutdownCompletion;
|
|
2091
1611
|
logAgentExit(shutdownSignal ? "signal" : "clean", {
|
|
2092
1612
|
exitCode: 0,
|
|
@@ -2106,34 +1626,14 @@ void checkSessionTaskIdentity({
|
|
|
2106
1626
|
});
|
|
2107
1627
|
return ctx.id;
|
|
2108
1628
|
},
|
|
2109
|
-
logger:
|
|
1629
|
+
logger: logger6
|
|
2110
1630
|
});
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
runner.connection.reportReviewSpawnFailure(
|
|
2118
|
-
data.sessionId,
|
|
2119
|
-
err instanceof Error ? err.message : String(err)
|
|
2120
|
-
);
|
|
2121
|
-
});
|
|
2122
|
-
});
|
|
2123
|
-
}
|
|
2124
|
-
if (hostsSpawnedChildren(CONVEYOR_MODE)) {
|
|
2125
|
-
runner.connection.onSpawnTui((data) => {
|
|
2126
|
-
void sessionChildren.spawn(data).catch((err) => {
|
|
2127
|
-
logger5.error("Session child spawn threw", {
|
|
2128
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2129
|
-
});
|
|
2130
|
-
runner.connection.reportSessionSpawnFailure(
|
|
2131
|
-
data.sessionId,
|
|
2132
|
-
err instanceof Error ? err.message : String(err)
|
|
2133
|
-
);
|
|
2134
|
-
});
|
|
2135
|
-
});
|
|
2136
|
-
}
|
|
1631
|
+
wireSpawnChildren(
|
|
1632
|
+
CONVEYOR_MODE,
|
|
1633
|
+
runner.connection,
|
|
1634
|
+
{ review: reviewChildren, builder: builderChildren, session: sessionChildren },
|
|
1635
|
+
logger6
|
|
1636
|
+
);
|
|
2137
1637
|
runner.run().then(async () => {
|
|
2138
1638
|
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2139
1639
|
const errored = runner.finalState === "error";
|
|
@@ -2147,7 +1647,7 @@ runner.run().then(async () => {
|
|
|
2147
1647
|
}).catch(async (error) => {
|
|
2148
1648
|
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2149
1649
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2150
|
-
|
|
1650
|
+
logger6.error("Agent runner failed", { error: msg });
|
|
2151
1651
|
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|
|
2152
1652
|
process.exit(1);
|
|
2153
1653
|
});
|