@rallycry/conveyor-agent 10.2.1 → 10.2.4
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-7TQO4ZF4.js +60 -0
- package/dist/chunk-7TQO4ZF4.js.map +1 -0
- package/dist/{chunk-YGLTLUGA.js → chunk-DEMRCBJN.js} +994 -862
- package/dist/chunk-DEMRCBJN.js.map +1 -0
- package/dist/cli.js +319 -49
- package/dist/cli.js.map +1 -1
- package/dist/heartbeat-worker.d.ts +2 -0
- package/dist/heartbeat-worker.js +47 -0
- package/dist/heartbeat-worker.js.map +1 -0
- package/dist/index.d.ts +74 -42
- package/dist/index.js +19 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-YGLTLUGA.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
fetchBootstrap,
|
|
14
14
|
loadConveyorConfig,
|
|
15
15
|
loadForwardPorts,
|
|
16
|
+
resolveSessionStart,
|
|
16
17
|
runSetupCommand,
|
|
17
18
|
runStartCommand,
|
|
18
19
|
sampleKeyUsage
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-DEMRCBJN.js";
|
|
21
|
+
import "./chunk-7TQO4ZF4.js";
|
|
20
22
|
|
|
21
23
|
// src/cli.ts
|
|
22
24
|
import { readFileSync } from "fs";
|
|
@@ -144,14 +146,14 @@ async function waitForSidecars(opts = {}) {
|
|
|
144
146
|
|
|
145
147
|
// src/utils/session-identity.ts
|
|
146
148
|
async function checkSessionTaskIdentity(params) {
|
|
147
|
-
const { sessionId, taskId, fetchSessionTaskId, logger:
|
|
149
|
+
const { sessionId, taskId, fetchSessionTaskId, logger: logger3 } = params;
|
|
148
150
|
if (!sessionId || sessionId === taskId) return "skipped";
|
|
149
151
|
let sessionTaskId;
|
|
150
152
|
try {
|
|
151
153
|
sessionTaskId = await fetchSessionTaskId(sessionId);
|
|
152
154
|
} catch (err) {
|
|
153
155
|
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
-
|
|
156
|
+
logger3.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
|
|
155
157
|
sessionId,
|
|
156
158
|
taskId,
|
|
157
159
|
error: message
|
|
@@ -159,7 +161,7 @@ async function checkSessionTaskIdentity(params) {
|
|
|
159
161
|
return "error";
|
|
160
162
|
}
|
|
161
163
|
if (sessionTaskId !== taskId) {
|
|
162
|
-
|
|
164
|
+
logger3.warn(
|
|
163
165
|
"!!! 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.",
|
|
164
166
|
{ sessionId, envTaskId: taskId, sessionTaskId }
|
|
165
167
|
);
|
|
@@ -168,15 +170,41 @@ async function checkSessionTaskIdentity(params) {
|
|
|
168
170
|
return "match";
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
// src/utils/agent-exit.ts
|
|
174
|
+
function exitLogLevel(reason) {
|
|
175
|
+
return reason === "clean" ? "info" : "warn";
|
|
176
|
+
}
|
|
177
|
+
function buildAgentExitLog(args) {
|
|
178
|
+
const log = {
|
|
179
|
+
event: "agent_exit",
|
|
180
|
+
reason: args.reason,
|
|
181
|
+
exitCode: args.exitCode,
|
|
182
|
+
uptimeSec: Math.round(args.uptimeSec * 10) / 10
|
|
183
|
+
};
|
|
184
|
+
if (args.signal !== void 0) log.signal = args.signal;
|
|
185
|
+
if (args.finalState !== void 0) log.finalState = args.finalState;
|
|
186
|
+
const id = args.identity;
|
|
187
|
+
if (id) {
|
|
188
|
+
if (id.sessionId !== void 0) log.sessionId = id.sessionId;
|
|
189
|
+
if (id.taskId !== void 0) log.taskId = id.taskId;
|
|
190
|
+
if (id.workspaceId !== void 0) log.workspaceId = id.workspaceId;
|
|
191
|
+
if (id.runnerMode !== void 0) log.runnerMode = id.runnerMode;
|
|
192
|
+
}
|
|
193
|
+
return log;
|
|
194
|
+
}
|
|
195
|
+
|
|
171
196
|
// src/setup/project-identity.ts
|
|
172
197
|
function resolveProjectRunnerIdentity(env) {
|
|
173
198
|
if (env.CONVEYOR_TASK_ID) return null;
|
|
174
199
|
if (!env.CONVEYOR_TASK_TOKEN) return null;
|
|
175
|
-
if (!env.CONVEYOR_PROJECT_ID || !env.CONVEYOR_SESSION_ID)
|
|
200
|
+
if (!env.CONVEYOR_PROJECT_ID || !env.CONVEYOR_SESSION_ID || !env.CONVEYOR_WORKSPACE_ID) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
176
203
|
if (env.CONVEYOR_MODE !== "pm" && env.CONVEYOR_MODE !== "adhoc") return null;
|
|
177
204
|
return {
|
|
178
205
|
projectId: env.CONVEYOR_PROJECT_ID,
|
|
179
206
|
sessionId: env.CONVEYOR_SESSION_ID,
|
|
207
|
+
workspaceId: env.CONVEYOR_WORKSPACE_ID,
|
|
180
208
|
taskToken: env.CONVEYOR_TASK_TOKEN
|
|
181
209
|
};
|
|
182
210
|
}
|
|
@@ -318,11 +346,12 @@ var ADHOC_SYSTEM_NOTE = "You are running in an ad-hoc Conveyor scratch pod \u201
|
|
|
318
346
|
function buildAdhocPtyBridge(connection) {
|
|
319
347
|
return {
|
|
320
348
|
sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
|
|
349
|
+
sendChatEvent: (event) => connection.sendPtyChatEvent(event),
|
|
321
350
|
onInput: (handler) => connection.onPtyInput(handler),
|
|
322
351
|
onResize: (handler) => connection.onPtyResize(handler)
|
|
323
352
|
};
|
|
324
353
|
}
|
|
325
|
-
function buildAdhocQueryOptions(workspaceDir, model, abortController) {
|
|
354
|
+
function buildAdhocQueryOptions(workspaceDir, model, abortController, session) {
|
|
326
355
|
return {
|
|
327
356
|
model,
|
|
328
357
|
systemPrompt: { type: "preset", preset: "claude_code", append: ADHOC_SYSTEM_NOTE },
|
|
@@ -336,7 +365,9 @@ function buildAdhocQueryOptions(workspaceDir, model, abortController) {
|
|
|
336
365
|
settingSources: ["user", "project"],
|
|
337
366
|
// Empty box, unsubmitted: the human types the first prompt themselves.
|
|
338
367
|
promptDelivery: "prefill",
|
|
339
|
-
abortController
|
|
368
|
+
abortController,
|
|
369
|
+
...session?.sessionId ? { sessionId: session.sessionId } : {},
|
|
370
|
+
...session?.resume ? { resume: session.resume } : {}
|
|
340
371
|
};
|
|
341
372
|
}
|
|
342
373
|
var AdhocSessionRunner = class {
|
|
@@ -414,7 +445,13 @@ var AdhocSessionRunner = class {
|
|
|
414
445
|
*/
|
|
415
446
|
async runInteractiveTui() {
|
|
416
447
|
const model = this.config.model ?? process.env.CONVEYOR_ADHOC_MODEL ?? DEFAULT_SONNET_MODEL;
|
|
417
|
-
const
|
|
448
|
+
const session = resolveSessionStart(this.config.workspaceId, this.config.workspaceDir);
|
|
449
|
+
const options = buildAdhocQueryOptions(
|
|
450
|
+
this.config.workspaceDir,
|
|
451
|
+
model,
|
|
452
|
+
this.abortController,
|
|
453
|
+
session
|
|
454
|
+
);
|
|
418
455
|
try {
|
|
419
456
|
for await (const _event of this.harness.executeQuery({ prompt: "", options })) {
|
|
420
457
|
if (this.stopped) break;
|
|
@@ -456,6 +493,163 @@ var AdhocSessionRunner = class {
|
|
|
456
493
|
}
|
|
457
494
|
};
|
|
458
495
|
|
|
496
|
+
// src/runner/review-child.ts
|
|
497
|
+
import { spawn as nodeSpawn, execFile } from "child_process";
|
|
498
|
+
import { promisify } from "util";
|
|
499
|
+
var execFileAsync = promisify(execFile);
|
|
500
|
+
var logger = createServiceLogger("ReviewChild");
|
|
501
|
+
var SPAWN_FAILURE_GRACE_MS = 15e3;
|
|
502
|
+
var KILL_WAIT_MS = 5e3;
|
|
503
|
+
function buildReviewChildEnv(baseEnv, data) {
|
|
504
|
+
const env = { ...baseEnv };
|
|
505
|
+
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
506
|
+
env.CONVEYOR_SESSION_ID = data.sessionId;
|
|
507
|
+
env.CONVEYOR_MODE = "code-review";
|
|
508
|
+
delete env.POD_BOOTSTRAP_TOKEN;
|
|
509
|
+
delete env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
510
|
+
delete env.CONVEYOR_SETUP_COMMAND;
|
|
511
|
+
delete env.CONVEYOR_START_COMMAND;
|
|
512
|
+
delete env.CONVEYOR_AGENT_MODE;
|
|
513
|
+
delete env.CONVEYOR_IS_AUTO;
|
|
514
|
+
return env;
|
|
515
|
+
}
|
|
516
|
+
var ReviewChildSupervisor = class {
|
|
517
|
+
constructor(connection, workspaceDir, spawnFn = nodeSpawn) {
|
|
518
|
+
this.connection = connection;
|
|
519
|
+
this.workspaceDir = workspaceDir;
|
|
520
|
+
this.spawnFn = spawnFn;
|
|
521
|
+
}
|
|
522
|
+
connection;
|
|
523
|
+
workspaceDir;
|
|
524
|
+
spawnFn;
|
|
525
|
+
child = null;
|
|
526
|
+
childSessionId = null;
|
|
527
|
+
/** Spawn (or replace) the review child for a `session:spawnReview` push. */
|
|
528
|
+
async spawn(data) {
|
|
529
|
+
logger.info("Spawning same-pod review child", {
|
|
530
|
+
reviewSessionId: data.sessionId,
|
|
531
|
+
branch: data.branch ?? void 0,
|
|
532
|
+
prNumber: data.prNumber ?? void 0
|
|
533
|
+
});
|
|
534
|
+
await this.stopCurrent("superseded by new review spawn");
|
|
535
|
+
await this.refreshCheckout(data.branch ?? null);
|
|
536
|
+
const env = buildReviewChildEnv(process.env, data);
|
|
537
|
+
const cliPath = process.argv[1];
|
|
538
|
+
if (!cliPath) {
|
|
539
|
+
this.connection.reportReviewSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
let child;
|
|
543
|
+
try {
|
|
544
|
+
child = this.spawnFn(process.execPath, [cliPath], {
|
|
545
|
+
cwd: this.workspaceDir,
|
|
546
|
+
env,
|
|
547
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
548
|
+
});
|
|
549
|
+
} catch (err) {
|
|
550
|
+
this.connection.reportReviewSpawnFailure(
|
|
551
|
+
data.sessionId,
|
|
552
|
+
err instanceof Error ? err.message : String(err)
|
|
553
|
+
);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.child = child;
|
|
557
|
+
this.childSessionId = data.sessionId;
|
|
558
|
+
const spawnedAt = Date.now();
|
|
559
|
+
let reported = false;
|
|
560
|
+
const reportOnce = (message) => {
|
|
561
|
+
if (reported) return;
|
|
562
|
+
reported = true;
|
|
563
|
+
this.connection.reportReviewSpawnFailure(data.sessionId, message);
|
|
564
|
+
};
|
|
565
|
+
child.stdout?.on("data", (chunk) => {
|
|
566
|
+
process.stdout.write(`[review-child] ${chunk.toString()}`);
|
|
567
|
+
});
|
|
568
|
+
child.stderr?.on("data", (chunk) => {
|
|
569
|
+
process.stderr.write(`[review-child] ${chunk.toString()}`);
|
|
570
|
+
});
|
|
571
|
+
child.on("error", (err) => {
|
|
572
|
+
logger.error("Review child spawn error", { error: err.message });
|
|
573
|
+
if (this.child === child) {
|
|
574
|
+
this.child = null;
|
|
575
|
+
this.childSessionId = null;
|
|
576
|
+
}
|
|
577
|
+
reportOnce(err.message);
|
|
578
|
+
});
|
|
579
|
+
child.on("exit", (code, signal) => {
|
|
580
|
+
const wasCurrent = this.child === child;
|
|
581
|
+
if (wasCurrent) {
|
|
582
|
+
this.child = null;
|
|
583
|
+
this.childSessionId = null;
|
|
584
|
+
}
|
|
585
|
+
logger.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
|
|
586
|
+
const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
|
|
587
|
+
if (wasCurrent && withinGrace && code !== null && code !== 0) {
|
|
588
|
+
reportOnce(`review child exited with code ${code} within startup grace`);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/** Stop any live child (parent shutdown / supersede). Best-effort. */
|
|
593
|
+
async stopAll() {
|
|
594
|
+
await this.stopCurrent("parent shutting down");
|
|
595
|
+
}
|
|
596
|
+
async stopCurrent(reason) {
|
|
597
|
+
const child = this.child;
|
|
598
|
+
if (!child || child.exitCode !== null || child.killed) {
|
|
599
|
+
this.child = null;
|
|
600
|
+
this.childSessionId = null;
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
logger.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
|
|
604
|
+
this.child = null;
|
|
605
|
+
this.childSessionId = null;
|
|
606
|
+
await new Promise((resolve) => {
|
|
607
|
+
const timer = setTimeout(() => {
|
|
608
|
+
try {
|
|
609
|
+
child.kill("SIGKILL");
|
|
610
|
+
} catch {
|
|
611
|
+
}
|
|
612
|
+
resolve();
|
|
613
|
+
}, KILL_WAIT_MS);
|
|
614
|
+
timer.unref();
|
|
615
|
+
child.once("exit", () => {
|
|
616
|
+
clearTimeout(timer);
|
|
617
|
+
resolve();
|
|
618
|
+
});
|
|
619
|
+
try {
|
|
620
|
+
child.kill("SIGTERM");
|
|
621
|
+
} catch {
|
|
622
|
+
clearTimeout(timer);
|
|
623
|
+
resolve();
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Best-effort branch freshen before the review reads the tree: fetch the PR
|
|
629
|
+
* branch and fast-forward only (never clobber local work — the builder's
|
|
630
|
+
* checkout is the source of truth on conflict). Failures are logged and
|
|
631
|
+
* skipped; the reviewer still reads the diff via the GitHub API.
|
|
632
|
+
*/
|
|
633
|
+
async refreshCheckout(branch) {
|
|
634
|
+
if (!branch) return;
|
|
635
|
+
try {
|
|
636
|
+
await execFileAsync("git", ["fetch", "origin", branch], {
|
|
637
|
+
cwd: this.workspaceDir,
|
|
638
|
+
timeout: 6e4
|
|
639
|
+
});
|
|
640
|
+
await execFileAsync("git", ["merge", "--ff-only", `origin/${branch}`], {
|
|
641
|
+
cwd: this.workspaceDir,
|
|
642
|
+
timeout: 3e4
|
|
643
|
+
});
|
|
644
|
+
} catch (err) {
|
|
645
|
+
logger.warn("Review checkout freshen skipped", {
|
|
646
|
+
branch,
|
|
647
|
+
error: err instanceof Error ? err.message : String(err)
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
459
653
|
// src/cli.ts
|
|
460
654
|
if (process.argv.includes("--version")) {
|
|
461
655
|
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -464,11 +658,26 @@ if (process.argv.includes("--version")) {
|
|
|
464
658
|
process.stdout.write(pkg.version + "\n");
|
|
465
659
|
process.exit(0);
|
|
466
660
|
}
|
|
467
|
-
var
|
|
661
|
+
var logger2 = createServiceLogger("CLI");
|
|
662
|
+
var exitContext = {};
|
|
663
|
+
var exitLogged = false;
|
|
664
|
+
function logAgentExit(reason, opts) {
|
|
665
|
+
if (exitLogged) return;
|
|
666
|
+
exitLogged = true;
|
|
667
|
+
const payload = buildAgentExitLog({
|
|
668
|
+
reason,
|
|
669
|
+
exitCode: opts.exitCode,
|
|
670
|
+
signal: opts.signal,
|
|
671
|
+
finalState: opts.finalState,
|
|
672
|
+
uptimeSec: process.uptime(),
|
|
673
|
+
identity: exitContext
|
|
674
|
+
});
|
|
675
|
+
logger2[exitLogLevel(reason)]("agent_exit", payload);
|
|
676
|
+
}
|
|
468
677
|
async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
469
678
|
const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
470
679
|
const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
|
|
471
|
-
|
|
680
|
+
logger2.info("Bootstrapping from codespace", {
|
|
472
681
|
codespace: instanceName,
|
|
473
682
|
apiUrl,
|
|
474
683
|
apiUrlFromEnv,
|
|
@@ -480,7 +689,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
480
689
|
bootstrapToken
|
|
481
690
|
});
|
|
482
691
|
if (!result.ok) {
|
|
483
|
-
|
|
692
|
+
logger2.error("Bootstrap failed after retries", {
|
|
484
693
|
reason: result.reason,
|
|
485
694
|
attempts: result.attempts,
|
|
486
695
|
status: result.status,
|
|
@@ -493,7 +702,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
493
702
|
process.exit(1);
|
|
494
703
|
}
|
|
495
704
|
applyBootstrapToEnv(result.config);
|
|
496
|
-
|
|
705
|
+
logger2.info("Bootstrap complete", {
|
|
497
706
|
taskId: result.config.taskId,
|
|
498
707
|
attempts: result.attempts
|
|
499
708
|
});
|
|
@@ -507,19 +716,21 @@ function isExpectedAbortError(error) {
|
|
|
507
716
|
process.on("uncaughtException", (err) => {
|
|
508
717
|
if (err.code === "EPIPE") return;
|
|
509
718
|
if (isExpectedAbortError(err)) {
|
|
510
|
-
|
|
719
|
+
logger2.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
|
|
511
720
|
return;
|
|
512
721
|
}
|
|
513
|
-
|
|
722
|
+
logger2.error("Uncaught exception", { error: err.message, code: err.code });
|
|
723
|
+
logAgentExit("uncaught_exception", { exitCode: 1 });
|
|
514
724
|
process.exit(1);
|
|
515
725
|
});
|
|
516
726
|
process.on("unhandledRejection", (reason) => {
|
|
517
727
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
518
728
|
if (isExpectedAbortError(err)) {
|
|
519
|
-
|
|
729
|
+
logger2.info("Ignored expected abort rejection after shutdown", { error: err.message });
|
|
520
730
|
return;
|
|
521
731
|
}
|
|
522
|
-
|
|
732
|
+
logger2.error("Unhandled rejection", { error: err.message });
|
|
733
|
+
logAgentExit("unhandled_rejection", { exitCode: 1 });
|
|
523
734
|
process.exit(1);
|
|
524
735
|
});
|
|
525
736
|
var DEFAULT_CONVEYOR_API_URL = "https://api.conveyor.rallycryapp.com";
|
|
@@ -527,7 +738,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
|
|
|
527
738
|
var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
|
|
528
739
|
if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
|
|
529
740
|
if (!conveyorApiUrl) {
|
|
530
|
-
|
|
741
|
+
logger2.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
|
|
531
742
|
process.exit(1);
|
|
532
743
|
}
|
|
533
744
|
await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
|
|
@@ -539,9 +750,13 @@ var CONVEYOR_WORKSPACE = process.env.CONVEYOR_WORKSPACE ?? process.cwd();
|
|
|
539
750
|
var CONVEYOR_MODE = process.env.CONVEYOR_MODE ?? "task";
|
|
540
751
|
var CONVEYOR_AGENT_MODE = process.env.CONVEYOR_AGENT_MODE || void 0;
|
|
541
752
|
var CONVEYOR_IS_AUTO = CONVEYOR_AGENT_MODE ? CONVEYOR_AGENT_MODE === "auto" : process.env.CONVEYOR_IS_AUTO === "true";
|
|
753
|
+
exitContext.taskId = CONVEYOR_TASK_ID;
|
|
754
|
+
exitContext.workspaceId = process.env.CONVEYOR_WORKSPACE_ID;
|
|
755
|
+
exitContext.runnerMode = CONVEYOR_MODE;
|
|
756
|
+
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
|
|
542
757
|
var projectIdentity = resolveProjectRunnerIdentity(process.env);
|
|
543
758
|
if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
544
|
-
|
|
759
|
+
logger2.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
|
|
545
760
|
const adhocRunner = new AdhocSessionRunner(
|
|
546
761
|
{
|
|
547
762
|
connection: {
|
|
@@ -551,22 +766,31 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
551
766
|
runnerMode: "adhoc"
|
|
552
767
|
},
|
|
553
768
|
projectId: projectIdentity.projectId,
|
|
769
|
+
workspaceId: projectIdentity.workspaceId,
|
|
554
770
|
workspaceDir: CONVEYOR_WORKSPACE,
|
|
555
771
|
...process.env.CLAUDESPACE_NAME ? { lifecycle: { idleTimeoutMs: 60 * 60 * 1e3 } } : {}
|
|
556
772
|
},
|
|
557
773
|
{
|
|
558
774
|
onEvent: (event) => {
|
|
559
|
-
|
|
775
|
+
logger2.info("Ad-hoc runner event", { eventType: event.type });
|
|
560
776
|
}
|
|
561
777
|
}
|
|
562
778
|
);
|
|
779
|
+
exitContext.sessionId = projectIdentity.sessionId;
|
|
780
|
+
exitContext.workspaceId = projectIdentity.workspaceId;
|
|
781
|
+
exitContext.runnerMode = "adhoc";
|
|
563
782
|
process.on("SIGTERM", () => adhocRunner.stop());
|
|
564
783
|
process.on("SIGINT", () => adhocRunner.stop());
|
|
565
784
|
await adhocRunner.run();
|
|
566
|
-
|
|
785
|
+
const adhocError = adhocRunner.finalState === "error";
|
|
786
|
+
logAgentExit(adhocError ? "error" : "clean", {
|
|
787
|
+
exitCode: adhocError ? 1 : 0,
|
|
788
|
+
finalState: adhocRunner.finalState ?? void 0
|
|
789
|
+
});
|
|
790
|
+
process.exit(adhocError ? 1 : 0);
|
|
567
791
|
}
|
|
568
792
|
if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
569
|
-
|
|
793
|
+
logger2.info("Starting project agent", { projectId: projectIdentity.projectId });
|
|
570
794
|
const projectRunner = new ProjectSessionRunner(
|
|
571
795
|
{
|
|
572
796
|
connection: {
|
|
@@ -581,38 +805,46 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
581
805
|
},
|
|
582
806
|
{
|
|
583
807
|
onEvent: (event) => {
|
|
584
|
-
|
|
808
|
+
logger2.info("Project runner event", { eventType: event.type });
|
|
585
809
|
}
|
|
586
810
|
}
|
|
587
811
|
);
|
|
812
|
+
exitContext.sessionId = projectIdentity.sessionId;
|
|
813
|
+
exitContext.workspaceId = projectIdentity.workspaceId;
|
|
814
|
+
exitContext.runnerMode = "pm";
|
|
588
815
|
process.on("SIGTERM", () => projectRunner.stop());
|
|
589
816
|
process.on("SIGINT", () => projectRunner.stop());
|
|
590
817
|
await projectRunner.run();
|
|
591
|
-
|
|
818
|
+
const projectError = projectRunner.finalState === "error";
|
|
819
|
+
logAgentExit(projectError ? "error" : "clean", {
|
|
820
|
+
exitCode: projectError ? 1 : 0,
|
|
821
|
+
finalState: projectRunner.finalState ?? void 0
|
|
822
|
+
});
|
|
823
|
+
process.exit(projectError ? 1 : 0);
|
|
592
824
|
}
|
|
593
825
|
if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
826
|
+
logger2.error("Missing required environment variables");
|
|
827
|
+
logger2.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
|
|
828
|
+
logger2.error(" CONVEYOR_TASK_ID - ID of the task to execute");
|
|
829
|
+
logger2.error("");
|
|
830
|
+
logger2.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
|
|
831
|
+
logger2.error("");
|
|
832
|
+
logger2.error("Optional:");
|
|
833
|
+
logger2.error(" CONVEYOR_MODE - Runner mode: 'task' (default) or 'pm'");
|
|
834
|
+
logger2.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
|
|
835
|
+
logger2.error(
|
|
604
836
|
" Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
|
|
605
837
|
);
|
|
606
838
|
process.exit(1);
|
|
607
839
|
}
|
|
608
840
|
if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc") {
|
|
609
|
-
|
|
841
|
+
logger2.error("Invalid CONVEYOR_MODE", {
|
|
610
842
|
mode: CONVEYOR_MODE,
|
|
611
843
|
expected: ["task", "pm", "code-review", "adhoc"]
|
|
612
844
|
});
|
|
613
845
|
process.exit(1);
|
|
614
846
|
}
|
|
615
|
-
|
|
847
|
+
logger2.info("Starting agent", { mode: CONVEYOR_MODE });
|
|
616
848
|
var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
|
|
617
849
|
var runner = new SessionRunner(
|
|
618
850
|
{
|
|
@@ -632,19 +864,26 @@ var runner = new SessionRunner(
|
|
|
632
864
|
},
|
|
633
865
|
{
|
|
634
866
|
onStatusChange: (status) => {
|
|
635
|
-
|
|
867
|
+
logger2.info("Status changed", { status });
|
|
636
868
|
},
|
|
637
869
|
onEvent: (event) => {
|
|
638
870
|
const detail = event.message ?? event.content ?? event.summary ?? "";
|
|
639
871
|
if (detail) {
|
|
640
|
-
|
|
872
|
+
logger2.info(detail, { eventType: event.type });
|
|
641
873
|
}
|
|
642
874
|
}
|
|
643
875
|
}
|
|
644
876
|
);
|
|
877
|
+
var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
878
|
+
var shutdownSignal;
|
|
645
879
|
var shutdownAgent = (signal) => {
|
|
646
|
-
|
|
880
|
+
logger2.info(`Received ${signal}, flushing git and stopping agent`);
|
|
881
|
+
shutdownSignal = signal;
|
|
647
882
|
void (async () => {
|
|
883
|
+
try {
|
|
884
|
+
await reviewChildren.stopAll();
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
648
887
|
try {
|
|
649
888
|
await runner.flushGitOnShutdown();
|
|
650
889
|
} finally {
|
|
@@ -652,7 +891,18 @@ var shutdownAgent = (signal) => {
|
|
|
652
891
|
}
|
|
653
892
|
})();
|
|
654
893
|
setTimeout(() => {
|
|
655
|
-
|
|
894
|
+
logger2.warn(`Forcing exit after ${signal} timeout`);
|
|
895
|
+
logger2.warn(
|
|
896
|
+
"agent_exit",
|
|
897
|
+
buildAgentExitLog({
|
|
898
|
+
reason: "force_timeout",
|
|
899
|
+
exitCode: 1,
|
|
900
|
+
signal,
|
|
901
|
+
finalState: runner.finalState ?? void 0,
|
|
902
|
+
uptimeSec: process.uptime(),
|
|
903
|
+
identity: exitContext
|
|
904
|
+
})
|
|
905
|
+
);
|
|
656
906
|
process.exit(1);
|
|
657
907
|
}, 45e3).unref();
|
|
658
908
|
};
|
|
@@ -680,7 +930,7 @@ var stopStartCommandChild = () => {
|
|
|
680
930
|
}
|
|
681
931
|
};
|
|
682
932
|
var launchStartCommand = (command) => {
|
|
683
|
-
|
|
933
|
+
logger2.info("Running start command", { command });
|
|
684
934
|
runner.connection.sendEvent({ type: "start_command_started" });
|
|
685
935
|
const child = runStartCommand(command, CONVEYOR_WORKSPACE, (stream, data) => {
|
|
686
936
|
runner.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
@@ -690,7 +940,7 @@ var launchStartCommand = (command) => {
|
|
|
690
940
|
child.on("exit", (code, signal) => {
|
|
691
941
|
if (startCommandChild === child) startCommandChild = null;
|
|
692
942
|
if (expectedStartCommandStops.has(child)) return;
|
|
693
|
-
|
|
943
|
+
logger2.info("Start command exited", { code, signal });
|
|
694
944
|
runner.connection.sendEvent({
|
|
695
945
|
type: "start_command_exited",
|
|
696
946
|
code,
|
|
@@ -706,7 +956,7 @@ var launchStartCommand = (command) => {
|
|
|
706
956
|
});
|
|
707
957
|
child.on("error", (err) => {
|
|
708
958
|
if (startCommandChild === child) startCommandChild = null;
|
|
709
|
-
|
|
959
|
+
logger2.error("Start command error", { error: err.message });
|
|
710
960
|
runner.connection.sendEvent({ type: "start_command_error", message: err.message });
|
|
711
961
|
});
|
|
712
962
|
};
|
|
@@ -722,8 +972,21 @@ void checkSessionTaskIdentity({
|
|
|
722
972
|
const ctx = await runner.connection.call("getTaskContext", { sessionId });
|
|
723
973
|
return ctx.id;
|
|
724
974
|
},
|
|
725
|
-
logger
|
|
975
|
+
logger: logger2
|
|
726
976
|
});
|
|
977
|
+
if (CONVEYOR_MODE === "task") {
|
|
978
|
+
runner.connection.onSpawnReview((data) => {
|
|
979
|
+
void reviewChildren.spawn(data).catch((err) => {
|
|
980
|
+
logger2.error("Review child spawn threw", {
|
|
981
|
+
error: err instanceof Error ? err.message : String(err)
|
|
982
|
+
});
|
|
983
|
+
runner.connection.reportReviewSpawnFailure(
|
|
984
|
+
data.sessionId,
|
|
985
|
+
err instanceof Error ? err.message : String(err)
|
|
986
|
+
);
|
|
987
|
+
});
|
|
988
|
+
});
|
|
989
|
+
}
|
|
727
990
|
var conveyorConfig = loadConveyorConfig();
|
|
728
991
|
runner.connection.onRunStartCommand(() => {
|
|
729
992
|
if (!conveyorConfig?.startCommand) return;
|
|
@@ -754,15 +1017,15 @@ if (conveyorConfig) {
|
|
|
754
1017
|
startLazy: false
|
|
755
1018
|
});
|
|
756
1019
|
if (conveyorConfig.setupCommand) {
|
|
757
|
-
|
|
1020
|
+
logger2.info("Running setup command (background)", {
|
|
758
1021
|
command: conveyorConfig.setupCommand
|
|
759
1022
|
});
|
|
760
1023
|
try {
|
|
761
1024
|
await runSetupCommand(conveyorConfig.setupCommand, CONVEYOR_WORKSPACE, logSetupOutput);
|
|
762
|
-
|
|
1025
|
+
logger2.info("Setup command completed");
|
|
763
1026
|
} catch (error) {
|
|
764
1027
|
const msg = error instanceof Error ? error.message : "Setup command failed";
|
|
765
|
-
|
|
1028
|
+
logger2.error("Setup command failed", { error: msg });
|
|
766
1029
|
runner.connection.sendEvent({ type: "setup_error", message: msg });
|
|
767
1030
|
}
|
|
768
1031
|
}
|
|
@@ -776,16 +1039,23 @@ if (conveyorConfig) {
|
|
|
776
1039
|
runner.connection.sendEvent({
|
|
777
1040
|
type: "setup_complete",
|
|
778
1041
|
startCommandRunning,
|
|
779
|
-
previewPort: conveyorConfig.previewPort,
|
|
780
1042
|
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
781
1043
|
});
|
|
782
1044
|
})();
|
|
783
1045
|
}
|
|
784
1046
|
runner.run().then(() => {
|
|
785
|
-
|
|
1047
|
+
const errored = runner.finalState === "error";
|
|
1048
|
+
const reason = shutdownSignal ? "signal" : errored ? "error" : "clean";
|
|
1049
|
+
logAgentExit(reason, {
|
|
1050
|
+
exitCode: errored ? 1 : 0,
|
|
1051
|
+
signal: shutdownSignal,
|
|
1052
|
+
finalState: runner.finalState ?? void 0
|
|
1053
|
+
});
|
|
1054
|
+
process.exit(errored ? 1 : 0);
|
|
786
1055
|
}).catch((error) => {
|
|
787
1056
|
const msg = error instanceof Error ? error.message : String(error);
|
|
788
|
-
|
|
1057
|
+
logger2.error("Agent runner failed", { error: msg });
|
|
1058
|
+
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|
|
789
1059
|
process.exit(1);
|
|
790
1060
|
});
|
|
791
1061
|
//# sourceMappingURL=cli.js.map
|