@delt/claude-alarm 1.4.0 → 1.4.1
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/cli.js +154 -55
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +104 -59
- package/dist/codex/main.js.map +1 -1
- package/dist/hub/server.js +27 -9
- package/dist/hub/server.js.map +1 -1
- package/dist/index.js +27 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -146,6 +146,52 @@ var init_logger = __esm({
|
|
|
146
146
|
}
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
// src/codex/shutdown.ts
|
|
150
|
+
async function closeWithinLimit(close) {
|
|
151
|
+
let timer;
|
|
152
|
+
try {
|
|
153
|
+
await Promise.race([
|
|
154
|
+
Promise.resolve().then(close),
|
|
155
|
+
new Promise((_resolve, reject) => {
|
|
156
|
+
timer = setTimeout(() => reject(new Error("Codex proxy cleanup timed out")), RUN_CLOSE_MS);
|
|
157
|
+
})
|
|
158
|
+
]);
|
|
159
|
+
} finally {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function exitAfterFlush(code, stdout, stderr, exit = process.exit) {
|
|
164
|
+
let exited2 = false;
|
|
165
|
+
let remaining = 2;
|
|
166
|
+
const finish = () => {
|
|
167
|
+
if (exited2) return;
|
|
168
|
+
exited2 = true;
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
exit(code);
|
|
171
|
+
};
|
|
172
|
+
const timer = setTimeout(finish, CLI_EXIT_FLUSH_MS);
|
|
173
|
+
const flushed = () => {
|
|
174
|
+
if (--remaining === 0) finish();
|
|
175
|
+
};
|
|
176
|
+
for (const stream of [stdout, stderr]) {
|
|
177
|
+
try {
|
|
178
|
+
stream.write("", flushed);
|
|
179
|
+
} catch {
|
|
180
|
+
flushed();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
var RUN_CLOSE_MS, SUPERVISOR_STOP_GRACE_MS, CLI_EXIT_FLUSH_MS;
|
|
185
|
+
var init_shutdown = __esm({
|
|
186
|
+
"src/codex/shutdown.ts"() {
|
|
187
|
+
"use strict";
|
|
188
|
+
init_logger();
|
|
189
|
+
RUN_CLOSE_MS = 15e3;
|
|
190
|
+
SUPERVISOR_STOP_GRACE_MS = 3e4;
|
|
191
|
+
CLI_EXIT_FLUSH_MS = 1e3;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
149
195
|
// src/shared/crash-guard.ts
|
|
150
196
|
import fs3 from "fs";
|
|
151
197
|
function appendToLog(text) {
|
|
@@ -1068,6 +1114,7 @@ var init_codex_supervisor = __esm({
|
|
|
1068
1114
|
"src/hub/codex-supervisor.ts"() {
|
|
1069
1115
|
"use strict";
|
|
1070
1116
|
init_logger();
|
|
1117
|
+
init_shutdown();
|
|
1071
1118
|
HEALTHY_RUN_MS = 6e4;
|
|
1072
1119
|
CodexSupervisor = class {
|
|
1073
1120
|
constructor(script, opts = {}) {
|
|
@@ -1075,7 +1122,7 @@ var init_codex_supervisor = __esm({
|
|
|
1075
1122
|
this.spawnFn = opts.spawnFn ?? spawn2;
|
|
1076
1123
|
this.minDelayMs = opts.minDelayMs ?? 2e3;
|
|
1077
1124
|
this.maxDelayMs = opts.maxDelayMs ?? 6e4;
|
|
1078
|
-
this.stopGraceMs = opts.stopGraceMs ??
|
|
1125
|
+
this.stopGraceMs = opts.stopGraceMs ?? SUPERVISOR_STOP_GRACE_MS;
|
|
1079
1126
|
this.env = opts.env;
|
|
1080
1127
|
this.delay = this.minDelayMs;
|
|
1081
1128
|
}
|
|
@@ -1098,11 +1145,22 @@ var init_codex_supervisor = __esm({
|
|
|
1098
1145
|
this.timer = void 0;
|
|
1099
1146
|
const child = this.child;
|
|
1100
1147
|
this.child = void 0;
|
|
1101
|
-
if (!child) return;
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1148
|
+
if (!child) return Promise.resolve();
|
|
1149
|
+
return new Promise((resolve) => {
|
|
1150
|
+
const done = () => {
|
|
1151
|
+
clearTimeout(force);
|
|
1152
|
+
child.removeListener("exit", done);
|
|
1153
|
+
resolve();
|
|
1154
|
+
};
|
|
1155
|
+
const force = setTimeout(() => {
|
|
1156
|
+
logger.warn(`Codex adapter did not exit within ${this.stopGraceMs}ms of stop; killing it`);
|
|
1157
|
+
child.kill();
|
|
1158
|
+
done();
|
|
1159
|
+
}, this.stopGraceMs);
|
|
1160
|
+
force.unref();
|
|
1161
|
+
child.once("exit", done);
|
|
1162
|
+
child.stdin?.end();
|
|
1163
|
+
});
|
|
1106
1164
|
}
|
|
1107
1165
|
launch() {
|
|
1108
1166
|
const startedAt = Date.now();
|
|
@@ -1351,9 +1409,9 @@ var init_server = __esm({
|
|
|
1351
1409
|
});
|
|
1352
1410
|
}
|
|
1353
1411
|
stop() {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1412
|
+
const adapterStopped = this.codexSupervisor?.stop();
|
|
1413
|
+
this.codexSupervisor = void 0;
|
|
1414
|
+
const serverStopped = new Promise((resolve) => {
|
|
1357
1415
|
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
1358
1416
|
if (this.telegramBot) this.telegramBot.stopPolling();
|
|
1359
1417
|
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
@@ -1373,6 +1431,8 @@ var init_server = __esm({
|
|
|
1373
1431
|
resolve();
|
|
1374
1432
|
}, 3e3);
|
|
1375
1433
|
});
|
|
1434
|
+
return Promise.all([serverStopped, adapterStopped]).then(() => {
|
|
1435
|
+
});
|
|
1376
1436
|
}
|
|
1377
1437
|
// --- HTTP Handler ---
|
|
1378
1438
|
handleHttp(req, res) {
|
|
@@ -2691,27 +2751,48 @@ var defaultSpawn = (command, args) => {
|
|
|
2691
2751
|
shellSpawns.add(child);
|
|
2692
2752
|
return child;
|
|
2693
2753
|
};
|
|
2694
|
-
var
|
|
2695
|
-
|
|
2696
|
-
execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout:
|
|
2697
|
-
if (err)
|
|
2698
|
-
|
|
2699
|
-
return;
|
|
2700
|
-
}
|
|
2701
|
-
try {
|
|
2702
|
-
const rows = JSON.parse(stdout);
|
|
2703
|
-
if (!Array.isArray(rows) || rows.some((row) => !Number.isInteger(row.pid) || !Number.isInteger(row.parentPid) || typeof row.creationTime !== "string")) {
|
|
2704
|
-
throw new Error("invalid process snapshot");
|
|
2705
|
-
}
|
|
2706
|
-
resolve(rows);
|
|
2707
|
-
} catch (error) {
|
|
2708
|
-
reject(error);
|
|
2709
|
-
}
|
|
2754
|
+
var POWERSHELL_TIMEOUT_MS = 5e3;
|
|
2755
|
+
var powershell = (script, env) => new Promise((resolve, reject) => {
|
|
2756
|
+
execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout: POWERSHELL_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, env }, (err, stdout) => {
|
|
2757
|
+
if (err) reject(err);
|
|
2758
|
+
else resolve(stdout);
|
|
2710
2759
|
});
|
|
2711
2760
|
});
|
|
2712
|
-
var
|
|
2713
|
-
|
|
2761
|
+
var QUERY_SCRIPT = "@(Get-CimInstance Win32_Process | Where-Object CreationDate | Select-Object @{ n = 'pid'; e = { [int]$_.ProcessId } }, @{ n = 'parentPid'; e = { [int]$_.ParentProcessId } }, @{ n = 'creationTime'; e = { $_.CreationDate.ToUniversalTime().ToString('o') } }) | ConvertTo-Json -Compress";
|
|
2762
|
+
var processQuery = (prelude = "") => async () => {
|
|
2763
|
+
const rows = JSON.parse(await powershell(prelude + QUERY_SCRIPT));
|
|
2764
|
+
if (!Array.isArray(rows) || rows.some((row) => !Number.isInteger(row.pid) || !Number.isInteger(row.parentPid) || typeof row.creationTime !== "string")) {
|
|
2765
|
+
throw new Error("invalid process snapshot");
|
|
2766
|
+
}
|
|
2767
|
+
return rows;
|
|
2714
2768
|
};
|
|
2769
|
+
var queryProcesses = processQuery();
|
|
2770
|
+
var END_SCRIPT = [
|
|
2771
|
+
"foreach ($t in ($env:CLAUDE_ALARM_END_TARGETS | ConvertFrom-Json)) {",
|
|
2772
|
+
" $p = Get-Process -Id $t.pid -ErrorAction SilentlyContinue",
|
|
2773
|
+
// Reading Handle opens the process once, and StartTime, Stop-Process and Wait-Process then use that handle; a getter that throws reads as $null.
|
|
2774
|
+
' if ($null -eq $p -or $null -eq $p.Handle) { "$($t.pid) is gone or cannot be opened"; continue }',
|
|
2775
|
+
" $start = $p.StartTime",
|
|
2776
|
+
' if ($null -eq $start) { "$($t.pid) has no readable creation time"; continue }',
|
|
2777
|
+
" $start = $start.ToUniversalTime()",
|
|
2778
|
+
// Win32_Process creation dates stop at microseconds.
|
|
2779
|
+
` if ($start.AddTicks(-($start.Ticks % 10)).ToString('o') -ne $t.creationTime) { "$($t.pid) now belongs to another process"; continue }`,
|
|
2780
|
+
' try { Stop-Process -InputObject $p -Force -ErrorAction Stop } catch { "$($t.pid) not ended: $($_.FullyQualifiedErrorId)" }',
|
|
2781
|
+
// A refused termination usually means the process is already exiting, so it is waited for all the same.
|
|
2782
|
+
' try { Wait-Process -InputObject $p -Timeout 1 -ErrorAction Stop } catch { "$($t.pid) has not exited yet" }',
|
|
2783
|
+
"}"
|
|
2784
|
+
].join("\n");
|
|
2785
|
+
var processEnder = (prelude = "") => async (targets) => {
|
|
2786
|
+
await Promise.all(targets.map(async (target) => {
|
|
2787
|
+
try {
|
|
2788
|
+
const notes = await powershell(prelude + END_SCRIPT, { ...process.env, CLAUDE_ALARM_END_TARGETS: JSON.stringify([target]) });
|
|
2789
|
+
for (const note of notes.split(/\r?\n/)) if (note) logger.debug(`codex process ${note}`);
|
|
2790
|
+
} catch (err) {
|
|
2791
|
+
logger.debug(`ending codex process ${target.pid} failed: ${err.killed ? "timed out" : String(err)}`);
|
|
2792
|
+
}
|
|
2793
|
+
}));
|
|
2794
|
+
};
|
|
2795
|
+
var endProcesses = processEnder();
|
|
2715
2796
|
function descendantsOf(rootPid, rows) {
|
|
2716
2797
|
const root = rows.find((row) => row.pid === rootPid);
|
|
2717
2798
|
if (!root) return void 0;
|
|
@@ -2733,16 +2814,32 @@ function descendantsOf(rootPid, rows) {
|
|
|
2733
2814
|
return found;
|
|
2734
2815
|
}
|
|
2735
2816
|
var exited = (child) => child.exitCode !== null || child.signalCode !== null;
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2817
|
+
var SHELL_EXIT_WAIT_MS = 1e3;
|
|
2818
|
+
var PROXY_TREE_CLOSE_BUDGET_MS = 2 * POWERSHELL_TIMEOUT_MS + SHELL_EXIT_WAIT_MS;
|
|
2819
|
+
var exitOf = (child) => new Promise((resolve) => {
|
|
2820
|
+
if (exited(child)) {
|
|
2821
|
+
resolve();
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
const timer = setTimeout(() => {
|
|
2825
|
+
logger.debug(`codex proxy shell ${child.pid} has not exited yet`);
|
|
2826
|
+
resolve();
|
|
2827
|
+
}, SHELL_EXIT_WAIT_MS);
|
|
2828
|
+
child.once("exit", () => {
|
|
2829
|
+
clearTimeout(timer);
|
|
2830
|
+
resolve();
|
|
2743
2831
|
});
|
|
2744
|
-
|
|
2745
|
-
|
|
2832
|
+
});
|
|
2833
|
+
var merged = (before, found) => [...before, ...found.filter((d) => !before.some((b) => b.pid === d.pid && b.creationTime === d.creationTime))];
|
|
2834
|
+
function treeKiller(platform = process.platform, end = endProcesses, query = queryProcesses, throughShell = spawnedThroughShell) {
|
|
2835
|
+
const recorded = /* @__PURE__ */ new WeakMap();
|
|
2836
|
+
const endByIdentity = async (descendants) => {
|
|
2837
|
+
if (!descendants.length) return;
|
|
2838
|
+
try {
|
|
2839
|
+
await end([...descendants].sort((a, b) => b.depth - a.depth).map(({ depth, ...identity }) => identity));
|
|
2840
|
+
} catch (err) {
|
|
2841
|
+
logger.debug(`ending codex processes failed: ${String(err)}`);
|
|
2842
|
+
}
|
|
2746
2843
|
};
|
|
2747
2844
|
const snapshot = async () => {
|
|
2748
2845
|
try {
|
|
@@ -2758,20 +2855,12 @@ function treeKiller(platform = process.platform, run = runFile, query = queryPro
|
|
|
2758
2855
|
if (!exited(child)) child.kill();
|
|
2759
2856
|
return;
|
|
2760
2857
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
return;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
const descendants = await recorded.get(child) ?? [];
|
|
2771
|
-
if (!descendants.length) return;
|
|
2772
|
-
const current = await snapshot();
|
|
2773
|
-
if (!current) return;
|
|
2774
|
-
await stopDeepestFirst(descendants.filter((d) => current.some((row) => row.pid === d.pid && row.creationTime === d.creationTime)));
|
|
2858
|
+
const [rows, before] = await Promise.all([exited(child) ? void 0 : snapshot(), recorded.get(child)]);
|
|
2859
|
+
const found = rows && !exited(child) ? descendantsOf(child.pid, rows) ?? [] : [];
|
|
2860
|
+
await endByIdentity(merged(before ?? [], found));
|
|
2861
|
+
if (exited(child)) return;
|
|
2862
|
+
child.kill();
|
|
2863
|
+
await exitOf(child);
|
|
2775
2864
|
};
|
|
2776
2865
|
killer.track = async (child) => {
|
|
2777
2866
|
if (!isShellTree(child) || exited(child)) return;
|
|
@@ -2779,8 +2868,7 @@ function treeKiller(platform = process.platform, run = runFile, query = queryPro
|
|
|
2779
2868
|
const latest = (async () => {
|
|
2780
2869
|
const rows = await snapshot();
|
|
2781
2870
|
const found = rows && !exited(child) ? descendantsOf(child.pid, rows) ?? [] : [];
|
|
2782
|
-
|
|
2783
|
-
return [...before, ...found.filter((d) => !before.some((b) => b.pid === d.pid && b.creationTime === d.creationTime))];
|
|
2871
|
+
return merged(await earlier ?? [], found);
|
|
2784
2872
|
})();
|
|
2785
2873
|
recorded.set(child, latest);
|
|
2786
2874
|
await latest;
|
|
@@ -2849,6 +2937,7 @@ function connectProxy(command, spawnFn = defaultSpawn, timeoutMs = HANDSHAKE_TIM
|
|
|
2849
2937
|
}
|
|
2850
2938
|
|
|
2851
2939
|
// src/codex/run.ts
|
|
2940
|
+
init_shutdown();
|
|
2852
2941
|
import path4 from "path";
|
|
2853
2942
|
|
|
2854
2943
|
// src/codex/rpc.ts
|
|
@@ -3134,7 +3223,14 @@ async function runCodexTask(opts) {
|
|
|
3134
3223
|
};
|
|
3135
3224
|
} finally {
|
|
3136
3225
|
for (const t of timers) clearTimeout(t);
|
|
3137
|
-
|
|
3226
|
+
try {
|
|
3227
|
+
await closeWithinLimit(() => conn.close());
|
|
3228
|
+
} catch (err) {
|
|
3229
|
+
try {
|
|
3230
|
+
progress(`warning: Codex proxy cleanup failed: ${err.message}`);
|
|
3231
|
+
} catch {
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3138
3234
|
}
|
|
3139
3235
|
}
|
|
3140
3236
|
var VALUE_FLAGS = {
|
|
@@ -3207,6 +3303,7 @@ async function runFromCli(argv, io) {
|
|
|
3207
3303
|
}
|
|
3208
3304
|
|
|
3209
3305
|
// src/cli.ts
|
|
3306
|
+
init_shutdown();
|
|
3210
3307
|
init_constants();
|
|
3211
3308
|
init_logger();
|
|
3212
3309
|
init_crash_guard();
|
|
@@ -3958,8 +4055,10 @@ async function main2() {
|
|
|
3958
4055
|
else if (sub === "start") await codexStart();
|
|
3959
4056
|
else if (sub === "stop") process.exitCode = await stopAdapter(controlDeps(), loadConfig().codex?.enabled === true);
|
|
3960
4057
|
else if (sub === "status") process.exitCode = await adapterStatus(controlDeps(), loadConfig().codex?.enabled === true);
|
|
3961
|
-
else if (sub === "run")
|
|
3962
|
-
|
|
4058
|
+
else if (sub === "run") {
|
|
4059
|
+
const code = await codexRun(args.slice(2));
|
|
4060
|
+
exitAfterFlush(code, process.stdout, process.stderr);
|
|
4061
|
+
} else {
|
|
3963
4062
|
console.error(`Unknown codex command: ${sub}`);
|
|
3964
4063
|
printUsage();
|
|
3965
4064
|
process.exit(1);
|