@delt/claude-alarm 1.2.0 → 1.3.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/README.md +2 -1
- package/dist/cli.js +408 -169
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +302 -65
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +14 -0
- package/dist/hub/server.js +143 -48
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +143 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +14 -0
package/dist/codex/main.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/codex/main.ts
|
|
4
|
-
import fs6 from "fs";
|
|
5
|
-
|
|
6
3
|
// src/shared/config.ts
|
|
7
4
|
import fs from "fs";
|
|
8
5
|
import path2 from "path";
|
|
@@ -244,12 +241,12 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
244
241
|
// src/channel/hub-client.ts
|
|
245
242
|
import WebSocket2 from "ws";
|
|
246
243
|
var HubClient = class {
|
|
247
|
-
constructor(sessionId, sessionName, hubHost = DEFAULT_HUB_HOST, hubPort = DEFAULT_HUB_PORT,
|
|
244
|
+
constructor(sessionId, sessionName, hubHost = DEFAULT_HUB_HOST, hubPort = DEFAULT_HUB_PORT, token, getPeerName = () => void 0, getRegistration = () => ({})) {
|
|
248
245
|
this.sessionId = sessionId;
|
|
249
246
|
this.sessionName = sessionName;
|
|
250
247
|
this.hubHost = hubHost;
|
|
251
248
|
this.hubPort = hubPort;
|
|
252
|
-
this.token =
|
|
249
|
+
this.token = token;
|
|
253
250
|
this.getPeerName = getPeerName;
|
|
254
251
|
this.getRegistration = getRegistration;
|
|
255
252
|
}
|
|
@@ -366,7 +363,8 @@ import path3 from "path";
|
|
|
366
363
|
import { Duplex } from "stream";
|
|
367
364
|
import WebSocket3 from "ws";
|
|
368
365
|
function resolveCommand(command, platform = process.platform, env = process.env) {
|
|
369
|
-
if (platform !== "win32"
|
|
366
|
+
if (platform !== "win32") return { file: command, shell: false };
|
|
367
|
+
if (/[\\/]/.test(command) || path3.extname(command)) return { file: command, shell: /\.(cmd|bat)$/i.test(command) };
|
|
370
368
|
const file = findCodex(command, platform, env);
|
|
371
369
|
return file ? { file, shell: file.endsWith(".cmd") } : { file: command, shell: false };
|
|
372
370
|
}
|
|
@@ -394,7 +392,8 @@ var defaultSpawn = (command, args) => {
|
|
|
394
392
|
if (shell) return spawn(`"${file}" ${args.join(" ")}`, { stdio: "pipe", windowsHide: true, shell: true });
|
|
395
393
|
return spawn(file, args, { stdio: "pipe", windowsHide: true });
|
|
396
394
|
};
|
|
397
|
-
|
|
395
|
+
var HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
396
|
+
function connectProxy(command, spawnFn = defaultSpawn, timeoutMs = HANDSHAKE_TIMEOUT_MS) {
|
|
398
397
|
const child = spawnFn(command, ["app-server", "proxy"]);
|
|
399
398
|
child.stdin?.on("error", () => {
|
|
400
399
|
});
|
|
@@ -428,9 +427,11 @@ function connectProxy(command, spawnFn = defaultSpawn) {
|
|
|
428
427
|
};
|
|
429
428
|
return new Promise((resolve, reject) => {
|
|
430
429
|
let settled = false;
|
|
430
|
+
const timer = setTimeout(() => fail(new Error(`codex daemon did not answer within ${timeoutMs / 1e3}s`)), timeoutMs);
|
|
431
431
|
const fail = (err) => {
|
|
432
432
|
if (settled) return;
|
|
433
433
|
settled = true;
|
|
434
|
+
clearTimeout(timer);
|
|
434
435
|
close();
|
|
435
436
|
reject(err);
|
|
436
437
|
};
|
|
@@ -440,6 +441,7 @@ function connectProxy(command, spawnFn = defaultSpawn) {
|
|
|
440
441
|
ws.once("open", () => {
|
|
441
442
|
if (settled) return;
|
|
442
443
|
settled = true;
|
|
444
|
+
clearTimeout(timer);
|
|
443
445
|
child.on("error", (err) => logger.warn(`codex proxy error: ${err.message}`));
|
|
444
446
|
ws.on("error", (err) => logger.warn(`codex daemon socket error: ${err.message}`));
|
|
445
447
|
resolve({ ws, close });
|
|
@@ -749,10 +751,10 @@ var CodexAdapter = class {
|
|
|
749
751
|
noticeNotFound() {
|
|
750
752
|
if (this.notFoundNotice !== "unsent") return;
|
|
751
753
|
this.notFoundNotice = "sending";
|
|
752
|
-
const { host
|
|
753
|
-
fetch(`http://${
|
|
754
|
+
const { host, port, token } = this.opts.hub;
|
|
755
|
+
fetch(`http://${host}:${port}/api/notify`, {
|
|
754
756
|
method: "POST",
|
|
755
|
-
headers: { "Content-Type": "application/json", ...
|
|
757
|
+
headers: { "Content-Type": "application/json", ...token ? { Authorization: `Bearer ${token}` } : {} },
|
|
756
758
|
body: JSON.stringify({
|
|
757
759
|
title: "Codex not found",
|
|
758
760
|
message: `The Codex adapter cannot find "${this.opts.command}". Open a new terminal and restart the hub, or set "codex.command" in ~/.claude-alarm/config.json.`,
|
|
@@ -831,18 +833,18 @@ var CodexAdapter = class {
|
|
|
831
833
|
this.drop(thread.id);
|
|
832
834
|
return;
|
|
833
835
|
}
|
|
834
|
-
const
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
|
|
836
|
+
const existing = this.threads.get(thread.id);
|
|
837
|
+
if (existing) {
|
|
838
|
+
existing.thread = thread;
|
|
839
|
+
existing.hub.reregister();
|
|
838
840
|
} else {
|
|
839
|
-
const { host
|
|
841
|
+
const { host, port, token } = this.opts.hub;
|
|
840
842
|
const hub = new HubClient(
|
|
841
843
|
codexSessionId(thread.id),
|
|
842
844
|
threadTitle(thread),
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
845
|
+
host,
|
|
846
|
+
port,
|
|
847
|
+
token,
|
|
846
848
|
() => void 0,
|
|
847
849
|
() => this.registration(thread.id)
|
|
848
850
|
);
|
|
@@ -890,6 +892,7 @@ var CodexAdapter = class {
|
|
|
890
892
|
}
|
|
891
893
|
} catch (err) {
|
|
892
894
|
logger.debug(`subscription sync for ${threadId} deferred: ${err.message}`);
|
|
895
|
+
if (t.wantSubscribed && !t.subscribed && t.thread.status.type === "active") t.unrelayed = true;
|
|
893
896
|
}
|
|
894
897
|
}
|
|
895
898
|
releaseLater(t) {
|
|
@@ -1108,7 +1111,7 @@ var CodexAdapter = class {
|
|
|
1108
1111
|
}
|
|
1109
1112
|
if (!t || !view) {
|
|
1110
1113
|
if (t && (broken || USER_REQUESTS.has(method))) {
|
|
1111
|
-
this.
|
|
1114
|
+
this.waitingInCodex(t.thread.id);
|
|
1112
1115
|
} else {
|
|
1113
1116
|
logger.debug(`Ignoring Codex server request ${method}`);
|
|
1114
1117
|
}
|
|
@@ -1118,17 +1121,26 @@ var CodexAdapter = class {
|
|
|
1118
1121
|
if (a.threadId === t.thread.id && a.rpcId === rpcId) return;
|
|
1119
1122
|
}
|
|
1120
1123
|
const requestId = randomUUID2();
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
this.approvals.set(requestId, { threadId: t.thread.id, rpcId, turnId: params.turnId, choices: view.choices, answered: false });
|
|
1126
|
+
t.hub.send({
|
|
1127
|
+
type: "permission_request",
|
|
1128
|
+
sessionId: codexSessionId(t.thread.id),
|
|
1129
|
+
requestId,
|
|
1130
|
+
toolName: view.toolName,
|
|
1131
|
+
description: view.description,
|
|
1132
|
+
inputPreview: view.inputPreview,
|
|
1133
|
+
timestamp: Date.now(),
|
|
1134
|
+
choices: view.choices.map((c, i) => ({ id: String(i), label: c.label }))
|
|
1135
|
+
});
|
|
1136
|
+
} catch (err) {
|
|
1137
|
+
this.approvals.delete(requestId);
|
|
1138
|
+
logger.warn(`Codex ${method} could not be relayed: ${err.message}`);
|
|
1139
|
+
this.waitingInCodex(t.thread.id);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
waitingInCodex(threadId) {
|
|
1143
|
+
this.notify(threadId, "Codex is waiting", "Codex asked for input that claude-alarm cannot relay. Handle it in Codex.", "warning");
|
|
1132
1144
|
}
|
|
1133
1145
|
answer(threadId, requestId, choiceId) {
|
|
1134
1146
|
const a = this.approvals.get(requestId);
|
|
@@ -1156,10 +1168,10 @@ var CodexAdapter = class {
|
|
|
1156
1168
|
|
|
1157
1169
|
// src/shared/hub-url.ts
|
|
1158
1170
|
import net from "net";
|
|
1159
|
-
function hubUrlHost(
|
|
1160
|
-
if (
|
|
1161
|
-
if (
|
|
1162
|
-
return net.isIPv6(
|
|
1171
|
+
function hubUrlHost(host) {
|
|
1172
|
+
if (host === "" || host === "0.0.0.0") return "127.0.0.1";
|
|
1173
|
+
if (host === "::") return "[::1]";
|
|
1174
|
+
return net.isIPv6(host) ? `[${host}]` : host;
|
|
1163
1175
|
}
|
|
1164
1176
|
|
|
1165
1177
|
// src/codex/hub-target.ts
|
|
@@ -1175,56 +1187,281 @@ function resolveAdapterHub(config2, env = process.env) {
|
|
|
1175
1187
|
};
|
|
1176
1188
|
}
|
|
1177
1189
|
|
|
1178
|
-
// src/codex/
|
|
1179
|
-
|
|
1190
|
+
// src/codex/instance-lock.ts
|
|
1191
|
+
import crypto from "crypto";
|
|
1192
|
+
import fs6 from "fs";
|
|
1193
|
+
import net2 from "net";
|
|
1194
|
+
import path5 from "path";
|
|
1195
|
+
var CONTROL_PROTOCOL = 1;
|
|
1196
|
+
var STALE_GUARD_MS = 1e4;
|
|
1197
|
+
var STOP_PROOF_LABEL = "claude-alarm codex stop";
|
|
1198
|
+
var MAX_LINE_BYTES = 4096;
|
|
1199
|
+
var REQUEST_IDLE_MS = 2e3;
|
|
1200
|
+
var MAX_SOCKET_PATH_BYTES = 103;
|
|
1201
|
+
function stopProof(token) {
|
|
1202
|
+
return crypto.createHmac("sha256", token).update(STOP_PROOF_LABEL).digest("hex");
|
|
1203
|
+
}
|
|
1204
|
+
function controlEndpoint(configDir, platform = process.platform) {
|
|
1205
|
+
if (platform !== "win32") return path5.posix.join(configDir, "codex.sock");
|
|
1206
|
+
let dir;
|
|
1180
1207
|
try {
|
|
1181
|
-
|
|
1182
|
-
return true;
|
|
1208
|
+
dir = fs6.realpathSync.native(configDir);
|
|
1183
1209
|
} catch {
|
|
1184
|
-
|
|
1210
|
+
dir = path5.win32.resolve(configDir);
|
|
1211
|
+
}
|
|
1212
|
+
const hash = crypto.createHash("sha256").update(dir.toLowerCase()).digest("hex").slice(0, 16);
|
|
1213
|
+
return `\\\\.\\pipe\\claude-alarm-codex-${hash}`;
|
|
1214
|
+
}
|
|
1215
|
+
function ask(endpoint2, request, timeoutMs) {
|
|
1216
|
+
return new Promise((resolve) => {
|
|
1217
|
+
let buffer = "";
|
|
1218
|
+
let settled = false;
|
|
1219
|
+
const socket = net2.connect(endpoint2);
|
|
1220
|
+
const finish = (answer) => {
|
|
1221
|
+
if (settled) return;
|
|
1222
|
+
settled = true;
|
|
1223
|
+
clearTimeout(timer);
|
|
1224
|
+
socket.destroy();
|
|
1225
|
+
resolve(answer);
|
|
1226
|
+
};
|
|
1227
|
+
const timer = setTimeout(() => finish({ state: "unknown", error: `no answer within ${timeoutMs}ms` }), timeoutMs);
|
|
1228
|
+
socket.setEncoding("utf8");
|
|
1229
|
+
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
1230
|
+
`));
|
|
1231
|
+
socket.on("data", (chunk) => {
|
|
1232
|
+
buffer += chunk;
|
|
1233
|
+
const end = buffer.indexOf("\n");
|
|
1234
|
+
if (end < 0) {
|
|
1235
|
+
if (Buffer.byteLength(buffer) > MAX_LINE_BYTES) finish({ state: "unknown", error: "reply too long" });
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
finish({ reply: JSON.parse(buffer.slice(0, end)) });
|
|
1240
|
+
} catch {
|
|
1241
|
+
finish({ state: "unknown", error: "unreadable reply" });
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
socket.on("end", () => finish({ state: "unknown", error: "closed without a reply" }));
|
|
1245
|
+
socket.on("error", (err) => {
|
|
1246
|
+
if (err.code === "ENOENT" || err.code === "ECONNREFUSED") finish({ state: "absent" });
|
|
1247
|
+
else finish({ state: "unknown", error: err.code ?? err.message });
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
async function queryOwner(endpoint2, timeoutMs = 1e3) {
|
|
1252
|
+
const answer = await ask(endpoint2, { type: "status" }, timeoutMs);
|
|
1253
|
+
if (!("reply" in answer)) return answer;
|
|
1254
|
+
const reply = answer.reply;
|
|
1255
|
+
if (reply?.type === "status" && reply.protocol === CONTROL_PROTOCOL && Number.isInteger(reply.pid)) {
|
|
1256
|
+
return { state: "running", pid: reply.pid };
|
|
1257
|
+
}
|
|
1258
|
+
return { state: "unknown", error: "unexpected reply" };
|
|
1259
|
+
}
|
|
1260
|
+
var line = (message) => `${JSON.stringify(message)}
|
|
1261
|
+
`;
|
|
1262
|
+
function controlServer(owner) {
|
|
1263
|
+
const expectedProof = Buffer.from(stopProof(owner.token));
|
|
1264
|
+
return net2.createServer((socket) => {
|
|
1265
|
+
let buffer = "";
|
|
1266
|
+
socket.setEncoding("utf8");
|
|
1267
|
+
socket.on("error", () => {
|
|
1268
|
+
});
|
|
1269
|
+
const idle = setTimeout(() => socket.destroy(), REQUEST_IDLE_MS);
|
|
1270
|
+
socket.on("close", () => clearTimeout(idle));
|
|
1271
|
+
const onData = (chunk) => {
|
|
1272
|
+
buffer += chunk;
|
|
1273
|
+
const end = buffer.indexOf("\n");
|
|
1274
|
+
if (end < 0) {
|
|
1275
|
+
if (Buffer.byteLength(buffer) > MAX_LINE_BYTES) socket.destroy();
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
socket.off("data", onData);
|
|
1279
|
+
clearTimeout(idle);
|
|
1280
|
+
let request = null;
|
|
1281
|
+
try {
|
|
1282
|
+
request = JSON.parse(buffer.slice(0, end));
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
if (request?.type === "status") {
|
|
1286
|
+
socket.end(line({ type: "status", protocol: CONTROL_PROTOCOL, pid: owner.pid }));
|
|
1287
|
+
} else if (request?.type === "stop") {
|
|
1288
|
+
const proof = typeof request.proof === "string" ? Buffer.from(request.proof) : void 0;
|
|
1289
|
+
if (owner.token !== "" && proof && proof.length === expectedProof.length && crypto.timingSafeEqual(proof, expectedProof)) {
|
|
1290
|
+
socket.end(line({ type: "stopping", pid: owner.pid }), () => owner.onStop());
|
|
1291
|
+
} else {
|
|
1292
|
+
socket.end(line({ type: "error", error: "unauthorized" }));
|
|
1293
|
+
}
|
|
1294
|
+
} else {
|
|
1295
|
+
socket.end(line({ type: "error", error: "unknown request" }));
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
socket.on("data", onData);
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function listenControl(endpoint2, owner) {
|
|
1302
|
+
const server = controlServer(owner);
|
|
1303
|
+
return new Promise((resolve) => {
|
|
1304
|
+
const onError = (err) => {
|
|
1305
|
+
resolve(err.code === "EADDRINUSE" ? { kind: "busy" } : { kind: "unknown", error: err.code ?? err.message });
|
|
1306
|
+
};
|
|
1307
|
+
server.once("error", onError);
|
|
1308
|
+
server.listen(endpoint2, () => {
|
|
1309
|
+
server.off("error", onError);
|
|
1310
|
+
server.on("error", () => {
|
|
1311
|
+
});
|
|
1312
|
+
resolve({ kind: "owner", close: () => new Promise((done) => server.close(() => done())) });
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
function settle(attempt, busy) {
|
|
1317
|
+
return attempt.kind === "busy" ? { kind: "unknown", error: busy } : attempt;
|
|
1318
|
+
}
|
|
1319
|
+
async function acquireLock(endpoint2, owner, deps = {}) {
|
|
1320
|
+
const platform = deps.platform ?? process.platform;
|
|
1321
|
+
const listen = deps.listen ?? listenControl;
|
|
1322
|
+
const query = deps.queryOwner ?? ((e) => queryOwner(e));
|
|
1323
|
+
if (platform !== "win32" && Buffer.byteLength(endpoint2) > MAX_SOCKET_PATH_BYTES) {
|
|
1324
|
+
return { kind: "unknown", error: `control socket path is too long: ${endpoint2}` };
|
|
1325
|
+
}
|
|
1326
|
+
const first = await listen(endpoint2, owner);
|
|
1327
|
+
if (first.kind !== "busy") return first;
|
|
1328
|
+
const state = await query(endpoint2);
|
|
1329
|
+
if (state.state === "running") return { kind: "held", pid: state.pid };
|
|
1330
|
+
if (state.state === "unknown") return { kind: "unknown", error: state.error };
|
|
1331
|
+
if (platform === "win32") return settle(await listen(endpoint2, owner), "the control pipe is in use but does not answer");
|
|
1332
|
+
return recoverSocket(endpoint2, owner, listen, query, deps.fs ?? fs6, deps.now ?? Date.now);
|
|
1333
|
+
}
|
|
1334
|
+
async function recoverSocket(endpoint2, owner, listen, query, f, now) {
|
|
1335
|
+
const guard = `${endpoint2}.lock`;
|
|
1336
|
+
const blocked = takeGuard(guard, f, now);
|
|
1337
|
+
if (blocked) return { kind: "unknown", error: blocked };
|
|
1338
|
+
try {
|
|
1339
|
+
const state = await query(endpoint2);
|
|
1340
|
+
if (state.state === "running") return { kind: "held", pid: state.pid };
|
|
1341
|
+
if (state.state === "unknown") return { kind: "unknown", error: state.error };
|
|
1342
|
+
try {
|
|
1343
|
+
f.unlinkSync(endpoint2);
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
if (err.code !== "ENOENT") return { kind: "unknown", error: err.message };
|
|
1346
|
+
}
|
|
1347
|
+
return settle(await listen(endpoint2, owner), "the control socket is in use but does not answer");
|
|
1348
|
+
} finally {
|
|
1349
|
+
try {
|
|
1350
|
+
f.rmdirSync(guard);
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function takeGuard(guard, f, now) {
|
|
1356
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1357
|
+
try {
|
|
1358
|
+
f.mkdirSync(guard);
|
|
1359
|
+
return void 0;
|
|
1360
|
+
} catch (err) {
|
|
1361
|
+
if (err.code !== "EEXIST") return err.message;
|
|
1362
|
+
}
|
|
1363
|
+
let age;
|
|
1364
|
+
try {
|
|
1365
|
+
age = now() - f.statSync(guard).mtimeMs;
|
|
1366
|
+
} catch {
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (age <= STALE_GUARD_MS) return "another Codex adapter is starting";
|
|
1370
|
+
try {
|
|
1371
|
+
f.rmdirSync(guard);
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1185
1374
|
}
|
|
1375
|
+
return "another Codex adapter is starting";
|
|
1186
1376
|
}
|
|
1377
|
+
|
|
1378
|
+
// src/codex/main.ts
|
|
1379
|
+
var WAIT_MIN_MS = 2e3;
|
|
1380
|
+
var WAIT_MAX_MS = 3e4;
|
|
1187
1381
|
installCrashGuard("codex adapter");
|
|
1188
1382
|
process.stdout.on("error", () => {
|
|
1189
1383
|
});
|
|
1190
1384
|
process.stderr.on("error", () => {
|
|
1191
1385
|
});
|
|
1192
1386
|
var config = loadConfig();
|
|
1193
|
-
var
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
fs6.writeFileSync(CODEX_PID_FILE, String(process.pid), "utf-8");
|
|
1199
|
-
logStartup("Codex adapter");
|
|
1200
|
-
var { host, port, token } = resolveAdapterHub(config);
|
|
1201
|
-
var adapter = new CodexAdapter({
|
|
1202
|
-
command: config.codex?.command ?? "codex",
|
|
1203
|
-
hub: { host, port, token },
|
|
1204
|
-
onFirstConnect: (outcome) => {
|
|
1205
|
-
if (!process.send || !process.connected) return;
|
|
1206
|
-
process.send({ type: "codex-first-connect", ...outcome }, void 0, void 0, (err) => {
|
|
1207
|
-
if (err) logger.debug(`First-connect report not delivered: ${err.message}`);
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
});
|
|
1211
|
-
adapter.start();
|
|
1387
|
+
var supervised = process.argv.includes("--watch-stdin");
|
|
1388
|
+
var endpoint = controlEndpoint(CONFIG_DIR);
|
|
1389
|
+
var adapter;
|
|
1390
|
+
var release;
|
|
1212
1391
|
var exiting = false;
|
|
1213
1392
|
var shutdown = () => {
|
|
1214
1393
|
if (exiting) return;
|
|
1215
1394
|
exiting = true;
|
|
1216
|
-
adapter
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
process.exit(0);
|
|
1395
|
+
adapter?.stop();
|
|
1396
|
+
const exit = () => process.exit(0);
|
|
1397
|
+
setTimeout(exit, 3e3).unref();
|
|
1398
|
+
if (release) release().then(exit, exit);
|
|
1399
|
+
else exit();
|
|
1222
1400
|
};
|
|
1223
1401
|
process.on("SIGINT", shutdown);
|
|
1224
1402
|
process.on("SIGTERM", shutdown);
|
|
1225
|
-
if (
|
|
1403
|
+
if (supervised) {
|
|
1226
1404
|
process.stdin.on("end", shutdown);
|
|
1227
1405
|
process.stdin.on("close", shutdown);
|
|
1228
1406
|
process.stdin.resume();
|
|
1229
1407
|
}
|
|
1408
|
+
function send(message, then) {
|
|
1409
|
+
if (!process.send || !process.connected) {
|
|
1410
|
+
then?.();
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
process.send(message, void 0, void 0, (err) => {
|
|
1414
|
+
if (err) logger.debug(`Report to the parent not delivered: ${err.message}`);
|
|
1415
|
+
then?.();
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1419
|
+
async function takeLock() {
|
|
1420
|
+
let delay = WAIT_MIN_MS;
|
|
1421
|
+
let waitReason;
|
|
1422
|
+
for (; ; ) {
|
|
1423
|
+
const lock = await acquireLock(endpoint, {
|
|
1424
|
+
pid: process.pid,
|
|
1425
|
+
token: config.hub.token ?? "",
|
|
1426
|
+
onStop: () => {
|
|
1427
|
+
if (supervised) logger.warn("Stopped by claude-alarm codex stop; the hub will not start the Codex adapter again until the hub restarts");
|
|
1428
|
+
shutdown();
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
if (lock.kind === "owner") {
|
|
1432
|
+
release = lock.close;
|
|
1433
|
+
if (waitReason !== void 0) logger.info("Codex adapter took over: the other adapter has stopped");
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
if (!supervised) {
|
|
1437
|
+
if (lock.kind === "held") {
|
|
1438
|
+
logger.info(`Codex adapter already running (PID: ${lock.pid})`);
|
|
1439
|
+
send({ type: "codex-already-running", pid: lock.pid }, () => process.exit(0));
|
|
1440
|
+
} else {
|
|
1441
|
+
logger.error(`Codex adapter cannot start: ${lock.error}`);
|
|
1442
|
+
send({ type: "codex-lock-failed", error: lock.error }, () => process.exit(1));
|
|
1443
|
+
}
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
1446
|
+
const reason = lock.kind === "held" ? `held:${lock.pid}` : `unknown:${lock.error}`;
|
|
1447
|
+
if (reason !== waitReason) {
|
|
1448
|
+
waitReason = reason;
|
|
1449
|
+
if (lock.kind === "held") logger.info(`Another Codex adapter is running (PID: ${lock.pid}); this one takes over when it stops`);
|
|
1450
|
+
else logger.warn(`Codex adapter control endpoint is unavailable (${lock.error}); retrying`);
|
|
1451
|
+
}
|
|
1452
|
+
await sleep(delay);
|
|
1453
|
+
delay = Math.min(delay * 2, WAIT_MAX_MS);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
void takeLock().then((owned) => {
|
|
1457
|
+
if (!owned) return;
|
|
1458
|
+
logStartup("Codex adapter");
|
|
1459
|
+
const { host, port, token } = resolveAdapterHub(config);
|
|
1460
|
+
adapter = new CodexAdapter({
|
|
1461
|
+
command: config.codex?.command ?? "codex",
|
|
1462
|
+
hub: { host, port, token },
|
|
1463
|
+
onFirstConnect: (outcome) => send({ type: "codex-first-connect", ...outcome })
|
|
1464
|
+
});
|
|
1465
|
+
adapter.start();
|
|
1466
|
+
});
|
|
1230
1467
|
//# sourceMappingURL=main.js.map
|