@rallycry/conveyor-agent 10.4.0 → 10.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TZYEU4QE.js → chunk-MSDYMAG3.js} +190 -91
- package/dist/chunk-MSDYMAG3.js.map +1 -0
- package/dist/cli.js +381 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +25 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TZYEU4QE.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
inheritedEnv,
|
|
21
21
|
loadConveyorConfig,
|
|
22
22
|
loadForwardPorts,
|
|
23
|
+
loadPtySpawn,
|
|
23
24
|
resolvePlaywrightMcpServer,
|
|
24
25
|
resolveSessionStart,
|
|
25
26
|
runSetupCommand,
|
|
26
27
|
runStartCommand,
|
|
27
28
|
sampleKeyUsage
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-MSDYMAG3.js";
|
|
29
30
|
import "./chunk-7TQO4ZF4.js";
|
|
30
31
|
|
|
31
32
|
// src/cli.ts
|
|
@@ -154,14 +155,14 @@ async function waitForSidecars(opts = {}) {
|
|
|
154
155
|
|
|
155
156
|
// src/utils/session-identity.ts
|
|
156
157
|
async function checkSessionTaskIdentity(params) {
|
|
157
|
-
const { sessionId, taskId, fetchSessionTaskId, logger:
|
|
158
|
+
const { sessionId, taskId, fetchSessionTaskId, logger: logger6 } = params;
|
|
158
159
|
if (!sessionId || sessionId === taskId) return "skipped";
|
|
159
160
|
let sessionTaskId;
|
|
160
161
|
try {
|
|
161
162
|
sessionTaskId = await fetchSessionTaskId(sessionId);
|
|
162
163
|
} catch (err) {
|
|
163
164
|
const message = err instanceof Error ? err.message : String(err);
|
|
164
|
-
|
|
165
|
+
logger6.warn("Could not verify session/task identity \u2014 continuing (defense-in-depth only)", {
|
|
165
166
|
sessionId,
|
|
166
167
|
taskId,
|
|
167
168
|
error: message
|
|
@@ -169,7 +170,7 @@ async function checkSessionTaskIdentity(params) {
|
|
|
169
170
|
return "error";
|
|
170
171
|
}
|
|
171
172
|
if (sessionTaskId !== taskId) {
|
|
172
|
-
|
|
173
|
+
logger6.warn(
|
|
173
174
|
"!!! 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.",
|
|
174
175
|
{ sessionId, envTaskId: taskId, sessionTaskId }
|
|
175
176
|
);
|
|
@@ -834,6 +835,295 @@ var ReviewChildSupervisor = class {
|
|
|
834
835
|
}
|
|
835
836
|
};
|
|
836
837
|
|
|
838
|
+
// src/runner/session-child.ts
|
|
839
|
+
import { spawn as nodeSpawn2 } from "child_process";
|
|
840
|
+
var logger3 = createServiceLogger("SessionChild");
|
|
841
|
+
var KILL_WAIT_MS2 = 5e3;
|
|
842
|
+
function buildSpawnedChildEnv(baseEnv, data) {
|
|
843
|
+
const env = { ...baseEnv };
|
|
844
|
+
env.CONVEYOR_TASK_TOKEN = data.sessionJwt;
|
|
845
|
+
env.CONVEYOR_SESSION_ID = data.sessionId;
|
|
846
|
+
env.CONVEYOR_MODE = data.mode;
|
|
847
|
+
env.CONVEYOR_PROJECT_ID = data.projectId;
|
|
848
|
+
delete env.POD_BOOTSTRAP_TOKEN;
|
|
849
|
+
delete env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
850
|
+
delete env.CONVEYOR_SETUP_COMMAND;
|
|
851
|
+
delete env.CONVEYOR_START_COMMAND;
|
|
852
|
+
delete env.CONVEYOR_AGENT_MODE;
|
|
853
|
+
delete env.CONVEYOR_IS_AUTO;
|
|
854
|
+
return env;
|
|
855
|
+
}
|
|
856
|
+
var SessionChildSupervisor = class {
|
|
857
|
+
constructor(connection, workspaceDir, spawnFn = nodeSpawn2) {
|
|
858
|
+
this.connection = connection;
|
|
859
|
+
this.workspaceDir = workspaceDir;
|
|
860
|
+
this.spawnFn = spawnFn;
|
|
861
|
+
}
|
|
862
|
+
connection;
|
|
863
|
+
workspaceDir;
|
|
864
|
+
spawnFn;
|
|
865
|
+
children = /* @__PURE__ */ new Map();
|
|
866
|
+
/** Live child count (for tests/diagnostics). */
|
|
867
|
+
get size() {
|
|
868
|
+
return this.children.size;
|
|
869
|
+
}
|
|
870
|
+
/** Spawn a TUI/shell child for a `session:spawnTui` push. Additive per session. */
|
|
871
|
+
async spawn(data) {
|
|
872
|
+
logger3.info("Spawning same-pod session child", {
|
|
873
|
+
spawnedSessionId: data.sessionId,
|
|
874
|
+
mode: data.mode
|
|
875
|
+
});
|
|
876
|
+
await this.stopChild(data.sessionId, "superseded by duplicate spawn");
|
|
877
|
+
const env = buildSpawnedChildEnv(process.env, data);
|
|
878
|
+
const cliPath = process.argv[1];
|
|
879
|
+
if (!cliPath) {
|
|
880
|
+
this.connection.reportSessionSpawnFailure(data.sessionId, "cannot resolve CLI entry path");
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
let child;
|
|
884
|
+
try {
|
|
885
|
+
child = this.spawnFn(process.execPath, [cliPath], {
|
|
886
|
+
cwd: this.workspaceDir,
|
|
887
|
+
env,
|
|
888
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
889
|
+
});
|
|
890
|
+
} catch (err) {
|
|
891
|
+
this.connection.reportSessionSpawnFailure(
|
|
892
|
+
data.sessionId,
|
|
893
|
+
err instanceof Error ? err.message : String(err)
|
|
894
|
+
);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
this.children.set(data.sessionId, child);
|
|
898
|
+
const spawnedAt = Date.now();
|
|
899
|
+
let reported = false;
|
|
900
|
+
const reportOnce = (message) => {
|
|
901
|
+
if (reported) return;
|
|
902
|
+
reported = true;
|
|
903
|
+
this.connection.reportSessionSpawnFailure(data.sessionId, message);
|
|
904
|
+
};
|
|
905
|
+
const prefix = `[${data.mode}-child]`;
|
|
906
|
+
child.stdout?.on("data", (chunk) => {
|
|
907
|
+
process.stdout.write(`${prefix} ${chunk.toString()}`);
|
|
908
|
+
});
|
|
909
|
+
child.stderr?.on("data", (chunk) => {
|
|
910
|
+
process.stderr.write(`${prefix} ${chunk.toString()}`);
|
|
911
|
+
});
|
|
912
|
+
child.on("error", (err) => {
|
|
913
|
+
logger3.error("Session child spawn error", { error: err.message });
|
|
914
|
+
if (this.children.get(data.sessionId) === child) {
|
|
915
|
+
this.children.delete(data.sessionId);
|
|
916
|
+
}
|
|
917
|
+
reportOnce(err.message);
|
|
918
|
+
});
|
|
919
|
+
child.on("exit", (code, signal) => {
|
|
920
|
+
const wasCurrent = this.children.get(data.sessionId) === child;
|
|
921
|
+
if (wasCurrent) this.children.delete(data.sessionId);
|
|
922
|
+
logger3.info("Session child exited", {
|
|
923
|
+
code,
|
|
924
|
+
signal,
|
|
925
|
+
spawnedSessionId: data.sessionId,
|
|
926
|
+
mode: data.mode
|
|
927
|
+
});
|
|
928
|
+
const withinGrace = Date.now() - spawnedAt < SPAWN_FAILURE_GRACE_MS;
|
|
929
|
+
if (wasCurrent && withinGrace && code !== null && code !== 0) {
|
|
930
|
+
reportOnce(`session child exited with code ${code} within startup grace`);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
/** Stop every live child (parent shutdown). Best-effort. */
|
|
935
|
+
async stopAll() {
|
|
936
|
+
const ids = [...this.children.keys()];
|
|
937
|
+
await Promise.all(ids.map((id) => this.stopChild(id, "parent shutting down")));
|
|
938
|
+
}
|
|
939
|
+
async stopChild(sessionId, reason) {
|
|
940
|
+
const child = this.children.get(sessionId);
|
|
941
|
+
this.children.delete(sessionId);
|
|
942
|
+
if (!child || child.exitCode !== null || child.killed) return;
|
|
943
|
+
logger3.info("Stopping session child", { reason, spawnedSessionId: sessionId });
|
|
944
|
+
await new Promise((resolve) => {
|
|
945
|
+
const timer = setTimeout(() => {
|
|
946
|
+
try {
|
|
947
|
+
child.kill("SIGKILL");
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
resolve();
|
|
951
|
+
}, KILL_WAIT_MS2);
|
|
952
|
+
timer.unref();
|
|
953
|
+
child.once("exit", () => {
|
|
954
|
+
clearTimeout(timer);
|
|
955
|
+
resolve();
|
|
956
|
+
});
|
|
957
|
+
try {
|
|
958
|
+
child.kill("SIGTERM");
|
|
959
|
+
} catch {
|
|
960
|
+
clearTimeout(timer);
|
|
961
|
+
resolve();
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
// src/runner/shell-session-runner.ts
|
|
968
|
+
var DEFAULT_COLS = 80;
|
|
969
|
+
var DEFAULT_ROWS = 24;
|
|
970
|
+
function buildShellEnv(baseEnv) {
|
|
971
|
+
const env = { ...baseEnv };
|
|
972
|
+
delete env.CONVEYOR_TASK_TOKEN;
|
|
973
|
+
delete env.CONVEYOR_SESSION_ID;
|
|
974
|
+
return env;
|
|
975
|
+
}
|
|
976
|
+
var ShellSessionRunner = class {
|
|
977
|
+
connection;
|
|
978
|
+
lifecycle;
|
|
979
|
+
config;
|
|
980
|
+
loadSpawn;
|
|
981
|
+
pty = null;
|
|
982
|
+
dims = { cols: DEFAULT_COLS, rows: DEFAULT_ROWS };
|
|
983
|
+
stopped = false;
|
|
984
|
+
_finalState = null;
|
|
985
|
+
constructor(config, deps) {
|
|
986
|
+
this.config = config;
|
|
987
|
+
this.connection = deps?.connection ?? new AgentConnection(config.connection);
|
|
988
|
+
this.loadSpawn = deps?.loadSpawn ?? loadPtySpawn;
|
|
989
|
+
this.lifecycle = new Lifecycle(
|
|
990
|
+
// No git flush — the human commits/pushes explicitly from the shell.
|
|
991
|
+
{ ...DEFAULT_LIFECYCLE_CONFIG, gitFlushIntervalMs: 0, ...config.lifecycle },
|
|
992
|
+
{
|
|
993
|
+
onHeartbeat: () => this.connection.sendHeartbeat(),
|
|
994
|
+
onIdleTimeout: () => this.requestStop(),
|
|
995
|
+
onDormantTimeout: () => this.requestStop(),
|
|
996
|
+
onTokenRefresh: () => void this.connection.refreshTaskTokenFromBootstrap().catch(() => {
|
|
997
|
+
}),
|
|
998
|
+
onGitFlush: () => {
|
|
999
|
+
},
|
|
1000
|
+
onUsageSample: () => {
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
get finalState() {
|
|
1006
|
+
return this._finalState;
|
|
1007
|
+
}
|
|
1008
|
+
get isStopped() {
|
|
1009
|
+
return this.stopped;
|
|
1010
|
+
}
|
|
1011
|
+
/** Connect, register, spawn the shell, and relay it until exit or stop. */
|
|
1012
|
+
async run() {
|
|
1013
|
+
try {
|
|
1014
|
+
await this.connection.connect();
|
|
1015
|
+
this.connection.sendEvent({
|
|
1016
|
+
type: "connected",
|
|
1017
|
+
sessionId: this.config.connection.sessionId
|
|
1018
|
+
});
|
|
1019
|
+
this.connection.onStop(() => this.requestStop());
|
|
1020
|
+
this.lifecycle.startHeartbeat();
|
|
1021
|
+
this.lifecycle.startTokenRefresh();
|
|
1022
|
+
await this.connection.call("connectAgent", {
|
|
1023
|
+
sessionId: this.config.connection.sessionId
|
|
1024
|
+
});
|
|
1025
|
+
await this.connection.emitStatus("running");
|
|
1026
|
+
this.lifecycle.startIdleTimer();
|
|
1027
|
+
await this.runShell();
|
|
1028
|
+
this._finalState = "finished";
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1031
|
+
process.stderr.write(`[conveyor-agent] Shell runner failed: ${message}
|
|
1032
|
+
`);
|
|
1033
|
+
this.connection.sendEvent({ type: "error", message });
|
|
1034
|
+
this._finalState = "error";
|
|
1035
|
+
} finally {
|
|
1036
|
+
this.shutdown();
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/** Spawn the login shell under a PTY and block until it exits or stop(). */
|
|
1040
|
+
async runShell() {
|
|
1041
|
+
const spawn = await this.loadSpawn();
|
|
1042
|
+
const shell = this.config.shell ?? process.env.SHELL ?? "bash";
|
|
1043
|
+
const pty = spawn(shell, ["-l"], {
|
|
1044
|
+
name: "xterm-256color",
|
|
1045
|
+
cols: this.dims.cols,
|
|
1046
|
+
rows: this.dims.rows,
|
|
1047
|
+
cwd: this.config.workspaceDir,
|
|
1048
|
+
env: buildShellEnv(inheritedEnv())
|
|
1049
|
+
});
|
|
1050
|
+
this.pty = pty;
|
|
1051
|
+
const unsubInput = this.connection.onPtyInput((data) => {
|
|
1052
|
+
this.lifecycle.startIdleTimer();
|
|
1053
|
+
pty.write(data);
|
|
1054
|
+
});
|
|
1055
|
+
const unsubResize = this.connection.onPtyResize((cols, rows) => {
|
|
1056
|
+
this.dims = { cols, rows };
|
|
1057
|
+
pty.resize(cols, rows);
|
|
1058
|
+
});
|
|
1059
|
+
pty.onData((data) => this.connection.sendPtyOutput(data, this.dims));
|
|
1060
|
+
try {
|
|
1061
|
+
await new Promise((resolve) => {
|
|
1062
|
+
pty.onExit(() => resolve());
|
|
1063
|
+
});
|
|
1064
|
+
} finally {
|
|
1065
|
+
unsubInput();
|
|
1066
|
+
unsubResize();
|
|
1067
|
+
this.pty = null;
|
|
1068
|
+
this.connection.sendPtyEnded();
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
stop() {
|
|
1072
|
+
this.requestStop();
|
|
1073
|
+
}
|
|
1074
|
+
requestStop() {
|
|
1075
|
+
if (this.stopped) return;
|
|
1076
|
+
this.stopped = true;
|
|
1077
|
+
try {
|
|
1078
|
+
this.pty?.kill();
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
shutdown() {
|
|
1083
|
+
this.stopped = true;
|
|
1084
|
+
this.lifecycle.destroy();
|
|
1085
|
+
this.connection.sendEvent({ type: "shutdown", reason: this._finalState ?? "finished" });
|
|
1086
|
+
this.connection.disconnect();
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// src/runner/spawned-child-boot.ts
|
|
1091
|
+
var logger4 = createServiceLogger("SpawnedChildBoot");
|
|
1092
|
+
function createSpawnedChildRunner(inputs) {
|
|
1093
|
+
if (inputs.mode === "shell") {
|
|
1094
|
+
return new ShellSessionRunner({
|
|
1095
|
+
connection: {
|
|
1096
|
+
apiUrl: inputs.apiUrl,
|
|
1097
|
+
taskToken: inputs.taskToken,
|
|
1098
|
+
sessionId: inputs.sessionId,
|
|
1099
|
+
runnerMode: "shell"
|
|
1100
|
+
},
|
|
1101
|
+
workspaceDir: inputs.workspaceDir
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
return new AdhocSessionRunner(
|
|
1105
|
+
{
|
|
1106
|
+
connection: {
|
|
1107
|
+
apiUrl: inputs.apiUrl,
|
|
1108
|
+
taskToken: inputs.taskToken,
|
|
1109
|
+
sessionId: inputs.sessionId,
|
|
1110
|
+
runnerMode: "adhoc"
|
|
1111
|
+
},
|
|
1112
|
+
projectId: inputs.projectId,
|
|
1113
|
+
// Session lineage key. NOT the inherited CONVEYOR_WORKSPACE_ID — that's
|
|
1114
|
+
// the builder's lineage; concurrent tabs must each be their own Claude
|
|
1115
|
+
// conversation, so key on the spawned session id.
|
|
1116
|
+
workspaceId: inputs.sessionId,
|
|
1117
|
+
workspaceDir: inputs.workspaceDir
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
onEvent: (event) => {
|
|
1121
|
+
logger4.info("Spawned TUI event", { eventType: event.type });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
837
1127
|
// src/cli.ts
|
|
838
1128
|
if (process.argv.includes("--version")) {
|
|
839
1129
|
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -842,7 +1132,7 @@ if (process.argv.includes("--version")) {
|
|
|
842
1132
|
process.stdout.write(pkg.version + "\n");
|
|
843
1133
|
process.exit(0);
|
|
844
1134
|
}
|
|
845
|
-
var
|
|
1135
|
+
var logger5 = createServiceLogger("CLI");
|
|
846
1136
|
var exitContext = {};
|
|
847
1137
|
var exitLogged = false;
|
|
848
1138
|
function logAgentExit(reason, opts) {
|
|
@@ -856,12 +1146,12 @@ function logAgentExit(reason, opts) {
|
|
|
856
1146
|
uptimeSec: process.uptime(),
|
|
857
1147
|
identity: exitContext
|
|
858
1148
|
});
|
|
859
|
-
|
|
1149
|
+
logger5[exitLogLevel(reason)]("agent_exit", payload);
|
|
860
1150
|
}
|
|
861
1151
|
async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
862
1152
|
const bootstrapToken = process.env.CONVEYOR_BOOTSTRAP_TOKEN;
|
|
863
1153
|
const apiUrlFromEnv = Boolean(process.env.CONVEYOR_API_URL);
|
|
864
|
-
|
|
1154
|
+
logger5.info("Bootstrapping from codespace", {
|
|
865
1155
|
codespace: instanceName,
|
|
866
1156
|
apiUrl,
|
|
867
1157
|
apiUrlFromEnv,
|
|
@@ -873,7 +1163,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
873
1163
|
bootstrapToken
|
|
874
1164
|
});
|
|
875
1165
|
if (!result.ok) {
|
|
876
|
-
|
|
1166
|
+
logger5.error("Bootstrap failed after retries", {
|
|
877
1167
|
reason: result.reason,
|
|
878
1168
|
attempts: result.attempts,
|
|
879
1169
|
status: result.status,
|
|
@@ -886,7 +1176,7 @@ async function bootstrapFromCodespace(apiUrl, instanceName) {
|
|
|
886
1176
|
process.exit(1);
|
|
887
1177
|
}
|
|
888
1178
|
applyBootstrapToEnv(result.config);
|
|
889
|
-
|
|
1179
|
+
logger5.info("Bootstrap complete", {
|
|
890
1180
|
taskId: result.config.taskId,
|
|
891
1181
|
attempts: result.attempts
|
|
892
1182
|
});
|
|
@@ -900,20 +1190,20 @@ function isExpectedAbortError(error) {
|
|
|
900
1190
|
process.on("uncaughtException", (err) => {
|
|
901
1191
|
if (err.code === "EPIPE") return;
|
|
902
1192
|
if (isExpectedAbortError(err)) {
|
|
903
|
-
|
|
1193
|
+
logger5.info("Ignored expected abort after shutdown", { error: err.message, code: err.code });
|
|
904
1194
|
return;
|
|
905
1195
|
}
|
|
906
|
-
|
|
1196
|
+
logger5.error("Uncaught exception", { error: err.message, code: err.code });
|
|
907
1197
|
logAgentExit("uncaught_exception", { exitCode: 1 });
|
|
908
1198
|
process.exit(1);
|
|
909
1199
|
});
|
|
910
1200
|
process.on("unhandledRejection", (reason) => {
|
|
911
1201
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
912
1202
|
if (isExpectedAbortError(err)) {
|
|
913
|
-
|
|
1203
|
+
logger5.info("Ignored expected abort rejection after shutdown", { error: err.message });
|
|
914
1204
|
return;
|
|
915
1205
|
}
|
|
916
|
-
|
|
1206
|
+
logger5.error("Unhandled rejection", { error: err.message });
|
|
917
1207
|
logAgentExit("unhandled_rejection", { exitCode: 1 });
|
|
918
1208
|
process.exit(1);
|
|
919
1209
|
});
|
|
@@ -922,7 +1212,7 @@ var conveyorApiUrl = process.env.CONVEYOR_API_URL || DEFAULT_CONVEYOR_API_URL;
|
|
|
922
1212
|
var INSTANCE_NAME = process.env.CODESPACE_NAME || process.env.CLAUDESPACE_NAME;
|
|
923
1213
|
if (INSTANCE_NAME && !process.env.CONVEYOR_TASK_TOKEN) {
|
|
924
1214
|
if (!conveyorApiUrl) {
|
|
925
|
-
|
|
1215
|
+
logger5.error("Could not resolve CONVEYOR_API_URL for codespace bootstrap");
|
|
926
1216
|
process.exit(1);
|
|
927
1217
|
}
|
|
928
1218
|
await bootstrapFromCodespace(conveyorApiUrl, INSTANCE_NAME);
|
|
@@ -940,7 +1230,7 @@ exitContext.runnerMode = CONVEYOR_MODE;
|
|
|
940
1230
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? CONVEYOR_TASK_ID;
|
|
941
1231
|
var projectIdentity = resolveProjectRunnerIdentity(process.env);
|
|
942
1232
|
if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
943
|
-
|
|
1233
|
+
logger5.info("Starting ad-hoc agent", { projectId: projectIdentity.projectId });
|
|
944
1234
|
const adhocRunner = new AdhocSessionRunner(
|
|
945
1235
|
{
|
|
946
1236
|
connection: {
|
|
@@ -956,7 +1246,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
956
1246
|
},
|
|
957
1247
|
{
|
|
958
1248
|
onEvent: (event) => {
|
|
959
|
-
|
|
1249
|
+
logger5.info("Ad-hoc runner event", { eventType: event.type });
|
|
960
1250
|
}
|
|
961
1251
|
}
|
|
962
1252
|
);
|
|
@@ -974,7 +1264,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity && CONVEYOR_MODE === "adhoc") {
|
|
|
974
1264
|
process.exit(adhocError ? 1 : 0);
|
|
975
1265
|
}
|
|
976
1266
|
if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
977
|
-
|
|
1267
|
+
logger5.info("Starting project agent", { projectId: projectIdentity.projectId });
|
|
978
1268
|
const projectRunner = new ProjectSessionRunner(
|
|
979
1269
|
{
|
|
980
1270
|
connection: {
|
|
@@ -989,7 +1279,7 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
989
1279
|
},
|
|
990
1280
|
{
|
|
991
1281
|
onEvent: (event) => {
|
|
992
|
-
|
|
1282
|
+
logger5.info("Project runner event", { eventType: event.type });
|
|
993
1283
|
}
|
|
994
1284
|
}
|
|
995
1285
|
);
|
|
@@ -1007,28 +1297,58 @@ if (!CONVEYOR_TASK_ID && projectIdentity) {
|
|
|
1007
1297
|
process.exit(projectError ? 1 : 0);
|
|
1008
1298
|
}
|
|
1009
1299
|
if (!CONVEYOR_TASK_TOKEN || !CONVEYOR_TASK_ID) {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1300
|
+
logger5.error("Missing required environment variables");
|
|
1301
|
+
logger5.error(" CONVEYOR_TASK_TOKEN - JWT token for task authentication");
|
|
1302
|
+
logger5.error(" CONVEYOR_TASK_ID - ID of the task to execute");
|
|
1303
|
+
logger5.error("");
|
|
1304
|
+
logger5.error("CONVEYOR_API_URL is provided via codespace secret or bootstrap.");
|
|
1305
|
+
logger5.error("");
|
|
1306
|
+
logger5.error("Optional:");
|
|
1307
|
+
logger5.error(" CONVEYOR_MODE - Runner mode: 'task' (default), 'pack', or 'pm'");
|
|
1308
|
+
logger5.error(" CONVEYOR_WORKSPACE - Working directory (defaults to cwd)");
|
|
1309
|
+
logger5.error(
|
|
1020
1310
|
" Project pods instead require CONVEYOR_PROJECT_ID + CONVEYOR_SESSION_ID + CONVEYOR_MODE=pm"
|
|
1021
1311
|
);
|
|
1022
1312
|
process.exit(1);
|
|
1023
1313
|
}
|
|
1024
|
-
if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack") {
|
|
1025
|
-
|
|
1314
|
+
if (CONVEYOR_MODE !== "task" && CONVEYOR_MODE !== "pm" && CONVEYOR_MODE !== "code-review" && CONVEYOR_MODE !== "adhoc" && CONVEYOR_MODE !== "pack" && CONVEYOR_MODE !== "shell") {
|
|
1315
|
+
logger5.error("Invalid CONVEYOR_MODE", {
|
|
1026
1316
|
mode: CONVEYOR_MODE,
|
|
1027
|
-
expected: ["task", "pm", "code-review", "adhoc", "pack"]
|
|
1317
|
+
expected: ["task", "pm", "code-review", "adhoc", "pack", "shell"]
|
|
1028
1318
|
});
|
|
1029
1319
|
process.exit(1);
|
|
1030
1320
|
}
|
|
1031
|
-
|
|
1321
|
+
if (CONVEYOR_MODE === "shell" || CONVEYOR_MODE === "adhoc") {
|
|
1322
|
+
const spawnedSessionId = process.env.CONVEYOR_SESSION_ID;
|
|
1323
|
+
if (!spawnedSessionId) {
|
|
1324
|
+
logger5.error("Spawned child requires CONVEYOR_SESSION_ID", { mode: CONVEYOR_MODE });
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
exitContext.sessionId = spawnedSessionId;
|
|
1328
|
+
exitContext.runnerMode = CONVEYOR_MODE;
|
|
1329
|
+
const childRunner = createSpawnedChildRunner({
|
|
1330
|
+
mode: CONVEYOR_MODE,
|
|
1331
|
+
apiUrl: conveyorApiUrl ?? "",
|
|
1332
|
+
taskToken: CONVEYOR_TASK_TOKEN,
|
|
1333
|
+
sessionId: spawnedSessionId,
|
|
1334
|
+
workspaceDir: CONVEYOR_WORKSPACE,
|
|
1335
|
+
projectId: process.env.CONVEYOR_PROJECT_ID ?? ""
|
|
1336
|
+
});
|
|
1337
|
+
logger5.info("Starting spawned session child", {
|
|
1338
|
+
mode: CONVEYOR_MODE,
|
|
1339
|
+
sessionId: spawnedSessionId
|
|
1340
|
+
});
|
|
1341
|
+
process.on("SIGTERM", () => childRunner.stop());
|
|
1342
|
+
process.on("SIGINT", () => childRunner.stop());
|
|
1343
|
+
await childRunner.run();
|
|
1344
|
+
const childError = childRunner.finalState === "error";
|
|
1345
|
+
logAgentExit(childError ? "error" : "clean", {
|
|
1346
|
+
exitCode: childError ? 1 : 0,
|
|
1347
|
+
finalState: childRunner.finalState ?? void 0
|
|
1348
|
+
});
|
|
1349
|
+
process.exit(childError ? 1 : 0);
|
|
1350
|
+
}
|
|
1351
|
+
logger5.info("Starting agent", { mode: CONVEYOR_MODE });
|
|
1032
1352
|
var lifecycleOverrides = process.env.CLAUDESPACE_NAME ? { idleTimeoutMs: 60 * 60 * 1e3 } : { gitFlushIntervalMs: 0 };
|
|
1033
1353
|
var runner = new SessionRunner(
|
|
1034
1354
|
{
|
|
@@ -1048,24 +1368,25 @@ var runner = new SessionRunner(
|
|
|
1048
1368
|
},
|
|
1049
1369
|
{
|
|
1050
1370
|
onStatusChange: (status) => {
|
|
1051
|
-
|
|
1371
|
+
logger5.info("Status changed", { status });
|
|
1052
1372
|
},
|
|
1053
1373
|
onEvent: (event) => {
|
|
1054
1374
|
const detail = event.message ?? event.content ?? event.summary ?? "";
|
|
1055
1375
|
if (detail) {
|
|
1056
|
-
|
|
1376
|
+
logger5.info(detail, { eventType: event.type });
|
|
1057
1377
|
}
|
|
1058
1378
|
}
|
|
1059
1379
|
}
|
|
1060
1380
|
);
|
|
1061
1381
|
var reviewChildren = new ReviewChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1382
|
+
var sessionChildren = new SessionChildSupervisor(runner.connection, CONVEYOR_WORKSPACE);
|
|
1062
1383
|
var shutdownSignal;
|
|
1063
1384
|
var shutdownAgent = (signal) => {
|
|
1064
|
-
|
|
1385
|
+
logger5.info(`Received ${signal}, flushing git and stopping agent`);
|
|
1065
1386
|
shutdownSignal = signal;
|
|
1066
1387
|
void (async () => {
|
|
1067
1388
|
try {
|
|
1068
|
-
await reviewChildren.stopAll();
|
|
1389
|
+
await Promise.all([reviewChildren.stopAll(), sessionChildren.stopAll()]);
|
|
1069
1390
|
} catch {
|
|
1070
1391
|
}
|
|
1071
1392
|
try {
|
|
@@ -1075,8 +1396,8 @@ var shutdownAgent = (signal) => {
|
|
|
1075
1396
|
}
|
|
1076
1397
|
})();
|
|
1077
1398
|
setTimeout(() => {
|
|
1078
|
-
|
|
1079
|
-
|
|
1399
|
+
logger5.warn(`Forcing exit after ${signal} timeout`);
|
|
1400
|
+
logger5.warn(
|
|
1080
1401
|
"agent_exit",
|
|
1081
1402
|
buildAgentExitLog({
|
|
1082
1403
|
reason: "force_timeout",
|
|
@@ -1114,7 +1435,7 @@ var stopStartCommandChild = () => {
|
|
|
1114
1435
|
}
|
|
1115
1436
|
};
|
|
1116
1437
|
var launchStartCommand = (command) => {
|
|
1117
|
-
|
|
1438
|
+
logger5.info("Running start command", { command });
|
|
1118
1439
|
runner.connection.sendEvent({ type: "start_command_started" });
|
|
1119
1440
|
const child = runStartCommand(command, CONVEYOR_WORKSPACE, (stream, data) => {
|
|
1120
1441
|
runner.connection.sendEvent({ type: "start_command_output", stream, data });
|
|
@@ -1124,7 +1445,7 @@ var launchStartCommand = (command) => {
|
|
|
1124
1445
|
child.on("exit", (code, signal) => {
|
|
1125
1446
|
if (startCommandChild === child) startCommandChild = null;
|
|
1126
1447
|
if (expectedStartCommandStops.has(child)) return;
|
|
1127
|
-
|
|
1448
|
+
logger5.info("Start command exited", { code, signal });
|
|
1128
1449
|
runner.connection.sendEvent({
|
|
1129
1450
|
type: "start_command_exited",
|
|
1130
1451
|
code,
|
|
@@ -1140,7 +1461,7 @@ var launchStartCommand = (command) => {
|
|
|
1140
1461
|
});
|
|
1141
1462
|
child.on("error", (err) => {
|
|
1142
1463
|
if (startCommandChild === child) startCommandChild = null;
|
|
1143
|
-
|
|
1464
|
+
logger5.error("Start command error", { error: err.message });
|
|
1144
1465
|
runner.connection.sendEvent({ type: "start_command_error", message: err.message });
|
|
1145
1466
|
});
|
|
1146
1467
|
};
|
|
@@ -1156,12 +1477,12 @@ void checkSessionTaskIdentity({
|
|
|
1156
1477
|
const ctx = await runner.connection.call("getTaskContext", { sessionId });
|
|
1157
1478
|
return ctx.id;
|
|
1158
1479
|
},
|
|
1159
|
-
logger:
|
|
1480
|
+
logger: logger5
|
|
1160
1481
|
});
|
|
1161
1482
|
if (CONVEYOR_MODE === "task") {
|
|
1162
1483
|
runner.connection.onSpawnReview((data) => {
|
|
1163
1484
|
void reviewChildren.spawn(data).catch((err) => {
|
|
1164
|
-
|
|
1485
|
+
logger5.error("Review child spawn threw", {
|
|
1165
1486
|
error: err instanceof Error ? err.message : String(err)
|
|
1166
1487
|
});
|
|
1167
1488
|
runner.connection.reportReviewSpawnFailure(
|
|
@@ -1171,6 +1492,19 @@ if (CONVEYOR_MODE === "task") {
|
|
|
1171
1492
|
});
|
|
1172
1493
|
});
|
|
1173
1494
|
}
|
|
1495
|
+
if (CONVEYOR_MODE === "task" || CONVEYOR_MODE === "pack") {
|
|
1496
|
+
runner.connection.onSpawnTui((data) => {
|
|
1497
|
+
void sessionChildren.spawn(data).catch((err) => {
|
|
1498
|
+
logger5.error("Session child spawn threw", {
|
|
1499
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1500
|
+
});
|
|
1501
|
+
runner.connection.reportSessionSpawnFailure(
|
|
1502
|
+
data.sessionId,
|
|
1503
|
+
err instanceof Error ? err.message : String(err)
|
|
1504
|
+
);
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1174
1508
|
var conveyorConfig = loadConveyorConfig();
|
|
1175
1509
|
runner.connection.onRunStartCommand(() => {
|
|
1176
1510
|
if (!conveyorConfig?.startCommand) return;
|
|
@@ -1201,15 +1535,15 @@ if (conveyorConfig) {
|
|
|
1201
1535
|
startLazy: false
|
|
1202
1536
|
});
|
|
1203
1537
|
if (conveyorConfig.setupCommand) {
|
|
1204
|
-
|
|
1538
|
+
logger5.info("Running setup command (background)", {
|
|
1205
1539
|
command: conveyorConfig.setupCommand
|
|
1206
1540
|
});
|
|
1207
1541
|
try {
|
|
1208
1542
|
await runSetupCommand(conveyorConfig.setupCommand, CONVEYOR_WORKSPACE, logSetupOutput);
|
|
1209
|
-
|
|
1543
|
+
logger5.info("Setup command completed");
|
|
1210
1544
|
} catch (error) {
|
|
1211
1545
|
const msg = error instanceof Error ? error.message : "Setup command failed";
|
|
1212
|
-
|
|
1546
|
+
logger5.error("Setup command failed", { error: msg });
|
|
1213
1547
|
runner.connection.sendEvent({ type: "setup_error", message: msg });
|
|
1214
1548
|
}
|
|
1215
1549
|
}
|
|
@@ -1238,7 +1572,7 @@ runner.run().then(() => {
|
|
|
1238
1572
|
process.exit(errored ? 1 : 0);
|
|
1239
1573
|
}).catch((error) => {
|
|
1240
1574
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1241
|
-
|
|
1575
|
+
logger5.error("Agent runner failed", { error: msg });
|
|
1242
1576
|
logAgentExit("error", { exitCode: 1, finalState: runner.finalState ?? void 0 });
|
|
1243
1577
|
process.exit(1);
|
|
1244
1578
|
});
|