chatroom-cli 1.43.2 → 1.43.3
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/index.js +382 -150
- package/dist/index.js.map +12 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69493,6 +69493,7 @@ var init_errorCodes = __esm(() => {
|
|
|
69493
69493
|
COMMAND_NOT_RUNNING: "COMMAND_NOT_RUNNING",
|
|
69494
69494
|
TOO_MANY_COMMANDS: "TOO_MANY_COMMANDS",
|
|
69495
69495
|
INVALID_STATE_TRANSITION: "INVALID_STATE_TRANSITION",
|
|
69496
|
+
INVALID_RUN_STATE_TRANSITION: "INVALID_RUN_STATE_TRANSITION",
|
|
69496
69497
|
OUTPUT_CHUNK_TOO_LARGE: "OUTPUT_CHUNK_TOO_LARGE",
|
|
69497
69498
|
NOT_FOUND: "NOT_FOUND",
|
|
69498
69499
|
INVALID_BOT_TOKEN: "INVALID_BOT_TOKEN",
|
|
@@ -69573,6 +69574,7 @@ var init_errorCodes = __esm(() => {
|
|
|
69573
69574
|
BACKEND_ERROR_CODES.INVALID_STDIN_FORMAT,
|
|
69574
69575
|
BACKEND_ERROR_CODES.INVALID_BOT_TOKEN,
|
|
69575
69576
|
BACKEND_ERROR_CODES.INVALID_STATE_TRANSITION,
|
|
69577
|
+
BACKEND_ERROR_CODES.INVALID_RUN_STATE_TRANSITION,
|
|
69576
69578
|
BACKEND_ERROR_CODES.TASK_INVALID_TRANSITION,
|
|
69577
69579
|
BACKEND_ERROR_CODES.TASK_MISSING_REQUIRED_FIELD,
|
|
69578
69580
|
BACKEND_ERROR_CODES.TASK_VALIDATION_FAILED,
|
|
@@ -75936,31 +75938,248 @@ var init_file_tree_subscription = __esm(() => {
|
|
|
75936
75938
|
init_convex_error();
|
|
75937
75939
|
});
|
|
75938
75940
|
|
|
75939
|
-
// src/commands/machine/daemon-start/handlers/
|
|
75940
|
-
import {
|
|
75941
|
-
import {
|
|
75942
|
-
|
|
75943
|
-
|
|
75944
|
-
|
|
75945
|
-
|
|
75946
|
-
|
|
75941
|
+
// src/commands/machine/daemon-start/handlers/orphan-tracker.ts
|
|
75942
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
75943
|
+
import {
|
|
75944
|
+
appendFileSync,
|
|
75945
|
+
existsSync as existsSync3,
|
|
75946
|
+
mkdirSync as mkdirSync3,
|
|
75947
|
+
readFileSync as readFileSync5,
|
|
75948
|
+
renameSync,
|
|
75949
|
+
unlinkSync as unlinkSync2,
|
|
75950
|
+
writeFileSync as writeFileSync3
|
|
75951
|
+
} from "node:fs";
|
|
75952
|
+
import { homedir as homedir6 } from "node:os";
|
|
75953
|
+
import { join as join15 } from "node:path";
|
|
75954
|
+
function getUrlHash2() {
|
|
75955
|
+
const url2 = getConvexUrl();
|
|
75956
|
+
return createHash6("sha256").update(url2).digest("hex").substring(0, 8);
|
|
75957
|
+
}
|
|
75958
|
+
function getChildPidsFilePath() {
|
|
75959
|
+
const dir = join15(homedir6(), ".chatroom");
|
|
75960
|
+
return join15(dir, `daemon-children-${getUrlHash2()}.pids`);
|
|
75961
|
+
}
|
|
75962
|
+
function ensureChatroomDir3() {
|
|
75963
|
+
const dir = join15(homedir6(), ".chatroom");
|
|
75964
|
+
if (!existsSync3(dir)) {
|
|
75965
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
75947
75966
|
}
|
|
75948
75967
|
}
|
|
75949
|
-
function
|
|
75950
|
-
|
|
75968
|
+
function readPids() {
|
|
75969
|
+
const filePath = getChildPidsFilePath();
|
|
75970
|
+
if (!existsSync3(filePath))
|
|
75971
|
+
return [];
|
|
75972
|
+
try {
|
|
75973
|
+
return readFileSync5(filePath, "utf-8").split(`
|
|
75974
|
+
`).map((line) => parseInt(line.trim(), 10)).filter((n) => Number.isFinite(n) && n > 0);
|
|
75975
|
+
} catch {
|
|
75976
|
+
return [];
|
|
75977
|
+
}
|
|
75951
75978
|
}
|
|
75952
|
-
|
|
75979
|
+
function trackChildPid(pid) {
|
|
75953
75980
|
try {
|
|
75954
|
-
|
|
75955
|
-
|
|
75956
|
-
|
|
75957
|
-
|
|
75958
|
-
|
|
75981
|
+
ensureChatroomDir3();
|
|
75982
|
+
appendFileSync(getChildPidsFilePath(), `${pid}
|
|
75983
|
+
`, "utf-8");
|
|
75984
|
+
} catch (err) {
|
|
75985
|
+
console.warn(`[orphan-tracker] Failed to track child PID ${pid}: ${err}`);
|
|
75986
|
+
}
|
|
75987
|
+
}
|
|
75988
|
+
function untrackChildPid(pid) {
|
|
75989
|
+
try {
|
|
75990
|
+
const filePath = getChildPidsFilePath();
|
|
75991
|
+
if (!existsSync3(filePath))
|
|
75992
|
+
return;
|
|
75993
|
+
const remaining = readFileSync5(filePath, "utf-8").split(`
|
|
75994
|
+
`).filter((line) => {
|
|
75995
|
+
const n = parseInt(line.trim(), 10);
|
|
75996
|
+
return Number.isFinite(n) && n > 0 && n !== pid;
|
|
75959
75997
|
});
|
|
75998
|
+
const tmpPath = `${filePath}.tmp`;
|
|
75999
|
+
writeFileSync3(tmpPath, remaining.join(`
|
|
76000
|
+
`) + (remaining.length > 0 ? `
|
|
76001
|
+
` : ""), "utf-8");
|
|
76002
|
+
renameSync(tmpPath, filePath);
|
|
75960
76003
|
} catch (err) {
|
|
75961
|
-
console.warn(`[
|
|
76004
|
+
console.warn(`[orphan-tracker] Failed to untrack child PID ${pid}: ${err}`);
|
|
75962
76005
|
}
|
|
75963
76006
|
}
|
|
76007
|
+
function clearTrackedPids() {
|
|
76008
|
+
try {
|
|
76009
|
+
const filePath = getChildPidsFilePath();
|
|
76010
|
+
if (existsSync3(filePath)) {
|
|
76011
|
+
unlinkSync2(filePath);
|
|
76012
|
+
}
|
|
76013
|
+
} catch {}
|
|
76014
|
+
}
|
|
76015
|
+
async function reapOrphanedProcessGroups() {
|
|
76016
|
+
const pids = readPids();
|
|
76017
|
+
let reaped = 0;
|
|
76018
|
+
const checked = pids.length;
|
|
76019
|
+
for (const pgid of pids) {
|
|
76020
|
+
if (process.platform === "win32")
|
|
76021
|
+
continue;
|
|
76022
|
+
try {
|
|
76023
|
+
process.kill(-pgid, 0);
|
|
76024
|
+
} catch {
|
|
76025
|
+
continue;
|
|
76026
|
+
}
|
|
76027
|
+
try {
|
|
76028
|
+
process.kill(-pgid, "SIGTERM");
|
|
76029
|
+
} catch {
|
|
76030
|
+
continue;
|
|
76031
|
+
}
|
|
76032
|
+
const deadline = Date.now() + 500;
|
|
76033
|
+
let alive = true;
|
|
76034
|
+
while (Date.now() < deadline) {
|
|
76035
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
76036
|
+
try {
|
|
76037
|
+
process.kill(-pgid, 0);
|
|
76038
|
+
} catch {
|
|
76039
|
+
alive = false;
|
|
76040
|
+
break;
|
|
76041
|
+
}
|
|
76042
|
+
}
|
|
76043
|
+
if (alive) {
|
|
76044
|
+
try {
|
|
76045
|
+
process.kill(-pgid, "SIGKILL");
|
|
76046
|
+
} catch {}
|
|
76047
|
+
}
|
|
76048
|
+
console.log(`[orphan-tracker] Reaped orphan process group ${pgid}`);
|
|
76049
|
+
reaped++;
|
|
76050
|
+
}
|
|
76051
|
+
clearTrackedPids();
|
|
76052
|
+
return { reaped, checked };
|
|
76053
|
+
}
|
|
76054
|
+
var CHATROOM_DIR5;
|
|
76055
|
+
var init_orphan_tracker = __esm(() => {
|
|
76056
|
+
init_client2();
|
|
76057
|
+
CHATROOM_DIR5 = join15(homedir6(), ".chatroom");
|
|
76058
|
+
});
|
|
76059
|
+
|
|
76060
|
+
// src/commands/machine/daemon-start/handlers/process/state.ts
|
|
76061
|
+
function deriveTerminalStatus(code2, signal, terminationIntent) {
|
|
76062
|
+
if (terminationIntent !== null)
|
|
76063
|
+
return terminationIntent;
|
|
76064
|
+
if (code2 === 0)
|
|
76065
|
+
return "completed";
|
|
76066
|
+
if (signal !== null)
|
|
76067
|
+
return "stopped";
|
|
76068
|
+
return "failed";
|
|
76069
|
+
}
|
|
76070
|
+
var TERMINAL_STATES, PENDING_STOP_TTL_MS = 60000, SIGTERM_GRACE_PERIOD_MS = 5000, SOFT_TIMEOUT_MS, OUTPUT_FLUSH_INTERVAL_MS = 3000, MAX_BUFFER_SIZE;
|
|
76071
|
+
var init_state2 = __esm(() => {
|
|
76072
|
+
TERMINAL_STATES = new Set(["completed", "failed", "stopped", "killed"]);
|
|
76073
|
+
SOFT_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
|
76074
|
+
MAX_BUFFER_SIZE = 100 * 1024;
|
|
76075
|
+
});
|
|
76076
|
+
|
|
76077
|
+
// src/commands/machine/daemon-start/handlers/process/manager.ts
|
|
76078
|
+
class ProcessManager {
|
|
76079
|
+
runningProcesses = new Map;
|
|
76080
|
+
runningProcessesByCommand = new Map;
|
|
76081
|
+
pendingStops = new Map;
|
|
76082
|
+
has(runId) {
|
|
76083
|
+
return this.runningProcesses.has(runId);
|
|
76084
|
+
}
|
|
76085
|
+
get(runId) {
|
|
76086
|
+
return this.runningProcesses.get(runId);
|
|
76087
|
+
}
|
|
76088
|
+
getByCommand(commandKey) {
|
|
76089
|
+
const runId = this.runningProcessesByCommand.get(commandKey);
|
|
76090
|
+
if (runId === undefined)
|
|
76091
|
+
return;
|
|
76092
|
+
return this.runningProcesses.get(runId);
|
|
76093
|
+
}
|
|
76094
|
+
getAll() {
|
|
76095
|
+
return [...this.runningProcesses.entries()];
|
|
76096
|
+
}
|
|
76097
|
+
get size() {
|
|
76098
|
+
return this.runningProcesses.size;
|
|
76099
|
+
}
|
|
76100
|
+
register(runId, commandKey, process2) {
|
|
76101
|
+
this.runningProcesses.set(runId, process2);
|
|
76102
|
+
this.runningProcessesByCommand.set(commandKey, runId);
|
|
76103
|
+
}
|
|
76104
|
+
unregister(runId, commandKey) {
|
|
76105
|
+
this.runningProcesses.delete(runId);
|
|
76106
|
+
if (this.runningProcessesByCommand.get(commandKey) === runId) {
|
|
76107
|
+
this.runningProcessesByCommand.delete(commandKey);
|
|
76108
|
+
}
|
|
76109
|
+
}
|
|
76110
|
+
markPendingStop(runId) {
|
|
76111
|
+
this.pendingStops.set(runId, Date.now());
|
|
76112
|
+
}
|
|
76113
|
+
hasPendingStop(runId) {
|
|
76114
|
+
return this.pendingStops.has(runId);
|
|
76115
|
+
}
|
|
76116
|
+
consumePendingStop(runId) {
|
|
76117
|
+
const has5 = this.pendingStops.has(runId);
|
|
76118
|
+
if (has5)
|
|
76119
|
+
this.pendingStops.delete(runId);
|
|
76120
|
+
return has5;
|
|
76121
|
+
}
|
|
76122
|
+
evictStalePendingStops() {
|
|
76123
|
+
const evictBefore = Date.now() - PENDING_STOP_TTL_MS;
|
|
76124
|
+
for (const [runId, ts] of this.pendingStops) {
|
|
76125
|
+
if (ts < evictBefore)
|
|
76126
|
+
this.pendingStops.delete(runId);
|
|
76127
|
+
}
|
|
76128
|
+
}
|
|
76129
|
+
clear() {
|
|
76130
|
+
this.runningProcesses.clear();
|
|
76131
|
+
this.runningProcessesByCommand.clear();
|
|
76132
|
+
this.pendingStops.clear();
|
|
76133
|
+
}
|
|
76134
|
+
waitForExit(runId, ms) {
|
|
76135
|
+
return new Promise((resolve5) => {
|
|
76136
|
+
const interval = 100;
|
|
76137
|
+
let elapsed3 = 0;
|
|
76138
|
+
const timer = setInterval(() => {
|
|
76139
|
+
if (!this.runningProcesses.has(runId)) {
|
|
76140
|
+
clearInterval(timer);
|
|
76141
|
+
resolve5(true);
|
|
76142
|
+
return;
|
|
76143
|
+
}
|
|
76144
|
+
elapsed3 += interval;
|
|
76145
|
+
if (elapsed3 >= ms) {
|
|
76146
|
+
clearInterval(timer);
|
|
76147
|
+
resolve5(false);
|
|
76148
|
+
}
|
|
76149
|
+
}, interval);
|
|
76150
|
+
});
|
|
76151
|
+
}
|
|
76152
|
+
}
|
|
76153
|
+
var processManager;
|
|
76154
|
+
var init_manager = __esm(() => {
|
|
76155
|
+
init_state2();
|
|
76156
|
+
processManager = new ProcessManager;
|
|
76157
|
+
});
|
|
76158
|
+
|
|
76159
|
+
// src/commands/machine/daemon-start/handlers/process/killer.ts
|
|
76160
|
+
function killProcess(child, signal) {
|
|
76161
|
+
if (child.pid == null)
|
|
76162
|
+
return;
|
|
76163
|
+
try {
|
|
76164
|
+
process.kill(-child.pid, signal);
|
|
76165
|
+
} catch {}
|
|
76166
|
+
}
|
|
76167
|
+
async function killTrackedProcess(tracked) {
|
|
76168
|
+
killProcess(tracked.process, "SIGTERM");
|
|
76169
|
+
const exited = await processManager.waitForExit(tracked.runId, SIGTERM_GRACE_PERIOD_MS);
|
|
76170
|
+
if (!exited) {
|
|
76171
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing process: ${tracked.runId}`);
|
|
76172
|
+
killProcess(tracked.process, "SIGKILL");
|
|
76173
|
+
await processManager.waitForExit(tracked.runId, 1000);
|
|
76174
|
+
}
|
|
76175
|
+
}
|
|
76176
|
+
var init_killer = __esm(() => {
|
|
76177
|
+
init_state2();
|
|
76178
|
+
init_manager();
|
|
76179
|
+
});
|
|
76180
|
+
|
|
76181
|
+
// src/commands/machine/daemon-start/handlers/process/spawner.ts
|
|
76182
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
75964
76183
|
async function flushOutput(ctx, tracked) {
|
|
75965
76184
|
if (tracked.outputBuffer.length === 0)
|
|
75966
76185
|
return;
|
|
@@ -75986,91 +76205,14 @@ function appendToBuffer(ctx, tracked, data) {
|
|
|
75986
76205
|
flushOutput(ctx, tracked).catch(() => {});
|
|
75987
76206
|
}
|
|
75988
76207
|
}
|
|
75989
|
-
function
|
|
75990
|
-
try {
|
|
75991
|
-
child.kill(signal);
|
|
75992
|
-
} catch {}
|
|
75993
|
-
}
|
|
75994
|
-
function waitForExit(runIdStr, ms) {
|
|
75995
|
-
return new Promise((resolve5) => {
|
|
75996
|
-
const interval = 100;
|
|
75997
|
-
let elapsed3 = 0;
|
|
75998
|
-
const timer = setInterval(() => {
|
|
75999
|
-
if (!runningProcesses.has(runIdStr)) {
|
|
76000
|
-
clearInterval(timer);
|
|
76001
|
-
resolve5(true);
|
|
76002
|
-
return;
|
|
76003
|
-
}
|
|
76004
|
-
elapsed3 += interval;
|
|
76005
|
-
if (elapsed3 >= ms) {
|
|
76006
|
-
clearInterval(timer);
|
|
76007
|
-
resolve5(false);
|
|
76008
|
-
}
|
|
76009
|
-
}, interval);
|
|
76010
|
-
});
|
|
76011
|
-
}
|
|
76012
|
-
async function killTrackedProcess(tracked) {
|
|
76013
|
-
killProcess(tracked.process, "SIGTERM");
|
|
76014
|
-
const exited = await waitForExit(tracked.runId, SIGTERM_GRACE_PERIOD_MS);
|
|
76015
|
-
if (!exited) {
|
|
76016
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing process: ${tracked.runId}`);
|
|
76017
|
-
killProcess(tracked.process, "SIGKILL");
|
|
76018
|
-
await waitForExit(tracked.runId, 1000);
|
|
76019
|
-
}
|
|
76020
|
-
}
|
|
76021
|
-
async function onCommandRun(ctx, event) {
|
|
76208
|
+
function spawnCommandProcess(ctx, event, commandKey) {
|
|
76022
76209
|
const { workingDir, commandName, script, runId } = event;
|
|
76023
76210
|
const runIdStr = runId.toString();
|
|
76024
|
-
const commandKey = buildCommandKey(ctx.machineId, workingDir, commandName);
|
|
76025
|
-
if (runningProcesses.has(runIdStr)) {
|
|
76026
|
-
console.log(`[${formatTimestamp()}] ⚠️ Command already running: ${runIdStr}`);
|
|
76027
|
-
return;
|
|
76028
|
-
}
|
|
76029
|
-
if (pendingStops.has(runIdStr)) {
|
|
76030
|
-
pendingStops.delete(runIdStr);
|
|
76031
|
-
console.log(`[${formatTimestamp()}] ⏭️ Skipping command run due to pending stop: ${commandName} (${runIdStr})`);
|
|
76032
|
-
try {
|
|
76033
|
-
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76034
|
-
sessionId: ctx.sessionId,
|
|
76035
|
-
machineId: ctx.machineId,
|
|
76036
|
-
runId,
|
|
76037
|
-
status: "stopped"
|
|
76038
|
-
});
|
|
76039
|
-
} catch (err) {
|
|
76040
|
-
console.warn(`[${formatTimestamp()}] ⚠️ Failed to update status to stopped for pending-stop skip: ${getErrorMessage(err)}`);
|
|
76041
|
-
}
|
|
76042
|
-
return;
|
|
76043
|
-
}
|
|
76044
|
-
const priorRunId = runningProcessesByCommand.get(commandKey);
|
|
76045
|
-
if (priorRunId) {
|
|
76046
|
-
const priorTracked = runningProcesses.get(priorRunId);
|
|
76047
|
-
if (priorTracked) {
|
|
76048
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDD04 Replacing prior run ${priorRunId} with ${runIdStr} for ${commandName}`);
|
|
76049
|
-
clearInterval(priorTracked.flushTimer);
|
|
76050
|
-
if (priorTracked.softTimeoutTimer)
|
|
76051
|
-
clearTimeout(priorTracked.softTimeoutTimer);
|
|
76052
|
-
await killTrackedProcess(priorTracked);
|
|
76053
|
-
runningProcesses.delete(priorRunId);
|
|
76054
|
-
runningProcessesByCommand.delete(commandKey);
|
|
76055
|
-
}
|
|
76056
|
-
}
|
|
76057
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDE80 Running command: ${commandName} → ${script}`);
|
|
76058
|
-
if (!workingDir.startsWith("/")) {
|
|
76059
|
-
console.error(`[${formatTimestamp()}] ❌ Rejected command: workingDir is not absolute: ${workingDir}`);
|
|
76060
|
-
await reportRunFailed(ctx, runId, "Working directory is not an absolute path");
|
|
76061
|
-
return;
|
|
76062
|
-
}
|
|
76063
|
-
try {
|
|
76064
|
-
await access3(workingDir);
|
|
76065
|
-
} catch {
|
|
76066
|
-
console.error(`[${formatTimestamp()}] ❌ Rejected command: workingDir not found: ${workingDir}`);
|
|
76067
|
-
await reportRunFailed(ctx, runId, "Working directory not found");
|
|
76068
|
-
return;
|
|
76069
|
-
}
|
|
76070
76211
|
const child = spawn4("sh", ["-c", script], {
|
|
76071
76212
|
cwd: workingDir,
|
|
76072
76213
|
env: { ...process.env },
|
|
76073
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
76214
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
76215
|
+
detached: true
|
|
76074
76216
|
});
|
|
76075
76217
|
const tracked = {
|
|
76076
76218
|
process: child,
|
|
@@ -76081,16 +76223,20 @@ async function onCommandRun(ctx, event) {
|
|
|
76081
76223
|
flushTimer: setInterval(() => {
|
|
76082
76224
|
flushOutput(ctx, tracked).catch(() => {});
|
|
76083
76225
|
}, OUTPUT_FLUSH_INTERVAL_MS),
|
|
76084
|
-
softTimeoutTimer: null
|
|
76226
|
+
softTimeoutTimer: null,
|
|
76227
|
+
terminationIntent: null
|
|
76085
76228
|
};
|
|
76086
76229
|
tracked.flushTimer.unref?.();
|
|
76087
|
-
|
|
76088
|
-
|
|
76230
|
+
processManager.register(runIdStr, commandKey, tracked);
|
|
76231
|
+
if (child.pid != null) {
|
|
76232
|
+
trackChildPid(child.pid);
|
|
76233
|
+
}
|
|
76089
76234
|
const softTimeoutTimer = setTimeout(async () => {
|
|
76090
76235
|
console.log(`[${formatTimestamp()}] ⏰ Command soft timeout (24h): ${commandName} (runId: ${runIdStr})`);
|
|
76091
|
-
const currentTracked =
|
|
76236
|
+
const currentTracked = processManager.get(runIdStr);
|
|
76092
76237
|
if (!currentTracked)
|
|
76093
76238
|
return;
|
|
76239
|
+
currentTracked.terminationIntent = "killed";
|
|
76094
76240
|
try {
|
|
76095
76241
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76096
76242
|
sessionId: ctx.sessionId,
|
|
@@ -76104,7 +76250,7 @@ async function onCommandRun(ctx, event) {
|
|
|
76104
76250
|
}
|
|
76105
76251
|
killProcess(child, "SIGTERM");
|
|
76106
76252
|
setTimeout(() => {
|
|
76107
|
-
if (!
|
|
76253
|
+
if (!processManager.has(runIdStr))
|
|
76108
76254
|
return;
|
|
76109
76255
|
console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing timed-out process: ${runIdStr}`);
|
|
76110
76256
|
killProcess(child, "SIGKILL");
|
|
@@ -76112,17 +76258,6 @@ async function onCommandRun(ctx, event) {
|
|
|
76112
76258
|
}, SOFT_TIMEOUT_MS);
|
|
76113
76259
|
softTimeoutTimer.unref?.();
|
|
76114
76260
|
tracked.softTimeoutTimer = softTimeoutTimer;
|
|
76115
|
-
try {
|
|
76116
|
-
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76117
|
-
sessionId: ctx.sessionId,
|
|
76118
|
-
machineId: ctx.machineId,
|
|
76119
|
-
runId,
|
|
76120
|
-
status: "running",
|
|
76121
|
-
pid: child.pid
|
|
76122
|
-
});
|
|
76123
|
-
} catch (err) {
|
|
76124
|
-
console.warn(`[${formatTimestamp()}] ⚠️ Failed to update run status to running: ${getErrorMessage(err)}`);
|
|
76125
|
-
}
|
|
76126
76261
|
child.stdout?.on("data", (data) => {
|
|
76127
76262
|
appendToBuffer(ctx, tracked, data.toString());
|
|
76128
76263
|
});
|
|
@@ -76132,14 +76267,14 @@ async function onCommandRun(ctx, event) {
|
|
|
76132
76267
|
child.on("exit", async (code2, signal) => {
|
|
76133
76268
|
console.log(`[${formatTimestamp()}] \uD83C\uDFC1 Command exited: ${commandName} (code=${code2}, signal=${signal})`);
|
|
76134
76269
|
await flushOutput(ctx, tracked).catch(() => {});
|
|
76270
|
+
if (tracked.process.pid != null) {
|
|
76271
|
+
untrackChildPid(tracked.process.pid);
|
|
76272
|
+
}
|
|
76135
76273
|
clearInterval(tracked.flushTimer);
|
|
76136
76274
|
if (tracked.softTimeoutTimer)
|
|
76137
76275
|
clearTimeout(tracked.softTimeoutTimer);
|
|
76138
|
-
|
|
76139
|
-
|
|
76140
|
-
runningProcessesByCommand.delete(commandKey);
|
|
76141
|
-
}
|
|
76142
|
-
const status3 = code2 === 0 ? "completed" : signal ? "stopped" : "failed";
|
|
76276
|
+
processManager.unregister(runIdStr, commandKey);
|
|
76277
|
+
const status3 = deriveTerminalStatus(code2, signal, tracked.terminationIntent);
|
|
76143
76278
|
try {
|
|
76144
76279
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76145
76280
|
sessionId: ctx.sessionId,
|
|
@@ -76154,13 +76289,13 @@ async function onCommandRun(ctx, event) {
|
|
|
76154
76289
|
});
|
|
76155
76290
|
child.on("error", async (err) => {
|
|
76156
76291
|
console.error(`[${formatTimestamp()}] ❌ Command spawn failed: ${commandName}: ${err.message}`);
|
|
76292
|
+
if (tracked.process.pid != null) {
|
|
76293
|
+
untrackChildPid(tracked.process.pid);
|
|
76294
|
+
}
|
|
76157
76295
|
clearInterval(tracked.flushTimer);
|
|
76158
76296
|
if (tracked.softTimeoutTimer)
|
|
76159
76297
|
clearTimeout(tracked.softTimeoutTimer);
|
|
76160
|
-
|
|
76161
|
-
if (runningProcessesByCommand.get(commandKey) === runIdStr) {
|
|
76162
|
-
runningProcessesByCommand.delete(commandKey);
|
|
76163
|
-
}
|
|
76298
|
+
processManager.unregister(runIdStr, commandKey);
|
|
76164
76299
|
try {
|
|
76165
76300
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76166
76301
|
sessionId: ctx.sessionId,
|
|
@@ -76172,13 +76307,111 @@ async function onCommandRun(ctx, event) {
|
|
|
76172
76307
|
console.warn(`[${formatTimestamp()}] ⚠️ Failed to update run status on error: ${getErrorMessage(updateErr)}`);
|
|
76173
76308
|
}
|
|
76174
76309
|
});
|
|
76310
|
+
return tracked;
|
|
76311
|
+
}
|
|
76312
|
+
var init_spawner = __esm(() => {
|
|
76313
|
+
init_api3();
|
|
76314
|
+
init_convex_error();
|
|
76315
|
+
init_orphan_tracker();
|
|
76316
|
+
init_state2();
|
|
76317
|
+
init_manager();
|
|
76318
|
+
init_killer();
|
|
76319
|
+
});
|
|
76320
|
+
|
|
76321
|
+
// src/commands/machine/daemon-start/handlers/command-runner.ts
|
|
76322
|
+
import { access as access3 } from "node:fs/promises";
|
|
76323
|
+
function buildCommandKey(machineId, workingDir, commandName) {
|
|
76324
|
+
return `${machineId}|${workingDir}|${commandName}`;
|
|
76325
|
+
}
|
|
76326
|
+
async function reportRunFailed(ctx, runId, reason) {
|
|
76327
|
+
try {
|
|
76328
|
+
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76329
|
+
sessionId: ctx.sessionId,
|
|
76330
|
+
machineId: ctx.machineId,
|
|
76331
|
+
runId,
|
|
76332
|
+
status: "failed"
|
|
76333
|
+
});
|
|
76334
|
+
} catch (err) {
|
|
76335
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Failed to report run failure (${reason}): ${getErrorMessage(err)}`);
|
|
76336
|
+
}
|
|
76337
|
+
}
|
|
76338
|
+
async function onCommandRun(ctx, event) {
|
|
76339
|
+
const { workingDir, commandName, script, runId } = event;
|
|
76340
|
+
const runIdStr = runId.toString();
|
|
76341
|
+
const commandKey = buildCommandKey(ctx.machineId, workingDir, commandName);
|
|
76342
|
+
if (processManager.has(runIdStr)) {
|
|
76343
|
+
console.log(`[${formatTimestamp()}] ⚠️ Command already running: ${runIdStr}`);
|
|
76344
|
+
return;
|
|
76345
|
+
}
|
|
76346
|
+
if (processManager.consumePendingStop(runIdStr)) {
|
|
76347
|
+
console.log(`[${formatTimestamp()}] ⏭️ Skipping command run due to pending stop: ${commandName} (${runIdStr})`);
|
|
76348
|
+
try {
|
|
76349
|
+
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76350
|
+
sessionId: ctx.sessionId,
|
|
76351
|
+
machineId: ctx.machineId,
|
|
76352
|
+
runId,
|
|
76353
|
+
status: "stopped"
|
|
76354
|
+
});
|
|
76355
|
+
} catch (err) {
|
|
76356
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Failed to update status to stopped for pending-stop skip: ${getErrorMessage(err)}`);
|
|
76357
|
+
}
|
|
76358
|
+
return;
|
|
76359
|
+
}
|
|
76360
|
+
try {
|
|
76361
|
+
const currentRun = await ctx.deps.backend.query(api.commands.getRunStatus, {
|
|
76362
|
+
sessionId: ctx.sessionId,
|
|
76363
|
+
machineId: ctx.machineId,
|
|
76364
|
+
runId
|
|
76365
|
+
});
|
|
76366
|
+
if (currentRun && TERMINAL_STATES.has(currentRun.status)) {
|
|
76367
|
+
console.log(`[${formatTimestamp()}] ⏭️ Skipping command run — row already ${currentRun.status}: ${commandName} (${runIdStr})`);
|
|
76368
|
+
return;
|
|
76369
|
+
}
|
|
76370
|
+
} catch (err) {
|
|
76371
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Failed to check run status before spawn: ${getErrorMessage(err)}`);
|
|
76372
|
+
}
|
|
76373
|
+
const priorTracked = processManager.getByCommand(commandKey);
|
|
76374
|
+
if (priorTracked) {
|
|
76375
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDD04 Replacing prior run ${priorTracked.runId} with ${runIdStr} for ${commandName}`);
|
|
76376
|
+
priorTracked.terminationIntent = "killed";
|
|
76377
|
+
clearInterval(priorTracked.flushTimer);
|
|
76378
|
+
if (priorTracked.softTimeoutTimer)
|
|
76379
|
+
clearTimeout(priorTracked.softTimeoutTimer);
|
|
76380
|
+
await killTrackedProcess(priorTracked);
|
|
76381
|
+
processManager.unregister(priorTracked.runId, commandKey);
|
|
76382
|
+
}
|
|
76383
|
+
console.log(`[${formatTimestamp()}] \uD83D\uDE80 Running command: ${commandName} → ${script}`);
|
|
76384
|
+
if (!workingDir.startsWith("/")) {
|
|
76385
|
+
console.error(`[${formatTimestamp()}] ❌ Rejected command: workingDir is not absolute: ${workingDir}`);
|
|
76386
|
+
await reportRunFailed(ctx, runId, "Working directory is not an absolute path");
|
|
76387
|
+
return;
|
|
76388
|
+
}
|
|
76389
|
+
try {
|
|
76390
|
+
await access3(workingDir);
|
|
76391
|
+
} catch {
|
|
76392
|
+
console.error(`[${formatTimestamp()}] ❌ Rejected command: workingDir not found: ${workingDir}`);
|
|
76393
|
+
await reportRunFailed(ctx, runId, "Working directory not found");
|
|
76394
|
+
return;
|
|
76395
|
+
}
|
|
76396
|
+
const tracked = spawnCommandProcess(ctx, event, commandKey);
|
|
76397
|
+
try {
|
|
76398
|
+
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76399
|
+
sessionId: ctx.sessionId,
|
|
76400
|
+
machineId: ctx.machineId,
|
|
76401
|
+
runId,
|
|
76402
|
+
status: "running",
|
|
76403
|
+
pid: tracked.process.pid
|
|
76404
|
+
});
|
|
76405
|
+
} catch (err) {
|
|
76406
|
+
console.warn(`[${formatTimestamp()}] ⚠️ Failed to update run status to running: ${getErrorMessage(err)}`);
|
|
76407
|
+
}
|
|
76175
76408
|
}
|
|
76176
76409
|
async function onCommandStop(ctx, event) {
|
|
76177
76410
|
const runIdStr = event.runId.toString();
|
|
76178
|
-
const tracked =
|
|
76411
|
+
const tracked = processManager.get(runIdStr);
|
|
76179
76412
|
if (!tracked) {
|
|
76180
76413
|
console.log(`[${formatTimestamp()}] ⚠️ No running process found for run: ${runIdStr} — marking as stopped`);
|
|
76181
|
-
|
|
76414
|
+
processManager.markPendingStop(runIdStr);
|
|
76182
76415
|
try {
|
|
76183
76416
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76184
76417
|
sessionId: ctx.sessionId,
|
|
@@ -76197,13 +76430,8 @@ async function onCommandStop(ctx, event) {
|
|
|
76197
76430
|
clearTimeout(tracked.softTimeoutTimer);
|
|
76198
76431
|
tracked.softTimeoutTimer = null;
|
|
76199
76432
|
}
|
|
76200
|
-
|
|
76201
|
-
|
|
76202
|
-
if (!exitedAfterSigterm) {
|
|
76203
|
-
console.log(`[${formatTimestamp()}] \uD83D\uDD2A Force-killing process: ${runIdStr}`);
|
|
76204
|
-
killProcess(tracked.process, "SIGKILL");
|
|
76205
|
-
await waitForExit(runIdStr, 1000);
|
|
76206
|
-
}
|
|
76433
|
+
tracked.terminationIntent = "stopped";
|
|
76434
|
+
await killTrackedProcess(tracked);
|
|
76207
76435
|
try {
|
|
76208
76436
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76209
76437
|
sessionId: ctx.sessionId,
|
|
@@ -76216,15 +76444,14 @@ async function onCommandStop(ctx, event) {
|
|
|
76216
76444
|
}
|
|
76217
76445
|
}
|
|
76218
76446
|
async function shutdownAllCommands(ctx) {
|
|
76219
|
-
if (
|
|
76447
|
+
if (processManager.size === 0)
|
|
76220
76448
|
return;
|
|
76221
|
-
console.log(`[${formatTimestamp()}] Shutting down ${
|
|
76222
|
-
const trackedEntries =
|
|
76449
|
+
console.log(`[${formatTimestamp()}] Shutting down ${processManager.size} running command(s)...`);
|
|
76450
|
+
const trackedEntries = processManager.getAll();
|
|
76223
76451
|
for (const [, tracked] of trackedEntries) {
|
|
76224
76452
|
clearInterval(tracked.flushTimer);
|
|
76225
76453
|
if (tracked.softTimeoutTimer)
|
|
76226
76454
|
clearTimeout(tracked.softTimeoutTimer);
|
|
76227
|
-
await flushOutput(ctx, tracked).catch(() => {});
|
|
76228
76455
|
try {
|
|
76229
76456
|
await ctx.deps.backend.mutation(api.commands.updateRunStatus, {
|
|
76230
76457
|
sessionId: ctx.sessionId,
|
|
@@ -76243,23 +76470,22 @@ async function shutdownAllCommands(ctx) {
|
|
|
76243
76470
|
t.unref?.();
|
|
76244
76471
|
});
|
|
76245
76472
|
for (const [, tracked] of trackedEntries) {
|
|
76246
|
-
if (
|
|
76473
|
+
if (processManager.has(tracked.runId)) {
|
|
76247
76474
|
killProcess(tracked.process, "SIGKILL");
|
|
76248
76475
|
}
|
|
76249
76476
|
}
|
|
76250
|
-
|
|
76251
|
-
|
|
76477
|
+
processManager.clear();
|
|
76478
|
+
clearTrackedPids();
|
|
76252
76479
|
console.log(`[${formatTimestamp()}] All commands stopped`);
|
|
76253
76480
|
}
|
|
76254
|
-
var runningProcesses, runningProcessesByCommand, pendingStops, PENDING_STOP_TTL_MS = 60000, OUTPUT_FLUSH_INTERVAL_MS = 3000, MAX_BUFFER_SIZE, SOFT_TIMEOUT_MS, SIGTERM_GRACE_PERIOD_MS = 5000;
|
|
76255
76481
|
var init_command_runner = __esm(() => {
|
|
76256
76482
|
init_api3();
|
|
76257
76483
|
init_convex_error();
|
|
76258
|
-
|
|
76259
|
-
|
|
76260
|
-
|
|
76261
|
-
|
|
76262
|
-
|
|
76484
|
+
init_orphan_tracker();
|
|
76485
|
+
init_state2();
|
|
76486
|
+
init_manager();
|
|
76487
|
+
init_spawner();
|
|
76488
|
+
init_killer();
|
|
76263
76489
|
});
|
|
76264
76490
|
|
|
76265
76491
|
// src/commands/machine/daemon-start/handlers/ping.ts
|
|
@@ -77228,21 +77454,25 @@ async function recoverState(ctx) {
|
|
|
77228
77454
|
console.log(` ⚠️ Failed to clear stale PIDs: ${getErrorMessage(e)}`);
|
|
77229
77455
|
}
|
|
77230
77456
|
try {
|
|
77231
|
-
const runResult = await ctx.deps.backend.mutation(api.commands.
|
|
77457
|
+
const runResult = await ctx.deps.backend.mutation(api.commands.reapOrphansForDaemonRestart, {
|
|
77232
77458
|
sessionId: ctx.sessionId,
|
|
77233
77459
|
machineId: ctx.machineId
|
|
77234
77460
|
});
|
|
77235
|
-
if (runResult.
|
|
77236
|
-
console.log(` \uD83E\uDDF9
|
|
77461
|
+
if (runResult.reapedCount > 0) {
|
|
77462
|
+
console.log(` \uD83E\uDDF9 Reaped ${runResult.reapedCount} command run(s) from previous daemon run (marked as daemon-restart)`);
|
|
77237
77463
|
}
|
|
77238
77464
|
} catch (e) {
|
|
77239
|
-
console.
|
|
77465
|
+
console.warn(` ⚠️ Failed to reap orphan command runs: ${getErrorMessage(e)}`);
|
|
77240
77466
|
}
|
|
77241
77467
|
}
|
|
77242
77468
|
async function initDaemon() {
|
|
77243
77469
|
if (!acquireLock()) {
|
|
77244
77470
|
process.exit(1);
|
|
77245
77471
|
}
|
|
77472
|
+
const { reaped } = await reapOrphanedProcessGroups();
|
|
77473
|
+
if (reaped > 0) {
|
|
77474
|
+
console.log(`[${formatTimestamp()}] Reaped ${reaped} orphaned process group(s) from previous daemon run`);
|
|
77475
|
+
}
|
|
77246
77476
|
const convexUrl = getConvexUrl();
|
|
77247
77477
|
const sessionId = await validateAuthentication(convexUrl);
|
|
77248
77478
|
const client4 = await getConvexClient();
|
|
@@ -77330,6 +77560,7 @@ var init_init2 = __esm(() => {
|
|
|
77330
77560
|
init_error_formatting();
|
|
77331
77561
|
init_version();
|
|
77332
77562
|
init_pid();
|
|
77563
|
+
init_orphan_tracker();
|
|
77333
77564
|
AUTH_WAIT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
77334
77565
|
});
|
|
77335
77566
|
|
|
@@ -78075,7 +78306,7 @@ function evictStaleDedupEntries(tracker) {
|
|
|
78075
78306
|
if (ts < evictBefore)
|
|
78076
78307
|
tracker.commandStopIds.delete(id3);
|
|
78077
78308
|
}
|
|
78078
|
-
evictStalePendingStops();
|
|
78309
|
+
processManager.evictStalePendingStops();
|
|
78079
78310
|
}
|
|
78080
78311
|
async function dispatchCommandEvent(ctx, event, tracker) {
|
|
78081
78312
|
const eventId = event._id.toString();
|
|
@@ -78331,6 +78562,7 @@ var init_command_loop = __esm(() => {
|
|
|
78331
78562
|
init_file_tree_subscription();
|
|
78332
78563
|
init_git_subscription();
|
|
78333
78564
|
init_command_runner();
|
|
78565
|
+
init_manager();
|
|
78334
78566
|
init_init2();
|
|
78335
78567
|
init_observed_sync();
|
|
78336
78568
|
init_api3();
|
|
@@ -79446,5 +79678,5 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
79446
79678
|
});
|
|
79447
79679
|
program2.parse();
|
|
79448
79680
|
|
|
79449
|
-
//# debugId=
|
|
79681
|
+
//# debugId=A50E5218AF2CBB4A64756E2164756E21
|
|
79450
79682
|
//# sourceMappingURL=index.js.map
|