@rallycry/conveyor-agent 10.13.18 → 10.13.51
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-YZCOIV3Q.js → boot-BZE36LT6.js} +156 -125
- package/dist/boot-BZE36LT6.js.map +1 -0
- package/dist/chunk-5OQQSDVT.js +142 -0
- package/dist/chunk-5OQQSDVT.js.map +1 -0
- package/dist/{chunk-3GDXX3OX.js → chunk-EQO7QFGN.js} +6201 -2125
- package/dist/chunk-EQO7QFGN.js.map +1 -0
- package/dist/{chunk-AGGVNADW.js → chunk-HVSPADKP.js} +59 -13
- package/dist/chunk-HVSPADKP.js.map +1 -0
- package/dist/{chunk-UZTJJD7Y.js → chunk-KCB7CSWJ.js} +43 -2
- package/dist/chunk-KCB7CSWJ.js.map +1 -0
- package/dist/{chunk-72AEN6LB.js → chunk-KZQBYSMN.js} +137 -2
- package/dist/chunk-KZQBYSMN.js.map +1 -0
- package/dist/{chunk-UDSRAHKP.js → chunk-QOJTJCYZ.js} +67 -19
- package/dist/chunk-QOJTJCYZ.js.map +1 -0
- package/dist/chunk-ULS4QPRE.js +138 -0
- package/dist/chunk-ULS4QPRE.js.map +1 -0
- package/dist/cli.js +245 -551
- package/dist/cli.js.map +1 -1
- package/dist/{client-BU4XA7CV.js → client-LRVVHTNG.js} +2 -2
- package/dist/heartbeat-worker.js +10 -12
- package/dist/heartbeat-worker.js.map +1 -1
- package/dist/index.d.ts +153 -8
- package/dist/index.js +5 -5
- package/dist/oom-watchdog-U7JERHA2.js +11 -0
- package/dist/server-PPTELJWY.js +10 -0
- package/dist/server-PPTELJWY.js.map +1 -0
- package/package.json +3 -3
- package/runtime/entrypoint.sh +5 -4
- package/dist/boot-YZCOIV3Q.js.map +0 -1
- package/dist/chunk-3GDXX3OX.js.map +0 -1
- package/dist/chunk-72AEN6LB.js.map +0 -1
- package/dist/chunk-7TQO4ZF4.js +0 -60
- package/dist/chunk-7TQO4ZF4.js.map +0 -1
- package/dist/chunk-AGGVNADW.js.map +0 -1
- package/dist/chunk-UDSRAHKP.js.map +0 -1
- package/dist/chunk-UZTJJD7Y.js.map +0 -1
- package/dist/server-BXAFMYDM.js +0 -9
- /package/dist/{client-BU4XA7CV.js.map → client-LRVVHTNG.js.map} +0 -0
- /package/dist/{server-BXAFMYDM.js.map → oom-watchdog-U7JERHA2.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,53 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TOMBSTONE_MESSAGE,
|
|
4
|
+
isLegacyEntrypointLaunch
|
|
5
|
+
} from "./chunk-6GS5ADIY.js";
|
|
2
6
|
import {
|
|
3
7
|
AgentConnection,
|
|
4
|
-
|
|
8
|
+
CodespacePortVisibility,
|
|
5
9
|
DEFAULT_LIFECYCLE_CONFIG,
|
|
6
10
|
DEFAULT_SONNET_MODEL,
|
|
7
11
|
Lifecycle,
|
|
8
12
|
PortDiscovery,
|
|
9
13
|
PtyHarness,
|
|
10
14
|
SessionRunner,
|
|
11
|
-
TUI_KINDS,
|
|
12
|
-
TuiUnavailableError,
|
|
13
15
|
applyBootstrapToEnv,
|
|
14
16
|
awaitGitReady,
|
|
17
|
+
buildProjectTools,
|
|
15
18
|
buildSessionPreviewPorts,
|
|
16
19
|
buildSynthesizedCredentials,
|
|
17
20
|
claudeJsonPath,
|
|
18
|
-
cleanTerminalOutput,
|
|
19
21
|
createServiceLogger,
|
|
20
|
-
defineTool,
|
|
21
22
|
fetchBootstrap,
|
|
22
|
-
|
|
23
|
+
isPermissionDeniedError,
|
|
23
24
|
loadConveyorConfig,
|
|
24
25
|
loadForwardPorts,
|
|
25
26
|
parseUsageGauges,
|
|
26
27
|
resolvePlaywrightMcpServer,
|
|
27
28
|
resolveSessionStart,
|
|
29
|
+
resolveTuiAdapter,
|
|
30
|
+
resolveTuiKindFromEnv,
|
|
28
31
|
runUsageProbe,
|
|
29
32
|
sampleKeyUsage,
|
|
30
|
-
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-7TQO4ZF4.js";
|
|
33
|
+
workspacePathExists
|
|
34
|
+
} from "./chunk-EQO7QFGN.js";
|
|
33
35
|
import {
|
|
34
|
-
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
reportBootMilestone
|
|
37
|
+
} from "./chunk-KCB7CSWJ.js";
|
|
38
|
+
import "./chunk-5OQQSDVT.js";
|
|
36
39
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} from "./chunk-6GS5ADIY.js";
|
|
40
|
-
import "./chunk-UZTJJD7Y.js";
|
|
40
|
+
getWorkbenchClient
|
|
41
|
+
} from "./chunk-QOJTJCYZ.js";
|
|
41
42
|
import {
|
|
42
43
|
workbenchEnabled
|
|
43
44
|
} from "./chunk-4VUQ2NPF.js";
|
|
44
45
|
import {
|
|
45
|
-
buildPromptBytes,
|
|
46
46
|
inheritedEnv,
|
|
47
|
-
|
|
47
|
+
resolvePtySpawn,
|
|
48
|
+
runSetupCommand,
|
|
48
49
|
runStartCommand,
|
|
50
|
+
sessionTempBase,
|
|
49
51
|
terminateProcessGroup
|
|
50
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-KZQBYSMN.js";
|
|
51
53
|
import "./chunk-JIGG755T.js";
|
|
52
54
|
|
|
53
55
|
// src/cli.ts
|
|
@@ -202,31 +204,71 @@ async function waitForSidecars(opts = {}) {
|
|
|
202
204
|
);
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
// src/setup/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
207
|
+
// src/setup/db-schema-sync.ts
|
|
208
|
+
import { basename, dirname, join } from "path";
|
|
209
|
+
var PRISMA_SCHEMA_CANDIDATES = [
|
|
210
|
+
"packages/db/prisma/schema.prisma",
|
|
211
|
+
"prisma/schema.prisma",
|
|
212
|
+
"apps/api/prisma/schema.prisma",
|
|
213
|
+
"packages/db/schema.prisma"
|
|
214
|
+
];
|
|
215
|
+
var PRISMA_DB_PUSH = "bunx prisma db push";
|
|
216
|
+
var SCHEMA_SYNC_TIMEOUT_MS = 18e4;
|
|
217
|
+
function depsInclude(rawDeps, dep) {
|
|
218
|
+
return (rawDeps ?? "").split(",").map((entry) => entry.trim()).includes(dep);
|
|
219
|
+
}
|
|
220
|
+
function isDisabled(env) {
|
|
221
|
+
const flag = env.CONVEYOR_DB_SCHEMA_SYNC?.trim().toLowerCase();
|
|
222
|
+
return flag === "0" || flag === "false";
|
|
223
|
+
}
|
|
224
|
+
function packageRootForSchema(schemaPath) {
|
|
225
|
+
const dir = dirname(schemaPath);
|
|
226
|
+
return basename(dir) === "prisma" ? dirname(dir) : dir;
|
|
227
|
+
}
|
|
228
|
+
async function resolveSchemaSyncPlan(options) {
|
|
229
|
+
const { workspaceDir, env } = options;
|
|
230
|
+
const pathExists = options.pathExists ?? workspacePathExists;
|
|
231
|
+
if (isDisabled(env)) return null;
|
|
232
|
+
const override = env.CONVEYOR_DB_SCHEMA_SYNC_COMMAND?.trim();
|
|
233
|
+
if (override) return { command: override, cwd: workspaceDir, source: "override" };
|
|
234
|
+
if (!depsInclude(env.CONVEYOR_DEPS, "postgresql")) return null;
|
|
235
|
+
if (!env.DATABASE_URL?.trim()) return null;
|
|
236
|
+
for (const relative of PRISMA_SCHEMA_CANDIDATES) {
|
|
237
|
+
const schemaPath = join(workspaceDir, relative);
|
|
238
|
+
if (await pathExists(schemaPath)) {
|
|
239
|
+
return { command: PRISMA_DB_PUSH, cwd: packageRootForSchema(schemaPath), source: "prisma" };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
async function runDbSchemaSync(options) {
|
|
245
|
+
let plan = null;
|
|
215
246
|
try {
|
|
216
|
-
|
|
217
|
-
method: "POST",
|
|
218
|
-
headers: {
|
|
219
|
-
"content-type": "application/json",
|
|
220
|
-
authorization: `Bearer ${token}`
|
|
221
|
-
},
|
|
222
|
-
body: JSON.stringify({ key: opts.key }),
|
|
223
|
-
signal: controller.signal
|
|
224
|
-
});
|
|
225
|
-
return res.ok;
|
|
247
|
+
plan = await resolveSchemaSyncPlan(options);
|
|
226
248
|
} catch {
|
|
227
|
-
return
|
|
249
|
+
return "skipped";
|
|
250
|
+
}
|
|
251
|
+
if (!plan) return "skipped";
|
|
252
|
+
const { onOutput, runCommand } = options;
|
|
253
|
+
onOutput("stdout", `[db] syncing schema (${plan.source}): ${plan.command}
|
|
254
|
+
`);
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const abort = () => controller.abort();
|
|
257
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
258
|
+
const timer = setTimeout(abort, options.timeoutMs ?? SCHEMA_SYNC_TIMEOUT_MS);
|
|
259
|
+
timer.unref();
|
|
260
|
+
try {
|
|
261
|
+
await runCommand(plan.command, plan.cwd, onOutput, controller.signal);
|
|
262
|
+
onOutput("stdout", "[db] schema in sync\n");
|
|
263
|
+
return "synced";
|
|
264
|
+
} catch (error) {
|
|
265
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
266
|
+
onOutput("stderr", `[db] schema sync failed: ${message}
|
|
267
|
+
`);
|
|
268
|
+
return "failed";
|
|
228
269
|
} finally {
|
|
229
270
|
clearTimeout(timer);
|
|
271
|
+
options.signal?.removeEventListener("abort", abort);
|
|
230
272
|
}
|
|
231
273
|
}
|
|
232
274
|
|
|
@@ -235,10 +277,11 @@ function defaultCommandExecutors() {
|
|
|
235
277
|
if (workbenchEnabled()) {
|
|
236
278
|
const client = getWorkbenchClient();
|
|
237
279
|
return {
|
|
238
|
-
runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput)
|
|
280
|
+
runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput),
|
|
281
|
+
runShellCommand: (cmd, cwd, onOutput, signal) => client.runSetupCommand(cmd, cwd, onOutput, signal)
|
|
239
282
|
};
|
|
240
283
|
}
|
|
241
|
-
return { runStartCommand };
|
|
284
|
+
return { runStartCommand, runShellCommand: runSetupCommand };
|
|
242
285
|
}
|
|
243
286
|
var defaultWriteOutput = (stream, data) => {
|
|
244
287
|
(stream === "stderr" ? process.stderr : process.stdout).write(data);
|
|
@@ -263,6 +306,7 @@ var WorkspaceCommandSupervisor = class {
|
|
|
263
306
|
awaitGitReadyFn;
|
|
264
307
|
waitForSidecarsFn;
|
|
265
308
|
runStartCommandFn;
|
|
309
|
+
syncDbSchemaFn;
|
|
266
310
|
loadForwardPortsFn;
|
|
267
311
|
writeOutput;
|
|
268
312
|
terminateStartCommand;
|
|
@@ -301,6 +345,13 @@ var WorkspaceCommandSupervisor = class {
|
|
|
301
345
|
this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
|
|
302
346
|
const executors = defaultCommandExecutors();
|
|
303
347
|
this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
|
|
348
|
+
this.syncDbSchemaFn = options.syncDbSchema ?? ((opts) => runDbSchemaSync({
|
|
349
|
+
workspaceDir: this.workspaceDir,
|
|
350
|
+
env: this.env,
|
|
351
|
+
runCommand: executors.runShellCommand,
|
|
352
|
+
onOutput: opts.onOutput,
|
|
353
|
+
signal: opts.signal
|
|
354
|
+
}));
|
|
304
355
|
this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
|
|
305
356
|
this.writeOutput = options.writeOutput ?? defaultWriteOutput;
|
|
306
357
|
this.terminateStartCommand = options.terminateStartCommand ?? terminateProcessGroup;
|
|
@@ -352,7 +403,6 @@ var WorkspaceCommandSupervisor = class {
|
|
|
352
403
|
});
|
|
353
404
|
return;
|
|
354
405
|
}
|
|
355
|
-
if (gitState === "ready") this.reportBootMilestoneFn("git_ready");
|
|
356
406
|
await this.waitForSidecarsFn({
|
|
357
407
|
onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
|
|
358
408
|
`),
|
|
@@ -362,6 +412,8 @@ var WorkspaceCommandSupervisor = class {
|
|
|
362
412
|
this.reportBootMilestoneFn("sidecars_ready");
|
|
363
413
|
await this.loopReady;
|
|
364
414
|
if (this.stopped) return;
|
|
415
|
+
await this.syncDbSchema();
|
|
416
|
+
if (this.stopped) return;
|
|
365
417
|
const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
|
|
366
418
|
if (this.stopped) return;
|
|
367
419
|
this.reportBootMilestoneFn("start_command_launched");
|
|
@@ -374,6 +426,20 @@ var WorkspaceCommandSupervisor = class {
|
|
|
374
426
|
...previewPorts.length > 0 ? { previewPorts } : {}
|
|
375
427
|
});
|
|
376
428
|
}
|
|
429
|
+
/** Run the schema sync, forwarding its output as setup output so it lands in
|
|
430
|
+
* the card's setup log. A failure is announced rather than thrown: it is the
|
|
431
|
+
* exact condition that used to render as a silent "Displaying 0 results". */
|
|
432
|
+
async syncDbSchema() {
|
|
433
|
+
const outcome = await this.syncDbSchemaFn({
|
|
434
|
+
onOutput: (stream, data) => this.forwardSetupOutput(stream, data),
|
|
435
|
+
signal: this.abortController.signal
|
|
436
|
+
});
|
|
437
|
+
if (outcome !== "failed" || this.stopped) return;
|
|
438
|
+
this.connection.sendEvent({
|
|
439
|
+
type: "setup_error",
|
|
440
|
+
message: "Database schema sync failed \u2014 the app may error on queries touching recently added columns (see pod logs)"
|
|
441
|
+
});
|
|
442
|
+
}
|
|
377
443
|
restartStartCommand() {
|
|
378
444
|
if (this.stopped || !this.config?.startCommand) return;
|
|
379
445
|
this.connection.sendEvent({
|
|
@@ -497,14 +563,14 @@ var WorkspaceCommandSupervisor = class {
|
|
|
497
563
|
|
|
498
564
|
// src/utils/session-identity.ts
|
|
499
565
|
async function checkSessionTaskIdentity(params) {
|
|
500
|
-
const { sessionId, taskId, fetchSessionTaskId, logger:
|
|
566
|
+
const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
|
|
501
567
|
if (!sessionId || sessionId === taskId) return "skipped";
|
|
502
568
|
let sessionTaskId;
|
|
503
569
|
try {
|
|
504
570
|
sessionTaskId = await fetchSessionTaskId(sessionId);
|
|
505
571
|
} catch (err) {
|
|
506
572
|
const message = err instanceof Error ? err.message : String(err);
|
|
507
|
-
|
|
573
|
+
logger6.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
|
|
508
574
|
sessionId,
|
|
509
575
|
taskId,
|
|
510
576
|
error: message
|
|
@@ -512,7 +578,7 @@ async function checkSessionTaskIdentity(params) {
|
|
|
512
578
|
return "error";
|
|
513
579
|
}
|
|
514
580
|
if (sessionTaskId !== taskId) {
|
|
515
|
-
|
|
581
|
+
logger6.warn(
|
|
516
582
|
"!!! 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.",
|
|
517
583
|
{ sessionId, envTaskId: taskId, sessionTaskId }
|
|
518
584
|
);
|
|
@@ -696,8 +762,7 @@ var ProjectSessionRunner = class {
|
|
|
696
762
|
|
|
697
763
|
// src/usage/multi-key-probe.ts
|
|
698
764
|
import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
|
|
699
|
-
import {
|
|
700
|
-
import { join } from "path";
|
|
765
|
+
import { join as join2 } from "path";
|
|
701
766
|
var logger = createServiceLogger("multi-key-probe");
|
|
702
767
|
function gaugesToSamples(stdout) {
|
|
703
768
|
const { sessionUsage, weeklyUsage, sessionResetsAt, weeklyResetsAt, gauges } = parseUsageGauges(stdout);
|
|
@@ -725,12 +790,12 @@ function gaugesToSamples(stdout) {
|
|
|
725
790
|
async function isolatedProbe(token, now) {
|
|
726
791
|
let dir = null;
|
|
727
792
|
try {
|
|
728
|
-
dir = await mkdtemp(
|
|
729
|
-
await writeFile(
|
|
793
|
+
dir = await mkdtemp(join2(sessionTempBase(), "conveyor-usage-"));
|
|
794
|
+
await writeFile(join2(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
|
|
730
795
|
encoding: "utf8",
|
|
731
796
|
mode: 384
|
|
732
797
|
});
|
|
733
|
-
await copyFile(claudeJsonPath(),
|
|
798
|
+
await copyFile(claudeJsonPath(), join2(dir, ".claude.json")).catch(() => {
|
|
734
799
|
});
|
|
735
800
|
return await runUsageProbe({ env: { ...process.env, CLAUDE_CONFIG_DIR: dir } });
|
|
736
801
|
} catch (error) {
|
|
@@ -765,428 +830,6 @@ async function probeKeysUsage(keys, deps = {}) {
|
|
|
765
830
|
return results;
|
|
766
831
|
}
|
|
767
832
|
|
|
768
|
-
// src/harness/pty/adapters/opencode-auth.ts
|
|
769
|
-
import { promises as fs } from "fs";
|
|
770
|
-
import { dirname, join as join2 } from "path";
|
|
771
|
-
import { homedir } from "os";
|
|
772
|
-
var logger2 = createServiceLogger("opencode-auth");
|
|
773
|
-
var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
|
|
774
|
-
var PLUGIN_PACKAGE = "opencode-openai-codex-auth";
|
|
775
|
-
function opencodeAuthPath(env) {
|
|
776
|
-
const dataHome = env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local", "share");
|
|
777
|
-
return join2(dataHome, "opencode", "auth.json");
|
|
778
|
-
}
|
|
779
|
-
function opencodeConfigPath(env) {
|
|
780
|
-
const configHome = env.XDG_CONFIG_HOME ?? join2(env.HOME ?? homedir(), ".config");
|
|
781
|
-
return join2(configHome, "opencode", "opencode.json");
|
|
782
|
-
}
|
|
783
|
-
function parseOauthSeed(b64) {
|
|
784
|
-
if (!b64) return null;
|
|
785
|
-
try {
|
|
786
|
-
const parsed = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
787
|
-
if (typeof parsed.access === "string" && typeof parsed.refresh === "string" && typeof parsed.expires === "number") {
|
|
788
|
-
return { access: parsed.access, refresh: parsed.refresh, expires: parsed.expires };
|
|
789
|
-
}
|
|
790
|
-
return null;
|
|
791
|
-
} catch {
|
|
792
|
-
return null;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
function shouldSeed(existingEntry, seed) {
|
|
796
|
-
if (!existingEntry || typeof existingEntry !== "object") return true;
|
|
797
|
-
const entry = existingEntry;
|
|
798
|
-
if (entry.type !== "oauth") return true;
|
|
799
|
-
if (typeof entry.access !== "string" || typeof entry.refresh !== "string") return true;
|
|
800
|
-
if (typeof entry.expires !== "number") return true;
|
|
801
|
-
return entry.expires < seed.expires;
|
|
802
|
-
}
|
|
803
|
-
async function readJsonFile(path) {
|
|
804
|
-
try {
|
|
805
|
-
return JSON.parse(await fs.readFile(path, "utf8"));
|
|
806
|
-
} catch {
|
|
807
|
-
return {};
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
async function writeJsonFile(path, value) {
|
|
811
|
-
await fs.mkdir(dirname(path), { recursive: true });
|
|
812
|
-
await fs.writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
813
|
-
`, { mode: 384 });
|
|
814
|
-
}
|
|
815
|
-
async function ensureAuthEntry(env, seed) {
|
|
816
|
-
const path = opencodeAuthPath(env);
|
|
817
|
-
const store = await readJsonFile(path);
|
|
818
|
-
if (!shouldSeed(store.openai, seed)) {
|
|
819
|
-
logger2.info("opencode oauth store is fresher than the seed; leaving it alone");
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
store.openai = {
|
|
823
|
-
type: "oauth",
|
|
824
|
-
access: seed.access,
|
|
825
|
-
refresh: seed.refresh,
|
|
826
|
-
expires: seed.expires
|
|
827
|
-
};
|
|
828
|
-
await writeJsonFile(path, store);
|
|
829
|
-
logger2.info("seeded opencode oauth store entry");
|
|
830
|
-
}
|
|
831
|
-
async function ensurePluginConfig(env) {
|
|
832
|
-
const path = opencodeConfigPath(env);
|
|
833
|
-
const config = await readJsonFile(path);
|
|
834
|
-
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
835
|
-
const isOurs = (p) => typeof p === "string" && (p === PLUGIN_PACKAGE || p.startsWith(`${PLUGIN_PACKAGE}@`));
|
|
836
|
-
const hasExactPin = plugins.includes(OPENCODE_CODEX_PLUGIN);
|
|
837
|
-
const hasStalePin = plugins.some((p) => isOurs(p) && p !== OPENCODE_CODEX_PLUGIN);
|
|
838
|
-
if (hasExactPin && !hasStalePin) return;
|
|
839
|
-
const kept = plugins.filter((p) => !isOurs(p));
|
|
840
|
-
config.plugin = [...kept, OPENCODE_CODEX_PLUGIN];
|
|
841
|
-
await writeJsonFile(path, config);
|
|
842
|
-
logger2.info("ensured opencode codex-auth plugin in config");
|
|
843
|
-
}
|
|
844
|
-
async function seedOpenCodeOauth(env) {
|
|
845
|
-
const seed = parseOauthSeed(env.CONVEYOR_OPENCODE_OAUTH);
|
|
846
|
-
if (!seed) return;
|
|
847
|
-
try {
|
|
848
|
-
await ensureAuthEntry(env, seed);
|
|
849
|
-
await ensurePluginConfig(env);
|
|
850
|
-
} catch (err) {
|
|
851
|
-
logger2.warn(
|
|
852
|
-
`failed to seed opencode oauth store: ${err instanceof Error ? err.message : String(err)}`
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// src/harness/pty/adapters/opencode.ts
|
|
858
|
-
var PROVIDER_KEY_ENV = {
|
|
859
|
-
openai: "OPENAI_API_KEY",
|
|
860
|
-
anthropic: "ANTHROPIC_API_KEY"
|
|
861
|
-
};
|
|
862
|
-
var OpenCodeTuiAdapter = class {
|
|
863
|
-
constructor(env = process.env) {
|
|
864
|
-
this.env = env;
|
|
865
|
-
}
|
|
866
|
-
env;
|
|
867
|
-
id = "opencode";
|
|
868
|
-
capabilities = {
|
|
869
|
-
resume: false,
|
|
870
|
-
structuredEvents: false,
|
|
871
|
-
prefill: false,
|
|
872
|
-
passiveTurns: false
|
|
873
|
-
};
|
|
874
|
-
resolveBinary(env = this.env) {
|
|
875
|
-
const override = env.CONVEYOR_OPENCODE_BIN;
|
|
876
|
-
const found = override ? findOnPath(override, env) : findOnPath("opencode", env);
|
|
877
|
-
if (!found) {
|
|
878
|
-
throw new TuiUnavailableError(
|
|
879
|
-
"opencode",
|
|
880
|
-
"The opencode CLI is not available in this environment. It must be baked into the pod image (see Dockerfile.base) \u2014 re-run Build Image for this project, or set CONVEYOR_OPENCODE_BIN to its absolute path."
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
return found;
|
|
884
|
-
}
|
|
885
|
-
buildSpawn(input) {
|
|
886
|
-
const env = { ...inheritedEnv() };
|
|
887
|
-
delete env.CONVEYOR_AGENT_KEY;
|
|
888
|
-
delete env.CONVEYOR_OPENCODE_OAUTH;
|
|
889
|
-
const oauthActive = Boolean(this.env.CONVEYOR_OPENCODE_OAUTH);
|
|
890
|
-
const key = this.env.CONVEYOR_AGENT_KEY;
|
|
891
|
-
const provider = this.env.CONVEYOR_AGENT_PROVIDER ?? "openai";
|
|
892
|
-
const keyEnvVar = PROVIDER_KEY_ENV[provider];
|
|
893
|
-
if (key && keyEnvVar && !oauthActive) env[keyEnvVar] = key;
|
|
894
|
-
const model = this.env.CONVEYOR_AGENT_MODEL ?? input.options.model;
|
|
895
|
-
const args = [];
|
|
896
|
-
if (model) args.push("--model", model.includes("/") ? model : `${provider}/${model}`);
|
|
897
|
-
return { file: this.resolveBinary(), args, env };
|
|
898
|
-
}
|
|
899
|
-
async prepareEnvironment() {
|
|
900
|
-
await seedOpenCodeOauth(this.env);
|
|
901
|
-
}
|
|
902
|
-
spawnFingerprint(input) {
|
|
903
|
-
return JSON.stringify(["opencode", input.model, input.cwd]);
|
|
904
|
-
}
|
|
905
|
-
encodePromptBytes(text) {
|
|
906
|
-
return buildPromptBytes(text);
|
|
907
|
-
}
|
|
908
|
-
buildExitErrors(exitCode, rawOutput) {
|
|
909
|
-
const errors = [`opencode exited (code ${exitCode}) without a result`];
|
|
910
|
-
const tail = cleanTerminalOutput(rawOutput);
|
|
911
|
-
if (tail) errors.push(`Last terminal output before exit:
|
|
912
|
-
${tail}`);
|
|
913
|
-
return errors;
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
// src/harness/pty/adapters/index.ts
|
|
918
|
-
function resolveTuiAdapter(kind = "claude-code") {
|
|
919
|
-
switch (kind) {
|
|
920
|
-
case "claude-code":
|
|
921
|
-
return new ClaudeTuiAdapter();
|
|
922
|
-
case "opencode":
|
|
923
|
-
return new OpenCodeTuiAdapter();
|
|
924
|
-
default:
|
|
925
|
-
throw new Error(`Unknown TUI kind: ${kind}`);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
// src/tools/project-tools.ts
|
|
930
|
-
import { z } from "zod";
|
|
931
|
-
var CONTEXT_PATH_SHAPE = z.object({
|
|
932
|
-
type: z.enum(["rule", "doc", "file", "folder"]).describe("Link kind"),
|
|
933
|
-
path: z.string().min(1).max(500).describe("Repo-relative path"),
|
|
934
|
-
label: z.string().max(100).optional()
|
|
935
|
-
});
|
|
936
|
-
function errText(prefix, error) {
|
|
937
|
-
return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
938
|
-
}
|
|
939
|
-
function buildListTagsTool(connection, projectId) {
|
|
940
|
-
return defineTool(
|
|
941
|
-
"list_tags",
|
|
942
|
-
"List this project's tags (id, name, color). Use the ids with update_tag.",
|
|
943
|
-
{},
|
|
944
|
-
async () => {
|
|
945
|
-
try {
|
|
946
|
-
const tags = await connection.call("listProjectTags", { projectId });
|
|
947
|
-
return textResult(JSON.stringify(tags, null, 2));
|
|
948
|
-
} catch (error) {
|
|
949
|
-
return errText("Failed to list tags", error);
|
|
950
|
-
}
|
|
951
|
-
},
|
|
952
|
-
{ annotations: { readOnlyHint: true } }
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
function buildCreateTagTool(connection, projectId) {
|
|
956
|
-
return defineTool(
|
|
957
|
-
"create_tag",
|
|
958
|
-
"Create a project tag. Include a crisp description and contextPaths (rule/doc/file/folder links agents auto-load when working on matching tasks). Fails if the name already exists.",
|
|
959
|
-
{
|
|
960
|
-
name: z.string().min(1).max(50),
|
|
961
|
-
color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("#RRGGBB (default gray)"),
|
|
962
|
-
description: z.string().max(500).optional(),
|
|
963
|
-
contextPaths: z.array(CONTEXT_PATH_SHAPE).max(20).optional()
|
|
964
|
-
},
|
|
965
|
-
async ({ name, color, description, contextPaths }) => {
|
|
966
|
-
try {
|
|
967
|
-
const result = await connection.call("createProjectTag", {
|
|
968
|
-
projectId,
|
|
969
|
-
name,
|
|
970
|
-
color,
|
|
971
|
-
description,
|
|
972
|
-
contextPaths
|
|
973
|
-
});
|
|
974
|
-
return textResult(`Tag created: ${result.id}`);
|
|
975
|
-
} catch (error) {
|
|
976
|
-
return errText("Failed to create tag", error);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
function buildUpdateTagTool(connection, projectId) {
|
|
982
|
-
return defineTool(
|
|
983
|
-
"update_tag",
|
|
984
|
-
"Update a tag's name, color, description, or contextPaths. contextPaths is a FULL replacement \u2014 include existing links you want to keep. Verify paths exist in the repo first.",
|
|
985
|
-
{
|
|
986
|
-
tagId: z.string().describe("Tag id from list_tags"),
|
|
987
|
-
name: z.string().min(1).max(50).optional(),
|
|
988
|
-
color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
|
|
989
|
-
description: z.string().max(500).optional(),
|
|
990
|
-
contextPaths: z.array(CONTEXT_PATH_SHAPE).max(20).optional()
|
|
991
|
-
},
|
|
992
|
-
async ({ tagId, name, color, description, contextPaths }) => {
|
|
993
|
-
try {
|
|
994
|
-
await connection.call("updateProjectTag", {
|
|
995
|
-
projectId,
|
|
996
|
-
tagId,
|
|
997
|
-
name,
|
|
998
|
-
color,
|
|
999
|
-
description,
|
|
1000
|
-
contextPaths
|
|
1001
|
-
});
|
|
1002
|
-
return textResult(`Tag updated: ${tagId}`);
|
|
1003
|
-
} catch (error) {
|
|
1004
|
-
return errText("Failed to update tag", error);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
);
|
|
1008
|
-
}
|
|
1009
|
-
function buildCreateSuggestionTool(connection, projectId) {
|
|
1010
|
-
return defineTool(
|
|
1011
|
-
"create_suggestion",
|
|
1012
|
-
"File a project suggestion (idea/improvement for maintainers to review). Duplicates are AI-deduped into an existing suggestion with an upvote. Returns the suggestion id.",
|
|
1013
|
-
{
|
|
1014
|
-
title: z.string().min(1).describe("Short title"),
|
|
1015
|
-
description: z.string().optional().describe("1-3 sentences: what should change and why"),
|
|
1016
|
-
tag_names: z.array(z.string()).optional().describe("Tag names to categorize")
|
|
1017
|
-
},
|
|
1018
|
-
async ({ title, description, tag_names }) => {
|
|
1019
|
-
try {
|
|
1020
|
-
const result = await connection.call("createProjectSuggestion", {
|
|
1021
|
-
projectId,
|
|
1022
|
-
title,
|
|
1023
|
-
description,
|
|
1024
|
-
tagNames: tag_names
|
|
1025
|
-
});
|
|
1026
|
-
return textResult(
|
|
1027
|
-
result.merged ? `Merged into existing suggestion ${result.mergedIntoId ?? result.id} (id: ${result.id})` : `Suggestion created: ${result.id}`
|
|
1028
|
-
);
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
return errText("Failed to create suggestion", error);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
function buildPostToProjectChatTool(connection, projectId) {
|
|
1036
|
-
return defineTool(
|
|
1037
|
-
"post_to_project_chat",
|
|
1038
|
-
"Post a markdown message to the PROJECT chat \u2014 use once at the end of an audit for the summary the team reads.",
|
|
1039
|
-
{
|
|
1040
|
-
message: z.string().min(1).max(2e4),
|
|
1041
|
-
kind: z.enum(["tag_audit_summary"]).optional().describe(
|
|
1042
|
-
"Set to 'tag_audit_summary' when posting a tag-audit summary so it is also saved to the persistent tag history"
|
|
1043
|
-
)
|
|
1044
|
-
},
|
|
1045
|
-
async ({ message, kind }) => {
|
|
1046
|
-
try {
|
|
1047
|
-
await connection.call("postToProjectChat", { projectId, content: message, kind });
|
|
1048
|
-
return textResult("Posted to project chat");
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
return errText("Failed to post to project chat", error);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
function buildGetProjectTaskTool(connection, projectId) {
|
|
1056
|
-
return defineTool(
|
|
1057
|
-
"get_project_task",
|
|
1058
|
-
"Fetch any task in the project by id or slug: title, description, plan, status, and metadata. The audit evidence trail starts here.",
|
|
1059
|
-
{
|
|
1060
|
-
taskId: z.string().describe("Task id or slug")
|
|
1061
|
-
},
|
|
1062
|
-
async ({ taskId }) => {
|
|
1063
|
-
try {
|
|
1064
|
-
const task = await connection.call("getProjectTask", { projectId, taskId });
|
|
1065
|
-
return textResult(JSON.stringify(task, null, 2));
|
|
1066
|
-
} catch (error) {
|
|
1067
|
-
return errText("Failed to get task", error);
|
|
1068
|
-
}
|
|
1069
|
-
},
|
|
1070
|
-
{ annotations: { readOnlyHint: true } }
|
|
1071
|
-
);
|
|
1072
|
-
}
|
|
1073
|
-
function buildReadProjectTaskChatTool(connection, projectId) {
|
|
1074
|
-
return defineTool(
|
|
1075
|
-
"read_project_task_chat",
|
|
1076
|
-
"Read any project task's chat messages (newest last). role 'user' rows are HUMAN turns; 'assistant'/'system' rows are agent posts and activity-log entries.",
|
|
1077
|
-
{
|
|
1078
|
-
taskId: z.string().describe("Task id or slug"),
|
|
1079
|
-
limit: z.number().int().min(1).max(200).optional().describe("Messages to fetch (default 50)")
|
|
1080
|
-
},
|
|
1081
|
-
async ({ taskId, limit }) => {
|
|
1082
|
-
try {
|
|
1083
|
-
const chat = await connection.call("getProjectTaskChat", {
|
|
1084
|
-
projectId,
|
|
1085
|
-
taskId,
|
|
1086
|
-
limit: limit ?? 50
|
|
1087
|
-
});
|
|
1088
|
-
return textResult(JSON.stringify(chat, null, 2));
|
|
1089
|
-
} catch (error) {
|
|
1090
|
-
return errText("Failed to read task chat", error);
|
|
1091
|
-
}
|
|
1092
|
-
},
|
|
1093
|
-
{ annotations: { readOnlyHint: true } }
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
1096
|
-
function buildGetProjectTaskLogsTool(connection, projectId) {
|
|
1097
|
-
return defineTool(
|
|
1098
|
-
"get_project_task_logs",
|
|
1099
|
-
"Read any project task's persisted agent event stream (message / tool_use / turn_end / error / completed). Turn boundaries are turn_end events. Entries are truncated to ~2KB each; max 500 per call.",
|
|
1100
|
-
{
|
|
1101
|
-
taskId: z.string().describe("Task id or slug"),
|
|
1102
|
-
limit: z.number().int().min(1).max(500).optional().describe("Entries to fetch (default 50)"),
|
|
1103
|
-
source: z.enum(["agent", "application"]).optional().describe("Filter: 'agent' = model events (default useful for grading)")
|
|
1104
|
-
},
|
|
1105
|
-
async ({ taskId, limit, source }) => {
|
|
1106
|
-
try {
|
|
1107
|
-
const logs = await connection.call("getProjectTaskCli", {
|
|
1108
|
-
projectId,
|
|
1109
|
-
taskId,
|
|
1110
|
-
limit: limit ?? 50,
|
|
1111
|
-
source
|
|
1112
|
-
});
|
|
1113
|
-
return textResult(JSON.stringify(logs, null, 2));
|
|
1114
|
-
} catch (error) {
|
|
1115
|
-
return errText("Failed to get task logs", error);
|
|
1116
|
-
}
|
|
1117
|
-
},
|
|
1118
|
-
{ annotations: { readOnlyHint: true } }
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
var TURN_GRADE_SHAPE = z.object({
|
|
1122
|
-
turnIndex: z.number().int().min(0),
|
|
1123
|
-
phase: z.enum(["planning", "building", "human"]),
|
|
1124
|
-
grade: z.enum(["correct", "neutral", "blunder"]),
|
|
1125
|
-
reasoning: z.string(),
|
|
1126
|
-
eventType: z.string().describe('e.g. "message", "tool_use", "human_message"'),
|
|
1127
|
-
eventSummary: z.string().max(200).describe("\u2264120 chars of what happened this turn")
|
|
1128
|
-
});
|
|
1129
|
-
var HUMAN_EVAL_SHAPE = z.object({
|
|
1130
|
-
messageIndex: z.number().int().min(0).describe("Index into the task's human messages, oldest first"),
|
|
1131
|
-
rating: z.number().int().min(-1).max(1),
|
|
1132
|
-
reasoning: z.string()
|
|
1133
|
-
});
|
|
1134
|
-
function buildReportTaskAuditResultTool(connection, projectId) {
|
|
1135
|
-
return defineTool(
|
|
1136
|
-
"report_task_audit_result",
|
|
1137
|
-
"Persist one audited task's grades (call once per task after grading it). Pass error instead to mark the audit failed when the evidence is unusable.",
|
|
1138
|
-
{
|
|
1139
|
-
taskId: z.string().describe("The audited task's id (NOT slug)"),
|
|
1140
|
-
summary: z.string().describe("3-6 sentences: what went well, what was wasted"),
|
|
1141
|
-
turnGrades: z.array(TURN_GRADE_SHAPE),
|
|
1142
|
-
planningAccuracy: z.number().min(0).max(1).nullable(),
|
|
1143
|
-
buildingAccuracy: z.number().min(0).max(1).nullable(),
|
|
1144
|
-
humanAccuracy: z.number().min(0).max(1).nullable(),
|
|
1145
|
-
planningCorrect: z.number().int().min(0),
|
|
1146
|
-
planningNeutral: z.number().int().min(0),
|
|
1147
|
-
planningBlunder: z.number().int().min(0),
|
|
1148
|
-
buildingCorrect: z.number().int().min(0),
|
|
1149
|
-
buildingNeutral: z.number().int().min(0),
|
|
1150
|
-
buildingBlunder: z.number().int().min(0),
|
|
1151
|
-
humanCorrect: z.number().int().min(0),
|
|
1152
|
-
humanNeutral: z.number().int().min(0),
|
|
1153
|
-
humanBlunder: z.number().int().min(0),
|
|
1154
|
-
humanEvaluations: z.array(HUMAN_EVAL_SHAPE).optional(),
|
|
1155
|
-
suggestionIds: z.array(z.string()).describe("Suggestion ids filed for this task, or []"),
|
|
1156
|
-
auditCostUsd: z.number().nullable(),
|
|
1157
|
-
model: z.string().nullable().describe("The model you are running as"),
|
|
1158
|
-
error: z.string().optional().describe("Set ONLY to mark this task's audit failed")
|
|
1159
|
-
},
|
|
1160
|
-
async (input) => {
|
|
1161
|
-
try {
|
|
1162
|
-
await connection.call("reportTaskAuditResult", {
|
|
1163
|
-
projectId,
|
|
1164
|
-
...input,
|
|
1165
|
-
humanEvaluations: input.humanEvaluations ?? []
|
|
1166
|
-
});
|
|
1167
|
-
return textResult(
|
|
1168
|
-
input.error ? `Audit for ${input.taskId} marked failed` : `Audit result saved for ${input.taskId}`
|
|
1169
|
-
);
|
|
1170
|
-
} catch (error) {
|
|
1171
|
-
return errText("Failed to report audit result", error);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
);
|
|
1175
|
-
}
|
|
1176
|
-
function buildProjectTools(connection, projectId) {
|
|
1177
|
-
return [
|
|
1178
|
-
buildListTagsTool(connection, projectId),
|
|
1179
|
-
buildCreateTagTool(connection, projectId),
|
|
1180
|
-
buildUpdateTagTool(connection, projectId),
|
|
1181
|
-
buildCreateSuggestionTool(connection, projectId),
|
|
1182
|
-
buildPostToProjectChatTool(connection, projectId),
|
|
1183
|
-
buildGetProjectTaskTool(connection, projectId),
|
|
1184
|
-
buildReadProjectTaskChatTool(connection, projectId),
|
|
1185
|
-
buildGetProjectTaskLogsTool(connection, projectId),
|
|
1186
|
-
buildReportTaskAuditResultTool(connection, projectId)
|
|
1187
|
-
];
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
833
|
// src/runner/adhoc-session-runner.ts
|
|
1191
834
|
var ADHOC_SYSTEM_NOTE = "You are running in an ad-hoc Conveyor scratch pod \u2014 an interactive terminal on the project's repository checked out at its default branch. There is no task or plan; help the human with whatever they ask directly.";
|
|
1192
835
|
var HEADLESS_SYSTEM_NOTE = "You are running a HEADLESS Conveyor session on the project's repository \u2014 your instructions were auto-submitted as the first message. Work them to completion autonomously and never wait for user input; a human may attach to this terminal to watch or interject, but none is required.";
|
|
@@ -1200,16 +843,15 @@ function resolveInitialPrompt(env = process.env) {
|
|
|
1200
843
|
return null;
|
|
1201
844
|
}
|
|
1202
845
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (TUI_KINDS.includes(raw)) return raw;
|
|
1206
|
-
throw new Error(`Unknown TUI "${raw}" in CONVEYOR_TUI (expected: ${TUI_KINDS.join(", ")})`);
|
|
1207
|
-
}
|
|
1208
|
-
function buildAdhocPtyBridge(connection) {
|
|
846
|
+
var resolveAdhocTui = resolveTuiKindFromEnv;
|
|
847
|
+
function buildAdhocPtyBridge(connection, onInputActivity) {
|
|
1209
848
|
return {
|
|
1210
849
|
sendOutput: (data, dims) => connection.sendPtyOutput(data, dims),
|
|
1211
850
|
sendChatEvent: (event) => connection.sendPtyChatEvent(event),
|
|
1212
|
-
onInput: (handler) => connection.onPtyInput(
|
|
851
|
+
onInput: (handler) => connection.onPtyInput((data) => {
|
|
852
|
+
onInputActivity?.();
|
|
853
|
+
handler(data);
|
|
854
|
+
}),
|
|
1213
855
|
onResize: (handler) => connection.onPtyResize(handler)
|
|
1214
856
|
};
|
|
1215
857
|
}
|
|
@@ -1258,11 +900,18 @@ var AdhocSessionRunner = class {
|
|
|
1258
900
|
this.callbacks = callbacks;
|
|
1259
901
|
this.connection = deps?.connection ?? new AgentConnection(config.connection);
|
|
1260
902
|
this.harness = deps?.harness ?? new PtyHarness(
|
|
1261
|
-
|
|
903
|
+
// A keystroke is real human activity — re-arm the idle backstop so an
|
|
904
|
+
// actively-typing human (or a >60-min audit run) is never hard-stopped
|
|
905
|
+
// by the idle timer, mirroring ShellSessionRunner. `this.lifecycle` is
|
|
906
|
+
// assigned later in this constructor but is always set by the time a
|
|
907
|
+
// keystroke can arrive at runtime.
|
|
908
|
+
buildAdhocPtyBridge(this.connection, () => this.lifecycle.startIdleTimer()),
|
|
1262
909
|
resolveTuiAdapter(resolveAdhocTui(process.env))
|
|
1263
910
|
);
|
|
911
|
+
const portVisibility = new CodespacePortVisibility();
|
|
1264
912
|
this.portDiscovery = deps?.portDiscovery ?? new PortDiscovery({
|
|
1265
|
-
report: (ports) => this.connection.reportDiscoveredPorts(ports)
|
|
913
|
+
report: (ports) => this.connection.reportDiscoveredPorts(ports),
|
|
914
|
+
onReported: (ports) => portVisibility.ensureVisible(ports.map(({ port }) => port))
|
|
1266
915
|
});
|
|
1267
916
|
this.commandSupervisor = deps?.commandSupervisor ?? new WorkspaceCommandSupervisor({
|
|
1268
917
|
config: loadConveyorConfig(),
|
|
@@ -1344,7 +993,11 @@ var AdhocSessionRunner = class {
|
|
|
1344
993
|
initialPrompt,
|
|
1345
994
|
conveyorMcpServer: this.harness.createMcpServer({
|
|
1346
995
|
name: "conveyor",
|
|
1347
|
-
tools: buildProjectTools(
|
|
996
|
+
tools: buildProjectTools(
|
|
997
|
+
this.connection,
|
|
998
|
+
this.config.projectId,
|
|
999
|
+
this.config.workspaceDir
|
|
1000
|
+
)
|
|
1348
1001
|
})
|
|
1349
1002
|
} : void 0;
|
|
1350
1003
|
const options = buildAdhocQueryOptions(
|
|
@@ -1455,7 +1108,7 @@ var AdhocSessionRunner = class {
|
|
|
1455
1108
|
import { spawn as nodeSpawn, execFile } from "child_process";
|
|
1456
1109
|
import { promisify } from "util";
|
|
1457
1110
|
var execFileAsync = promisify(execFile);
|
|
1458
|
-
var
|
|
1111
|
+
var logger2 = createServiceLogger("ReviewChild");
|
|
1459
1112
|
var SPAWN_FAILURE_GRACE_MS = 15e3;
|
|
1460
1113
|
var KILL_WAIT_MS = 5e3;
|
|
1461
1114
|
function buildReviewChildEnv(baseEnv, data) {
|
|
@@ -1463,14 +1116,15 @@ function buildReviewChildEnv(baseEnv, data) {
|
|
|
1463
1116
|
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
1464
1117
|
env.CONVEYOR_SESSION_ID = data.sessionId;
|
|
1465
1118
|
env.CONVEYOR_MODE = "code-review";
|
|
1119
|
+
env.CONVEYOR_WORKBENCH_TOKEN ??= baseEnv.POD_BOOTSTRAP_TOKEN;
|
|
1466
1120
|
delete env.POD_BOOTSTRAP_TOKEN;
|
|
1467
1121
|
delete env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1468
|
-
delete env.CONVEYOR_SETUP_COMMAND;
|
|
1469
1122
|
delete env.CONVEYOR_START_COMMAND;
|
|
1470
1123
|
delete env.CONVEYOR_GIT_READY;
|
|
1471
1124
|
delete env.CONVEYOR_GIT_READY_MARKER;
|
|
1472
1125
|
delete env.CONVEYOR_AGENT_MODE;
|
|
1473
1126
|
delete env.CONVEYOR_IS_AUTO;
|
|
1127
|
+
delete env.CONVEYOR_TUI;
|
|
1474
1128
|
return env;
|
|
1475
1129
|
}
|
|
1476
1130
|
var ReviewChildSupervisor = class {
|
|
@@ -1486,7 +1140,7 @@ var ReviewChildSupervisor = class {
|
|
|
1486
1140
|
childSessionId = null;
|
|
1487
1141
|
/** Spawn (or replace) the review child for a `session:spawnReview` push. */
|
|
1488
1142
|
async spawn(data) {
|
|
1489
|
-
|
|
1143
|
+
logger2.info("Spawning same-pod review child", {
|
|
1490
1144
|
reviewSessionId: data.sessionId,
|
|
1491
1145
|
branch: data.branch ?? void 0,
|
|
1492
1146
|
prNumber: data.prNumber ?? void 0
|
|
@@ -1529,7 +1183,7 @@ var ReviewChildSupervisor = class {
|
|
|
1529
1183
|
process.stderr.write(`[review-child] ${chunk.toString()}`);
|
|
1530
1184
|
});
|
|
1531
1185
|
child.on("error", (err) => {
|
|
1532
|
-
|
|
1186
|
+
logger2.error("Review child spawn error", { error: err.message });
|
|
1533
1187
|
if (this.child === child) {
|
|
1534
1188
|
this.child = null;
|
|
1535
1189
|
this.childSessionId = null;
|
|
@@ -1542,7 +1196,7 @@ var ReviewChildSupervisor = class {
|
|
|
1542
1196
|
this.child = null;
|
|
1543
1197
|
this.childSessionId = null;
|
|
1544
1198
|
}
|
|
1545
|
-
|
|
1199
|
+
logger2.info("Review child exited", { code, signal, reviewSessionId: data.sessionId });
|
|
1546
1200
|
const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
|
|
1547
1201
|
if (wasCurrent && withinGrace && code !== null && code !== 0) {
|
|
1548
1202
|
reportOnce(`review child exited with code ${code} within startup grace`);
|
|
@@ -1560,7 +1214,7 @@ var ReviewChildSupervisor = class {
|
|
|
1560
1214
|
this.childSessionId = null;
|
|
1561
1215
|
return;
|
|
1562
1216
|
}
|
|
1563
|
-
|
|
1217
|
+
logger2.info("Stopping review child", { reason, reviewSessionId: this.childSessionId });
|
|
1564
1218
|
this.child = null;
|
|
1565
1219
|
this.childSessionId = null;
|
|
1566
1220
|
await new Promise((resolve) => {
|
|
@@ -1602,7 +1256,7 @@ var ReviewChildSupervisor = class {
|
|
|
1602
1256
|
timeout: 3e4
|
|
1603
1257
|
});
|
|
1604
1258
|
} catch (err) {
|
|
1605
|
-
|
|
1259
|
+
logger2.warn("Review checkout freshen skipped", {
|
|
1606
1260
|
branch,
|
|
1607
1261
|
error: err instanceof Error ? err.message : String(err)
|
|
1608
1262
|
});
|
|
@@ -1612,7 +1266,7 @@ var ReviewChildSupervisor = class {
|
|
|
1612
1266
|
|
|
1613
1267
|
// src/runner/session-child.ts
|
|
1614
1268
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
1615
|
-
var
|
|
1269
|
+
var logger3 = createServiceLogger("SessionChild");
|
|
1616
1270
|
function buildSpawnedChildEnv(baseEnv, data) {
|
|
1617
1271
|
const env = { ...baseEnv };
|
|
1618
1272
|
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
@@ -1621,7 +1275,6 @@ function buildSpawnedChildEnv(baseEnv, data) {
|
|
|
1621
1275
|
env.CONVEYOR_PROJECT_ID = data.projectId;
|
|
1622
1276
|
delete env.POD_BOOTSTRAP_TOKEN;
|
|
1623
1277
|
delete env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1624
|
-
delete env.CONVEYOR_SETUP_COMMAND;
|
|
1625
1278
|
delete env.CONVEYOR_START_COMMAND;
|
|
1626
1279
|
delete env.CONVEYOR_AGENT_MODE;
|
|
1627
1280
|
delete env.CONVEYOR_IS_AUTO;
|
|
@@ -1643,7 +1296,7 @@ var SessionChildSupervisor = class {
|
|
|
1643
1296
|
}
|
|
1644
1297
|
/** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
|
|
1645
1298
|
async spawn(data) {
|
|
1646
|
-
|
|
1299
|
+
logger3.info("Spawning same-pod session child", {
|
|
1647
1300
|
spawnedSessionId: data.sessionId,
|
|
1648
1301
|
mode: data.mode
|
|
1649
1302
|
});
|
|
@@ -1684,7 +1337,7 @@ var SessionChildSupervisor = class {
|
|
|
1684
1337
|
process.stderr.write(`${prefix} ${chunk.toString()}`);
|
|
1685
1338
|
});
|
|
1686
1339
|
child.on("error", (err) => {
|
|
1687
|
-
|
|
1340
|
+
logger3.error("Session child spawn error", { error: err.message });
|
|
1688
1341
|
if (this.children.get(data.sessionId) === child) {
|
|
1689
1342
|
this.children.delete(data.sessionId);
|
|
1690
1343
|
}
|
|
@@ -1693,7 +1346,7 @@ var SessionChildSupervisor = class {
|
|
|
1693
1346
|
child.on("exit", (code, signal) => {
|
|
1694
1347
|
const wasCurrent = this.children.get(data.sessionId) === child;
|
|
1695
1348
|
if (wasCurrent) this.children.delete(data.sessionId);
|
|
1696
|
-
|
|
1349
|
+
logger3.info("Session child exited", {
|
|
1697
1350
|
code,
|
|
1698
1351
|
signal,
|
|
1699
1352
|
spawnedSessionId: data.sessionId,
|
|
@@ -1714,7 +1367,7 @@ var SessionChildSupervisor = class {
|
|
|
1714
1367
|
const child = this.children.get(sessionId);
|
|
1715
1368
|
this.children.delete(sessionId);
|
|
1716
1369
|
if (!child || child.exitCode !== null || child.killed) return;
|
|
1717
|
-
|
|
1370
|
+
logger3.info("Stopping session child", { reason, spawnedSessionId: sessionId });
|
|
1718
1371
|
await new Promise((resolve) => {
|
|
1719
1372
|
const timer = setTimeout(() => {
|
|
1720
1373
|
try {
|
|
@@ -1759,7 +1412,7 @@ var ShellSessionRunner = class {
|
|
|
1759
1412
|
constructor(config, deps) {
|
|
1760
1413
|
this.config = config;
|
|
1761
1414
|
this.connection = deps?.connection ?? new AgentConnection(config.connection);
|
|
1762
|
-
this.loadSpawn = deps?.loadSpawn ??
|
|
1415
|
+
this.loadSpawn = deps?.loadSpawn ?? resolvePtySpawn;
|
|
1763
1416
|
this.lifecycle = new Lifecycle(
|
|
1764
1417
|
// No git flush — the human commits/pushes explicitly from the shell.
|
|
1765
1418
|
{ ...DEFAULT_LIFECYCLE_CONFIG, gitFlushIntervalMs: 0, ...config.lifecycle },
|
|
@@ -1862,7 +1515,7 @@ var ShellSessionRunner = class {
|
|
|
1862
1515
|
};
|
|
1863
1516
|
|
|
1864
1517
|
// src/runner/spawned-child-boot.ts
|
|
1865
|
-
var
|
|
1518
|
+
var logger4 = createServiceLogger("SpawnedChildBoot");
|
|
1866
1519
|
function createSpawnedChildRunner(inputs) {
|
|
1867
1520
|
if (inputs.mode === "shell") {
|
|
1868
1521
|
return new ShellSessionRunner({
|
|
@@ -1892,15 +1545,20 @@ function createSpawnedChildRunner(inputs) {
|
|
|
1892
1545
|
},
|
|
1893
1546
|
{
|
|
1894
1547
|
onEvent: (event) => {
|
|
1895
|
-
|
|
1548
|
+
logger4.info("Spawned TUI event", { eventType: event.type });
|
|
1896
1549
|
}
|
|
1897
1550
|
}
|
|
1898
1551
|
);
|
|
1899
1552
|
}
|
|
1900
1553
|
|
|
1554
|
+
// src/runner/host-modes.ts
|
|
1555
|
+
function hostsSpawnedChildren(mode) {
|
|
1556
|
+
return mode === "task" || mode === "pack";
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1901
1559
|
// src/cli.ts
|
|
1902
1560
|
if (process.argv[2] === "boot") {
|
|
1903
|
-
const { runBoot } = await import("./boot-
|
|
1561
|
+
const { runBoot } = await import("./boot-BZE36LT6.js");
|
|
1904
1562
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1905
1563
|
}
|
|
1906
1564
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1915,9 +1573,10 @@ if (process.argv.includes("--version")) {
|
|
|
1915
1573
|
process.stdout.write(pkg.version + "\n");
|
|
1916
1574
|
process.exit(0);
|
|
1917
1575
|
}
|
|
1918
|
-
var
|
|
1576
|
+
var logger5 = createServiceLogger("CLI");
|
|
1919
1577
|
var exitContext = {};
|
|
1920
1578
|
var exitLogged = false;
|
|
1579
|
+
var shuttingDown = false;
|
|
1921
1580
|
function logAgentExit(reason, opts) {
|
|
1922
1581
|
if (exitLogged) return;
|
|
1923
1582
|
exitLogged = true;
|
|
@@ -1929,12 +1588,12 @@ function logAgentExit(reason, opts) {
|
|
|
1929
1588
|
uptimeSec: process.uptime(),
|
|
1930
1589
|
identity: exitContext
|
|
1931
1590
|
});
|
|
1932
|
-
|
|
1591
|
+
logger5[exitLogLevel(reason)]("agent_exit", payload);
|
|
1933
1592
|
}
|
|
1934
1593
|
async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
1935
1594
|
const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
1936
1595
|
const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
|
|
1937
|
-
|
|
1596
|
+
logger5.info("Bootstrapping from codespace", {
|
|
1938
1597
|
codespace: instanceName,
|
|
1939
1598
|
apiUrl,
|
|
1940
1599
|
apiUrlFromEnv,
|
|
@@ -1946,7 +1605,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1946
1605
|
bootstrapToken
|
|
1947
1606
|
});
|
|
1948
1607
|
if (!result.ok) {
|
|
1949
|
-
|
|
1608
|
+
logger5.error("Bootstrap failed after retries", {
|
|
1950
1609
|
reason: result.reason,
|
|
1951
1610
|
attempts: result.attempts,
|
|
1952
1611
|
status: result.status,
|
|
@@ -1959,7 +1618,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
1959
1618
|
process.exit(1);
|
|
1960
1619
|
}
|
|
1961
1620
|
applyBootstrapToEnv(result.config);
|
|
1962
|
-
|
|
1621
|
+
logger5.info("Bootstrap complete", {
|
|
1963
1622
|
taskId: result.config.taskId,
|
|
1964
1623
|
attempts: result.attempts
|
|
1965
1624
|
});
|
|
@@ -1970,33 +1629,44 @@ function isExpectedAbortError(error) {
|
|
|
1970
1629
|
const hasAbortCode = !("code" in error) || error.code === void 0 || error.code === "ABORT_ERR";
|
|
1971
1630
|
return hasAbortMessage && hasAbortCode;
|
|
1972
1631
|
}
|
|
1632
|
+
function exitOnPermissionDenial(err) {
|
|
1633
|
+
if (!isPermissionDeniedError(err)) return false;
|
|
1634
|
+
logger5.error("Not authorized for this project \u2014 parking instead of crash-looping", {
|
|
1635
|
+
error: err.message
|
|
1636
|
+
});
|
|
1637
|
+
logAgentExit("unauthorized", { exitCode: 0 });
|
|
1638
|
+
process.exit(0);
|
|
1639
|
+
}
|
|
1973
1640
|
process.on("uncaughtException", (err) => {
|
|
1974
1641
|
if (err.code === "EPIPE") return;
|
|
1975
|
-
if (isExpectedAbortError(err)) {
|
|
1976
|
-
|
|
1642
|
+
if (shuttingDown && isExpectedAbortError(err)) {
|
|
1643
|
+
logger5.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
|
|
1977
1644
|
return;
|
|
1978
1645
|
}
|
|
1979
|
-
|
|
1646
|
+
if (exitOnPermissionDenial(err)) return;
|
|
1647
|
+
logger5.error("Uncaught exception", { error: err.message, code: err.code });
|
|
1980
1648
|
logAgentExit("uncaught_exception", { exitCode: 1 });
|
|
1981
1649
|
process.exit(1);
|
|
1982
1650
|
});
|
|
1983
1651
|
process.on("unhandledRejection", (reason) => {
|
|
1984
1652
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
1985
|
-
if (isExpectedAbortError(err)) {
|
|
1986
|
-
|
|
1653
|
+
if (shuttingDown && isExpectedAbortError(err)) {
|
|
1654
|
+
logger5.info("Ignored expected abort rejection after shutdown", { error: err.message });
|
|
1987
1655
|
return;
|
|
1988
1656
|
}
|
|
1989
|
-
|
|
1657
|
+
if (exitOnPermissionDenial(err)) return;
|
|
1658
|
+
logger5.error("Unhandled rejection", { error: err.message });
|
|
1990
1659
|
logAgentExit("unhandled_rejection", { exitCode: 1 });
|
|
1991
1660
|
process.exit(1);
|
|
1992
1661
|
});
|
|
1993
1662
|
if (process.env.CONVEYOR_MODE === "workbench") {
|
|
1994
|
-
const { startWorkbenchServer } = await import("./server-
|
|
1663
|
+
const { startWorkbenchServer } = await import("./server-PPTELJWY.js");
|
|
1664
|
+
const { oomWatchdogOptionsFromEnv } = await import("./oom-watchdog-U7JERHA2.js");
|
|
1995
1665
|
const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QLVS5W6O.js");
|
|
1996
1666
|
const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
|
|
1997
1667
|
const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
|
|
1998
1668
|
if (!token) {
|
|
1999
|
-
|
|
1669
|
+
logger5.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
|
|
2000
1670
|
process.exit(1);
|
|
2001
1671
|
}
|
|
2002
1672
|
const pkgDir = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -2004,9 +1674,14 @@ if (process.env.CONVEYOR_MODE === "workbench") {
|
|
|
2004
1674
|
const handle = await startWorkbenchServer({
|
|
2005
1675
|
port,
|
|
2006
1676
|
token,
|
|
2007
|
-
version: pkg.version ?? "unknown"
|
|
1677
|
+
version: pkg.version ?? "unknown",
|
|
1678
|
+
oomWatchdog: oomWatchdogOptionsFromEnv({
|
|
1679
|
+
info: (m) => logger5.info(m),
|
|
1680
|
+
warn: (m) => logger5.warn(m),
|
|
1681
|
+
error: (m) => logger5.error(m)
|
|
1682
|
+
})
|
|
2008
1683
|
});
|
|
2009
|
-
|
|
1684
|
+
logger5.info(`Workbench launcher listening on 127.0.0.1:${handle.port}`);
|
|
2010
1685
|
process.on("SIGTERM", () => process.exit(0));
|
|
2011
1686
|
await new Promise(() => {
|
|
2012
1687
|
});
|
|
@@ -2016,7 +1691,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
|
|
|
2016
1691
|
var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
|
|
2017
1692
|
if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
|
|
2018
1693
|
if (!conveyorApiUrl) {
|
|
2019
|
-
|
|
1694
|
+
logger5.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
|
|
2020
1695
|
process.exit(1);
|
|
2021
1696
|
}
|
|
2022
1697
|
await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
|
|
@@ -2034,7 +1709,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
|
|
|
2034
1709
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
|
|
2035
1710
|
var projectIdentity = resolveProjectRunnerIdentity(process.env);
|
|
2036
1711
|
if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
2037
|
-
|
|
1712
|
+
logger5.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
|
|
2038
1713
|
const adhocRunner = new AdhocSessionRunner(
|
|
2039
1714
|
{
|
|
2040
1715
|
connection: {
|
|
@@ -2053,15 +1728,21 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
2053
1728
|
},
|
|
2054
1729
|
{
|
|
2055
1730
|
onEvent: (event) => {
|
|
2056
|
-
|
|
1731
|
+
logger5.info("Ad-hoc runner event", { eventType: event.type });
|
|
2057
1732
|
}
|
|
2058
1733
|
}
|
|
2059
1734
|
);
|
|
2060
1735
|
exitContext.sessionId = projectIdentity.sessionId;
|
|
2061
1736
|
exitContext.workspaceId = projectIdentity.workspaceId;
|
|
2062
1737
|
exitContext.runnerMode = "adhoc";
|
|
2063
|
-
process.on("SIGTERM", () =>
|
|
2064
|
-
|
|
1738
|
+
process.on("SIGTERM", () => {
|
|
1739
|
+
shuttingDown = true;
|
|
1740
|
+
adhocRunner.stop();
|
|
1741
|
+
});
|
|
1742
|
+
process.on("SIGINT", () => {
|
|
1743
|
+
shuttingDown = true;
|
|
1744
|
+
adhocRunner.stop();
|
|
1745
|
+
});
|
|
2065
1746
|
await adhocRunner.run();
|
|
2066
1747
|
const adhocError = adhocRunner.finalState === "error";
|
|
2067
1748
|
logAgentExit(adhocError ? "error" : "clean", {
|
|
@@ -2071,7 +1752,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
2071
1752
|
process.exit(adhocError ? 1 : 0);
|
|
2072
1753
|
}
|
|
2073
1754
|
if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
2074
|
-
|
|
1755
|
+
logger5.info("Starting project agent", { projectId: projectIdentity.projectId });
|
|
2075
1756
|
const projectRunner = new ProjectSessionRunner(
|
|
2076
1757
|
{
|
|
2077
1758
|
connection: {
|
|
@@ -2086,15 +1767,21 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
2086
1767
|
},
|
|
2087
1768
|
{
|
|
2088
1769
|
onEvent: (event) => {
|
|
2089
|
-
|
|
1770
|
+
logger5.info("Project runner event", { eventType: event.type });
|
|
2090
1771
|
}
|
|
2091
1772
|
}
|
|
2092
1773
|
);
|
|
2093
1774
|
exitContext.sessionId = projectIdentity.sessionId;
|
|
2094
1775
|
exitContext.workspaceId = projectIdentity.workspaceId;
|
|
2095
1776
|
exitContext.runnerMode = "pm";
|
|
2096
|
-
process.on("SIGTERM", () =>
|
|
2097
|
-
|
|
1777
|
+
process.on("SIGTERM", () => {
|
|
1778
|
+
shuttingDown = true;
|
|
1779
|
+
projectRunner.stop();
|
|
1780
|
+
});
|
|
1781
|
+
process.on("SIGINT", () => {
|
|
1782
|
+
shuttingDown = true;
|
|
1783
|
+
projectRunner.stop();
|
|
1784
|
+
});
|
|
2098
1785
|
await projectRunner.run();
|
|
2099
1786
|
const projectError = projectRunner.finalState === "error";
|
|
2100
1787
|
logAgentExit(projectError ? "error" : "clean", {
|
|
@@ -2104,22 +1791,22 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
2104
1791
|
process.exit(projectError ? 1 : 0);
|
|
2105
1792
|
}
|
|
2106
1793
|
if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
1794
|
+
logger5.error("Missing required environment variables");
|
|
1795
|
+
logger5.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
|
|
1796
|
+
logger5.error(" CONVEYOR_TASK_ID - ID of the task to execute");
|
|
1797
|
+
logger5.error("");
|
|
1798
|
+
logger5.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
|
|
1799
|
+
logger5.error("");
|
|
1800
|
+
logger5.error("Optional:");
|
|
1801
|
+
logger5.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
|
|
1802
|
+
logger5.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
|
|
1803
|
+
logger5.error(
|
|
2117
1804
|
" Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
|
|
2118
1805
|
);
|
|
2119
1806
|
process.exit(1);
|
|
2120
1807
|
}
|
|
2121
1808
|
if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
|
|
2122
|
-
|
|
1809
|
+
logger5.error("Invalid CONVEYOR_MODE", {
|
|
2123
1810
|
mode: CONVEYOR_MODE,
|
|
2124
1811
|
expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
|
|
2125
1812
|
});
|
|
@@ -2128,7 +1815,7 @@ if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "cod
|
|
|
2128
1815
|
if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
2129
1816
|
const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
|
|
2130
1817
|
if (!spawnedSessionId) {
|
|
2131
|
-
|
|
1818
|
+
logger5.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
|
|
2132
1819
|
process.exit(1);
|
|
2133
1820
|
}
|
|
2134
1821
|
exitContext.sessionId = spawnedSessionId;
|
|
@@ -2141,12 +1828,18 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
2141
1828
|
workspaceDir: CONVEYOR_WORKSPACE,
|
|
2142
1829
|
projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
|
|
2143
1830
|
});
|
|
2144
|
-
|
|
1831
|
+
logger5.info("Starting spawned session child", {
|
|
2145
1832
|
mode: CONVEYOR_MODE,
|
|
2146
1833
|
sessionId: spawnedSessionId
|
|
2147
1834
|
});
|
|
2148
|
-
process.on("SIGTERM", () =>
|
|
2149
|
-
|
|
1835
|
+
process.on("SIGTERM", () => {
|
|
1836
|
+
shuttingDown = true;
|
|
1837
|
+
childRunner.stop();
|
|
1838
|
+
});
|
|
1839
|
+
process.on("SIGINT", () => {
|
|
1840
|
+
shuttingDown = true;
|
|
1841
|
+
childRunner.stop();
|
|
1842
|
+
});
|
|
2150
1843
|
await childRunner.run();
|
|
2151
1844
|
const childError = childRunner.finalState === "error";
|
|
2152
1845
|
logAgentExit(childError ? "error" : "clean", {
|
|
@@ -2155,7 +1848,7 @@ if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
|
2155
1848
|
});
|
|
2156
1849
|
process.exit(childError ? 1 : 0);
|
|
2157
1850
|
}
|
|
2158
|
-
|
|
1851
|
+
logger5.info("Starting agent", { mode: CONVEYOR_MODE });
|
|
2159
1852
|
var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
|
|
2160
1853
|
var runner = new SessionRunner(
|
|
2161
1854
|
{
|
|
@@ -2175,12 +1868,12 @@ var runner = new SessionRunner(
|
|
|
2175
1868
|
},
|
|
2176
1869
|
{
|
|
2177
1870
|
onStatusChange: (status) => {
|
|
2178
|
-
|
|
1871
|
+
logger5.info("Status changed", { status });
|
|
2179
1872
|
},
|
|
2180
1873
|
onEvent: (event) => {
|
|
2181
1874
|
const detail = event.message ?? event.content ?? event.summary ?? "";
|
|
2182
1875
|
if (detail) {
|
|
2183
|
-
|
|
1876
|
+
logger5.info(detail, { eventType: event.type });
|
|
2184
1877
|
}
|
|
2185
1878
|
}
|
|
2186
1879
|
}
|
|
@@ -2191,7 +1884,8 @@ var shutdownSignal;
|
|
|
2191
1884
|
var workspaceCommandSupervisor = null;
|
|
2192
1885
|
var shutdownCompletion = null;
|
|
2193
1886
|
var shutdownAgent = (signal) => {
|
|
2194
|
-
|
|
1887
|
+
shuttingDown = true;
|
|
1888
|
+
logger5.info(`Received ${signal}, flushing git and stopping agent`);
|
|
2195
1889
|
const commandShutdown = stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2196
1890
|
shutdownSignal = signal;
|
|
2197
1891
|
shutdownCompletion ??= (async () => {
|
|
@@ -2208,8 +1902,8 @@ var shutdownAgent = (signal) => {
|
|
|
2208
1902
|
})();
|
|
2209
1903
|
void shutdownCompletion;
|
|
2210
1904
|
setTimeout(() => {
|
|
2211
|
-
|
|
2212
|
-
|
|
1905
|
+
logger5.warn(`Forcing exit after ${signal} timeout`);
|
|
1906
|
+
logger5.warn(
|
|
2213
1907
|
"agent_exit",
|
|
2214
1908
|
buildAgentExitLog({
|
|
2215
1909
|
reason: "force_timeout",
|
|
@@ -2235,7 +1929,7 @@ workspaceCommandSupervisor = await startWorkspaceCommandsAfterConnect({
|
|
|
2235
1929
|
})
|
|
2236
1930
|
});
|
|
2237
1931
|
if (!workspaceCommandSupervisor) {
|
|
2238
|
-
|
|
1932
|
+
logger5.info("Agent stopped before workspace commands were started");
|
|
2239
1933
|
if (shutdownCompletion) await shutdownCompletion;
|
|
2240
1934
|
logAgentExit(shutdownSignal ? "signal" : "clean", {
|
|
2241
1935
|
exitCode: 0,
|
|
@@ -2252,12 +1946,12 @@ void checkSessionTaskIdentity({
|
|
|
2252
1946
|
const ctx = await runner.connection.call("getTaskContext", { sessionId });
|
|
2253
1947
|
return ctx.id;
|
|
2254
1948
|
},
|
|
2255
|
-
logger:
|
|
1949
|
+
logger: logger5
|
|
2256
1950
|
});
|
|
2257
|
-
if (CONVEYOR_MODE
|
|
1951
|
+
if (hostsSpawnedChildren(CONVEYOR_MODE)) {
|
|
2258
1952
|
runner.connection.onSpawnReview((data) => {
|
|
2259
1953
|
void reviewChildren.spawn(data).catch((err) => {
|
|
2260
|
-
|
|
1954
|
+
logger5.error("Review child spawn threw", {
|
|
2261
1955
|
error: err instanceof Error ? err.message : String(err)
|
|
2262
1956
|
});
|
|
2263
1957
|
runner.connection.reportReviewSpawnFailure(
|
|
@@ -2267,10 +1961,10 @@ if (CONVEYOR_MODE === "task") {
|
|
|
2267
1961
|
});
|
|
2268
1962
|
});
|
|
2269
1963
|
}
|
|
2270
|
-
if (CONVEYOR_MODE
|
|
1964
|
+
if (hostsSpawnedChildren(CONVEYOR_MODE)) {
|
|
2271
1965
|
runner.connection.onSpawnTui((data) => {
|
|
2272
1966
|
void sessionChildren.spawn(data).catch((err) => {
|
|
2273
|
-
|
|
1967
|
+
logger5.error("Session child spawn threw", {
|
|
2274
1968
|
error: err instanceof Error ? err.message : String(err)
|
|
2275
1969
|
});
|
|
2276
1970
|
runner.connection.reportSessionSpawnFailure(
|
|
@@ -2293,7 +1987,7 @@ runner.run().then(async () => {
|
|
|
2293
1987
|
}).catch(async (error) => {
|
|
2294
1988
|
await stopWorkspaceCommands(workspaceCommandSupervisor);
|
|
2295
1989
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2296
|
-
|
|
1990
|
+
logger5.error("Agent runner failed", { error: msg });
|
|
2297
1991
|
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|
|
2298
1992
|
process.exit(1);
|
|
2299
1993
|
});
|