@rallycry/conveyor-agent 10.13.3 → 10.13.9
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-P4VWWR5O.js → chunk-PXQJ4NVO.js} +380 -171
- package/dist/chunk-PXQJ4NVO.js.map +1 -0
- package/dist/cli.js +217 -69
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/entrypoint.sh +66 -6
- package/dist/chunk-P4VWWR5O.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
awaitGitReady,
|
|
15
15
|
buildPromptBytes,
|
|
16
16
|
buildSessionPreviewPorts,
|
|
17
|
+
buildSynthesizedCredentials,
|
|
18
|
+
claudeJsonPath,
|
|
17
19
|
cleanTerminalOutput,
|
|
18
20
|
createServiceLogger,
|
|
19
21
|
defineTool,
|
|
@@ -23,19 +25,21 @@ import {
|
|
|
23
25
|
loadConveyorConfig,
|
|
24
26
|
loadForwardPorts,
|
|
25
27
|
loadPtySpawn,
|
|
28
|
+
parseUsageGauges,
|
|
26
29
|
resolvePlaywrightMcpServer,
|
|
27
30
|
resolveSessionStart,
|
|
28
31
|
runSetupCommand,
|
|
29
32
|
runStartCommand,
|
|
33
|
+
runUsageProbe,
|
|
30
34
|
sampleKeyUsage,
|
|
31
35
|
terminateProcessGroup,
|
|
32
36
|
textResult
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-PXQJ4NVO.js";
|
|
34
38
|
import "./chunk-7TQO4ZF4.js";
|
|
35
39
|
|
|
36
40
|
// src/cli.ts
|
|
37
41
|
import { readFileSync } from "fs";
|
|
38
|
-
import { join as
|
|
42
|
+
import { join as join3, dirname as dirname3 } from "path";
|
|
39
43
|
import { fileURLToPath } from "url";
|
|
40
44
|
|
|
41
45
|
// src/setup/sidecars.ts
|
|
@@ -207,6 +211,34 @@ async function waitForSidecars(opts = {}) {
|
|
|
207
211
|
);
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
// src/setup/boot-milestone.ts
|
|
215
|
+
var REPORT_TIMEOUT_MS = 5e3;
|
|
216
|
+
async function reportBootMilestone(opts) {
|
|
217
|
+
const env = opts.env ?? process.env;
|
|
218
|
+
const apiUrl = env.CONVEYOR_API_URL;
|
|
219
|
+
const token = env.POD_BOOTSTRAP_TOKEN;
|
|
220
|
+
if (!apiUrl || !token) return false;
|
|
221
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? REPORT_TIMEOUT_MS);
|
|
224
|
+
try {
|
|
225
|
+
const res = await fetchFn(`${apiUrl.replace(/\/$/, "")}/api/v3/pods/boot-milestone`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: {
|
|
228
|
+
"content-type": "application/json",
|
|
229
|
+
authorization: `Bearer ${token}`
|
|
230
|
+
},
|
|
231
|
+
body: JSON.stringify({ key: opts.key }),
|
|
232
|
+
signal: controller.signal
|
|
233
|
+
});
|
|
234
|
+
return res.ok;
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
} finally {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
210
242
|
// src/setup/workspace-command-supervisor.ts
|
|
211
243
|
var defaultWriteOutput = (stream, data) => {
|
|
212
244
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
@@ -236,6 +268,7 @@ var WorkspaceCommandSupervisor = class {
|
|
|
236
268
|
loadForwardPortsFn;
|
|
237
269
|
writeOutput;
|
|
238
270
|
terminateStartCommand;
|
|
271
|
+
reportBootMilestoneFn;
|
|
239
272
|
startCommandChild = null;
|
|
240
273
|
liveStartCommandChildren = /* @__PURE__ */ new Set();
|
|
241
274
|
startCommandTerminations = /* @__PURE__ */ new WeakMap();
|
|
@@ -263,6 +296,9 @@ var WorkspaceCommandSupervisor = class {
|
|
|
263
296
|
this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
|
|
264
297
|
this.writeOutput = options.writeOutput ?? defaultWriteOutput;
|
|
265
298
|
this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
|
|
299
|
+
this.reportBootMilestoneFn = options.reportBootMilestone ?? ((key) => {
|
|
300
|
+
void reportBootMilestone({ key, env: this.env });
|
|
301
|
+
});
|
|
266
302
|
}
|
|
267
303
|
start() {
|
|
268
304
|
if (this.started || this.stopped) return;
|
|
@@ -308,6 +344,7 @@ var WorkspaceCommandSupervisor = class {
|
|
|
308
344
|
});
|
|
309
345
|
return;
|
|
310
346
|
}
|
|
347
|
+
if (gitState === "ready") this.reportBootMilestoneFn("git_ready");
|
|
311
348
|
await this.waitForSidecarsFn({
|
|
312
349
|
onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
|
|
313
350
|
`),
|
|
@@ -315,8 +352,10 @@ var WorkspaceCommandSupervisor = class {
|
|
|
315
352
|
signal: this.abortController.signal
|
|
316
353
|
});
|
|
317
354
|
if (this.stopped) return;
|
|
355
|
+
this.reportBootMilestoneFn("sidecars_ready");
|
|
318
356
|
await this.runConfiguredSetup();
|
|
319
357
|
if (this.stopped) return;
|
|
358
|
+
this.reportBootMilestoneFn("setup_complete");
|
|
320
359
|
const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
|
|
321
360
|
if (this.stopped) return;
|
|
322
361
|
const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
|
|
@@ -469,14 +508,14 @@ var WorkspaceCommandSupervisor = class {
|
|
|
469
508
|
|
|
470
509
|
// src/utils/session-identity.ts
|
|
471
510
|
async function checkSessionTaskIdentity(params) {
|
|
472
|
-
const { sessionId, taskId, fetchSessionTaskId, logger:
|
|
511
|
+
const { sessionId, taskId, fetchSessionTaskId, logger: logger7 } = params;
|
|
473
512
|
if (!sessionId || sessionId === taskId) return "skipped";
|
|
474
513
|
let sessionTaskId;
|
|
475
514
|
try {
|
|
476
515
|
sessionTaskId = await fetchSessionTaskId(sessionId);
|
|
477
516
|
} catch (err) {
|
|
478
517
|
const message = err instanceof Error ? err.message : String(err);
|
|
479
|
-
|
|
518
|
+
logger7.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
|
|
480
519
|
sessionId,
|
|
481
520
|
taskId,
|
|
482
521
|
error: message
|
|
@@ -484,7 +523,7 @@ async function checkSessionTaskIdentity(params) {
|
|
|
484
523
|
return "error";
|
|
485
524
|
}
|
|
486
525
|
if (sessionTaskId !== taskId) {
|
|
487
|
-
|
|
526
|
+
logger7.warn(
|
|
488
527
|
"!!! 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.",
|
|
489
528
|
{ sessionId, envTaskId: taskId, sessionTaskId }
|
|
490
529
|
);
|
|
@@ -666,20 +705,91 @@ var ProjectSessionRunner = class {
|
|
|
666
705
|
}
|
|
667
706
|
};
|
|
668
707
|
|
|
708
|
+
// src/usage/multi-key-probe.ts
|
|
709
|
+
import { mkdtemp, writeFile as writeFile2, copyFile, rm } from "fs/promises";
|
|
710
|
+
import { tmpdir } from "os";
|
|
711
|
+
import { join } from "path";
|
|
712
|
+
var logger = createServiceLogger("multi-key-probe");
|
|
713
|
+
function gaugesToSamples(stdout) {
|
|
714
|
+
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
715
|
+
const samples = [];
|
|
716
|
+
if (sessionUsage !== null) {
|
|
717
|
+
samples.push({
|
|
718
|
+
rateLimitType: "five_hour",
|
|
719
|
+
utilization: sessionUsage,
|
|
720
|
+
status: "allowed",
|
|
721
|
+
resetsAt: sessionResetsAt,
|
|
722
|
+
gauges
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (weeklyUsage !== null) {
|
|
726
|
+
samples.push({
|
|
727
|
+
rateLimitType: "seven_day",
|
|
728
|
+
utilization: weeklyUsage,
|
|
729
|
+
status: "allowed",
|
|
730
|
+
resetsAt: weeklyResetsAt,
|
|
731
|
+
gauges
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return samples;
|
|
735
|
+
}
|
|
736
|
+
async function isolatedProbe(token, now) {
|
|
737
|
+
let dir = null;
|
|
738
|
+
try {
|
|
739
|
+
dir = await mkdtemp(join(tmpdir(), "conveyor-usage-"));
|
|
740
|
+
await writeFile2(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
|
|
741
|
+
encoding: "utf8",
|
|
742
|
+
mode: 384
|
|
743
|
+
});
|
|
744
|
+
await copyFile(claudeJsonPath(), join(dir, ".claude.json")).catch(() => {
|
|
745
|
+
});
|
|
746
|
+
return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
|
|
747
|
+
} catch (error) {
|
|
748
|
+
logger.info("isolated usage probe failed", {
|
|
749
|
+
error: error instanceof Error ? error.message : String(error)
|
|
750
|
+
});
|
|
751
|
+
return "";
|
|
752
|
+
} finally {
|
|
753
|
+
if (dir) await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function probeKeysUsage(keys, deps = {}) {
|
|
758
|
+
const now = deps.now ?? Date.now;
|
|
759
|
+
const probeOne = deps.probeOne ?? ((token) => isolatedProbe(token, now()));
|
|
760
|
+
const results = [];
|
|
761
|
+
for (const key of keys) {
|
|
762
|
+
if (!key.oauthToken) continue;
|
|
763
|
+
try {
|
|
764
|
+
const stdout = await probeOne(key.oauthToken);
|
|
765
|
+
const samples = gaugesToSamples(stdout);
|
|
766
|
+
if (samples.length > 0) {
|
|
767
|
+
results.push({ codingAgentKeyId: key.codingAgentKeyId, samples });
|
|
768
|
+
}
|
|
769
|
+
} catch (error) {
|
|
770
|
+
logger.info("usage probe for key failed", {
|
|
771
|
+
codingAgentKeyId: key.codingAgentKeyId,
|
|
772
|
+
error: error instanceof Error ? error.message : String(error)
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return results;
|
|
777
|
+
}
|
|
778
|
+
|
|
669
779
|
// src/harness/pty/adapters/opencode-auth.ts
|
|
670
780
|
import { promises as fs } from "fs";
|
|
671
|
-
import { dirname as dirname2, join } from "path";
|
|
781
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
672
782
|
import { homedir } from "os";
|
|
673
|
-
var
|
|
783
|
+
var logger2 = createServiceLogger("opencode-auth");
|
|
674
784
|
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
675
785
|
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
676
786
|
function opencodeAuthPath(env) {
|
|
677
|
-
const dataHome = env.XDG_DATA_HOME ??
|
|
678
|
-
return
|
|
787
|
+
const dataHome = env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local", "share");
|
|
788
|
+
return join2(dataHome, "opencode", "auth.json");
|
|
679
789
|
}
|
|
680
790
|
function opencodeConfigPath(env) {
|
|
681
|
-
const configHome = env.XDG_CONFIG_HOME ??
|
|
682
|
-
return
|
|
791
|
+
const configHome = env.XDG_CONFIG_HOME ?? join2(env.HOME ?? homedir(), ".config");
|
|
792
|
+
return join2(configHome, "opencode", "opencode.json");
|
|
683
793
|
}
|
|
684
794
|
function parseOauthSeed(b64) {
|
|
685
795
|
if (!b64) return null;
|
|
@@ -717,7 +827,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
717
827
|
const path = opencodeAuthPath(env);
|
|
718
828
|
const store = await readJsonFile(path);
|
|
719
829
|
if (!shouldSeed(store.openai, seed)) {
|
|
720
|
-
|
|
830
|
+
logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
721
831
|
return;
|
|
722
832
|
}
|
|
723
833
|
store.openai = {
|
|
@@ -727,7 +837,7 @@ async function ensureAuthEntry(env, seed) {
|
|
|
727
837
|
expires: seed.expires
|
|
728
838
|
};
|
|
729
839
|
await writeJsonFile(path, store);
|
|
730
|
-
|
|
840
|
+
logger2.info("seeded opencode oauth store entry");
|
|
731
841
|
}
|
|
732
842
|
async function ensurePluginConfig(env) {
|
|
733
843
|
const path = opencodeConfigPath(env);
|
|
@@ -740,7 +850,7 @@ async function ensurePluginConfig(env) {
|
|
|
740
850
|
const kept = plugins.filter((p) => !isOurs(p));
|
|
741
851
|
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
742
852
|
await writeJsonFile(path, config);
|
|
743
|
-
|
|
853
|
+
logger2.info("ensured opencode codex-auth plugin in config");
|
|
744
854
|
}
|
|
745
855
|
async function seedOpenCodeOauth(env) {
|
|
746
856
|
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
@@ -749,7 +859,7 @@ async function seedOpenCodeOauth(env) {
|
|
|
749
859
|
await ensureAuthEntry(env, seed);
|
|
750
860
|
await ensurePluginConfig(env);
|
|
751
861
|
} catch (err) {
|
|
752
|
-
|
|
862
|
+
logger2.warn(
|
|
753
863
|
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
754
864
|
);
|
|
755
865
|
}
|
|
@@ -1182,7 +1292,7 @@ var AdhocSessionRunner = class {
|
|
|
1182
1292
|
}),
|
|
1183
1293
|
onGitFlush: () => {
|
|
1184
1294
|
},
|
|
1185
|
-
onUsageSample: () => void this.
|
|
1295
|
+
onUsageSample: () => void this.sampleAndReportAllKeys()
|
|
1186
1296
|
}
|
|
1187
1297
|
);
|
|
1188
1298
|
}
|
|
@@ -1202,6 +1312,7 @@ var AdhocSessionRunner = class {
|
|
|
1202
1312
|
projectId: this.config.projectId
|
|
1203
1313
|
});
|
|
1204
1314
|
this.connection.onStop(() => this.requestStop());
|
|
1315
|
+
this.connection.onProbeUsage(() => void this.sampleAndReportAllKeys());
|
|
1205
1316
|
this.lifecycle.startHeartbeat();
|
|
1206
1317
|
this.lifecycle.startTokenRefresh();
|
|
1207
1318
|
this.lifecycle.startUsageSample();
|
|
@@ -1289,7 +1400,44 @@ var AdhocSessionRunner = class {
|
|
|
1289
1400
|
resolve();
|
|
1290
1401
|
}
|
|
1291
1402
|
}
|
|
1292
|
-
|
|
1403
|
+
/**
|
|
1404
|
+
* Multi-key usage refresh: ask the server for every subscription key the
|
|
1405
|
+
* OWNER has, probe each one's `/usage` from THIS pod (isolated per key), and
|
|
1406
|
+
* report each key's gauges tagged with its explicit `codingAgentKeyId`. Falls
|
|
1407
|
+
* back to the single-key self-sample when the owner has no probeable keys
|
|
1408
|
+
* (e.g. API-key-only, or all decrypts failed). Best-effort — never throws.
|
|
1409
|
+
*/
|
|
1410
|
+
async sampleAndReportAllKeys() {
|
|
1411
|
+
if (this.stopped) return;
|
|
1412
|
+
let keys = [];
|
|
1413
|
+
try {
|
|
1414
|
+
const res = await this.connection.call("listKeysToProbe", {
|
|
1415
|
+
sessionId: this.config.connection.sessionId
|
|
1416
|
+
});
|
|
1417
|
+
keys = res?.keys ?? [];
|
|
1418
|
+
} catch {
|
|
1419
|
+
}
|
|
1420
|
+
if (keys.length === 0) {
|
|
1421
|
+
await this.sampleAndReportOwnKey();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
const probed = await probeKeysUsage(keys);
|
|
1425
|
+
for (const { codingAgentKeyId, samples } of probed) {
|
|
1426
|
+
for (const sample of samples) {
|
|
1427
|
+
this.connection.sendEvent({
|
|
1428
|
+
type: "rate_limit_update",
|
|
1429
|
+
rateLimitType: sample.rateLimitType,
|
|
1430
|
+
utilization: sample.utilization,
|
|
1431
|
+
status: sample.status,
|
|
1432
|
+
resetsAt: sample.resetsAt ?? void 0,
|
|
1433
|
+
gauges: sample.gauges,
|
|
1434
|
+
codingAgentKeyId
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
/** Single-key fallback: sample only the key this pod booted under. */
|
|
1440
|
+
async sampleAndReportOwnKey() {
|
|
1293
1441
|
if (this.stopped) return;
|
|
1294
1442
|
const samples = await sampleKeyUsage(process.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
1295
1443
|
for (const sample of samples) {
|
|
@@ -1317,7 +1465,7 @@ var AdhocSessionRunner = class {
|
|
|
1317
1465
|
import { spawn as nodeSpawn, execFile } from "child_process";
|
|
1318
1466
|
import { promisify } from "util";
|
|
1319
1467
|
var execFileAsync = promisify(execFile);
|
|
1320
|
-
var
|
|
1468
|
+
var logger3 = createServiceLogger("ReviewChild");
|
|
1321
1469
|
var SPAWN_FAILURE_GRACE_MS = 15e3;
|
|
1322
1470
|
var KILL_WAIT_MS = 5e3;
|
|
1323
1471
|
function buildReviewChildEnv(baseEnv, data) {
|
|
@@ -1346,7 +1494,7 @@ var ReviewChildSupervisor = class {
|
|
|
1346
1494
|
childSessionId = null;
|
|
1347
1495
|
/** Spawn (or replace) the review child for a `session:spawnReview` push. */
|
|
1348
1496
|
async spawn(data) {
|
|
1349
|
-
|
|
1497
|
+
logger3.info("Spawning same-pod review child", {
|
|
1350
1498
|
reviewSessionId: data.sessionId,
|
|
1351
1499
|
branch: data.branch ?? void 0,
|
|
1352
1500
|
prNumber: data.prNumber ?? void 0
|
|
@@ -1389,7 +1537,7 @@ var ReviewChildSupervisor = class {
|
|
|
1389
1537
|
process.stderr.write(`[review-child] ${chunk.toString()}`);
|
|
1390
1538
|
});
|
|
1391
1539
|
child.on("error", (err) => {
|
|
1392
|
-
|
|
1540
|
+
logger3.error("Review child spawn error", { error: err.message });
|
|
1393
1541
|
if (this.child === child) {
|
|
1394
1542
|
this.child = null;
|
|
1395
1543
|
this.childSessionId = null;
|
|
@@ -1402,7 +1550,7 @@ var ReviewChildSupervisor = class {
|
|
|
1402
1550
|
this.child = null;
|
|
1403
1551
|
this.childSessionId = null;
|
|
1404
1552
|
}
|
|
1405
|
-
|
|
1553
|
+
logger3.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
|
|
1406
1554
|
const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
|
|
1407
1555
|
if (wasCurrent && withinGrace && code !== null && code !== 0) {
|
|
1408
1556
|
reportOnce(`review child exited with code ${code} within startup grace`);
|
|
@@ -1420,7 +1568,7 @@ var ReviewChildSupervisor = class {
|
|
|
1420
1568
|
this.childSessionId = null;
|
|
1421
1569
|
return;
|
|
1422
1570
|
}
|
|
1423
|
-
|
|
1571
|
+
logger3.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
|
|
1424
1572
|
this.child = null;
|
|
1425
1573
|
this.childSessionId = null;
|
|
1426
1574
|
await new Promise((resolve) => {
|
|
@@ -1462,7 +1610,7 @@ var ReviewChildSupervisor = class {
|
|
|
1462
1610
|
timeout: 3e4
|
|
1463
1611
|
});
|
|
1464
1612
|
} catch (err) {
|
|
1465
|
-
|
|
1613
|
+
logger3.warn("Review checkout freshen skipped", {
|
|
1466
1614
|
branch,
|
|
1467
1615
|
error: err instanceof Error ? err.message : String(err)
|
|
1468
1616
|
});
|
|
@@ -1472,7 +1620,7 @@ var ReviewChildSupervisor = class {
|
|
|
1472
1620
|
|
|
1473
1621
|
// src/runner/session-child.ts
|
|
1474
1622
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
1475
|
-
var
|
|
1623
|
+
var logger4 = createServiceLogger("SessionChild");
|
|
1476
1624
|
function buildSpawnedChildEnv(baseEnv, data) {
|
|
1477
1625
|
const env = { ...baseEnv };
|
|
1478
1626
|
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
@@ -1503,7 +1651,7 @@ var SessionChildSupervisor = class {
|
|
|
1503
1651
|
}
|
|
1504
1652
|
/** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
|
|
1505
1653
|
async spawn(data) {
|
|
1506
|
-
|
|
1654
|
+
logger4.info("Spawning same-pod session child", {
|
|
1507
1655
|
spawnedSessionId: data.sessionId,
|
|
1508
1656
|
mode: data.mode
|
|
1509
1657
|
});
|
|
@@ -1544,7 +1692,7 @@ var SessionChildSupervisor = class {
|
|
|
1544
1692
|
process.stderr.write(`${prefix} ${chunk.toString()}`);
|
|
1545
1693
|
});
|
|
1546
1694
|
child.on("error", (err) => {
|
|
1547
|
-
|
|
1695
|
+
logger4.error("Session child spawn error", { error: err.message });
|
|
1548
1696
|
if (this.children.get(data.sessionId) === child) {
|
|
1549
1697
|
this.children.delete(data.sessionId);
|
|
1550
1698
|
}
|
|
@@ -1553,7 +1701,7 @@ var SessionChildSupervisor = class {
|
|
|
1553
1701
|
child.on("exit", (code, signal) => {
|
|
1554
1702
|
const wasCurrent = this.children.get(data.sessionId) === child;
|
|
1555
1703
|
if (wasCurrent) this.children.delete(data.sessionId);
|
|
1556
|
-
|
|
1704
|
+
logger4.info("Session child exited", {
|
|
1557
1705
|
code,
|
|
1558
1706
|
signal,
|
|
1559
1707
|
spawnedSessionId: data.sessionId,
|
|
@@ -1574,7 +1722,7 @@ var SessionChildSupervisor = class {
|
|
|
1574
1722
|
const child = this.children.get(sessionId);
|
|
1575
1723
|
this.children.delete(sessionId);
|
|
1576
1724
|
if (!child || child.exitCode !== null || child.killed) return;
|
|
1577
|
-
|
|
1725
|
+
logger4.info("Stopping session child", { reason, spawnedSessionId: sessionId });
|
|
1578
1726
|
await new Promise((resolve) => {
|
|
1579
1727
|
const timer = setTimeout(() => {
|
|
1580
1728
|
try {
|
|
@@ -1722,7 +1870,7 @@ var ShellSessionRunner = class {
|
|
|
1722
1870
|
};
|
|
1723
1871
|
|
|
1724
1872
|
// src/runner/spawned-child-boot.ts
|
|
1725
|
-
var
|
|
1873
|
+
var logger5 = createServiceLogger("SpawnedChildBoot");
|
|
1726
1874
|
function createSpawnedChildRunner(inputs) {
|
|
1727
1875
|
if (inputs.mode === "shell") {
|
|
1728
1876
|
return new ShellSessionRunner({
|
|
@@ -1752,7 +1900,7 @@ function createSpawnedChildRunner(inputs) {
|
|
|
1752
1900
|
},
|
|
1753
1901
|
{
|
|
1754
1902
|
onEvent: (event) => {
|
|
1755
|
-
|
|
1903
|
+
logger5.info("Spawned TUI event", { eventType: event.type });
|
|
1756
1904
|
}
|
|
1757
1905
|
}
|
|
1758
1906
|
);
|
|
@@ -1761,12 +1909,12 @@ function createSpawnedChildRunner(inputs) {
|
|
|
1761
1909
|
// src/cli.ts
|
|
1762
1910
|
if (process.argv.includes("--version")) {
|
|
1763
1911
|
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
1764
|
-
const pkgPath =
|
|
1912
|
+
const pkgPath = join3(__dirname, "..", "package.json");
|
|
1765
1913
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1766
1914
|
process.stdout.write(pkg.version + "\n");
|
|
1767
1915
|
process.exit(0);
|
|
1768
1916
|
}
|
|
1769
|
-
var
|
|
1917
|
+
var logger6 = createServiceLogger("CLI");
|
|
1770
1918
|
var exitContext = {};
|
|
1771
1919
|
var exitLogged = false;
|
|
1772
1920
|
function logAgentExit(reason, opts) {
|
|
@@ -1780,12 +1928,12 @@ function logAgentExit(reason, opts) {
|
|
|
1780
1928
|
uptimeSec: process.uptime(),
|
|
1781
1929
|
identity: exitContext
|
|
1782
1930
|
});
|
|
1783
|
-
|
|
1931
|
+
logger6[exitLogLevel(reason)]("agent_exit", payload);
|
|
1784
1932
|
}
|
|
1785
1933
|
async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
1786
1934
|
const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1787
1935
|
const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
|
|
1788
|
-
|
|
1936
|
+
logger6.info("Bootstrapping from codespace", {
|
|
1789
1937
|
codespace: instanceName,
|
|
1790
1938
|
apiUrl,
|
|
1791
1939
|
apiUrlFromEnv,
|
|
@@ -1797,7 +1945,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1797
1945
|
bootstrapToken
|
|
1798
1946
|
});
|
|
1799
1947
|
if (!result.ok) {
|
|
1800
|
-
|
|
1948
|
+
logger6.error("Bootstrap failed after retries", {
|
|
1801
1949
|
reason: result.reason,
|
|
1802
1950
|
attempts: result.attempts,
|
|
1803
1951
|
status: result.status,
|
|
@@ -1810,7 +1958,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1810
1958
|
process.exit(1);
|
|
1811
1959
|
}
|
|
1812
1960
|
applyBootstrapToEnv(result.config);
|
|
1813
|
-
|
|
1961
|
+
logger6.info("Bootstrap complete", {
|
|
1814
1962
|
taskId: result.config.taskId,
|
|
1815
1963
|
attempts: result.attempts
|
|
1816
1964
|
});
|
|
@@ -1824,20 +1972,20 @@ function isExpectedAbortError(error) {
|
|
|
1824
1972
|
process.on("uncaughtException", (err) => {
|
|
1825
1973
|
if (err.code === "EPIPE") return;
|
|
1826
1974
|
if (isExpectedAbortError(err)) {
|
|
1827
|
-
|
|
1975
|
+
logger6.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
|
|
1828
1976
|
return;
|
|
1829
1977
|
}
|
|
1830
|
-
|
|
1978
|
+
logger6.error("Uncaught exception", { error: err.message, code: err.code });
|
|
1831
1979
|
logAgentExit("uncaught_exception", { exitCode: 1 });
|
|
1832
1980
|
process.exit(1);
|
|
1833
1981
|
});
|
|
1834
1982
|
process.on("unhandledRejection", (reason) => {
|
|
1835
1983
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
1836
1984
|
if (isExpectedAbortError(err)) {
|
|
1837
|
-
|
|
1985
|
+
logger6.info("Ignored expected abort rejection after shutdown", { error: err.message });
|
|
1838
1986
|
return;
|
|
1839
1987
|
}
|
|
1840
|
-
|
|
1988
|
+
logger6.error("Unhandled rejection", { error: err.message });
|
|
1841
1989
|
logAgentExit("unhandled_rejection", { exitCode: 1 });
|
|
1842
1990
|
process.exit(1);
|
|
1843
1991
|
});
|
|
@@ -1846,7 +1994,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
|
|
|
1846
1994
|
var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
|
|
1847
1995
|
if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
|
|
1848
1996
|
if (!conveyorApiUrl) {
|
|
1849
|
-
|
|
1997
|
+
logger6.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
|
|
1850
1998
|
process.exit(1);
|
|
1851
1999
|
}
|
|
1852
2000
|
await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
|
|
@@ -1864,7 +2012,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
|
|
|
1864
2012
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
|
|
1865
2013
|
var projectIdentity = resolveProjectRunnerIdentity(process.env);
|
|
1866
2014
|
if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
1867
|
-
|
|
2015
|
+
logger6.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
|
|
1868
2016
|
const adhocRunner = new AdhocSessionRunner(
|
|
1869
2017
|
{
|
|
1870
2018
|
connection: {
|
|
@@ -1883,7 +2031,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
1883
2031
|
},
|
|
1884
2032
|
{
|
|
1885
2033
|
onEvent: (event) => {
|
|
1886
|
-
|
|
2034
|
+
logger6.info("Ad-hoc runner event", { eventType: event.type });
|
|
1887
2035
|
}
|
|
1888
2036
|
}
|
|
1889
2037
|
);
|
|
@@ -1901,7 +2049,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
1901
2049
|
process.exit(adhocError ? 1 : 0);
|
|
1902
2050
|
}
|
|
1903
2051
|
if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
1904
|
-
|
|
2052
|
+
logger6.info("Starting project agent", { projectId: projectIdentity.projectId });
|
|
1905
2053
|
const projectRunner = new ProjectSessionRunner(
|
|
1906
2054
|
{
|
|
1907
2055
|
connection: {
|
|
@@ -1916,7 +2064,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
1916
2064
|
},
|
|
1917
2065
|
{
|
|
1918
2066
|
onEvent: (event) => {
|
|
1919
|
-
|
|
2067
|
+
logger6.info("Project runner event", { eventType: event.type });
|
|
1920
2068
|
}
|
|
1921
2069
|
}
|
|
1922
2070
|
);
|
|
@@ -1934,22 +2082,22 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
1934
2082
|
process.exit(projectError ? 1 : 0);
|
|
1935
2083
|
}
|
|
1936
2084
|
if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2085
|
+
logger6.error("Missing required environment variables");
|
|
2086
|
+
logger6.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
|
|
2087
|
+
logger6.error(" CONVEYOR_TASK_ID - ID of the task to execute");
|
|
2088
|
+
logger6.error("");
|
|
2089
|
+
logger6.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
|
|
2090
|
+
logger6.error("");
|
|
2091
|
+
logger6.error("Optional:");
|
|
2092
|
+
logger6.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
|
|
2093
|
+
logger6.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
|
|
2094
|
+
logger6.error(
|
|
1947
2095
|
" Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
|
|
1948
2096
|
);
|
|
1949
2097
|
process.exit(1);
|
|
1950
2098
|
}
|
|
1951
2099
|
if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
|
|
1952
|
-
|
|
2100
|
+
logger6.error("Invalid CONVEYOR_MODE", {
|
|
1953
2101
|
mode: CONVEYOR_MODE,
|
|
1954
2102
|
expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
|
|
1955
2103
|
});
|
|
@@ -1958,7 +2106,7 @@ if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "cod
|
|
|
1958
2106
|
if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
1959
2107
|
const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
|
|
1960
2108
|
if (!spawnedSessionId) {
|
|
1961
|
-
|
|
2109
|
+
logger6.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
|
|
1962
2110
|
process.exit(1);
|
|
1963
2111
|
}
|
|
1964
2112
|
exitContext.sessionId = spawnedSessionId;
|
|
@@ -1971,7 +2119,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
1971
2119
|
workspaceDir: CONVEYOR_WORKSPACE,
|
|
1972
2120
|
projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
|
|
1973
2121
|
});
|
|
1974
|
-
|
|
2122
|
+
logger6.info("Starting spawned session child", {
|
|
1975
2123
|
mode: CONVEYOR_MODE,
|
|
1976
2124
|
sessionId: spawnedSessionId
|
|
1977
2125
|
});
|
|
@@ -1985,7 +2133,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
1985
2133
|
});
|
|
1986
2134
|
process.exit(childError ? 1 : 0);
|
|
1987
2135
|
}
|
|
1988
|
-
|
|
2136
|
+
logger6.info("Starting agent", { mode: CONVEYOR_MODE });
|
|
1989
2137
|
var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
|
|
1990
2138
|
var runner = new SessionRunner(
|
|
1991
2139
|
{
|
|
@@ -2005,12 +2153,12 @@ var runner = new SessionRunner(
|
|
|
2005
2153
|
},
|
|
2006
2154
|
{
|
|
2007
2155
|
onStatusChange: (status) => {
|
|
2008
|
-
|
|
2156
|
+
logger6.info("Status changed", { status });
|
|
2009
2157
|
},
|
|
2010
2158
|
onEvent: (event) => {
|
|
2011
2159
|
const detail = event.message ?? event.content ?? event.summary ?? "";
|
|
2012
2160
|
if (detail) {
|
|
2013
|
-
|
|
2161
|
+
logger6.info(detail, { eventType: event.type });
|
|
2014
2162
|
}
|
|
2015
2163
|
}
|
|
2016
2164
|
}
|
|
@@ -2021,7 +2169,7 @@ var shutdownSignal;
|
|
|
2021
2169
|
var workspaceCommandSupervisor = null;
|
|
2022
2170
|
var shutdownCompletion = null;
|
|
2023
2171
|
var shutdownAgent = (signal) => {
|
|
2024
|
-
|
|
2172
|
+
logger6.info(`Received ${signal}, flushing git and stopping agent`);
|
|
2025
2173
|
const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2026
2174
|
shutdownSignal = signal;
|
|
2027
2175
|
shutdownCompletion ??= (async () => {
|
|
@@ -2038,8 +2186,8 @@ var shutdownAgent = (signal) => {
|
|
|
2038
2186
|
})();
|
|
2039
2187
|
void shutdownCompletion;
|
|
2040
2188
|
setTimeout(() => {
|
|
2041
|
-
|
|
2042
|
-
|
|
2189
|
+
logger6.warn(`Forcing exit after ${signal} timeout`);
|
|
2190
|
+
logger6.warn(
|
|
2043
2191
|
"agent_exit",
|
|
2044
2192
|
buildAgentExitLog({
|
|
2045
2193
|
reason: "force_timeout",
|
|
@@ -2065,7 +2213,7 @@ workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
|
|
|
2065
2213
|
})
|
|
2066
2214
|
});
|
|
2067
2215
|
if (!workspaceCommandSupervisor) {
|
|
2068
|
-
|
|
2216
|
+
logger6.info("Agent stopped before workspace commands were started");
|
|
2069
2217
|
if (shutdownCompletion) await shutdownCompletion;
|
|
2070
2218
|
logAgentExit(shutdownSignal ? "signal" : "clean", {
|
|
2071
2219
|
exitCode: 0,
|
|
@@ -2081,12 +2229,12 @@ void checkSessionTaskIdentity({
|
|
|
2081
2229
|
const ctx = await runner.connection.call("getTaskContext", { sessionId });
|
|
2082
2230
|
return ctx.id;
|
|
2083
2231
|
},
|
|
2084
|
-
logger:
|
|
2232
|
+
logger: logger6
|
|
2085
2233
|
});
|
|
2086
2234
|
if (CONVEYOR_MODE === "task") {
|
|
2087
2235
|
runner.connection.onSpawnReview((data) => {
|
|
2088
2236
|
void reviewChildren.spawn(data).catch((err) => {
|
|
2089
|
-
|
|
2237
|
+
logger6.error("Review child spawn threw", {
|
|
2090
2238
|
error: err instanceof Error ? err.message : String(err)
|
|
2091
2239
|
});
|
|
2092
2240
|
runner.connection.reportReviewSpawnFailure(
|
|
@@ -2099,7 +2247,7 @@ if (CONVEYOR_MODE === "task") {
|
|
|
2099
2247
|
if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
|
|
2100
2248
|
runner.connection.onSpawnTui((data) => {
|
|
2101
2249
|
void sessionChildren.spawn(data).catch((err) => {
|
|
2102
|
-
|
|
2250
|
+
logger6.error("Session child spawn threw", {
|
|
2103
2251
|
error: err instanceof Error ? err.message : String(err)
|
|
2104
2252
|
});
|
|
2105
2253
|
runner.connection.reportSessionSpawnFailure(
|
|
@@ -2122,7 +2270,7 @@ runner.run().then(async () => {
|
|
|
2122
2270
|
}).catch(async (error) => {
|
|
2123
2271
|
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2124
2272
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2125
|
-
|
|
2273
|
+
logger6.error("Agent runner failed", { error: msg });
|
|
2126
2274
|
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|
|
2127
2275
|
process.exit(1);
|
|
2128
2276
|
});
|