@linzumi/cli 0.0.107-beta → 0.0.108-beta
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 +1 -1
- package/dist/index.js +1802 -168
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1439,6 +1439,57 @@ function parseKandanChatAttachments(attachments) {
|
|
|
1439
1439
|
];
|
|
1440
1440
|
});
|
|
1441
1441
|
}
|
|
1442
|
+
function kandanChatAttachmentsToWire(attachments) {
|
|
1443
|
+
return attachments.map((attachment) => ({
|
|
1444
|
+
...attachment.id === void 0 ? {} : { id: attachment.id },
|
|
1445
|
+
...attachment.kind === void 0 ? {} : { kind: attachment.kind },
|
|
1446
|
+
...attachment.fileName === void 0 ? {} : { file_name: attachment.fileName },
|
|
1447
|
+
...attachment.contentType === void 0 ? {} : { content_type: attachment.contentType },
|
|
1448
|
+
...attachment.sizeBytes === void 0 ? {} : { size_bytes: attachment.sizeBytes },
|
|
1449
|
+
...attachment.url === void 0 ? {} : { url: attachment.url }
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1452
|
+
function serializeKandanChatEventForQueue(event) {
|
|
1453
|
+
return {
|
|
1454
|
+
seq: event.seq,
|
|
1455
|
+
type: event.type,
|
|
1456
|
+
...event.actorKind === void 0 ? {} : { actor_kind: event.actorKind },
|
|
1457
|
+
...event.actorSlug === void 0 ? {} : { actor_slug: event.actorSlug },
|
|
1458
|
+
...event.actorUserId === void 0 ? {} : { actor_user_id: event.actorUserId },
|
|
1459
|
+
...event.threadId === void 0 ? {} : { thread_id: event.threadId },
|
|
1460
|
+
...event.threadTitle === void 0 ? {} : { thread_title: event.threadTitle },
|
|
1461
|
+
...event.replyToSeq === void 0 ? {} : { reply_to_seq: event.replyToSeq },
|
|
1462
|
+
...event.localRunnerEventType === void 0 ? {} : { local_runner_event_type: event.localRunnerEventType },
|
|
1463
|
+
body: event.body,
|
|
1464
|
+
attachments: kandanChatAttachmentsToWire(event.attachments)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
function deserializeKandanChatEventFromQueue(value) {
|
|
1468
|
+
const payload = objectValue(value);
|
|
1469
|
+
if (payload === void 0) {
|
|
1470
|
+
return void 0;
|
|
1471
|
+
}
|
|
1472
|
+
const seq = integerValue(payload.seq);
|
|
1473
|
+
const type = stringValue(payload.type);
|
|
1474
|
+
if (seq === void 0 || type === void 0) {
|
|
1475
|
+
return void 0;
|
|
1476
|
+
}
|
|
1477
|
+
return {
|
|
1478
|
+
seq,
|
|
1479
|
+
type,
|
|
1480
|
+
actorKind: stringValue(payload.actor_kind),
|
|
1481
|
+
actorSlug: stringValue(payload.actor_slug),
|
|
1482
|
+
actorUserId: integerValue(payload.actor_user_id),
|
|
1483
|
+
threadId: stringValue(payload.thread_id),
|
|
1484
|
+
threadTitle: stringValue(payload.thread_title),
|
|
1485
|
+
replyToSeq: integerValue(payload.reply_to_seq),
|
|
1486
|
+
localRunnerEventType: stringValue(payload.local_runner_event_type),
|
|
1487
|
+
body: stringValue(payload.body) ?? "",
|
|
1488
|
+
attachments: parseKandanChatAttachments(
|
|
1489
|
+
arrayValue(payload.attachments) ?? []
|
|
1490
|
+
)
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1442
1493
|
function isCodexAuthoredEvent(event) {
|
|
1443
1494
|
if (event.localRunnerEventType !== void 0) {
|
|
1444
1495
|
return true;
|
|
@@ -3716,7 +3767,9 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
|
|
|
3716
3767
|
controlBacklogFetchesInFlight.add(topic);
|
|
3717
3768
|
void fetchRemainingControlBacklog(topic).catch(() => void 0).finally(() => controlBacklogFetchesInFlight.delete(topic));
|
|
3718
3769
|
};
|
|
3770
|
+
const lastRejoinReplies = /* @__PURE__ */ new Map();
|
|
3719
3771
|
const replayJoins = async () => {
|
|
3772
|
+
lastRejoinReplies.clear();
|
|
3720
3773
|
for (const [topic, registrations] of joins) {
|
|
3721
3774
|
const reply = await pushOnOpenSocket(
|
|
3722
3775
|
topic,
|
|
@@ -3731,6 +3784,9 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
|
|
|
3731
3784
|
if (!isJoinReply(reply)) {
|
|
3732
3785
|
throw new Error(`phoenix rejoin failed for ${topic}`);
|
|
3733
3786
|
}
|
|
3787
|
+
if (isJsonObject(reply.response)) {
|
|
3788
|
+
lastRejoinReplies.set(topic, reply.response);
|
|
3789
|
+
}
|
|
3734
3790
|
}
|
|
3735
3791
|
};
|
|
3736
3792
|
const scheduleReconnect = () => {
|
|
@@ -3832,8 +3888,9 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url, protoc
|
|
|
3832
3888
|
startHeartbeat();
|
|
3833
3889
|
markReady();
|
|
3834
3890
|
if (state.connectionGeneration > 1) {
|
|
3891
|
+
const rejoinReplies = new Map(lastRejoinReplies);
|
|
3835
3892
|
reconnectCallbacks.forEach((callback) => {
|
|
3836
|
-
void Promise.resolve(callback()).catch(() => void 0);
|
|
3893
|
+
void Promise.resolve(callback(rejoinReplies)).catch(() => void 0);
|
|
3837
3894
|
});
|
|
3838
3895
|
}
|
|
3839
3896
|
} catch (error) {
|
|
@@ -6900,6 +6957,11 @@ function createSessionPipelineBuilders(host) {
|
|
|
6900
6957
|
channel: wire.channelSlug,
|
|
6901
6958
|
thread_id: wire.kandanThreadId,
|
|
6902
6959
|
stream_key: streamKey,
|
|
6960
|
+
// SEAM 2: present the held lease epoch on the turn-execution write so
|
|
6961
|
+
// the server fences a stale, half-connected runner's duplicate output
|
|
6962
|
+
// (validate_turn_execution_lease_epoch). Omitted when unknown (the
|
|
6963
|
+
// server fence fails open on a nil epoch).
|
|
6964
|
+
...wire.leaseEpoch === void 0 ? {} : { lease_epoch: wire.leaseEpoch },
|
|
6903
6965
|
body: entryBody(data),
|
|
6904
6966
|
payload: {
|
|
6905
6967
|
...host.runnerPayload(
|
|
@@ -6946,7 +7008,14 @@ function createSessionPipelineBuilders(host) {
|
|
|
6946
7008
|
seq,
|
|
6947
7009
|
status,
|
|
6948
7010
|
...stringValue(data.reason) === void 0 ? {} : { reason: stringValue(data.reason) },
|
|
6949
|
-
...stringValue(data.turn_id) === void 0 ? {} : { turn_id: stringValue(data.turn_id) }
|
|
7011
|
+
...stringValue(data.turn_id) === void 0 ? {} : { turn_id: stringValue(data.turn_id) },
|
|
7012
|
+
// SEAM 2 (blocker #5): echo the held lease_epoch (snake_case) on the
|
|
7013
|
+
// message_state report too, not only the turn-EXECUTION write, so the
|
|
7014
|
+
// server fence (validate_turn_execution_lease_epoch) sees the current
|
|
7015
|
+
// epoch on the terminal report and does NOT fail open for a stale,
|
|
7016
|
+
// half-connected runner. Omitted for a pre-lease/old server (nil fence
|
|
7017
|
+
// fails open exactly as before).
|
|
7018
|
+
...wire.leaseEpoch === void 0 ? {} : { lease_epoch: wire.leaseEpoch }
|
|
6950
7019
|
}
|
|
6951
7020
|
};
|
|
6952
7021
|
}
|
|
@@ -8772,6 +8841,9 @@ function runnerActionErrorMessage(error) {
|
|
|
8772
8841
|
return userFacingBaseErrorMessage(message);
|
|
8773
8842
|
}
|
|
8774
8843
|
function userFacingBaseErrorMessage(message) {
|
|
8844
|
+
if (message.includes("daily_token_limit_exceeded") || message.includes("free Linzumi tokens")) {
|
|
8845
|
+
return "You've used all of your free Linzumi tokens for today. Your free allowance resets at midnight UTC - check back then, or email support@linzumi.com if you need more.";
|
|
8846
|
+
}
|
|
8775
8847
|
if (message === "phoenix join failed: workspace_not_found") {
|
|
8776
8848
|
return "Linzumi could not find this workspace. The command may be stale or copied from a different signup session. Return to setup and copy the latest command.";
|
|
8777
8849
|
}
|
|
@@ -9107,7 +9179,8 @@ function createChannelSessionPipeline(args, state, payloadContext) {
|
|
|
9107
9179
|
channelSlug: session.channelSlug,
|
|
9108
9180
|
kandanThreadId: state.kandanThreadId,
|
|
9109
9181
|
codexThreadId: state.codexThreadId,
|
|
9110
|
-
rootSeq: state.rootSeq
|
|
9182
|
+
rootSeq: state.rootSeq,
|
|
9183
|
+
leaseEpoch: state.kandanThreadId === void 0 ? void 0 : args.leaseEpochFor?.(state.kandanThreadId)
|
|
9111
9184
|
}),
|
|
9112
9185
|
runnerPayload: (eventType, codexThreadId, sourceMessageSeq, extra) => localRunnerPayload(
|
|
9113
9186
|
args.options,
|
|
@@ -11055,6 +11128,13 @@ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
|
|
|
11055
11128
|
state.turn = { status: "active", turnId, queuedSeq: next.seq };
|
|
11056
11129
|
rememberTurnReplyTarget(state, turnId, next.seq);
|
|
11057
11130
|
args.log("codex.turn_started", { turn_id: turnId });
|
|
11131
|
+
if (state.kandanThreadId !== void 0) {
|
|
11132
|
+
args.options.onLiveSourceSeqAccepted?.({
|
|
11133
|
+
channel: args.options.channelSession.channelSlug,
|
|
11134
|
+
threadId: state.kandanThreadId,
|
|
11135
|
+
sourceSeq: next.seq
|
|
11136
|
+
});
|
|
11137
|
+
}
|
|
11058
11138
|
if (interruptAfterStart) {
|
|
11059
11139
|
await recoverInterruptedCodexTurn(
|
|
11060
11140
|
args,
|
|
@@ -19998,7 +20078,7 @@ var linzumiCliVersion, linzumiCliVersionText;
|
|
|
19998
20078
|
var init_version = __esm({
|
|
19999
20079
|
"src/version.ts"() {
|
|
20000
20080
|
"use strict";
|
|
20001
|
-
linzumiCliVersion = "0.0.
|
|
20081
|
+
linzumiCliVersion = "0.0.108-beta";
|
|
20002
20082
|
linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
20003
20083
|
}
|
|
20004
20084
|
});
|
|
@@ -23586,33 +23666,901 @@ var init_remoteCodexSandboxRunner = __esm({
|
|
|
23586
23666
|
}
|
|
23587
23667
|
});
|
|
23588
23668
|
|
|
23669
|
+
// src/durableInboundQueue.ts
|
|
23670
|
+
import {
|
|
23671
|
+
closeSync as closeSync3,
|
|
23672
|
+
fsyncSync,
|
|
23673
|
+
mkdirSync as mkdirSync13,
|
|
23674
|
+
openSync as openSync4,
|
|
23675
|
+
readFileSync as readFileSync17,
|
|
23676
|
+
renameSync as renameSync4,
|
|
23677
|
+
writeSync as writeSync2
|
|
23678
|
+
} from "node:fs";
|
|
23679
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
23680
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
23681
|
+
import { homedir as homedir14 } from "node:os";
|
|
23682
|
+
function crc32Hex(bytes) {
|
|
23683
|
+
let crc = 4294967295;
|
|
23684
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
23685
|
+
crc ^= bytes[i];
|
|
23686
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
23687
|
+
const mask = -(crc & 1);
|
|
23688
|
+
crc = crc >>> 1 ^ 3988292384 & mask;
|
|
23689
|
+
}
|
|
23690
|
+
}
|
|
23691
|
+
return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
|
|
23692
|
+
}
|
|
23693
|
+
function frameRecord(record) {
|
|
23694
|
+
const json = JSON.stringify(record);
|
|
23695
|
+
const bytes = Buffer.from(json, "utf8");
|
|
23696
|
+
return `${bytes.length}:${crc32Hex(bytes)}:${json}
|
|
23697
|
+
`;
|
|
23698
|
+
}
|
|
23699
|
+
function parseFramedLine(line) {
|
|
23700
|
+
const firstColon = line.indexOf(":");
|
|
23701
|
+
const secondColon = line.indexOf(":", firstColon + 1);
|
|
23702
|
+
if (firstColon <= 0 || secondColon <= firstColon) {
|
|
23703
|
+
return void 0;
|
|
23704
|
+
}
|
|
23705
|
+
const declaredLen = Number.parseInt(line.slice(0, firstColon), 10);
|
|
23706
|
+
const checksum = line.slice(firstColon + 1, secondColon);
|
|
23707
|
+
const json = line.slice(secondColon + 1);
|
|
23708
|
+
if (!Number.isInteger(declaredLen) || declaredLen <= 0) {
|
|
23709
|
+
return void 0;
|
|
23710
|
+
}
|
|
23711
|
+
const bytes = Buffer.from(json, "utf8");
|
|
23712
|
+
if (bytes.length !== declaredLen) {
|
|
23713
|
+
return void 0;
|
|
23714
|
+
}
|
|
23715
|
+
if (crc32Hex(bytes) !== checksum) {
|
|
23716
|
+
return void 0;
|
|
23717
|
+
}
|
|
23718
|
+
let parsed;
|
|
23719
|
+
try {
|
|
23720
|
+
parsed = JSON.parse(json);
|
|
23721
|
+
} catch {
|
|
23722
|
+
return void 0;
|
|
23723
|
+
}
|
|
23724
|
+
if (!isJsonObject(parsed)) {
|
|
23725
|
+
return void 0;
|
|
23726
|
+
}
|
|
23727
|
+
return coerceRecord(parsed);
|
|
23728
|
+
}
|
|
23729
|
+
function coerceRecord(value) {
|
|
23730
|
+
const op = value.op;
|
|
23731
|
+
const sourceSeq = value.sourceSeq;
|
|
23732
|
+
if (typeof sourceSeq !== "number" || !Number.isInteger(sourceSeq)) {
|
|
23733
|
+
return void 0;
|
|
23734
|
+
}
|
|
23735
|
+
if (op === "ack") {
|
|
23736
|
+
return { op: "ack", sourceSeq };
|
|
23737
|
+
}
|
|
23738
|
+
if (op === "dead") {
|
|
23739
|
+
return { op: "dead", sourceSeq };
|
|
23740
|
+
}
|
|
23741
|
+
if (op === "attempt") {
|
|
23742
|
+
const attempts = value.attempts;
|
|
23743
|
+
if (typeof attempts !== "number" || !Number.isInteger(attempts)) {
|
|
23744
|
+
return void 0;
|
|
23745
|
+
}
|
|
23746
|
+
return { op: "attempt", sourceSeq, attempts };
|
|
23747
|
+
}
|
|
23748
|
+
if (op === "enqueue") {
|
|
23749
|
+
const kind = value.kind === "replay" ? "replay" : "live";
|
|
23750
|
+
const payload = isJsonObject(value.payload) ? value.payload : void 0;
|
|
23751
|
+
const enqueuedAtMs = typeof value.enqueuedAtMs === "number" && Number.isFinite(value.enqueuedAtMs) ? value.enqueuedAtMs : void 0;
|
|
23752
|
+
if (payload === void 0 || enqueuedAtMs === void 0) {
|
|
23753
|
+
return void 0;
|
|
23754
|
+
}
|
|
23755
|
+
return { op: "enqueue", sourceSeq, kind, payload, enqueuedAtMs };
|
|
23756
|
+
}
|
|
23757
|
+
return void 0;
|
|
23758
|
+
}
|
|
23759
|
+
function foldQueueRecords(lines) {
|
|
23760
|
+
const enqueued = /* @__PURE__ */ new Map();
|
|
23761
|
+
const dead = /* @__PURE__ */ new Set();
|
|
23762
|
+
let highWater;
|
|
23763
|
+
let tornTail = false;
|
|
23764
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
23765
|
+
const line = lines[i];
|
|
23766
|
+
if (line === "") {
|
|
23767
|
+
continue;
|
|
23768
|
+
}
|
|
23769
|
+
const record = parseFramedLine(line);
|
|
23770
|
+
if (record === void 0) {
|
|
23771
|
+
tornTail = true;
|
|
23772
|
+
break;
|
|
23773
|
+
}
|
|
23774
|
+
switch (record.op) {
|
|
23775
|
+
case "enqueue": {
|
|
23776
|
+
highWater = highWater === void 0 ? record.sourceSeq : Math.max(highWater, record.sourceSeq);
|
|
23777
|
+
if (dead.has(record.sourceSeq)) {
|
|
23778
|
+
break;
|
|
23779
|
+
}
|
|
23780
|
+
enqueued.set(record.sourceSeq, {
|
|
23781
|
+
sourceSeq: record.sourceSeq,
|
|
23782
|
+
kind: record.kind,
|
|
23783
|
+
payload: record.payload,
|
|
23784
|
+
enqueuedAtMs: record.enqueuedAtMs,
|
|
23785
|
+
attempts: 0
|
|
23786
|
+
});
|
|
23787
|
+
break;
|
|
23788
|
+
}
|
|
23789
|
+
case "ack": {
|
|
23790
|
+
enqueued.delete(record.sourceSeq);
|
|
23791
|
+
highWater = highWater === void 0 ? record.sourceSeq : Math.max(highWater, record.sourceSeq);
|
|
23792
|
+
break;
|
|
23793
|
+
}
|
|
23794
|
+
case "dead": {
|
|
23795
|
+
enqueued.delete(record.sourceSeq);
|
|
23796
|
+
dead.add(record.sourceSeq);
|
|
23797
|
+
highWater = highWater === void 0 ? record.sourceSeq : Math.max(highWater, record.sourceSeq);
|
|
23798
|
+
break;
|
|
23799
|
+
}
|
|
23800
|
+
case "attempt": {
|
|
23801
|
+
const existing = enqueued.get(record.sourceSeq);
|
|
23802
|
+
if (existing !== void 0) {
|
|
23803
|
+
enqueued.set(record.sourceSeq, {
|
|
23804
|
+
...existing,
|
|
23805
|
+
attempts: record.attempts
|
|
23806
|
+
});
|
|
23807
|
+
}
|
|
23808
|
+
break;
|
|
23809
|
+
}
|
|
23810
|
+
}
|
|
23811
|
+
}
|
|
23812
|
+
const entries = [...enqueued.values()].sort(
|
|
23813
|
+
(a, b) => a.sourceSeq - b.sourceSeq
|
|
23814
|
+
);
|
|
23815
|
+
return { entries, highWaterSourceSeq: highWater, tornTail };
|
|
23816
|
+
}
|
|
23817
|
+
function shouldDeadLetter(entry, policy, nowMs) {
|
|
23818
|
+
if (policy.maxAttempts > 0 && entry.attempts >= policy.maxAttempts) {
|
|
23819
|
+
return true;
|
|
23820
|
+
}
|
|
23821
|
+
if (policy.maxAgeMs > 0 && nowMs - entry.enqueuedAtMs >= policy.maxAgeMs) {
|
|
23822
|
+
return true;
|
|
23823
|
+
}
|
|
23824
|
+
return false;
|
|
23825
|
+
}
|
|
23826
|
+
function createDurableInboundThreadQueue(path2, log2, deps = {}) {
|
|
23827
|
+
const fsyncFd = deps.fsyncFd ?? ((fd) => fsyncSync(fd));
|
|
23828
|
+
const fsyncDirFd = deps.fsyncDirFd ?? ((fd) => fsyncSync(fd));
|
|
23829
|
+
const folded = foldFile(path2, log2);
|
|
23830
|
+
const liveEntries = /* @__PURE__ */ new Map();
|
|
23831
|
+
for (const entry of folded.entries) {
|
|
23832
|
+
liveEntries.set(entry.sourceSeq, entry);
|
|
23833
|
+
}
|
|
23834
|
+
let highWater = folded.highWaterSourceSeq;
|
|
23835
|
+
let tornTail = folded.tornTail;
|
|
23836
|
+
const append = (record) => {
|
|
23837
|
+
try {
|
|
23838
|
+
mkdirSync13(dirname14(path2), { recursive: true });
|
|
23839
|
+
const fd = openSync4(path2, "a");
|
|
23840
|
+
try {
|
|
23841
|
+
writeSync2(fd, frameRecord(record), null, "utf8");
|
|
23842
|
+
fsyncFd(fd);
|
|
23843
|
+
} finally {
|
|
23844
|
+
closeSync3(fd);
|
|
23845
|
+
}
|
|
23846
|
+
} catch (error) {
|
|
23847
|
+
log2("inbound_queue.append_failed", {
|
|
23848
|
+
path: path2,
|
|
23849
|
+
op: record.op,
|
|
23850
|
+
sourceSeq: record.sourceSeq,
|
|
23851
|
+
message: error instanceof Error ? error.message : String(error)
|
|
23852
|
+
});
|
|
23853
|
+
}
|
|
23854
|
+
};
|
|
23855
|
+
const fsyncDir = () => {
|
|
23856
|
+
try {
|
|
23857
|
+
const dirFd = openSync4(dirname14(path2), "r");
|
|
23858
|
+
try {
|
|
23859
|
+
fsyncDirFd(dirFd);
|
|
23860
|
+
} finally {
|
|
23861
|
+
closeSync3(dirFd);
|
|
23862
|
+
}
|
|
23863
|
+
} catch {
|
|
23864
|
+
}
|
|
23865
|
+
};
|
|
23866
|
+
return {
|
|
23867
|
+
enqueue: (sourceSeq, kind, payload, nowMs) => {
|
|
23868
|
+
if (highWater !== void 0 && sourceSeq <= highWater) {
|
|
23869
|
+
return { accepted: false, reason: "already_seen" };
|
|
23870
|
+
}
|
|
23871
|
+
const entry = {
|
|
23872
|
+
sourceSeq,
|
|
23873
|
+
kind,
|
|
23874
|
+
payload,
|
|
23875
|
+
enqueuedAtMs: nowMs,
|
|
23876
|
+
attempts: 0
|
|
23877
|
+
};
|
|
23878
|
+
append({ op: "enqueue", sourceSeq, kind, payload, enqueuedAtMs: nowMs });
|
|
23879
|
+
liveEntries.set(sourceSeq, entry);
|
|
23880
|
+
highWater = sourceSeq;
|
|
23881
|
+
return { accepted: true, entry };
|
|
23882
|
+
},
|
|
23883
|
+
entries: () => [...liveEntries.values()].sort((a, b) => a.sourceSeq - b.sourceSeq),
|
|
23884
|
+
head: () => {
|
|
23885
|
+
let head;
|
|
23886
|
+
for (const entry of liveEntries.values()) {
|
|
23887
|
+
if (head === void 0 || entry.sourceSeq < head.sourceSeq) {
|
|
23888
|
+
head = entry;
|
|
23889
|
+
}
|
|
23890
|
+
}
|
|
23891
|
+
return head;
|
|
23892
|
+
},
|
|
23893
|
+
recordAttempt: (sourceSeq) => {
|
|
23894
|
+
const existing = liveEntries.get(sourceSeq);
|
|
23895
|
+
if (existing === void 0) {
|
|
23896
|
+
return 0;
|
|
23897
|
+
}
|
|
23898
|
+
const attempts = existing.attempts + 1;
|
|
23899
|
+
liveEntries.set(sourceSeq, { ...existing, attempts });
|
|
23900
|
+
append({ op: "attempt", sourceSeq, attempts });
|
|
23901
|
+
return attempts;
|
|
23902
|
+
},
|
|
23903
|
+
ack: (sourceSeq) => {
|
|
23904
|
+
if (!liveEntries.has(sourceSeq)) {
|
|
23905
|
+
return;
|
|
23906
|
+
}
|
|
23907
|
+
append({ op: "ack", sourceSeq });
|
|
23908
|
+
liveEntries.delete(sourceSeq);
|
|
23909
|
+
},
|
|
23910
|
+
deadLetter: (sourceSeq) => {
|
|
23911
|
+
const entry = liveEntries.get(sourceSeq);
|
|
23912
|
+
if (entry === void 0) {
|
|
23913
|
+
return void 0;
|
|
23914
|
+
}
|
|
23915
|
+
append({ op: "dead", sourceSeq });
|
|
23916
|
+
liveEntries.delete(sourceSeq);
|
|
23917
|
+
log2("inbound_queue.dead_lettered", {
|
|
23918
|
+
path: path2,
|
|
23919
|
+
sourceSeq,
|
|
23920
|
+
attempts: entry.attempts,
|
|
23921
|
+
ageMs: Date.now() - entry.enqueuedAtMs
|
|
23922
|
+
});
|
|
23923
|
+
return entry;
|
|
23924
|
+
},
|
|
23925
|
+
highWaterSourceSeq: () => highWater,
|
|
23926
|
+
tornTail: () => tornTail,
|
|
23927
|
+
reconciledTornTail: () => {
|
|
23928
|
+
tornTail = false;
|
|
23929
|
+
},
|
|
23930
|
+
compact: () => {
|
|
23931
|
+
const records = [...liveEntries.values()].sort((a, b) => a.sourceSeq - b.sourceSeq).flatMap((entry) => {
|
|
23932
|
+
const out = [
|
|
23933
|
+
{
|
|
23934
|
+
op: "enqueue",
|
|
23935
|
+
sourceSeq: entry.sourceSeq,
|
|
23936
|
+
kind: entry.kind,
|
|
23937
|
+
payload: entry.payload,
|
|
23938
|
+
enqueuedAtMs: entry.enqueuedAtMs
|
|
23939
|
+
}
|
|
23940
|
+
];
|
|
23941
|
+
if (entry.attempts > 0) {
|
|
23942
|
+
out.push({
|
|
23943
|
+
op: "attempt",
|
|
23944
|
+
sourceSeq: entry.sourceSeq,
|
|
23945
|
+
attempts: entry.attempts
|
|
23946
|
+
});
|
|
23947
|
+
}
|
|
23948
|
+
return out;
|
|
23949
|
+
});
|
|
23950
|
+
if (highWater !== void 0 && !liveEntries.has(highWater)) {
|
|
23951
|
+
records.push({ op: "ack", sourceSeq: highWater });
|
|
23952
|
+
}
|
|
23953
|
+
try {
|
|
23954
|
+
mkdirSync13(dirname14(path2), { recursive: true });
|
|
23955
|
+
const tmpPath = `${path2}.tmp`;
|
|
23956
|
+
const tmpFd = openSync4(tmpPath, "w");
|
|
23957
|
+
try {
|
|
23958
|
+
writeSync2(tmpFd, records.map(frameRecord).join(""), null, "utf8");
|
|
23959
|
+
fsyncFd(tmpFd);
|
|
23960
|
+
} finally {
|
|
23961
|
+
closeSync3(tmpFd);
|
|
23962
|
+
}
|
|
23963
|
+
renameSync4(tmpPath, path2);
|
|
23964
|
+
fsyncDir();
|
|
23965
|
+
} catch (error) {
|
|
23966
|
+
log2("inbound_queue.compact_failed", {
|
|
23967
|
+
path: path2,
|
|
23968
|
+
message: error instanceof Error ? error.message : String(error)
|
|
23969
|
+
});
|
|
23970
|
+
}
|
|
23971
|
+
}
|
|
23972
|
+
};
|
|
23973
|
+
}
|
|
23974
|
+
function foldFile(path2, log2) {
|
|
23975
|
+
let raw;
|
|
23976
|
+
try {
|
|
23977
|
+
raw = readFileSync17(path2, "utf8");
|
|
23978
|
+
} catch (error) {
|
|
23979
|
+
if (error?.code === "ENOENT") {
|
|
23980
|
+
return { entries: [], highWaterSourceSeq: void 0, tornTail: false };
|
|
23981
|
+
}
|
|
23982
|
+
log2("inbound_queue.read_failed", {
|
|
23983
|
+
path: path2,
|
|
23984
|
+
message: error instanceof Error ? error.message : String(error)
|
|
23985
|
+
});
|
|
23986
|
+
return { entries: [], highWaterSourceSeq: void 0, tornTail: false };
|
|
23987
|
+
}
|
|
23988
|
+
const folded = foldQueueRecords(raw.split("\n"));
|
|
23989
|
+
if (folded.tornTail) {
|
|
23990
|
+
log2("inbound_queue.torn_tail_detected", {
|
|
23991
|
+
path: path2,
|
|
23992
|
+
liveEntries: folded.entries.length,
|
|
23993
|
+
highWaterSourceSeq: folded.highWaterSourceSeq ?? null
|
|
23994
|
+
});
|
|
23995
|
+
}
|
|
23996
|
+
return folded;
|
|
23997
|
+
}
|
|
23998
|
+
function durableInboundQueueDir() {
|
|
23999
|
+
const override = process.env.LINZUMI_INBOUND_QUEUE_DIR?.trim();
|
|
24000
|
+
if (override !== void 0 && override !== "") {
|
|
24001
|
+
return override;
|
|
24002
|
+
}
|
|
24003
|
+
return join21(homedir14(), ".linzumi", "inbound-queue");
|
|
24004
|
+
}
|
|
24005
|
+
function durableInboundQueuePath(workspace, channel, threadId) {
|
|
24006
|
+
const composite = `${workspace}:${channel}:${threadId}`;
|
|
24007
|
+
const sanitized = composite.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
24008
|
+
const digest = createHash6("sha256").update(composite).digest("hex").slice(0, 12);
|
|
24009
|
+
const stem = sanitized === "" ? "thread" : sanitized;
|
|
24010
|
+
return join21(durableInboundQueueDir(), `${stem}.${digest}.log`);
|
|
24011
|
+
}
|
|
24012
|
+
var defaultDeadLetterPolicy;
|
|
24013
|
+
var init_durableInboundQueue = __esm({
|
|
24014
|
+
"src/durableInboundQueue.ts"() {
|
|
24015
|
+
"use strict";
|
|
24016
|
+
init_protocol();
|
|
24017
|
+
defaultDeadLetterPolicy = {
|
|
24018
|
+
maxAttempts: 8,
|
|
24019
|
+
maxAgeMs: 30 * 60 * 1e3
|
|
24020
|
+
};
|
|
24021
|
+
}
|
|
24022
|
+
});
|
|
24023
|
+
|
|
24024
|
+
// src/appliedSourceSeqWatermark.ts
|
|
24025
|
+
function threadSourceSeqWatermarkKey(workspace, channel, threadId) {
|
|
24026
|
+
return `${workspace} ${channel} ${threadId}`;
|
|
24027
|
+
}
|
|
24028
|
+
function shouldApplySourceSeq(appliedSourceSeq, sourceSeq) {
|
|
24029
|
+
return appliedSourceSeq === void 0 || sourceSeq > appliedSourceSeq;
|
|
24030
|
+
}
|
|
24031
|
+
function commitAppliedSourceSeq(store, key, sourceSeq) {
|
|
24032
|
+
store.onAdvance(key, sourceSeq);
|
|
24033
|
+
store.flush();
|
|
24034
|
+
}
|
|
24035
|
+
function highestProcessedSourceSeq(rows) {
|
|
24036
|
+
let highest;
|
|
24037
|
+
for (const row of rows) {
|
|
24038
|
+
if (row.status === "processed" || row.status === "failed") {
|
|
24039
|
+
highest = highest === void 0 ? row.sourceSeq : Math.max(highest, row.sourceSeq);
|
|
24040
|
+
}
|
|
24041
|
+
}
|
|
24042
|
+
return highest;
|
|
24043
|
+
}
|
|
24044
|
+
function reconcileAppliedSourceSeq(localWatermark, serverRows) {
|
|
24045
|
+
const serverHigh = highestProcessedSourceSeq(serverRows);
|
|
24046
|
+
if (localWatermark === void 0) {
|
|
24047
|
+
return serverHigh;
|
|
24048
|
+
}
|
|
24049
|
+
if (serverHigh === void 0) {
|
|
24050
|
+
return localWatermark;
|
|
24051
|
+
}
|
|
24052
|
+
return Math.max(localWatermark, serverHigh);
|
|
24053
|
+
}
|
|
24054
|
+
function parseResumeReconcileEntries(value) {
|
|
24055
|
+
if (!Array.isArray(value)) {
|
|
24056
|
+
return [];
|
|
24057
|
+
}
|
|
24058
|
+
const entries = [];
|
|
24059
|
+
for (const item of value) {
|
|
24060
|
+
if (!isJsonObject(item)) {
|
|
24061
|
+
continue;
|
|
24062
|
+
}
|
|
24063
|
+
const threadId = item.threadId;
|
|
24064
|
+
if (typeof threadId !== "string") {
|
|
24065
|
+
continue;
|
|
24066
|
+
}
|
|
24067
|
+
const rawChannelId = item.channelId;
|
|
24068
|
+
const channelId = typeof rawChannelId === "string" ? rawChannelId : typeof rawChannelId === "number" ? String(rawChannelId) : "";
|
|
24069
|
+
const highest = item.highestProcessedSourceSeq;
|
|
24070
|
+
const leaseEpoch = item.leaseEpoch;
|
|
24071
|
+
entries.push({
|
|
24072
|
+
channelId,
|
|
24073
|
+
threadId,
|
|
24074
|
+
highestProcessedSourceSeq: typeof highest === "number" && Number.isInteger(highest) ? highest : void 0,
|
|
24075
|
+
leaseEpoch: typeof leaseEpoch === "number" && Number.isInteger(leaseEpoch) ? leaseEpoch : void 0
|
|
24076
|
+
});
|
|
24077
|
+
}
|
|
24078
|
+
return entries;
|
|
24079
|
+
}
|
|
24080
|
+
function resumeReconcileServerRows(entry) {
|
|
24081
|
+
if (entry.highestProcessedSourceSeq === void 0) {
|
|
24082
|
+
return [];
|
|
24083
|
+
}
|
|
24084
|
+
return [{ sourceSeq: entry.highestProcessedSourceSeq, status: "processed" }];
|
|
24085
|
+
}
|
|
24086
|
+
var init_appliedSourceSeqWatermark = __esm({
|
|
24087
|
+
"src/appliedSourceSeqWatermark.ts"() {
|
|
24088
|
+
"use strict";
|
|
24089
|
+
init_protocol();
|
|
24090
|
+
}
|
|
24091
|
+
});
|
|
24092
|
+
|
|
24093
|
+
// src/threadReconcilerLock.ts
|
|
24094
|
+
function createThreadReconcilerLock() {
|
|
24095
|
+
const tails = /* @__PURE__ */ new Map();
|
|
24096
|
+
const depth = /* @__PURE__ */ new Map();
|
|
24097
|
+
const runExclusive = (threadId, fn, deadlineMs) => {
|
|
24098
|
+
const prior = tails.get(threadId) ?? Promise.resolve();
|
|
24099
|
+
depth.set(threadId, (depth.get(threadId) ?? 0) + 1);
|
|
24100
|
+
const run = prior.catch(() => void 0).then(() => fn());
|
|
24101
|
+
let caller;
|
|
24102
|
+
if (deadlineMs === void 0 || deadlineMs <= 0) {
|
|
24103
|
+
caller = run;
|
|
24104
|
+
} else {
|
|
24105
|
+
caller = new Promise((resolve12, reject) => {
|
|
24106
|
+
const timer = setTimeout(() => {
|
|
24107
|
+
reject(new ReconcilerLockTimeoutError(threadId, deadlineMs));
|
|
24108
|
+
}, deadlineMs);
|
|
24109
|
+
timer.unref?.();
|
|
24110
|
+
run.then(
|
|
24111
|
+
(value) => {
|
|
24112
|
+
clearTimeout(timer);
|
|
24113
|
+
resolve12(value);
|
|
24114
|
+
},
|
|
24115
|
+
(error) => {
|
|
24116
|
+
clearTimeout(timer);
|
|
24117
|
+
reject(error);
|
|
24118
|
+
}
|
|
24119
|
+
);
|
|
24120
|
+
});
|
|
24121
|
+
}
|
|
24122
|
+
const settled = run.then(
|
|
24123
|
+
() => void 0,
|
|
24124
|
+
() => void 0
|
|
24125
|
+
);
|
|
24126
|
+
tails.set(threadId, settled);
|
|
24127
|
+
void settled.then(() => {
|
|
24128
|
+
const remaining = (depth.get(threadId) ?? 1) - 1;
|
|
24129
|
+
if (remaining <= 0) {
|
|
24130
|
+
depth.delete(threadId);
|
|
24131
|
+
if (tails.get(threadId) === settled) {
|
|
24132
|
+
tails.delete(threadId);
|
|
24133
|
+
}
|
|
24134
|
+
} else {
|
|
24135
|
+
depth.set(threadId, remaining);
|
|
24136
|
+
}
|
|
24137
|
+
});
|
|
24138
|
+
return caller;
|
|
24139
|
+
};
|
|
24140
|
+
return {
|
|
24141
|
+
runExclusive,
|
|
24142
|
+
isBusy: (threadId) => (depth.get(threadId) ?? 0) > 0,
|
|
24143
|
+
activeThreadCount: () => tails.size
|
|
24144
|
+
};
|
|
24145
|
+
}
|
|
24146
|
+
var ReconcilerLockTimeoutError;
|
|
24147
|
+
var init_threadReconcilerLock = __esm({
|
|
24148
|
+
"src/threadReconcilerLock.ts"() {
|
|
24149
|
+
"use strict";
|
|
24150
|
+
ReconcilerLockTimeoutError = class extends Error {
|
|
24151
|
+
threadId;
|
|
24152
|
+
timeoutMs;
|
|
24153
|
+
constructor(threadId, timeoutMs) {
|
|
24154
|
+
super(
|
|
24155
|
+
`reconciler section for thread ${threadId} exceeded ${timeoutMs}ms`
|
|
24156
|
+
);
|
|
24157
|
+
this.name = "ReconcilerLockTimeoutError";
|
|
24158
|
+
this.threadId = threadId;
|
|
24159
|
+
this.timeoutMs = timeoutMs;
|
|
24160
|
+
}
|
|
24161
|
+
};
|
|
24162
|
+
}
|
|
24163
|
+
});
|
|
24164
|
+
|
|
24165
|
+
// src/runnerLifecycleGuards.ts
|
|
24166
|
+
async function runDrain(steps, deadlineMs, log2) {
|
|
24167
|
+
let timedOut = false;
|
|
24168
|
+
const deadline = new Promise((resolve12) => {
|
|
24169
|
+
const timer = setTimeout(() => resolve12("timeout"), deadlineMs);
|
|
24170
|
+
timer.unref?.();
|
|
24171
|
+
});
|
|
24172
|
+
const ordered = (async () => {
|
|
24173
|
+
await steps.stopAccepting();
|
|
24174
|
+
await steps.checkpointCurrentTurn();
|
|
24175
|
+
await steps.flushInboundQueues();
|
|
24176
|
+
await steps.flushOutbox();
|
|
24177
|
+
return "done";
|
|
24178
|
+
})();
|
|
24179
|
+
const outcome = await Promise.race([
|
|
24180
|
+
ordered.then(() => "done"),
|
|
24181
|
+
deadline
|
|
24182
|
+
]);
|
|
24183
|
+
if (outcome === "timeout") {
|
|
24184
|
+
timedOut = true;
|
|
24185
|
+
log2("runner.drain_timed_out", { deadlineMs });
|
|
24186
|
+
} else {
|
|
24187
|
+
log2("runner.drain_complete", {});
|
|
24188
|
+
}
|
|
24189
|
+
return { drained: outcome === "done", timedOut };
|
|
24190
|
+
}
|
|
24191
|
+
function installDrainOnSigterm(args) {
|
|
24192
|
+
const exit = args.exit ?? ((code) => process.exit(code));
|
|
24193
|
+
const signals2 = args.signals ?? ["SIGTERM", "SIGINT"];
|
|
24194
|
+
let draining = false;
|
|
24195
|
+
const onSignal = (signal) => {
|
|
24196
|
+
if (draining) {
|
|
24197
|
+
args.log("runner.drain_signal_ignored", { signal });
|
|
24198
|
+
return;
|
|
24199
|
+
}
|
|
24200
|
+
draining = true;
|
|
24201
|
+
args.log("runner.drain_started", { signal });
|
|
24202
|
+
void runDrain(args.steps, args.deadlineMs, args.log).catch((error) => {
|
|
24203
|
+
args.log("runner.drain_failed", {
|
|
24204
|
+
message: error instanceof Error ? error.message : String(error)
|
|
24205
|
+
});
|
|
24206
|
+
return { drained: false, timedOut: false };
|
|
24207
|
+
}).finally(() => exit(0));
|
|
24208
|
+
};
|
|
24209
|
+
const handlers = signals2.map((signal) => {
|
|
24210
|
+
const handler = () => onSignal(signal);
|
|
24211
|
+
process.on(signal, handler);
|
|
24212
|
+
return { signal, handler };
|
|
24213
|
+
});
|
|
24214
|
+
return () => {
|
|
24215
|
+
for (const { signal, handler } of handlers) {
|
|
24216
|
+
process.off(signal, handler);
|
|
24217
|
+
}
|
|
24218
|
+
};
|
|
24219
|
+
}
|
|
24220
|
+
var init_runnerLifecycleGuards = __esm({
|
|
24221
|
+
"src/runnerLifecycleGuards.ts"() {
|
|
24222
|
+
"use strict";
|
|
24223
|
+
}
|
|
24224
|
+
});
|
|
24225
|
+
|
|
24226
|
+
// src/leaseEpochGuard.ts
|
|
24227
|
+
function createLeaseEpochRegistry() {
|
|
24228
|
+
const epochs = /* @__PURE__ */ new Map();
|
|
24229
|
+
return {
|
|
24230
|
+
current: (threadId) => epochs.get(threadId),
|
|
24231
|
+
adopt: (threadId, leaseEpoch) => {
|
|
24232
|
+
if (!Number.isInteger(leaseEpoch)) {
|
|
24233
|
+
return false;
|
|
24234
|
+
}
|
|
24235
|
+
const existing = epochs.get(threadId);
|
|
24236
|
+
if (existing !== void 0 && leaseEpoch < existing) {
|
|
24237
|
+
return false;
|
|
24238
|
+
}
|
|
24239
|
+
if (existing === leaseEpoch) {
|
|
24240
|
+
return false;
|
|
24241
|
+
}
|
|
24242
|
+
epochs.set(threadId, leaseEpoch);
|
|
24243
|
+
return true;
|
|
24244
|
+
},
|
|
24245
|
+
release: (threadId) => {
|
|
24246
|
+
epochs.delete(threadId);
|
|
24247
|
+
},
|
|
24248
|
+
snapshot: () => [...epochs.entries()].map(([threadId, leaseEpoch]) => ({
|
|
24249
|
+
threadId,
|
|
24250
|
+
leaseEpoch
|
|
24251
|
+
}))
|
|
24252
|
+
};
|
|
24253
|
+
}
|
|
24254
|
+
function isFencedClaim(heldEpoch, controlEpoch) {
|
|
24255
|
+
if (controlEpoch === void 0) {
|
|
24256
|
+
return false;
|
|
24257
|
+
}
|
|
24258
|
+
if (heldEpoch === void 0) {
|
|
24259
|
+
return false;
|
|
24260
|
+
}
|
|
24261
|
+
return controlEpoch < heldEpoch;
|
|
24262
|
+
}
|
|
24263
|
+
function readLeaseEpoch(value) {
|
|
24264
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
24265
|
+
return void 0;
|
|
24266
|
+
}
|
|
24267
|
+
const epoch = value.lease_epoch;
|
|
24268
|
+
return typeof epoch === "number" && Number.isInteger(epoch) ? epoch : void 0;
|
|
24269
|
+
}
|
|
24270
|
+
var init_leaseEpochGuard = __esm({
|
|
24271
|
+
"src/leaseEpochGuard.ts"() {
|
|
24272
|
+
"use strict";
|
|
24273
|
+
}
|
|
24274
|
+
});
|
|
24275
|
+
|
|
24276
|
+
// src/resumeKeystone.ts
|
|
24277
|
+
function commitLiveAcceptedSourceSeq(args) {
|
|
24278
|
+
const applied = args.store.initial(args.watermarkKey);
|
|
24279
|
+
if (!shouldApplySourceSeq(applied, args.sourceSeq)) {
|
|
24280
|
+
return false;
|
|
24281
|
+
}
|
|
24282
|
+
commitAppliedSourceSeq(args.store, args.watermarkKey, args.sourceSeq);
|
|
24283
|
+
return true;
|
|
24284
|
+
}
|
|
24285
|
+
async function applyResumeDispatchOutcome(args) {
|
|
24286
|
+
const policy = args.policy ?? defaultDeadLetterPolicy;
|
|
24287
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
24288
|
+
if (args.outcome === "dispatched") {
|
|
24289
|
+
commitAppliedSourceSeq(args.store, args.watermarkKey, args.sourceSeq);
|
|
24290
|
+
args.queue.ack(args.sourceSeq);
|
|
24291
|
+
return;
|
|
24292
|
+
}
|
|
24293
|
+
if (args.outcome === "ignored") {
|
|
24294
|
+
args.queue.ack(args.sourceSeq);
|
|
24295
|
+
return;
|
|
24296
|
+
}
|
|
24297
|
+
if (args.outcome === "permanent_failed") {
|
|
24298
|
+
const attempts2 = findEntryAttempts(args.queue, args.sourceSeq);
|
|
24299
|
+
args.queue.deadLetter(args.sourceSeq);
|
|
24300
|
+
await args.onDeadLetter?.({
|
|
24301
|
+
threadId: args.threadId,
|
|
24302
|
+
sourceSeq: args.sourceSeq,
|
|
24303
|
+
reason: "permanent",
|
|
24304
|
+
attempts: attempts2,
|
|
24305
|
+
alreadyPublished: true
|
|
24306
|
+
});
|
|
24307
|
+
return;
|
|
24308
|
+
}
|
|
24309
|
+
const attempts = args.queue.recordAttempt(args.sourceSeq);
|
|
24310
|
+
const entry = args.queue.entries().find((candidate) => candidate.sourceSeq === args.sourceSeq);
|
|
24311
|
+
if (entry !== void 0 && shouldDeadLetter(entry, policy, nowMs)) {
|
|
24312
|
+
args.log("resume_keystone.dead_lettered", {
|
|
24313
|
+
threadId: args.threadId,
|
|
24314
|
+
sourceSeq: args.sourceSeq,
|
|
24315
|
+
attempts
|
|
24316
|
+
});
|
|
24317
|
+
args.queue.deadLetter(args.sourceSeq);
|
|
24318
|
+
await args.onDeadLetter?.({
|
|
24319
|
+
threadId: args.threadId,
|
|
24320
|
+
sourceSeq: args.sourceSeq,
|
|
24321
|
+
reason: "exhausted",
|
|
24322
|
+
attempts,
|
|
24323
|
+
alreadyPublished: false
|
|
24324
|
+
});
|
|
24325
|
+
}
|
|
24326
|
+
}
|
|
24327
|
+
function findEntryAttempts(queue, sourceSeq) {
|
|
24328
|
+
return queue.entries().find((candidate) => candidate.sourceSeq === sourceSeq)?.attempts ?? 0;
|
|
24329
|
+
}
|
|
24330
|
+
async function drainQueueHeadInOrder(args) {
|
|
24331
|
+
const dispatched = [];
|
|
24332
|
+
let headOutcome = "ignored";
|
|
24333
|
+
let firstOutcomeRecorded = false;
|
|
24334
|
+
let guard = args.queue.entries().length + 1;
|
|
24335
|
+
while (guard > 0) {
|
|
24336
|
+
guard -= 1;
|
|
24337
|
+
const head = args.queue.head();
|
|
24338
|
+
if (head === void 0) {
|
|
24339
|
+
break;
|
|
24340
|
+
}
|
|
24341
|
+
const applied = args.store.initial(args.watermarkKey);
|
|
24342
|
+
if (!shouldApplySourceSeq(applied, head.sourceSeq)) {
|
|
24343
|
+
args.queue.ack(head.sourceSeq);
|
|
24344
|
+
continue;
|
|
24345
|
+
}
|
|
24346
|
+
if (args.onlyDispatchUnattempted && args.serverRows.length === 0 && head.attempts > 0) {
|
|
24347
|
+
args.log("resume_keystone.attempted_entry_withheld", {
|
|
24348
|
+
threadId: args.threadId,
|
|
24349
|
+
sourceSeq: head.sourceSeq,
|
|
24350
|
+
attempts: head.attempts
|
|
24351
|
+
});
|
|
24352
|
+
args.queue.deadLetter(head.sourceSeq);
|
|
24353
|
+
await args.onDeadLetter?.({
|
|
24354
|
+
threadId: args.threadId,
|
|
24355
|
+
sourceSeq: head.sourceSeq,
|
|
24356
|
+
reason: "withheld",
|
|
24357
|
+
attempts: head.attempts,
|
|
24358
|
+
alreadyPublished: false
|
|
24359
|
+
});
|
|
24360
|
+
continue;
|
|
24361
|
+
}
|
|
24362
|
+
const outcome = await args.dispatch(head.sourceSeq, head.payload);
|
|
24363
|
+
await applyResumeDispatchOutcome({
|
|
24364
|
+
queue: args.queue,
|
|
24365
|
+
store: args.store,
|
|
24366
|
+
watermarkKey: args.watermarkKey,
|
|
24367
|
+
sourceSeq: head.sourceSeq,
|
|
24368
|
+
threadId: args.threadId,
|
|
24369
|
+
outcome,
|
|
24370
|
+
log: args.log,
|
|
24371
|
+
policy: args.policy,
|
|
24372
|
+
nowMs: args.nowMs,
|
|
24373
|
+
onDeadLetter: args.onDeadLetter
|
|
24374
|
+
});
|
|
24375
|
+
if (!firstOutcomeRecorded) {
|
|
24376
|
+
headOutcome = outcome;
|
|
24377
|
+
firstOutcomeRecorded = true;
|
|
24378
|
+
}
|
|
24379
|
+
if (outcome === "dispatched") {
|
|
24380
|
+
dispatched.push(head.sourceSeq);
|
|
24381
|
+
}
|
|
24382
|
+
if (outcome === "transient_failed") {
|
|
24383
|
+
break;
|
|
24384
|
+
}
|
|
24385
|
+
}
|
|
24386
|
+
return { dispatched, headOutcome };
|
|
24387
|
+
}
|
|
24388
|
+
async function receiveResumeMessage(args) {
|
|
24389
|
+
args.queue.enqueue(
|
|
24390
|
+
args.sourceSeq,
|
|
24391
|
+
args.kind,
|
|
24392
|
+
args.payload,
|
|
24393
|
+
args.nowMs ?? Date.now()
|
|
24394
|
+
);
|
|
24395
|
+
const heldEpoch = args.leases.current(args.threadId);
|
|
24396
|
+
if (isFencedClaim(heldEpoch, args.leaseEpoch)) {
|
|
24397
|
+
args.log("resume_keystone.fenced", {
|
|
24398
|
+
threadId: args.threadId,
|
|
24399
|
+
heldLeaseEpoch: heldEpoch ?? null,
|
|
24400
|
+
controlLeaseEpoch: args.leaseEpoch ?? null,
|
|
24401
|
+
sourceSeq: args.sourceSeq
|
|
24402
|
+
});
|
|
24403
|
+
args.queue.ack(args.sourceSeq);
|
|
24404
|
+
return "ignored";
|
|
24405
|
+
}
|
|
24406
|
+
if (args.leaseEpoch !== void 0) {
|
|
24407
|
+
args.leases.adopt(args.threadId, args.leaseEpoch);
|
|
24408
|
+
}
|
|
24409
|
+
const serverRows = args.serverRows ?? [];
|
|
24410
|
+
if (serverRows.length > 0) {
|
|
24411
|
+
const localWatermark = args.store.initial(args.watermarkKey);
|
|
24412
|
+
const reconciled = reconcileAppliedSourceSeq(localWatermark, serverRows);
|
|
24413
|
+
if (reconciled !== void 0 && reconciled !== localWatermark) {
|
|
24414
|
+
commitAppliedSourceSeq(args.store, args.watermarkKey, reconciled);
|
|
24415
|
+
args.log("resume_keystone.live_miss_reconciled", {
|
|
24416
|
+
threadId: args.threadId,
|
|
24417
|
+
reconciledWatermark: reconciled,
|
|
24418
|
+
sourceSeq: args.sourceSeq
|
|
24419
|
+
});
|
|
24420
|
+
}
|
|
24421
|
+
}
|
|
24422
|
+
const applied = args.store.initial(args.watermarkKey);
|
|
24423
|
+
if (!shouldApplySourceSeq(applied, args.sourceSeq)) {
|
|
24424
|
+
args.log("resume_keystone.already_applied", {
|
|
24425
|
+
threadId: args.threadId,
|
|
24426
|
+
sourceSeq: args.sourceSeq,
|
|
24427
|
+
appliedSourceSeq: applied ?? null
|
|
24428
|
+
});
|
|
24429
|
+
args.queue.ack(args.sourceSeq);
|
|
24430
|
+
return "ignored";
|
|
24431
|
+
}
|
|
24432
|
+
const deadlineMs = args.reconcilerDeadlineMs ?? defaultResumeReconcilerDeadlineMs;
|
|
24433
|
+
try {
|
|
24434
|
+
const result = await args.lock.runExclusive(
|
|
24435
|
+
args.threadId,
|
|
24436
|
+
() => drainQueueHeadInOrder({
|
|
24437
|
+
queue: args.queue,
|
|
24438
|
+
store: args.store,
|
|
24439
|
+
watermarkKey: args.watermarkKey,
|
|
24440
|
+
threadId: args.threadId,
|
|
24441
|
+
serverRows,
|
|
24442
|
+
dispatch: args.dispatch,
|
|
24443
|
+
log: args.log,
|
|
24444
|
+
nowMs: args.nowMs,
|
|
24445
|
+
policy: args.policy,
|
|
24446
|
+
onlyDispatchUnattempted: args.onlyDispatchUnattempted ?? false,
|
|
24447
|
+
onDeadLetter: args.onDeadLetter
|
|
24448
|
+
}),
|
|
24449
|
+
deadlineMs
|
|
24450
|
+
);
|
|
24451
|
+
return result.headOutcome;
|
|
24452
|
+
} catch (error) {
|
|
24453
|
+
if (error instanceof ReconcilerLockTimeoutError) {
|
|
24454
|
+
args.log("resume_keystone.spawn_deadline_exceeded", {
|
|
24455
|
+
threadId: args.threadId,
|
|
24456
|
+
sourceSeq: args.sourceSeq,
|
|
24457
|
+
deadlineMs
|
|
24458
|
+
});
|
|
24459
|
+
return "transient_failed";
|
|
24460
|
+
}
|
|
24461
|
+
throw error;
|
|
24462
|
+
}
|
|
24463
|
+
}
|
|
24464
|
+
async function drainResumeQueue(args) {
|
|
24465
|
+
let dispatched = [];
|
|
24466
|
+
const serverRows = args.serverRows ?? [];
|
|
24467
|
+
const deadlineMs = args.reconcilerDeadlineMs ?? defaultResumeReconcilerDeadlineMs;
|
|
24468
|
+
const runDrain2 = async () => {
|
|
24469
|
+
const localWatermark = args.store.initial(args.watermarkKey);
|
|
24470
|
+
const reconciled = reconcileAppliedSourceSeq(localWatermark, serverRows);
|
|
24471
|
+
if (reconciled !== void 0 && reconciled !== localWatermark) {
|
|
24472
|
+
commitAppliedSourceSeq(args.store, args.watermarkKey, reconciled);
|
|
24473
|
+
}
|
|
24474
|
+
if (args.queue.tornTail()) {
|
|
24475
|
+
if (serverRows.length > 0) {
|
|
24476
|
+
args.log("resume_keystone.torn_tail", {
|
|
24477
|
+
threadId: args.threadId,
|
|
24478
|
+
reconciledWatermark: reconciled ?? null,
|
|
24479
|
+
recovery: "server_reconcile"
|
|
24480
|
+
});
|
|
24481
|
+
args.queue.reconciledTornTail();
|
|
24482
|
+
} else {
|
|
24483
|
+
args.log("resume_keystone.torn_tail_unrecoverable", {
|
|
24484
|
+
threadId: args.threadId,
|
|
24485
|
+
reconciledWatermark: reconciled ?? null
|
|
24486
|
+
});
|
|
24487
|
+
if (args.onTornTailUnrecoverable !== void 0) {
|
|
24488
|
+
await args.onTornTailUnrecoverable(args.threadId);
|
|
24489
|
+
}
|
|
24490
|
+
args.queue.reconciledTornTail();
|
|
24491
|
+
}
|
|
24492
|
+
}
|
|
24493
|
+
const result = await drainQueueHeadInOrder({
|
|
24494
|
+
queue: args.queue,
|
|
24495
|
+
store: args.store,
|
|
24496
|
+
watermarkKey: args.watermarkKey,
|
|
24497
|
+
threadId: args.threadId,
|
|
24498
|
+
serverRows,
|
|
24499
|
+
dispatch: args.dispatch,
|
|
24500
|
+
log: args.log,
|
|
24501
|
+
nowMs: args.nowMs,
|
|
24502
|
+
policy: args.policy,
|
|
24503
|
+
onlyDispatchUnattempted: args.onlyDispatchUnattempted ?? false,
|
|
24504
|
+
onDeadLetter: args.onDeadLetter
|
|
24505
|
+
});
|
|
24506
|
+
dispatched = result.dispatched;
|
|
24507
|
+
};
|
|
24508
|
+
try {
|
|
24509
|
+
await args.lock.runExclusive(args.threadId, runDrain2, deadlineMs);
|
|
24510
|
+
} catch (error) {
|
|
24511
|
+
if (error instanceof ReconcilerLockTimeoutError) {
|
|
24512
|
+
args.log("resume_keystone.drain_deadline_exceeded", {
|
|
24513
|
+
threadId: args.threadId,
|
|
24514
|
+
deadlineMs
|
|
24515
|
+
});
|
|
24516
|
+
return dispatched;
|
|
24517
|
+
}
|
|
24518
|
+
throw error;
|
|
24519
|
+
}
|
|
24520
|
+
return dispatched;
|
|
24521
|
+
}
|
|
24522
|
+
var defaultResumeReconcilerDeadlineMs;
|
|
24523
|
+
var init_resumeKeystone = __esm({
|
|
24524
|
+
"src/resumeKeystone.ts"() {
|
|
24525
|
+
"use strict";
|
|
24526
|
+
init_durableInboundQueue();
|
|
24527
|
+
init_appliedSourceSeqWatermark();
|
|
24528
|
+
init_leaseEpochGuard();
|
|
24529
|
+
init_threadReconcilerLock();
|
|
24530
|
+
defaultResumeReconcilerDeadlineMs = 12e4;
|
|
24531
|
+
}
|
|
24532
|
+
});
|
|
24533
|
+
|
|
23589
24534
|
// src/runner.ts
|
|
23590
24535
|
import { spawn as spawn9, spawnSync as spawnSync5 } from "node:child_process";
|
|
23591
|
-
import { createHash as
|
|
24536
|
+
import { createHash as createHash7, randomUUID as randomUUID4 } from "node:crypto";
|
|
23592
24537
|
import {
|
|
23593
24538
|
chmodSync as chmodSync2,
|
|
24539
|
+
closeSync as closeSync4,
|
|
23594
24540
|
existsSync as existsSync13,
|
|
24541
|
+
fsyncSync as fsyncSync2,
|
|
23595
24542
|
lstatSync,
|
|
23596
|
-
mkdirSync as
|
|
24543
|
+
mkdirSync as mkdirSync14,
|
|
23597
24544
|
mkdtempSync as mkdtempSync4,
|
|
24545
|
+
openSync as openSync5,
|
|
23598
24546
|
readdirSync as readdirSync4,
|
|
23599
|
-
readFileSync as
|
|
24547
|
+
readFileSync as readFileSync18,
|
|
23600
24548
|
realpathSync as realpathSync6,
|
|
23601
|
-
renameSync as
|
|
24549
|
+
renameSync as renameSync5,
|
|
23602
24550
|
rmSync as rmSync5,
|
|
23603
24551
|
statSync as statSync3,
|
|
23604
24552
|
writeFileSync as writeFileSync12
|
|
23605
24553
|
} from "node:fs";
|
|
23606
24554
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
23607
24555
|
import { createServer as createServer3 } from "node:http";
|
|
23608
|
-
import { homedir as
|
|
24556
|
+
import { homedir as homedir15, hostname as hostname2, tmpdir as tmpdir3 } from "node:os";
|
|
23609
24557
|
import { createInterface } from "node:readline";
|
|
23610
24558
|
import {
|
|
23611
24559
|
basename as basename8,
|
|
23612
|
-
dirname as
|
|
24560
|
+
dirname as dirname15,
|
|
23613
24561
|
extname as extname2,
|
|
23614
24562
|
isAbsolute as isAbsolute5,
|
|
23615
|
-
join as
|
|
24563
|
+
join as join22,
|
|
23616
24564
|
resolve as resolve8
|
|
23617
24565
|
} from "node:path";
|
|
23618
24566
|
async function runLocalCodexRunner(options) {
|
|
@@ -23623,7 +24571,11 @@ async function runLocalCodexRunner(options) {
|
|
|
23623
24571
|
removeHandlers: void 0
|
|
23624
24572
|
};
|
|
23625
24573
|
const close = () => closeCleanupStack(cleanup);
|
|
23626
|
-
|
|
24574
|
+
const runnerDrainHooks = {
|
|
24575
|
+
stopAccepting: () => void 0,
|
|
24576
|
+
flushInboundQueues: () => void 0
|
|
24577
|
+
};
|
|
24578
|
+
cleanup.removeHandlers = installCleanupHandlers(close, runnerDrainHooks, log2);
|
|
23627
24579
|
log2("runner.starting", {
|
|
23628
24580
|
runnerId: options.runnerId,
|
|
23629
24581
|
cwd: options.cwd,
|
|
@@ -23667,7 +24619,13 @@ async function runLocalCodexRunner(options) {
|
|
|
23667
24619
|
runnerId: options.runnerId
|
|
23668
24620
|
});
|
|
23669
24621
|
}
|
|
23670
|
-
return await openLocalCodexRunner(
|
|
24622
|
+
return await openLocalCodexRunner(
|
|
24623
|
+
options,
|
|
24624
|
+
log2,
|
|
24625
|
+
cleanup,
|
|
24626
|
+
close,
|
|
24627
|
+
runnerDrainHooks
|
|
24628
|
+
);
|
|
23671
24629
|
} catch (error) {
|
|
23672
24630
|
await close().catch(() => void 0);
|
|
23673
24631
|
throw error;
|
|
@@ -23715,12 +24673,81 @@ async function runThreadCodexWorker(options) {
|
|
|
23715
24673
|
await waitForThreadCodexWorkerStop(started.process);
|
|
23716
24674
|
stop();
|
|
23717
24675
|
}
|
|
23718
|
-
|
|
24676
|
+
function codexAuthStatusFromLoginStatus(exitCode, stdout, stderr) {
|
|
24677
|
+
const output = `${stdout}
|
|
24678
|
+
${stderr}`.toLowerCase();
|
|
24679
|
+
const loggedOut = output.includes("not logged in") || output.includes("not authenticated") || output.includes("login required") || output.includes("please log in");
|
|
24680
|
+
if (loggedOut) {
|
|
24681
|
+
return "not_logged_in";
|
|
24682
|
+
}
|
|
24683
|
+
if (exitCode === 0) {
|
|
24684
|
+
return "logged_in";
|
|
24685
|
+
}
|
|
24686
|
+
return "probe_failed";
|
|
24687
|
+
}
|
|
24688
|
+
function errorMatchesCode(error, code) {
|
|
24689
|
+
return error.code === code;
|
|
24690
|
+
}
|
|
24691
|
+
function probeCodexAuthStatus(codexBin, cwd) {
|
|
24692
|
+
return new Promise((resolve12) => {
|
|
24693
|
+
const stdoutChunks = [];
|
|
24694
|
+
const stderrChunks = [];
|
|
24695
|
+
let resolved = false;
|
|
24696
|
+
let timeout;
|
|
24697
|
+
const resolveOnce = (status) => {
|
|
24698
|
+
if (resolved) {
|
|
24699
|
+
return;
|
|
24700
|
+
}
|
|
24701
|
+
resolved = true;
|
|
24702
|
+
if (timeout !== void 0) {
|
|
24703
|
+
clearTimeout(timeout);
|
|
24704
|
+
}
|
|
24705
|
+
resolve12(status);
|
|
24706
|
+
};
|
|
24707
|
+
const child = spawn9(codexBin, ["login", "status"], {
|
|
24708
|
+
cwd,
|
|
24709
|
+
env: process.env,
|
|
24710
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
24711
|
+
});
|
|
24712
|
+
timeout = setTimeout(() => {
|
|
24713
|
+
child.kill("SIGTERM");
|
|
24714
|
+
resolveOnce("probe_failed");
|
|
24715
|
+
}, CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS);
|
|
24716
|
+
child.stdout?.on("data", (chunk) => {
|
|
24717
|
+
stdoutChunks.push(chunk);
|
|
24718
|
+
});
|
|
24719
|
+
child.stderr?.on("data", (chunk) => {
|
|
24720
|
+
stderrChunks.push(chunk);
|
|
24721
|
+
});
|
|
24722
|
+
child.once("error", (error) => {
|
|
24723
|
+
resolveOnce(
|
|
24724
|
+
errorMatchesCode(error, "ENOENT") ? "binary_missing" : "probe_failed"
|
|
24725
|
+
);
|
|
24726
|
+
});
|
|
24727
|
+
child.once("exit", (code) => {
|
|
24728
|
+
resolveOnce(
|
|
24729
|
+
codexAuthStatusFromLoginStatus(
|
|
24730
|
+
code,
|
|
24731
|
+
Buffer.concat(stdoutChunks).toString("utf8"),
|
|
24732
|
+
Buffer.concat(stderrChunks).toString("utf8")
|
|
24733
|
+
)
|
|
24734
|
+
);
|
|
24735
|
+
});
|
|
24736
|
+
});
|
|
24737
|
+
}
|
|
24738
|
+
async function openLocalCodexRunner(options, log2, cleanup, close, runnerDrainHooks) {
|
|
23719
24739
|
const agentProviders = availableRunnerAgentProviders(options);
|
|
23720
24740
|
const localCodexAppServerAvailable = hasLocalCodexAppServerCapability(options);
|
|
23721
24741
|
const dependencyStatusRef = {
|
|
23722
24742
|
value: options.dependencyStatus
|
|
23723
24743
|
};
|
|
24744
|
+
const codexAuthStatusRef = {
|
|
24745
|
+
value: await probeCodexAuthStatus(options.codexBin, options.cwd)
|
|
24746
|
+
};
|
|
24747
|
+
log2("runner.codex_auth_status_probed", {
|
|
24748
|
+
runnerId: options.runnerId,
|
|
24749
|
+
codex_auth_status: codexAuthStatusRef.value
|
|
24750
|
+
});
|
|
23724
24751
|
const refreshDependencyStatus = async () => {
|
|
23725
24752
|
if (dependencyStatusRef.value === void 0) {
|
|
23726
24753
|
return;
|
|
@@ -23746,6 +24773,27 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
23746
24773
|
});
|
|
23747
24774
|
}
|
|
23748
24775
|
};
|
|
24776
|
+
const refreshCodexAuthStatus = async () => {
|
|
24777
|
+
try {
|
|
24778
|
+
const refreshed = await probeCodexAuthStatus(
|
|
24779
|
+
options.codexBin,
|
|
24780
|
+
options.cwd
|
|
24781
|
+
);
|
|
24782
|
+
const previous = codexAuthStatusRef.value;
|
|
24783
|
+
codexAuthStatusRef.value = refreshed;
|
|
24784
|
+
log2("runner.codex_auth_status_reprobed", {
|
|
24785
|
+
runnerId: options.runnerId,
|
|
24786
|
+
codex_auth_status: refreshed,
|
|
24787
|
+
codex_auth_status_changed: previous !== refreshed
|
|
24788
|
+
});
|
|
24789
|
+
} catch (error) {
|
|
24790
|
+
codexAuthStatusRef.value = "probe_failed";
|
|
24791
|
+
log2("runner.codex_auth_status_reprobe_failed", {
|
|
24792
|
+
runnerId: options.runnerId,
|
|
24793
|
+
message: error instanceof Error ? error.message : String(error)
|
|
24794
|
+
});
|
|
24795
|
+
}
|
|
24796
|
+
};
|
|
23749
24797
|
const allowedForwardPorts = options.allowedForwardPorts ?? [];
|
|
23750
24798
|
const liveForwardPorts = new Set(allowedForwardPorts);
|
|
23751
24799
|
const managedForwardPorts = /* @__PURE__ */ new Set();
|
|
@@ -23899,6 +24947,16 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
23899
24947
|
// would silently drop it (and run against the user's personal account).
|
|
23900
24948
|
// Old servers ignore the extra key.
|
|
23901
24949
|
modelProviders: [...ADVERTISED_MODEL_PROVIDERS],
|
|
24950
|
+
codexAuthStatus: codexAuthStatusRef.value,
|
|
24951
|
+
// Launch policy from plans/2026-06-22-wafer-fallback-implementation-note.md:
|
|
24952
|
+
// Wafer is globally offered by shipped Electron runners during launch, so
|
|
24953
|
+
// this is intentionally an unconditional `true`. The server keeps a
|
|
24954
|
+
// defensive fallback (`"wafer" in modelProviders`) for older CLIs that
|
|
24955
|
+
// never advertised this key; that fallback is deliberately unreachable from
|
|
24956
|
+
// current builds. If a future use case needs to disable Wafer per runner
|
|
24957
|
+
// (e.g. enterprise/self-hosted credentials), make this conditional here
|
|
24958
|
+
// rather than relying on the server inferring it from modelProviders.
|
|
24959
|
+
waferAvailable: true,
|
|
23902
24960
|
defaultAgentProvider: "codex",
|
|
23903
24961
|
startInstance: allowedCwds.value.length > 0,
|
|
23904
24962
|
durableClaudeSessionStore: true,
|
|
@@ -23947,6 +25005,123 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
23947
25005
|
runnerThreadSessionRegistryPath(options.runnerId),
|
|
23948
25006
|
log2
|
|
23949
25007
|
);
|
|
25008
|
+
const appliedSourceSeqFileStore = createControlCursorFileStore(
|
|
25009
|
+
appliedSourceSeqStorePath(options.runnerId),
|
|
25010
|
+
log2,
|
|
25011
|
+
// POWER-LOSS DURABLE (SEAM 1): this watermark is the SOLE fence that stops a
|
|
25012
|
+
// confirmed-dispatched turn from being re-dispatched on restart, and it is
|
|
25013
|
+
// paired with the fsynced durable inbound queue. fsync it on every flush so
|
|
25014
|
+
// a power loss cannot lose a dispatch-confirm advance while the queue ack
|
|
25015
|
+
// survived (which would double-execute the turn on reopen).
|
|
25016
|
+
{ fsyncOnWrite: true }
|
|
25017
|
+
);
|
|
25018
|
+
cleanup.actions.push(() => appliedSourceSeqFileStore.flush());
|
|
25019
|
+
const appliedSourceSeqStore = {
|
|
25020
|
+
initial: (key) => appliedSourceSeqFileStore.initial(key),
|
|
25021
|
+
onAdvance: (key, sourceSeq) => appliedSourceSeqFileStore.onAdvance(key, sourceSeq),
|
|
25022
|
+
flush: () => appliedSourceSeqFileStore.flush()
|
|
25023
|
+
};
|
|
25024
|
+
const commitLiveWorkerAcceptedSourceSeq = (args) => {
|
|
25025
|
+
if (args.sourceSeq === void 0) {
|
|
25026
|
+
return;
|
|
25027
|
+
}
|
|
25028
|
+
const watermarkKey = threadSourceSeqWatermarkKey(
|
|
25029
|
+
inboundQueueWorkspace(),
|
|
25030
|
+
args.channel,
|
|
25031
|
+
args.threadId
|
|
25032
|
+
);
|
|
25033
|
+
const advanced = commitLiveAcceptedSourceSeq({
|
|
25034
|
+
store: appliedSourceSeqStore,
|
|
25035
|
+
watermarkKey,
|
|
25036
|
+
sourceSeq: args.sourceSeq
|
|
25037
|
+
});
|
|
25038
|
+
if (advanced) {
|
|
25039
|
+
log2("runner.live_worker_source_seq_committed", {
|
|
25040
|
+
kandanThreadId: args.threadId,
|
|
25041
|
+
channel: args.channel,
|
|
25042
|
+
sourceSeq: args.sourceSeq
|
|
25043
|
+
});
|
|
25044
|
+
}
|
|
25045
|
+
capturedLiveFollowUpEvents.delete(
|
|
25046
|
+
capturedFollowUpKey(args.channel, args.threadId, args.sourceSeq)
|
|
25047
|
+
);
|
|
25048
|
+
};
|
|
25049
|
+
const liveSourceSeqAlreadyApplied = (args) => {
|
|
25050
|
+
if (args.sourceSeq === void 0) {
|
|
25051
|
+
return false;
|
|
25052
|
+
}
|
|
25053
|
+
const watermarkKey = threadSourceSeqWatermarkKey(
|
|
25054
|
+
inboundQueueWorkspace(),
|
|
25055
|
+
args.channel,
|
|
25056
|
+
args.threadId
|
|
25057
|
+
);
|
|
25058
|
+
const applied = appliedSourceSeqStore.initial(watermarkKey);
|
|
25059
|
+
return !shouldApplySourceSeq(applied, args.sourceSeq);
|
|
25060
|
+
};
|
|
25061
|
+
const threadReconcilerLock = createThreadReconcilerLock();
|
|
25062
|
+
const leaseEpochRegistry = createLeaseEpochRegistry();
|
|
25063
|
+
const durableInboundQueues = /* @__PURE__ */ new Map();
|
|
25064
|
+
const latestResumeReconcileByThread = /* @__PURE__ */ new Map();
|
|
25065
|
+
let acceptingInbound = true;
|
|
25066
|
+
const inboundQueueWorkspace = () => runnerWorkspaceSlug(options) ?? "default";
|
|
25067
|
+
const durableInboundQueueFor = (channel, kandanThreadId) => {
|
|
25068
|
+
const workspace = inboundQueueWorkspace();
|
|
25069
|
+
const cacheKey = `${workspace}:${channel}:${kandanThreadId}`;
|
|
25070
|
+
const existing = durableInboundQueues.get(cacheKey);
|
|
25071
|
+
if (existing !== void 0) {
|
|
25072
|
+
return existing;
|
|
25073
|
+
}
|
|
25074
|
+
const queue = createDurableInboundThreadQueue(
|
|
25075
|
+
durableInboundQueuePath(workspace, channel, kandanThreadId),
|
|
25076
|
+
log2
|
|
25077
|
+
);
|
|
25078
|
+
durableInboundQueues.set(cacheKey, queue);
|
|
25079
|
+
return queue;
|
|
25080
|
+
};
|
|
25081
|
+
const capturedLiveFollowUpEvents = /* @__PURE__ */ new Map();
|
|
25082
|
+
const capturedFollowUpKey = (channel, threadId, seq2) => `${inboundQueueWorkspace()}:${channel}:${threadId}:${seq2}`;
|
|
25083
|
+
const captureLiveFollowUpEvent = (channel, threadId, event) => {
|
|
25084
|
+
capturedLiveFollowUpEvents.set(
|
|
25085
|
+
capturedFollowUpKey(channel, threadId, event.seq),
|
|
25086
|
+
event
|
|
25087
|
+
);
|
|
25088
|
+
};
|
|
25089
|
+
const reEnqueueUndeliveredFollowUps = (args) => {
|
|
25090
|
+
const queue = durableInboundQueueFor(args.channel, args.threadId);
|
|
25091
|
+
for (const seq2 of args.sourceSeqs) {
|
|
25092
|
+
const key = capturedFollowUpKey(args.channel, args.threadId, seq2);
|
|
25093
|
+
const event = capturedLiveFollowUpEvents.get(key);
|
|
25094
|
+
if (event === void 0) {
|
|
25095
|
+
log2("runner.live_follow_up_recapture_missing", {
|
|
25096
|
+
kandanThreadId: args.threadId,
|
|
25097
|
+
channel: args.channel,
|
|
25098
|
+
sourceSeq: seq2
|
|
25099
|
+
});
|
|
25100
|
+
continue;
|
|
25101
|
+
}
|
|
25102
|
+
const enqueued = queue.enqueue(
|
|
25103
|
+
seq2,
|
|
25104
|
+
"replay",
|
|
25105
|
+
serializeKandanChatEventForQueue(event),
|
|
25106
|
+
Date.now()
|
|
25107
|
+
);
|
|
25108
|
+
log2("runner.live_follow_up_re_enqueued_on_close", {
|
|
25109
|
+
kandanThreadId: args.threadId,
|
|
25110
|
+
channel: args.channel,
|
|
25111
|
+
sourceSeq: seq2,
|
|
25112
|
+
enqueued: enqueued.accepted
|
|
25113
|
+
});
|
|
25114
|
+
capturedLiveFollowUpEvents.delete(key);
|
|
25115
|
+
}
|
|
25116
|
+
};
|
|
25117
|
+
runnerDrainHooks.stopAccepting = () => {
|
|
25118
|
+
acceptingInbound = false;
|
|
25119
|
+
};
|
|
25120
|
+
runnerDrainHooks.flushInboundQueues = () => {
|
|
25121
|
+
for (const queue of durableInboundQueues.values()) {
|
|
25122
|
+
queue.compact();
|
|
25123
|
+
}
|
|
25124
|
+
};
|
|
23950
25125
|
const controlEpochStore = createRunnerControlEpochStore(
|
|
23951
25126
|
controlCursorStore,
|
|
23952
25127
|
appliedStartTurnStore,
|
|
@@ -24788,6 +25963,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
24788
25963
|
codex,
|
|
24789
25964
|
topic,
|
|
24790
25965
|
instanceId,
|
|
25966
|
+
// SEAM 2: source the held lease epoch for the stream-write fence echo.
|
|
25967
|
+
leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
|
|
24791
25968
|
options: {
|
|
24792
25969
|
kandanUrl: options.kandanUrl,
|
|
24793
25970
|
token: options.token,
|
|
@@ -24824,6 +26001,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
24824
26001
|
// session is attached (the durable thread's worker fully died). Late-
|
|
24825
26002
|
// bound to dodge the const's temporal dead zone (defined below).
|
|
24826
26003
|
onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
|
|
26004
|
+
// BLOCKER B: codex live dispatch-confirm watermark commit on the main
|
|
26005
|
+
// channel session too, off the same persisted authority the keystone
|
|
26006
|
+
// gates on.
|
|
26007
|
+
onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
|
|
24827
26008
|
channelSession: options.channelSession
|
|
24828
26009
|
},
|
|
24829
26010
|
log: log2
|
|
@@ -25174,6 +26355,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25174
26355
|
codex: sessionCodex,
|
|
25175
26356
|
topic,
|
|
25176
26357
|
instanceId,
|
|
26358
|
+
// SEAM 2: dynamic per-thread session also echoes the held lease epoch.
|
|
26359
|
+
leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
|
|
25177
26360
|
options: {
|
|
25178
26361
|
kandanUrl: options.kandanUrl,
|
|
25179
26362
|
token: options.token,
|
|
@@ -25188,6 +26371,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25188
26371
|
// it out of the const's temporal dead zone while still resolving the
|
|
25189
26372
|
// live handler when an unrouted chat event actually arrives.
|
|
25190
26373
|
onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
|
|
26374
|
+
// BLOCKER B: codex live dispatch-confirm watermark commit (the
|
|
26375
|
+
// production-default double-exec fix). Fires from drainKandanMessageQueue
|
|
26376
|
+
// when turn/start replies, off the SAME persisted authority the keystone
|
|
26377
|
+
// gates on, so a codex worker-death + replay is gated 'ignored'.
|
|
26378
|
+
onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
|
|
25191
26379
|
pipelinePersistDir: commanderOutboxPersistDir(),
|
|
25192
26380
|
enablePortForwardWatch: true,
|
|
25193
26381
|
initialForwardPorts: allowedForwardPorts,
|
|
@@ -25566,7 +26754,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25566
26754
|
control,
|
|
25567
26755
|
log2,
|
|
25568
26756
|
onStartedThread,
|
|
25569
|
-
void 0
|
|
26757
|
+
void 0,
|
|
26758
|
+
(id) => leaseEpochRegistry.current(id),
|
|
26759
|
+
commitLiveWorkerAcceptedSourceSeq,
|
|
26760
|
+
captureLiveFollowUpEvent,
|
|
26761
|
+
reEnqueueUndeliveredFollowUps,
|
|
26762
|
+
liveSourceSeqAlreadyApplied
|
|
25570
26763
|
)
|
|
25571
26764
|
);
|
|
25572
26765
|
}
|
|
@@ -25609,6 +26802,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25609
26802
|
attachments: event.attachments,
|
|
25610
26803
|
boundStartTimeout: true
|
|
25611
26804
|
});
|
|
26805
|
+
return "dispatched";
|
|
25612
26806
|
} catch (error) {
|
|
25613
26807
|
log2("runner.spawn_on_miss_failed", {
|
|
25614
26808
|
kandanThreadId: event.threadId ?? null,
|
|
@@ -25622,6 +26816,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25622
26816
|
`could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
|
|
25623
26817
|
record.codexThreadId
|
|
25624
26818
|
);
|
|
26819
|
+
return "transient_failed";
|
|
25625
26820
|
}
|
|
25626
26821
|
};
|
|
25627
26822
|
const handleUnroutedThreadMessage = async (event) => {
|
|
@@ -25652,6 +26847,246 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25652
26847
|
if (record === void 0) {
|
|
25653
26848
|
return "ignored";
|
|
25654
26849
|
}
|
|
26850
|
+
const queueChannel = record.channel;
|
|
26851
|
+
const watermarkKey = threadSourceSeqWatermarkKey(
|
|
26852
|
+
inboundQueueWorkspace(),
|
|
26853
|
+
queueChannel,
|
|
26854
|
+
kandanThreadId
|
|
26855
|
+
);
|
|
26856
|
+
const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
|
|
26857
|
+
if (!acceptingInbound) {
|
|
26858
|
+
const enqueued = queue.enqueue(
|
|
26859
|
+
event.seq,
|
|
26860
|
+
event.localRunnerEventType === void 0 ? "live" : "replay",
|
|
26861
|
+
serializeKandanChatEventForQueue(event),
|
|
26862
|
+
Date.now()
|
|
26863
|
+
);
|
|
26864
|
+
log2("runner.spawn_on_miss_drain_in_progress", {
|
|
26865
|
+
kandanThreadId,
|
|
26866
|
+
seq: event.seq,
|
|
26867
|
+
enqueued: enqueued.accepted
|
|
26868
|
+
});
|
|
26869
|
+
return "spawned";
|
|
26870
|
+
}
|
|
26871
|
+
const outcome = await receiveResumeMessage({
|
|
26872
|
+
queue,
|
|
26873
|
+
store: appliedSourceSeqStore,
|
|
26874
|
+
lock: threadReconcilerLock,
|
|
26875
|
+
leases: leaseEpochRegistry,
|
|
26876
|
+
threadId: kandanThreadId,
|
|
26877
|
+
watermarkKey,
|
|
26878
|
+
sourceSeq: event.seq,
|
|
26879
|
+
kind: event.localRunnerEventType === void 0 ? "live" : "replay",
|
|
26880
|
+
payload: serializeKandanChatEventForQueue(event),
|
|
26881
|
+
leaseEpoch: readLeaseEpoch(event),
|
|
26882
|
+
// Blocker #2: the keystone drains the queue HEAD in source_seq order, so
|
|
26883
|
+
// the dispatch is keyed by (sourceSeq, payload) - it may run a LOWER
|
|
26884
|
+
// pending seq before this arrived one. Reconstruct the event from the
|
|
26885
|
+
// head's payload, exactly as the boot/rejoin drain does.
|
|
26886
|
+
dispatch: async (sourceSeq, payload) => {
|
|
26887
|
+
const drained = deserializeKandanChatEventFromQueue(payload);
|
|
26888
|
+
if (drained === void 0) {
|
|
26889
|
+
log2("runner.spawn_on_miss_bad_payload", {
|
|
26890
|
+
kandanThreadId,
|
|
26891
|
+
seq: sourceSeq
|
|
26892
|
+
});
|
|
26893
|
+
return "permanent_failed";
|
|
26894
|
+
}
|
|
26895
|
+
return await dispatchResumeForOwnedThread(drained, record);
|
|
26896
|
+
},
|
|
26897
|
+
// BLOCKER #4: pass the server's per-thread processed floor (the most-recent
|
|
26898
|
+
// resume_reconcile rows) so the live-miss path reconciles
|
|
26899
|
+
// highestProcessedSourceSeq into the watermark BEFORE dispatching - a
|
|
26900
|
+
// completed-but-not-locally-acked turn that replays is gated 'ignored', not
|
|
26901
|
+
// re-run. Empty until the server SEAM 1 half ships; then the floor is
|
|
26902
|
+
// authoritative. The live-miss path leaves onlyDispatchUnattempted at its
|
|
26903
|
+
// default (false): the arrived message is attempts===0 and the blind
|
|
26904
|
+
// post-completion fence is owned by the boot/rejoin recovery drain, not this
|
|
26905
|
+
// on-receipt path (changing it here would alter the existing live behavior).
|
|
26906
|
+
serverRows: latestResumeReconcileByThread.get(kandanThreadId) ?? [],
|
|
26907
|
+
// Blocker #3: surface a LOUD terminal `failed` state on any dead-letter.
|
|
26908
|
+
onDeadLetter: deadLetterPublisherFor(record),
|
|
26909
|
+
log: log2
|
|
26910
|
+
});
|
|
26911
|
+
return outcome === "ignored" ? "ignored" : "spawned";
|
|
26912
|
+
};
|
|
26913
|
+
const drainOwnedThreadQueue = async (record, serverRows = []) => {
|
|
26914
|
+
if (!acceptingInbound) {
|
|
26915
|
+
return;
|
|
26916
|
+
}
|
|
26917
|
+
const kandanThreadId = record.kandanThreadId;
|
|
26918
|
+
const queueChannel = record.channel;
|
|
26919
|
+
const watermarkKey = threadSourceSeqWatermarkKey(
|
|
26920
|
+
inboundQueueWorkspace(),
|
|
26921
|
+
queueChannel,
|
|
26922
|
+
kandanThreadId
|
|
26923
|
+
);
|
|
26924
|
+
const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
|
|
26925
|
+
await drainResumeQueue({
|
|
26926
|
+
queue,
|
|
26927
|
+
store: appliedSourceSeqStore,
|
|
26928
|
+
lock: threadReconcilerLock,
|
|
26929
|
+
threadId: kandanThreadId,
|
|
26930
|
+
watermarkKey,
|
|
26931
|
+
serverRows,
|
|
26932
|
+
log: log2,
|
|
26933
|
+
// Until the SERVER message_state reconcile half (SEAM 1 server) ships, the
|
|
26934
|
+
// drain runs with serverRows=[] in production. In that mode a post-
|
|
26935
|
+
// completion-kill (turn confirmed-dispatched, then SIGKILL before the
|
|
26936
|
+
// watermark flush + ack) leaves an attempted-but-unacked entry. Blindly
|
|
26937
|
+
// re-dispatching it would double-execute the turn (real edits/commits), so
|
|
26938
|
+
// gate the drain to UNATTEMPTED entries only: an attempted entry is dead-
|
|
26939
|
+
// lettered with a loud `failed` state rather than re-run. Once serverRows
|
|
26940
|
+
// is populated the reconcile fences already-run seqs and this gate is moot.
|
|
26941
|
+
onlyDispatchUnattempted: serverRows.length === 0,
|
|
26942
|
+
// Blocker #3: surface a LOUD terminal `failed` state on any dead-letter
|
|
26943
|
+
// (exhausted retries, a withheld post-completion-kill entry, or a permanent
|
|
26944
|
+
// failure) so a persistently-unbindable thread is never silently dropped.
|
|
26945
|
+
onDeadLetter: deadLetterPublisherFor(record),
|
|
26946
|
+
// A torn tail with no server reconcile cannot be re-derived on this branch
|
|
26947
|
+
// (no server outbox/replay). Publish a loud "please resend" terminal state
|
|
26948
|
+
// for the thread instead of silently dropping the lost tail.
|
|
26949
|
+
onTornTailUnrecoverable: async (threadId) => {
|
|
26950
|
+
const control = rehydrationReconnectControl(record);
|
|
26951
|
+
await publishSpawnOnMissMessageState(
|
|
26952
|
+
control,
|
|
26953
|
+
"failed",
|
|
26954
|
+
"a message may have been lost during an unexpected shutdown (power loss). Please resend your last message to continue this job.",
|
|
26955
|
+
record.codexThreadId
|
|
26956
|
+
);
|
|
26957
|
+
log2("runner.inbound_torn_tail_resend_published", {
|
|
26958
|
+
kandanThreadId: threadId
|
|
26959
|
+
});
|
|
26960
|
+
},
|
|
26961
|
+
dispatch: async (sourceSeq, payload) => {
|
|
26962
|
+
const event = deserializeKandanChatEventFromQueue(payload);
|
|
26963
|
+
if (event === void 0) {
|
|
26964
|
+
log2("runner.inbound_drain_bad_payload", {
|
|
26965
|
+
kandanThreadId,
|
|
26966
|
+
seq: sourceSeq
|
|
26967
|
+
});
|
|
26968
|
+
return "permanent_failed";
|
|
26969
|
+
}
|
|
26970
|
+
return await dispatchResumeForOwnedThread(event, record);
|
|
26971
|
+
}
|
|
26972
|
+
});
|
|
26973
|
+
};
|
|
26974
|
+
const consumeResumeReconcile = (reconcileValue) => {
|
|
26975
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
26976
|
+
const entries = parseResumeReconcileEntries(reconcileValue);
|
|
26977
|
+
for (const entry of entries) {
|
|
26978
|
+
if (entry.leaseEpoch !== void 0) {
|
|
26979
|
+
const adopted = leaseEpochRegistry.adopt(entry.threadId, entry.leaseEpoch);
|
|
26980
|
+
if (adopted) {
|
|
26981
|
+
log2("runner.resume_reconcile_lease_adopted", {
|
|
26982
|
+
kandanThreadId: entry.threadId,
|
|
26983
|
+
leaseEpoch: entry.leaseEpoch
|
|
26984
|
+
});
|
|
26985
|
+
}
|
|
26986
|
+
}
|
|
26987
|
+
const rows = resumeReconcileServerRows(entry);
|
|
26988
|
+
byThread.set(entry.threadId, rows);
|
|
26989
|
+
latestResumeReconcileByThread.set(entry.threadId, rows);
|
|
26990
|
+
}
|
|
26991
|
+
if (entries.length > 0) {
|
|
26992
|
+
log2("runner.resume_reconcile_consumed", { threadCount: entries.length });
|
|
26993
|
+
}
|
|
26994
|
+
return byThread;
|
|
26995
|
+
};
|
|
26996
|
+
const drainOrphanedThreadQueues = async () => {
|
|
26997
|
+
const dir = durableInboundQueueDir();
|
|
26998
|
+
let files;
|
|
26999
|
+
try {
|
|
27000
|
+
files = readdirSync4(dir).filter((name) => name.endsWith(".log"));
|
|
27001
|
+
} catch (error) {
|
|
27002
|
+
if (error?.code === "ENOENT") {
|
|
27003
|
+
return;
|
|
27004
|
+
}
|
|
27005
|
+
log2("runner.inbound_orphan_scan_failed", {
|
|
27006
|
+
message: error instanceof Error ? error.message : String(error)
|
|
27007
|
+
});
|
|
27008
|
+
return;
|
|
27009
|
+
}
|
|
27010
|
+
const workspace = inboundQueueWorkspace();
|
|
27011
|
+
const owned = new Set(
|
|
27012
|
+
threadSessionRegistry.list().map(
|
|
27013
|
+
(record) => durableInboundQueuePath(
|
|
27014
|
+
workspace,
|
|
27015
|
+
record.channel,
|
|
27016
|
+
record.kandanThreadId
|
|
27017
|
+
)
|
|
27018
|
+
)
|
|
27019
|
+
);
|
|
27020
|
+
for (const name of files) {
|
|
27021
|
+
const path2 = join22(dir, name);
|
|
27022
|
+
if (owned.has(path2)) {
|
|
27023
|
+
continue;
|
|
27024
|
+
}
|
|
27025
|
+
try {
|
|
27026
|
+
const queue = createDurableInboundThreadQueue(path2, log2);
|
|
27027
|
+
const stranded = queue.entries();
|
|
27028
|
+
if (stranded.length === 0) {
|
|
27029
|
+
continue;
|
|
27030
|
+
}
|
|
27031
|
+
for (const entry of stranded) {
|
|
27032
|
+
queue.deadLetter(entry.sourceSeq);
|
|
27033
|
+
}
|
|
27034
|
+
queue.compact();
|
|
27035
|
+
log2("runner.inbound_orphan_dead_lettered", {
|
|
27036
|
+
path: path2,
|
|
27037
|
+
strandedCount: stranded.length,
|
|
27038
|
+
strandedSeqs: stranded.map((e) => e.sourceSeq)
|
|
27039
|
+
});
|
|
27040
|
+
} catch (error) {
|
|
27041
|
+
log2("runner.inbound_orphan_drain_failed", {
|
|
27042
|
+
path: path2,
|
|
27043
|
+
message: error instanceof Error ? error.message : String(error)
|
|
27044
|
+
});
|
|
27045
|
+
}
|
|
27046
|
+
}
|
|
27047
|
+
};
|
|
27048
|
+
const drainAllOwnedThreadQueues = async (reconcileByThread) => {
|
|
27049
|
+
if (!acceptingInbound) {
|
|
27050
|
+
return;
|
|
27051
|
+
}
|
|
27052
|
+
const records = threadSessionRegistry.list();
|
|
27053
|
+
await Promise.all(
|
|
27054
|
+
records.map(
|
|
27055
|
+
(record) => drainOwnedThreadQueue(
|
|
27056
|
+
record,
|
|
27057
|
+
reconcileByThread?.get(record.kandanThreadId) ?? []
|
|
27058
|
+
).catch((error) => {
|
|
27059
|
+
log2("runner.inbound_drain_failed", {
|
|
27060
|
+
kandanThreadId: record.kandanThreadId,
|
|
27061
|
+
message: error instanceof Error ? error.message : String(error)
|
|
27062
|
+
});
|
|
27063
|
+
})
|
|
27064
|
+
)
|
|
27065
|
+
);
|
|
27066
|
+
await drainOrphanedThreadQueues();
|
|
27067
|
+
};
|
|
27068
|
+
const bootReconcile = consumeResumeReconcile(
|
|
27069
|
+
objectValue(joinResponse)?.resume_reconcile
|
|
27070
|
+
);
|
|
27071
|
+
const bootDrain = threadSessionRehydration.then(() => drainAllOwnedThreadQueues(bootReconcile)).catch((error) => {
|
|
27072
|
+
log2("runner.inbound_boot_drain_failed", {
|
|
27073
|
+
message: error instanceof Error ? error.message : String(error)
|
|
27074
|
+
});
|
|
27075
|
+
});
|
|
27076
|
+
cleanup.actions.push(() => bootDrain);
|
|
27077
|
+
kandan.onReconnect((rejoinReplies) => {
|
|
27078
|
+
const rejoinReply = rejoinReplies.get(topic);
|
|
27079
|
+
const reconcile = consumeResumeReconcile(
|
|
27080
|
+
rejoinReply === void 0 ? void 0 : rejoinReply.resume_reconcile
|
|
27081
|
+
);
|
|
27082
|
+
void drainAllOwnedThreadQueues(reconcile).catch((error) => {
|
|
27083
|
+
log2("runner.inbound_rejoin_drain_failed", {
|
|
27084
|
+
message: error instanceof Error ? error.message : String(error)
|
|
27085
|
+
});
|
|
27086
|
+
});
|
|
27087
|
+
});
|
|
27088
|
+
const dispatchResumeForOwnedThread = async (event, record) => {
|
|
27089
|
+
const kandanThreadId = record.kandanThreadId;
|
|
25655
27090
|
const baseControl = rehydrationReconnectControl(record);
|
|
25656
27091
|
const providerBinding = threadModelProviderBindings.get(kandanThreadId);
|
|
25657
27092
|
const reconnectControl = {
|
|
@@ -25681,7 +27116,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25681
27116
|
`could not resume the thread's coding session: working directory ${record.cwd} is not an allowed root (${cwd.reason})`,
|
|
25682
27117
|
record.codexThreadId
|
|
25683
27118
|
);
|
|
25684
|
-
return "
|
|
27119
|
+
return "permanent_failed";
|
|
25685
27120
|
}
|
|
25686
27121
|
const resolvedCwd = cwd.cwd;
|
|
25687
27122
|
const inFlightSpawn = spawnOnMissInFlight.get(kandanThreadId);
|
|
@@ -25701,14 +27136,13 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25701
27136
|
`could not resume the thread's coding session: no live session after spawn`,
|
|
25702
27137
|
record.codexThreadId
|
|
25703
27138
|
);
|
|
25704
|
-
return "
|
|
27139
|
+
return "transient_failed";
|
|
25705
27140
|
}
|
|
25706
|
-
await replaySpawnOnMissMessageOnLiveSession(
|
|
27141
|
+
return await replaySpawnOnMissMessageOnLiveSession(
|
|
25707
27142
|
event,
|
|
25708
27143
|
record,
|
|
25709
27144
|
reconnectControl
|
|
25710
27145
|
);
|
|
25711
|
-
return "spawned";
|
|
25712
27146
|
}
|
|
25713
27147
|
log2("runner.spawn_on_miss_resuming", {
|
|
25714
27148
|
kandanThreadId,
|
|
@@ -25729,12 +27163,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25729
27163
|
);
|
|
25730
27164
|
if (threadStartDelegatedToSibling(startResult)) {
|
|
25731
27165
|
spawnSettle("spawned");
|
|
25732
|
-
await replaySpawnOnMissMessageOnLiveSession(
|
|
27166
|
+
return await replaySpawnOnMissMessageOnLiveSession(
|
|
25733
27167
|
event,
|
|
25734
27168
|
record,
|
|
25735
27169
|
reconnectControl
|
|
25736
27170
|
);
|
|
25737
|
-
return "spawned";
|
|
25738
27171
|
}
|
|
25739
27172
|
if (startResult === void 0 || startResult.ok === false) {
|
|
25740
27173
|
spawnSettle("no_session");
|
|
@@ -25751,10 +27184,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25751
27184
|
`could not resume the thread's coding session: ${startError}`,
|
|
25752
27185
|
record.codexThreadId
|
|
25753
27186
|
);
|
|
25754
|
-
return "
|
|
27187
|
+
return "transient_failed";
|
|
25755
27188
|
}
|
|
25756
27189
|
spawnSettle("spawned");
|
|
25757
|
-
return "
|
|
27190
|
+
return "dispatched";
|
|
25758
27191
|
} catch (error) {
|
|
25759
27192
|
spawnSettle("no_session");
|
|
25760
27193
|
log2("runner.spawn_on_miss_failed", {
|
|
@@ -25769,7 +27202,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25769
27202
|
`could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
|
|
25770
27203
|
record.codexThreadId
|
|
25771
27204
|
);
|
|
25772
|
-
return "
|
|
27205
|
+
return "transient_failed";
|
|
25773
27206
|
} finally {
|
|
25774
27207
|
spawnSettle("spawned");
|
|
25775
27208
|
if (spawnOnMissInFlight.get(kandanThreadId) === spawnPromise) {
|
|
@@ -25795,6 +27228,26 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
25795
27228
|
});
|
|
25796
27229
|
}
|
|
25797
27230
|
};
|
|
27231
|
+
const deadLetterPublisherFor = (record) => {
|
|
27232
|
+
return async ({ sourceSeq, reason, attempts, alreadyPublished }) => {
|
|
27233
|
+
log2("runner.inbound_dead_lettered", {
|
|
27234
|
+
kandanThreadId: record.kandanThreadId,
|
|
27235
|
+
seq: sourceSeq,
|
|
27236
|
+
reason,
|
|
27237
|
+
attempts,
|
|
27238
|
+
alreadyPublished
|
|
27239
|
+
});
|
|
27240
|
+
if (alreadyPublished) {
|
|
27241
|
+
return;
|
|
27242
|
+
}
|
|
27243
|
+
await publishSpawnOnMissMessageState(
|
|
27244
|
+
rehydrationReconnectControl(record),
|
|
27245
|
+
"failed",
|
|
27246
|
+
deadLetterFailedReason(reason),
|
|
27247
|
+
record.codexThreadId
|
|
27248
|
+
);
|
|
27249
|
+
};
|
|
27250
|
+
};
|
|
25798
27251
|
const heartbeatPayload = () => ({
|
|
25799
27252
|
instanceId,
|
|
25800
27253
|
clientId,
|
|
@@ -26285,7 +27738,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
26285
27738
|
control,
|
|
26286
27739
|
log2,
|
|
26287
27740
|
attachThreadSession,
|
|
26288
|
-
shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0
|
|
27741
|
+
shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0,
|
|
27742
|
+
(id) => leaseEpochRegistry.current(id),
|
|
27743
|
+
commitLiveWorkerAcceptedSourceSeq,
|
|
27744
|
+
captureLiveFollowUpEvent,
|
|
27745
|
+
reEnqueueUndeliveredFollowUps,
|
|
27746
|
+
liveSourceSeqAlreadyApplied
|
|
26289
27747
|
);
|
|
26290
27748
|
}),
|
|
26291
27749
|
commitStartTurnWatermark
|
|
@@ -26316,7 +27774,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
26316
27774
|
pendingControls,
|
|
26317
27775
|
controlDispatcher,
|
|
26318
27776
|
dispatchControl,
|
|
26319
|
-
|
|
27777
|
+
async () => {
|
|
27778
|
+
await refreshDependencyStatus();
|
|
27779
|
+
await refreshCodexAuthStatus();
|
|
27780
|
+
}
|
|
26320
27781
|
);
|
|
26321
27782
|
return { instanceId, codexUrl, close };
|
|
26322
27783
|
}
|
|
@@ -26330,28 +27791,34 @@ function commanderOutboxPersistDir() {
|
|
|
26330
27791
|
if (override !== void 0 && override !== "") {
|
|
26331
27792
|
return override;
|
|
26332
27793
|
}
|
|
26333
|
-
return
|
|
27794
|
+
return join22(homedir15(), ".linzumi", "commander-outbox");
|
|
27795
|
+
}
|
|
27796
|
+
function controlCursorsDir() {
|
|
27797
|
+
const override = process.env.LINZUMI_CONTROL_CURSOR_DIR?.trim();
|
|
27798
|
+
if (override !== void 0 && override !== "") {
|
|
27799
|
+
return override;
|
|
27800
|
+
}
|
|
27801
|
+
return join22(homedir15(), ".linzumi", "control-cursors");
|
|
26334
27802
|
}
|
|
26335
27803
|
function controlCursorStorePath(runnerId) {
|
|
26336
27804
|
const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
26337
|
-
const digest =
|
|
27805
|
+
const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
|
|
26338
27806
|
const stem = sanitized === "" ? "runner" : sanitized;
|
|
26339
|
-
return
|
|
26340
|
-
homedir14(),
|
|
26341
|
-
".linzumi",
|
|
26342
|
-
"control-cursors",
|
|
26343
|
-
`${stem}.${digest}.json`
|
|
26344
|
-
);
|
|
27807
|
+
return join22(controlCursorsDir(), `${stem}.${digest}.json`);
|
|
26345
27808
|
}
|
|
26346
27809
|
function appliedStartTurnStorePath(runnerId) {
|
|
26347
27810
|
const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
26348
|
-
const digest =
|
|
27811
|
+
const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
|
|
27812
|
+
const stem = sanitized === "" ? "runner" : sanitized;
|
|
27813
|
+
return join22(controlCursorsDir(), `${stem}.${digest}.start-turn.json`);
|
|
27814
|
+
}
|
|
27815
|
+
function appliedSourceSeqStorePath(runnerId) {
|
|
27816
|
+
const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
27817
|
+
const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
|
|
26349
27818
|
const stem = sanitized === "" ? "runner" : sanitized;
|
|
26350
|
-
return
|
|
26351
|
-
|
|
26352
|
-
|
|
26353
|
-
"control-cursors",
|
|
26354
|
-
`${stem}.${digest}.start-turn.json`
|
|
27819
|
+
return join22(
|
|
27820
|
+
controlCursorsDir(),
|
|
27821
|
+
`${stem}.${digest}.source-seq.json`
|
|
26355
27822
|
);
|
|
26356
27823
|
}
|
|
26357
27824
|
function startTurnWatermarkKey(topic, threadId) {
|
|
@@ -26473,6 +27940,7 @@ function createConnectionStalenessTracker(log2, now = Date.now, thresholdMs = co
|
|
|
26473
27940
|
}
|
|
26474
27941
|
function createControlCursorFileStore(path2, log2, options) {
|
|
26475
27942
|
const discardSeqsOnFirstEpochAdoption = options?.discardSeqsOnFirstEpochAdoption === true;
|
|
27943
|
+
const fsyncOnWrite = options?.fsyncOnWrite === true;
|
|
26476
27944
|
const { cursors, epoch: persistedEpoch } = readControlCursorFile(path2, log2);
|
|
26477
27945
|
let epoch = persistedEpoch;
|
|
26478
27946
|
let dirty = false;
|
|
@@ -26483,18 +27951,32 @@ function createControlCursorFileStore(path2, log2, options) {
|
|
|
26483
27951
|
}
|
|
26484
27952
|
dirty = false;
|
|
26485
27953
|
try {
|
|
26486
|
-
|
|
27954
|
+
mkdirSync14(dirname15(path2), { recursive: true });
|
|
26487
27955
|
const tmpPath = `${path2}.tmp`;
|
|
26488
|
-
|
|
26489
|
-
|
|
26490
|
-
|
|
26491
|
-
|
|
26492
|
-
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
|
|
26496
|
-
|
|
26497
|
-
|
|
27956
|
+
const contents = `${JSON.stringify({
|
|
27957
|
+
...epoch === void 0 ? {} : { [controlCursorEpochKey]: epoch },
|
|
27958
|
+
...Object.fromEntries(cursors)
|
|
27959
|
+
})}
|
|
27960
|
+
`;
|
|
27961
|
+
if (fsyncOnWrite) {
|
|
27962
|
+
const fileFd = openSync5(tmpPath, "w");
|
|
27963
|
+
try {
|
|
27964
|
+
writeFileSync12(fileFd, contents, "utf8");
|
|
27965
|
+
fsyncSync2(fileFd);
|
|
27966
|
+
} finally {
|
|
27967
|
+
closeSync4(fileFd);
|
|
27968
|
+
}
|
|
27969
|
+
renameSync5(tmpPath, path2);
|
|
27970
|
+
const dirFd = openSync5(dirname15(path2), "r");
|
|
27971
|
+
try {
|
|
27972
|
+
fsyncSync2(dirFd);
|
|
27973
|
+
} finally {
|
|
27974
|
+
closeSync4(dirFd);
|
|
27975
|
+
}
|
|
27976
|
+
} else {
|
|
27977
|
+
writeFileSync12(tmpPath, contents, "utf8");
|
|
27978
|
+
renameSync5(tmpPath, path2);
|
|
27979
|
+
}
|
|
26498
27980
|
} catch (error) {
|
|
26499
27981
|
log2("control_cursor_store.write_failed", {
|
|
26500
27982
|
path: path2,
|
|
@@ -26593,7 +28075,7 @@ function readControlCursorFile(path2, log2) {
|
|
|
26593
28075
|
const cursors = /* @__PURE__ */ new Map();
|
|
26594
28076
|
let raw;
|
|
26595
28077
|
try {
|
|
26596
|
-
raw =
|
|
28078
|
+
raw = readFileSync18(path2, "utf8");
|
|
26597
28079
|
} catch (error) {
|
|
26598
28080
|
if (!isErrnoCode2(error, "ENOENT")) {
|
|
26599
28081
|
log2("control_cursor_store.read_failed", {
|
|
@@ -26938,21 +28420,47 @@ function makeRunnerLogger(options) {
|
|
|
26938
28420
|
consoleReporter
|
|
26939
28421
|
);
|
|
26940
28422
|
}
|
|
26941
|
-
function installCleanupHandlers(close) {
|
|
26942
|
-
const
|
|
26943
|
-
|
|
26944
|
-
|
|
28423
|
+
function installCleanupHandlers(close, drainHooks, log2) {
|
|
28424
|
+
const uninstallDrain = installDrainOnSigterm({
|
|
28425
|
+
steps: {
|
|
28426
|
+
stopAccepting: () => {
|
|
28427
|
+
drainHooks.stopAccepting();
|
|
28428
|
+
},
|
|
28429
|
+
// SCOPE NOTE (BET1, partial): checkpointing an in-flight turn on a LIVE
|
|
28430
|
+
// worker (persisting enough codex turn state to resume it) is NOT
|
|
28431
|
+
// implemented in this branch - that turn still relies on the server's
|
|
28432
|
+
// rejoin replay. What IS durable is every durably-owned message that was
|
|
28433
|
+
// ENQUEUED into the per-thread inbound queue (the respawn-window / spawn-on-
|
|
28434
|
+
// miss path): flushInboundQueues fsyncs those below, and the boot/rejoin
|
|
28435
|
+
// drain replays them. Left as an explicit no-op (not silently absent) so the
|
|
28436
|
+
// gap is visible rather than implied-handled.
|
|
28437
|
+
checkpointCurrentTurn: () => void 0,
|
|
28438
|
+
flushInboundQueues: () => {
|
|
28439
|
+
drainHooks.flushInboundQueues();
|
|
28440
|
+
},
|
|
28441
|
+
// The final flush also performs the connection teardown via close(): the
|
|
28442
|
+
// cleanup stack runs after kandan.close() so no advance races the final
|
|
28443
|
+
// cursor/watermark write.
|
|
28444
|
+
flushOutbox: () => close().catch(() => void 0)
|
|
28445
|
+
},
|
|
28446
|
+
deadlineMs: runnerDrainExitDeadlineMs,
|
|
28447
|
+
log: (event, fields) => log2(event, fields),
|
|
28448
|
+
// SIGHUP is a drain-AND-EXIT signal too: a detached child of the Electron
|
|
28449
|
+
// shell that receives a parent hangup must drain its inbound queue + cursor
|
|
28450
|
+
// stores and then EXIT, never linger as a defunct lock-holder that does no
|
|
28451
|
+
// work (the prior close()-without-exit handler left a zombie that stopped
|
|
28452
|
+
// accepting, tore down its connection, yet never exited - stranding prior
|
|
28453
|
+
// jobs and holding the runner lock). installDrainOnSigterm's handler exits
|
|
28454
|
+
// after the drain (deadline-bounded), so routing SIGHUP through it fixes the
|
|
28455
|
+
// orphan-zombie regression.
|
|
28456
|
+
signals: ["SIGTERM", "SIGINT", "SIGHUP"]
|
|
28457
|
+
});
|
|
26945
28458
|
const closeOnExit = () => {
|
|
26946
28459
|
void close().catch(() => void 0);
|
|
26947
28460
|
};
|
|
26948
|
-
process.once("SIGINT", closeAndExit);
|
|
26949
|
-
process.once("SIGTERM", closeAndExit);
|
|
26950
|
-
process.once("SIGHUP", closeAndExit);
|
|
26951
28461
|
process.once("exit", closeOnExit);
|
|
26952
28462
|
return () => {
|
|
26953
|
-
|
|
26954
|
-
process.off("SIGTERM", closeAndExit);
|
|
26955
|
-
process.off("SIGHUP", closeAndExit);
|
|
28463
|
+
uninstallDrain();
|
|
26956
28464
|
process.off("exit", closeOnExit);
|
|
26957
28465
|
};
|
|
26958
28466
|
}
|
|
@@ -27255,7 +28763,7 @@ async function applyCodexUnavailableControl(kandan, topic, instanceId, control,
|
|
|
27255
28763
|
}
|
|
27256
28764
|
throw new Error(error);
|
|
27257
28765
|
}
|
|
27258
|
-
async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart) {
|
|
28766
|
+
async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart, leaseEpochFor, onLiveSourceSeqAccepted, captureLiveFollowUpEvent, onUndeliveredFollowUpsAtClose, liveSourceSeqAlreadyApplied) {
|
|
27259
28767
|
if (codex === void 0) {
|
|
27260
28768
|
switch (control.type) {
|
|
27261
28769
|
case "remote_codex_exec":
|
|
@@ -27448,6 +28956,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
|
|
|
27448
28956
|
ensureClaudeCodeForwardPortSession,
|
|
27449
28957
|
disposeClaudeCodeForwardPortSession,
|
|
27450
28958
|
resumeSessionId: normalizedWorkDescription(control.resumeSessionId),
|
|
28959
|
+
leaseEpochFor,
|
|
28960
|
+
onLiveSourceSeqAccepted,
|
|
28961
|
+
onUndeliveredFollowUpsAtClose,
|
|
27451
28962
|
setStartupStage: (stage) => {
|
|
27452
28963
|
startupStage = stage;
|
|
27453
28964
|
}
|
|
@@ -27650,6 +29161,51 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
|
|
|
27650
29161
|
message
|
|
27651
29162
|
};
|
|
27652
29163
|
}
|
|
29164
|
+
const followUpThreadId = optionalThreadControlField(
|
|
29165
|
+
control,
|
|
29166
|
+
"threadId"
|
|
29167
|
+
);
|
|
29168
|
+
const followUpChannel = optionalThreadControlField(
|
|
29169
|
+
control,
|
|
29170
|
+
"channel"
|
|
29171
|
+
);
|
|
29172
|
+
if (sourceSeq !== void 0 && followUpThreadId !== void 0 && followUpChannel !== void 0) {
|
|
29173
|
+
captureLiveFollowUpEvent?.(followUpChannel, followUpThreadId, {
|
|
29174
|
+
seq: sourceSeq,
|
|
29175
|
+
type: "thread.message",
|
|
29176
|
+
actorKind: void 0,
|
|
29177
|
+
actorSlug: void 0,
|
|
29178
|
+
actorUserId: void 0,
|
|
29179
|
+
threadId: followUpThreadId,
|
|
29180
|
+
threadTitle: void 0,
|
|
29181
|
+
replyToSeq: integerValue(control.rootSeq),
|
|
29182
|
+
localRunnerEventType: void 0,
|
|
29183
|
+
body: workDescription,
|
|
29184
|
+
attachments
|
|
29185
|
+
});
|
|
29186
|
+
}
|
|
29187
|
+
if (followUpChannel !== void 0 && followUpThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
|
|
29188
|
+
channel: followUpChannel,
|
|
29189
|
+
threadId: followUpThreadId,
|
|
29190
|
+
sourceSeq
|
|
29191
|
+
}) === true) {
|
|
29192
|
+
log2("claude_code.message_duplicate_ignored", {
|
|
29193
|
+
linzumi_thread_id: followUpThreadId,
|
|
29194
|
+
claude_session_id: codexThreadId,
|
|
29195
|
+
body_preview: claudeConsoleBodyPreview(workDescription),
|
|
29196
|
+
source_seq: sourceSeq ?? null
|
|
29197
|
+
});
|
|
29198
|
+
return {
|
|
29199
|
+
instanceId,
|
|
29200
|
+
controlType: control.type,
|
|
29201
|
+
agentProvider: "claude-code",
|
|
29202
|
+
cwd: cwd.cwd,
|
|
29203
|
+
matchedRoot: cwd.matchedRoot,
|
|
29204
|
+
codexThreadId,
|
|
29205
|
+
queuedInput: true,
|
|
29206
|
+
duplicate: true
|
|
29207
|
+
};
|
|
29208
|
+
}
|
|
27653
29209
|
try {
|
|
27654
29210
|
const enqueueResult = activeSession.enqueueInput({
|
|
27655
29211
|
content: contentResult.content,
|
|
@@ -27754,6 +29310,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
|
|
|
27754
29310
|
pendingClaudeCodeApprovals,
|
|
27755
29311
|
ensureClaudeCodeForwardPortSession,
|
|
27756
29312
|
disposeClaudeCodeForwardPortSession,
|
|
29313
|
+
leaseEpochFor,
|
|
29314
|
+
onLiveSourceSeqAccepted,
|
|
29315
|
+
onUndeliveredFollowUpsAtClose,
|
|
27757
29316
|
setStartupStage: (stage) => {
|
|
27758
29317
|
startupStage = stage;
|
|
27759
29318
|
}
|
|
@@ -27797,6 +29356,30 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
|
|
|
27797
29356
|
if (startedThreadSession === void 0 || sourceSeq === void 0) {
|
|
27798
29357
|
throw new Error("cannot reconnect a Kandan thread without a session");
|
|
27799
29358
|
}
|
|
29359
|
+
const reconnectChannel = optionalThreadControlField(control, "channel");
|
|
29360
|
+
const reconnectThreadId = optionalThreadControlField(
|
|
29361
|
+
control,
|
|
29362
|
+
"threadId"
|
|
29363
|
+
);
|
|
29364
|
+
if (reconnectChannel !== void 0 && reconnectThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
|
|
29365
|
+
channel: reconnectChannel,
|
|
29366
|
+
threadId: reconnectThreadId,
|
|
29367
|
+
sourceSeq
|
|
29368
|
+
}) === true) {
|
|
29369
|
+
log2("kandan.reconnect_thread_seq_already_applied", {
|
|
29370
|
+
linzumi_thread_id: reconnectThreadId,
|
|
29371
|
+
codex_thread_id: codexThreadId,
|
|
29372
|
+
source_seq: sourceSeq
|
|
29373
|
+
});
|
|
29374
|
+
return {
|
|
29375
|
+
instanceId,
|
|
29376
|
+
controlType: control.type,
|
|
29377
|
+
cwd: cwd.cwd,
|
|
29378
|
+
matchedRoot: cwd.matchedRoot,
|
|
29379
|
+
codexThreadId,
|
|
29380
|
+
duplicate: true
|
|
29381
|
+
};
|
|
29382
|
+
}
|
|
27800
29383
|
await resumeCodexThreadForReconnect(
|
|
27801
29384
|
codex,
|
|
27802
29385
|
codexThreadId,
|
|
@@ -28543,11 +30126,13 @@ function claudeCodeTurnStallThresholdMs(env) {
|
|
|
28543
30126
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultClaudeCodeTurnStallThresholdMs;
|
|
28544
30127
|
}
|
|
28545
30128
|
function createClaudeCodeInputQueue(input) {
|
|
30129
|
+
const onSourceSeqDelivered = input.onSourceSeqDelivered;
|
|
28546
30130
|
const pendingMessages = [
|
|
28547
30131
|
claudeCodeUserInputMessage(input.content)
|
|
28548
30132
|
];
|
|
28549
30133
|
const pendingSourceSeqs = [input.sourceSeq];
|
|
28550
30134
|
const deferredFollowUps = [];
|
|
30135
|
+
const deliveredSourceSeqs = /* @__PURE__ */ new Set();
|
|
28551
30136
|
const waiters = [];
|
|
28552
30137
|
const state = {
|
|
28553
30138
|
closed: false
|
|
@@ -28563,6 +30148,10 @@ function createClaudeCodeInputQueue(input) {
|
|
|
28563
30148
|
};
|
|
28564
30149
|
const deliverNow = (message, sourceSeq) => {
|
|
28565
30150
|
pendingSourceSeqs.push(sourceSeq);
|
|
30151
|
+
if (sourceSeq !== void 0 && onSourceSeqDelivered !== void 0 && !deliveredSourceSeqs.has(sourceSeq)) {
|
|
30152
|
+
deliveredSourceSeqs.add(sourceSeq);
|
|
30153
|
+
onSourceSeqDelivered(sourceSeq);
|
|
30154
|
+
}
|
|
28566
30155
|
const waiter = waiters.shift();
|
|
28567
30156
|
if (waiter === void 0) {
|
|
28568
30157
|
pendingMessages.push(message);
|
|
@@ -28631,6 +30220,17 @@ function createClaudeCodeInputQueue(input) {
|
|
|
28631
30220
|
},
|
|
28632
30221
|
enqueue,
|
|
28633
30222
|
enqueueSteer,
|
|
30223
|
+
// BLOCKER A (don't drop deferred follow-ups on a kill): the parked,
|
|
30224
|
+
// never-delivered follow-ups (a turn was in flight at close, so close()'s
|
|
30225
|
+
// drain is intentionally skipped to avoid folding them into the active turn).
|
|
30226
|
+
// Their watermark was NOT committed (commit fires only at deliverNow), so the
|
|
30227
|
+
// runner must re-enqueue them into the DURABLE inbound queue; a kill-before-
|
|
30228
|
+
// run then replays + runs them exactly once. Drains the parked list so a
|
|
30229
|
+
// double-call cannot double-enqueue. Returns the un-run seqs+messages.
|
|
30230
|
+
drainUndeliveredFollowUps: () => {
|
|
30231
|
+
const drained = deferredFollowUps.splice(0);
|
|
30232
|
+
return drained;
|
|
30233
|
+
},
|
|
28634
30234
|
completeTurn: () => {
|
|
28635
30235
|
const completed = pendingSourceSeqs.shift();
|
|
28636
30236
|
if (!state.closed && pendingSourceSeqs.length === 0) {
|
|
@@ -28857,7 +30457,21 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
28857
30457
|
}
|
|
28858
30458
|
const inputQueue = createClaudeCodeInputQueue({
|
|
28859
30459
|
content: initialContentResult.content,
|
|
28860
|
-
sourceSeq
|
|
30460
|
+
sourceSeq,
|
|
30461
|
+
// BLOCKER A: commit the persisted dedup watermark at DISPATCH (deliverNow),
|
|
30462
|
+
// not at enqueue-accept. enqueueInput used to commit unconditionally right
|
|
30463
|
+
// after inputQueue.enqueue, but enqueue PARKS a mid-turn follow-up in
|
|
30464
|
+
// deferredFollowUps (a bare return) and a close-in-flight drops it - so a
|
|
30465
|
+
// never-run seq was marked applied and its replay was gated 'ignored' (the
|
|
30466
|
+
// drop). Committing only when the seq is actually delivered onto the live
|
|
30467
|
+
// stream means a parked-but-unrun seq is never marked applied.
|
|
30468
|
+
onSourceSeqDelivered: (deliveredSeq) => {
|
|
30469
|
+
args.onLiveSourceSeqAccepted?.({
|
|
30470
|
+
channel,
|
|
30471
|
+
threadId,
|
|
30472
|
+
sourceSeq: deliveredSeq
|
|
30473
|
+
});
|
|
30474
|
+
}
|
|
28861
30475
|
});
|
|
28862
30476
|
const developerPrompt = normalizedWorkDescription(
|
|
28863
30477
|
args.control.developerPrompt
|
|
@@ -28966,7 +30580,8 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
28966
30580
|
channelSlug: channel,
|
|
28967
30581
|
kandanThreadId: threadId,
|
|
28968
30582
|
codexThreadId: activeSessionId ?? args.resumeSessionId,
|
|
28969
|
-
rootSeq
|
|
30583
|
+
rootSeq,
|
|
30584
|
+
leaseEpoch: args.leaseEpochFor?.(threadId)
|
|
28970
30585
|
}),
|
|
28971
30586
|
push: async (event, payload, _timeoutMs, onSent) => {
|
|
28972
30587
|
const reply = await args.kandan.push(args.topic, event, payload, onSent);
|
|
@@ -29014,7 +30629,7 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
29014
30629
|
log: args.log
|
|
29015
30630
|
});
|
|
29016
30631
|
const liveBashCapture = args.options.claudeCodeRunner === void 0 && claudeLiveBashCaptureEnabled(process.env) ? createClaudeCodeLiveBashCapture({
|
|
29017
|
-
captureDir:
|
|
30632
|
+
captureDir: join22(
|
|
29018
30633
|
tmpdir3(),
|
|
29019
30634
|
`linzumi-claude-live-bash-${args.instanceId}`
|
|
29020
30635
|
),
|
|
@@ -29133,6 +30748,7 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
29133
30748
|
linzumi_thread_id: threadId,
|
|
29134
30749
|
claude_session_id: event.sessionId
|
|
29135
30750
|
});
|
|
30751
|
+
args.onLiveSourceSeqAccepted?.({ channel, threadId, sourceSeq });
|
|
29136
30752
|
args.activeClaudeCodeSessions.set(event.sessionId, {
|
|
29137
30753
|
abortController,
|
|
29138
30754
|
workspace,
|
|
@@ -29341,6 +30957,15 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
29341
30957
|
onTranscriptEvent
|
|
29342
30958
|
});
|
|
29343
30959
|
} finally {
|
|
30960
|
+
const undeliveredFollowUps = inputQueue.drainUndeliveredFollowUps();
|
|
30961
|
+
const undeliveredSeqs = undeliveredFollowUps.map((followUp) => followUp.sourceSeq).filter((seq) => seq !== void 0);
|
|
30962
|
+
if (undeliveredSeqs.length > 0 && args.onUndeliveredFollowUpsAtClose !== void 0) {
|
|
30963
|
+
args.onUndeliveredFollowUpsAtClose({
|
|
30964
|
+
channel,
|
|
30965
|
+
threadId,
|
|
30966
|
+
sourceSeqs: undeliveredSeqs
|
|
30967
|
+
});
|
|
30968
|
+
}
|
|
29344
30969
|
inputQueue.close();
|
|
29345
30970
|
mcpAuthCleanup?.();
|
|
29346
30971
|
liveBashCapture?.close();
|
|
@@ -29864,15 +31489,15 @@ function rehydrationControlSnapshot(args) {
|
|
|
29864
31489
|
...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
|
|
29865
31490
|
};
|
|
29866
31491
|
}
|
|
29867
|
-
function
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
}
|
|
31492
|
+
function deadLetterFailedReason(reason) {
|
|
31493
|
+
switch (reason) {
|
|
31494
|
+
case "exhausted":
|
|
31495
|
+
return "couldn't resume this job after several attempts; please resend your last message to continue.";
|
|
31496
|
+
case "withheld":
|
|
31497
|
+
return "this message may have already run before an unexpected shutdown; it was not re-run to avoid duplicating work. Please resend if it did not complete.";
|
|
31498
|
+
case "permanent":
|
|
31499
|
+
return "couldn't resume this job's coding session.";
|
|
31500
|
+
}
|
|
29876
31501
|
}
|
|
29877
31502
|
function rehydrationReconnectControl(record) {
|
|
29878
31503
|
const snapshot = record.control;
|
|
@@ -30920,8 +32545,8 @@ function onboardingConversationOfficeImport(response) {
|
|
|
30920
32545
|
return objectValue(metadata?.office_channel_import);
|
|
30921
32546
|
}
|
|
30922
32547
|
function onboardingConversationImportClientMessageId(importKey, itemKey) {
|
|
30923
|
-
const importHash =
|
|
30924
|
-
const itemHash =
|
|
32548
|
+
const importHash = createHash7("sha256").update(importKey).digest("hex");
|
|
32549
|
+
const itemHash = createHash7("sha256").update(itemKey).digest("hex");
|
|
30925
32550
|
return `onboarding-import:${importHash.slice(0, 32)}:${itemHash.slice(0, 24)}`;
|
|
30926
32551
|
}
|
|
30927
32552
|
async function uploadLocalConversationAttachments(args) {
|
|
@@ -31019,7 +32644,7 @@ async function localConversationAttachmentFile(candidatePath, attachment) {
|
|
|
31019
32644
|
};
|
|
31020
32645
|
}
|
|
31021
32646
|
case "path": {
|
|
31022
|
-
const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(
|
|
32647
|
+
const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(dirname15(candidatePath), attachment.path);
|
|
31023
32648
|
try {
|
|
31024
32649
|
const bytes = await readFile2(path2);
|
|
31025
32650
|
return {
|
|
@@ -31474,9 +33099,9 @@ function mcpOwnerUsername(options) {
|
|
|
31474
33099
|
return options.channelSession?.listenUser ?? identityFromAccessToken(options.token).actorUsername;
|
|
31475
33100
|
}
|
|
31476
33101
|
function writeEphemeralMcpAuthFile(options) {
|
|
31477
|
-
const directory = mkdtempSync4(
|
|
33102
|
+
const directory = mkdtempSync4(join22(tmpdir3(), "linzumi-mcp-auth-"));
|
|
31478
33103
|
chmodSync2(directory, 448);
|
|
31479
|
-
const path2 =
|
|
33104
|
+
const path2 = join22(directory, "auth.json");
|
|
31480
33105
|
writeCachedLocalRunnerToken({
|
|
31481
33106
|
kandanUrl: options.kandanUrl,
|
|
31482
33107
|
accessToken: options.token,
|
|
@@ -31552,7 +33177,7 @@ function configuredAllowedCwds(values, options = {}) {
|
|
|
31552
33177
|
const absolutePath = resolve8(expandUserPath(value));
|
|
31553
33178
|
try {
|
|
31554
33179
|
if (options.createMissing === true) {
|
|
31555
|
-
|
|
33180
|
+
mkdirSync14(absolutePath, { recursive: true });
|
|
31556
33181
|
}
|
|
31557
33182
|
const realPath = realpathSync6(absolutePath);
|
|
31558
33183
|
allowedCwds.push(
|
|
@@ -31589,7 +33214,7 @@ function allowedCwdProjects(allowedCwds) {
|
|
|
31589
33214
|
});
|
|
31590
33215
|
}
|
|
31591
33216
|
function isGitProjectDirectory(cwd) {
|
|
31592
|
-
const gitPath =
|
|
33217
|
+
const gitPath = join22(cwd, ".git");
|
|
31593
33218
|
try {
|
|
31594
33219
|
const gitPathStats = statSync3(gitPath);
|
|
31595
33220
|
return gitPathStats.isDirectory() || gitPathStats.isFile();
|
|
@@ -31612,9 +33237,9 @@ function browseRunnerDirectory(control, options) {
|
|
|
31612
33237
|
error: "not_directory"
|
|
31613
33238
|
};
|
|
31614
33239
|
}
|
|
31615
|
-
const parent =
|
|
33240
|
+
const parent = dirname15(currentPath);
|
|
31616
33241
|
const entries = readdirSync4(currentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => visibleRunnerDirectoryEntryName(entry.name)).map((entry) => {
|
|
31617
|
-
const path2 =
|
|
33242
|
+
const path2 = join22(currentPath, entry.name);
|
|
31618
33243
|
return {
|
|
31619
33244
|
name: entry.name,
|
|
31620
33245
|
path: path2,
|
|
@@ -31655,7 +33280,7 @@ function projectDirectoryName(name) {
|
|
|
31655
33280
|
function availableProjectDirectoryName(projectsRoot, baseName, suffix = 0) {
|
|
31656
33281
|
for (let nextSuffix = suffix; ; nextSuffix += 1) {
|
|
31657
33282
|
const candidate = nextSuffix === 0 ? baseName : `${baseName}-${nextSuffix}`;
|
|
31658
|
-
if (!projectPathExists(
|
|
33283
|
+
if (!projectPathExists(join22(projectsRoot, candidate))) {
|
|
31659
33284
|
return candidate;
|
|
31660
33285
|
}
|
|
31661
33286
|
}
|
|
@@ -31683,9 +33308,9 @@ function createRunnerProject(control, options, allowedCwds) {
|
|
|
31683
33308
|
error: "invalid_project_template"
|
|
31684
33309
|
};
|
|
31685
33310
|
}
|
|
31686
|
-
const projectsRoot =
|
|
33311
|
+
const projectsRoot = join22(currentHomeDirectory(), "linzumi");
|
|
31687
33312
|
const resolvedProjectDirName = template === "hello_linzumi_demo" ? availableProjectDirectoryName(projectsRoot, projectDirName) : projectDirName;
|
|
31688
|
-
const projectPath =
|
|
33313
|
+
const projectPath = join22(projectsRoot, resolvedProjectDirName);
|
|
31689
33314
|
let createdProjectPath = false;
|
|
31690
33315
|
try {
|
|
31691
33316
|
if (template !== "hello_linzumi_demo" && projectPathExists(projectPath)) {
|
|
@@ -31697,7 +33322,7 @@ function createRunnerProject(control, options, allowedCwds) {
|
|
|
31697
33322
|
error: "project_directory_exists"
|
|
31698
33323
|
};
|
|
31699
33324
|
}
|
|
31700
|
-
|
|
33325
|
+
mkdirSync14(projectsRoot, { recursive: true });
|
|
31701
33326
|
if (template === "hello_linzumi_demo") {
|
|
31702
33327
|
createdProjectPath = true;
|
|
31703
33328
|
createHelloLinzumiProject({
|
|
@@ -31705,7 +33330,7 @@ function createRunnerProject(control, options, allowedCwds) {
|
|
|
31705
33330
|
name: resolvedProjectDirName
|
|
31706
33331
|
});
|
|
31707
33332
|
} else {
|
|
31708
|
-
|
|
33333
|
+
mkdirSync14(projectPath, { recursive: false });
|
|
31709
33334
|
createdProjectPath = true;
|
|
31710
33335
|
}
|
|
31711
33336
|
const git = spawnSync5("git", ["init"], {
|
|
@@ -31818,7 +33443,7 @@ async function suggestRunnerTasks(control, options, allowedCwds) {
|
|
|
31818
33443
|
};
|
|
31819
33444
|
}
|
|
31820
33445
|
}
|
|
31821
|
-
var THREAD_RUNNER_READY_TIMEOUT_MS, THREAD_RUNNER_REAPER_INTERVAL_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
|
|
33446
|
+
var THREAD_RUNNER_READY_TIMEOUT_MS, THREAD_RUNNER_REAPER_INTERVAL_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, runnerDrainExitDeadlineMs, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
|
|
31822
33447
|
var init_runner = __esm({
|
|
31823
33448
|
"src/runner.ts"() {
|
|
31824
33449
|
"use strict";
|
|
@@ -31826,6 +33451,7 @@ var init_runner = __esm({
|
|
|
31826
33451
|
init_runnerThreadSessionRegistry();
|
|
31827
33452
|
init_outboxJournal();
|
|
31828
33453
|
init_channelSessionSupport();
|
|
33454
|
+
init_channelSessionSupport();
|
|
31829
33455
|
init_commanderAttachments();
|
|
31830
33456
|
init_claudeCodePipeline();
|
|
31831
33457
|
init_claudeCodePlanMirror();
|
|
@@ -31868,11 +33494,18 @@ var init_runner = __esm({
|
|
|
31868
33494
|
init_signupTaskSuggestions();
|
|
31869
33495
|
init_userFacingErrors();
|
|
31870
33496
|
init_remoteCodexSandboxRunner();
|
|
33497
|
+
init_durableInboundQueue();
|
|
33498
|
+
init_appliedSourceSeqWatermark();
|
|
33499
|
+
init_threadReconcilerLock();
|
|
33500
|
+
init_runnerLifecycleGuards();
|
|
33501
|
+
init_leaseEpochGuard();
|
|
33502
|
+
init_resumeKeystone();
|
|
31871
33503
|
THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
|
|
31872
33504
|
THREAD_RUNNER_REAPER_INTERVAL_MS = 3e4;
|
|
31873
33505
|
onboardingDiscoveryAgentStartKeys = /* @__PURE__ */ new Set();
|
|
31874
33506
|
onboardingConversationImportKeys = /* @__PURE__ */ new Set();
|
|
31875
33507
|
onboardingConversationDiscoveryReportLimit = 100;
|
|
33508
|
+
CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS = 3e3;
|
|
31876
33509
|
staleStartInstanceWindowMs = 6e4;
|
|
31877
33510
|
connectionStaleThresholdMs = 12e4;
|
|
31878
33511
|
connectionUnrecoverableAfterMs = 10 * 60 * 1e3;
|
|
@@ -31885,6 +33518,7 @@ var init_runner = __esm({
|
|
|
31885
33518
|
"turn/canceled",
|
|
31886
33519
|
"turn/cancelled"
|
|
31887
33520
|
]);
|
|
33521
|
+
runnerDrainExitDeadlineMs = 8e3;
|
|
31888
33522
|
claudeSessionStoreSequenceHighWater = /* @__PURE__ */ new Map();
|
|
31889
33523
|
claudeCodeLlmProxySessionHeader = "x-linzumi-llm-proxy-token";
|
|
31890
33524
|
onboardingConversationTitleFirstMessages = 4;
|
|
@@ -31897,7 +33531,7 @@ var init_runner = __esm({
|
|
|
31897
33531
|
});
|
|
31898
33532
|
|
|
31899
33533
|
// src/kandanTls.ts
|
|
31900
|
-
import { existsSync as existsSync14, readFileSync as
|
|
33534
|
+
import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
|
|
31901
33535
|
import { Agent } from "undici";
|
|
31902
33536
|
import WsWebSocket from "ws";
|
|
31903
33537
|
function kandanTlsTrustFromEnv() {
|
|
@@ -31911,7 +33545,7 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
31911
33545
|
if (!existsSync14(trimmed)) {
|
|
31912
33546
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
31913
33547
|
}
|
|
31914
|
-
const ca =
|
|
33548
|
+
const ca = readFileSync19(trimmed, "utf8");
|
|
31915
33549
|
return {
|
|
31916
33550
|
caFile: trimmed,
|
|
31917
33551
|
ca,
|
|
@@ -50695,7 +52329,7 @@ var init_RemoveFileError = __esm({
|
|
|
50695
52329
|
|
|
50696
52330
|
// ../../node_modules/@inquirer/external-editor/dist/esm/index.js
|
|
50697
52331
|
import { spawn as spawn11, spawnSync as spawnSync6 } from "child_process";
|
|
50698
|
-
import { readFileSync as
|
|
52332
|
+
import { readFileSync as readFileSync22, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
|
|
50699
52333
|
import path from "node:path";
|
|
50700
52334
|
import os from "node:os";
|
|
50701
52335
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -50819,7 +52453,7 @@ var init_esm5 = __esm({
|
|
|
50819
52453
|
}
|
|
50820
52454
|
readTemporaryFile() {
|
|
50821
52455
|
try {
|
|
50822
|
-
const tempFileBuffer =
|
|
52456
|
+
const tempFileBuffer = readFileSync22(this.tempFile);
|
|
50823
52457
|
if (tempFileBuffer.length === 0) {
|
|
50824
52458
|
this.text = "";
|
|
50825
52459
|
} else {
|
|
@@ -52188,17 +53822,17 @@ import { spawn as spawn12, spawnSync as spawnSync7 } from "node:child_process";
|
|
|
52188
53822
|
import {
|
|
52189
53823
|
existsSync as existsSync17,
|
|
52190
53824
|
constants as fsConstants,
|
|
52191
|
-
mkdirSync as
|
|
53825
|
+
mkdirSync as mkdirSync17,
|
|
52192
53826
|
mkdtempSync as mkdtempSync6,
|
|
52193
|
-
readFileSync as
|
|
53827
|
+
readFileSync as readFileSync23,
|
|
52194
53828
|
readdirSync as readdirSync5,
|
|
52195
53829
|
rmSync as rmSync7,
|
|
52196
53830
|
statSync as statSync4,
|
|
52197
53831
|
writeFileSync as writeFileSync16
|
|
52198
53832
|
} from "node:fs";
|
|
52199
53833
|
import { access } from "node:fs/promises";
|
|
52200
|
-
import { homedir as
|
|
52201
|
-
import { delimiter as delimiter3, dirname as
|
|
53834
|
+
import { homedir as homedir18, tmpdir as tmpdir5 } from "node:os";
|
|
53835
|
+
import { delimiter as delimiter3, dirname as dirname19, join as join26, resolve as resolve10 } from "node:path";
|
|
52202
53836
|
import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
|
|
52203
53837
|
import { emitKeypressEvents } from "node:readline";
|
|
52204
53838
|
function signupHelpText() {
|
|
@@ -52319,7 +53953,7 @@ function defaultSignupDraftStore(serviceUrl) {
|
|
|
52319
53953
|
}
|
|
52320
53954
|
let parsed;
|
|
52321
53955
|
try {
|
|
52322
|
-
parsed = JSON.parse(
|
|
53956
|
+
parsed = JSON.parse(readFileSync23(path2, "utf8"));
|
|
52323
53957
|
} catch (_error) {
|
|
52324
53958
|
return void 0;
|
|
52325
53959
|
}
|
|
@@ -52329,7 +53963,7 @@ function defaultSignupDraftStore(serviceUrl) {
|
|
|
52329
53963
|
return comparableServiceUrl(parsed.serviceUrl) === comparableServiceUrl(serviceUrl) ? parsed : void 0;
|
|
52330
53964
|
},
|
|
52331
53965
|
write: (draft) => {
|
|
52332
|
-
|
|
53966
|
+
mkdirSync17(dirname19(path2), { recursive: true });
|
|
52333
53967
|
writeFileSync16(path2, `${JSON.stringify(draft, null, 2)}
|
|
52334
53968
|
`, {
|
|
52335
53969
|
encoding: "utf8",
|
|
@@ -52342,8 +53976,8 @@ function defaultSignupDraftStore(serviceUrl) {
|
|
|
52342
53976
|
};
|
|
52343
53977
|
}
|
|
52344
53978
|
function defaultSignupDraftPath(serviceUrl) {
|
|
52345
|
-
return
|
|
52346
|
-
|
|
53979
|
+
return join26(
|
|
53980
|
+
dirname19(localConfigPath()),
|
|
52347
53981
|
`signup-draft.${localConfigScopeFileStem(serviceUrl)}.json`
|
|
52348
53982
|
);
|
|
52349
53983
|
}
|
|
@@ -52377,7 +54011,7 @@ function defaultSignupTaskCacheStore(serviceUrl) {
|
|
|
52377
54011
|
}
|
|
52378
54012
|
}
|
|
52379
54013
|
};
|
|
52380
|
-
|
|
54014
|
+
mkdirSync17(dirname19(path2), { recursive: true });
|
|
52381
54015
|
writeFileSync16(path2, `${JSON.stringify(next, null, 2)}
|
|
52382
54016
|
`, {
|
|
52383
54017
|
encoding: "utf8",
|
|
@@ -52387,8 +54021,8 @@ function defaultSignupTaskCacheStore(serviceUrl) {
|
|
|
52387
54021
|
};
|
|
52388
54022
|
}
|
|
52389
54023
|
function defaultSignupTaskCachePath(serviceUrl) {
|
|
52390
|
-
return
|
|
52391
|
-
|
|
54024
|
+
return join26(
|
|
54025
|
+
dirname19(localConfigPath()),
|
|
52392
54026
|
`signup-task-cache.${localConfigScopeFileStem(serviceUrl)}.json`
|
|
52393
54027
|
);
|
|
52394
54028
|
}
|
|
@@ -52398,7 +54032,7 @@ function readSignupTaskCache(path2) {
|
|
|
52398
54032
|
}
|
|
52399
54033
|
let parsed;
|
|
52400
54034
|
try {
|
|
52401
|
-
parsed = JSON.parse(
|
|
54035
|
+
parsed = JSON.parse(readFileSync23(path2, "utf8"));
|
|
52402
54036
|
} catch (_error) {
|
|
52403
54037
|
return { version: 1, entries: {} };
|
|
52404
54038
|
}
|
|
@@ -54283,7 +55917,7 @@ function autocompletePathSuggestions(pathInput) {
|
|
|
54283
55917
|
try {
|
|
54284
55918
|
const showHidden = prefix.startsWith(".");
|
|
54285
55919
|
const currentPath = directoryExists(normalizedInput) ? [normalizedInput.replace(/\/$/, "") || "/"] : [];
|
|
54286
|
-
const childPaths = readdirSync5(directoryPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => showHidden || !entry.name.startsWith(".")).map((entry) =>
|
|
55920
|
+
const childPaths = readdirSync5(directoryPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => showHidden || !entry.name.startsWith(".")).map((entry) => join26(directoryPath, entry.name)).map((path2) => ({
|
|
54287
55921
|
path: path2,
|
|
54288
55922
|
score: fuzzyPathSegmentScore(path2.split("/").at(-1) ?? path2, prefix)
|
|
54289
55923
|
})).filter((candidate) => candidate.score !== void 0).sort((left, right) => {
|
|
@@ -54529,7 +56163,7 @@ async function runSignupPreflights(runtime) {
|
|
|
54529
56163
|
function defaultPreflightRuntime() {
|
|
54530
56164
|
return {
|
|
54531
56165
|
cwd: process.cwd(),
|
|
54532
|
-
homeDir:
|
|
56166
|
+
homeDir: homedir18(),
|
|
54533
56167
|
probeTool,
|
|
54534
56168
|
readGitEmail,
|
|
54535
56169
|
discoverCodeRoots,
|
|
@@ -54674,9 +56308,9 @@ async function suggestTasksWithCodex(projectPath, retryPolicy) {
|
|
|
54674
56308
|
);
|
|
54675
56309
|
}
|
|
54676
56310
|
async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolicy) {
|
|
54677
|
-
const tempRoot = mkdtempSync6(
|
|
54678
|
-
const schemaPath =
|
|
54679
|
-
const outputPath =
|
|
56311
|
+
const tempRoot = mkdtempSync6(join26(tmpdir5(), "linzumi-signup-codex-tasks-"));
|
|
56312
|
+
const schemaPath = join26(tempRoot, "task-suggestions.schema.json");
|
|
56313
|
+
const outputPath = join26(tempRoot, "task-suggestions.json");
|
|
54680
56314
|
const model = process.env.LINZUMI_SIGNUP_TASK_MODEL ?? "gpt-5.4-mini";
|
|
54681
56315
|
const prompt = taskSuggestionPrompt2(previousResponse);
|
|
54682
56316
|
const codexCommand = await resolveSignupCodexCommand();
|
|
@@ -54701,7 +56335,7 @@ async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolic
|
|
|
54701
56335
|
),
|
|
54702
56336
|
retryPolicy
|
|
54703
56337
|
);
|
|
54704
|
-
return
|
|
56338
|
+
return readFileSync23(outputPath, "utf8");
|
|
54705
56339
|
} finally {
|
|
54706
56340
|
rmSync7(tempRoot, { recursive: true, force: true });
|
|
54707
56341
|
}
|
|
@@ -54738,7 +56372,7 @@ function codexTaskSuggestionProcess2(args) {
|
|
|
54738
56372
|
function signupCodexTaskSuggestionProcessForTest(args) {
|
|
54739
56373
|
return codexTaskSuggestionProcess2(args);
|
|
54740
56374
|
}
|
|
54741
|
-
async function resolveSignupCodexCommand(env = process.env, homeDir =
|
|
56375
|
+
async function resolveSignupCodexCommand(env = process.env, homeDir = homedir18(), executableExists = fileIsExecutable) {
|
|
54742
56376
|
const override = firstConfiguredValue([
|
|
54743
56377
|
env.LINZUMI_SIGNUP_CODEX_BIN,
|
|
54744
56378
|
env.LINZUMI_CODEX_BIN
|
|
@@ -54793,7 +56427,7 @@ function resolveHomePath(path2, homeDir) {
|
|
|
54793
56427
|
return homeDir;
|
|
54794
56428
|
}
|
|
54795
56429
|
if (path2.startsWith("~/")) {
|
|
54796
|
-
return
|
|
56430
|
+
return join26(homeDir, path2.slice(2));
|
|
54797
56431
|
}
|
|
54798
56432
|
return resolve10(path2);
|
|
54799
56433
|
}
|
|
@@ -54806,9 +56440,9 @@ function commandLooksPathLike(command) {
|
|
|
54806
56440
|
}
|
|
54807
56441
|
function homeManagedCodexCandidates(homeDir) {
|
|
54808
56442
|
return [
|
|
54809
|
-
|
|
54810
|
-
|
|
54811
|
-
|
|
56443
|
+
join26(homeDir, ".volta", "bin", "codex"),
|
|
56444
|
+
join26(homeDir, ".local", "bin", "codex"),
|
|
56445
|
+
join26(homeDir, "bin", "codex")
|
|
54812
56446
|
];
|
|
54813
56447
|
}
|
|
54814
56448
|
async function firstExecutablePath(paths, executableExists) {
|
|
@@ -54823,7 +56457,7 @@ function commandPathCandidates(path2) {
|
|
|
54823
56457
|
if (path2 === void 0 || path2.trim() === "") {
|
|
54824
56458
|
return [];
|
|
54825
56459
|
}
|
|
54826
|
-
return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) =>
|
|
56460
|
+
return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) => join26(entry, "codex"));
|
|
54827
56461
|
}
|
|
54828
56462
|
async function fileIsExecutable(path2) {
|
|
54829
56463
|
try {
|
|
@@ -55056,11 +56690,11 @@ function codexPreflightLocation(command, homeDir) {
|
|
|
55056
56690
|
switch (command) {
|
|
55057
56691
|
case "codex":
|
|
55058
56692
|
return "on PATH";
|
|
55059
|
-
case
|
|
56693
|
+
case join26(homeDir, ".volta", "bin", "codex"):
|
|
55060
56694
|
return "via Volta";
|
|
55061
|
-
case
|
|
56695
|
+
case join26(homeDir, ".local", "bin", "codex"):
|
|
55062
56696
|
return "via ~/.local/bin";
|
|
55063
|
-
case
|
|
56697
|
+
case join26(homeDir, "bin", "codex"):
|
|
55064
56698
|
return "via ~/bin";
|
|
55065
56699
|
default:
|
|
55066
56700
|
return "from configured path";
|
|
@@ -55219,7 +56853,7 @@ function probeToolWithArgs(command, args, cwd) {
|
|
|
55219
56853
|
}
|
|
55220
56854
|
async function discoverCodeRoots(homeDir) {
|
|
55221
56855
|
const candidates = ["src", "code", "projects"].map(
|
|
55222
|
-
(name) =>
|
|
56856
|
+
(name) => join26(homeDir, name)
|
|
55223
56857
|
);
|
|
55224
56858
|
return candidates.filter((path2) => existsSync17(path2)).flatMap((path2) => discoveredProjectNames(path2));
|
|
55225
56859
|
}
|
|
@@ -55273,7 +56907,7 @@ function discoverProjectsFromCurrentDirectory(cwd) {
|
|
|
55273
56907
|
}
|
|
55274
56908
|
function discoverProjectsFromGuessedRoots(homeDir) {
|
|
55275
56909
|
const guessedRoots = ["src", "code", "projects"].map(
|
|
55276
|
-
(name) =>
|
|
56910
|
+
(name) => join26(homeDir, name)
|
|
55277
56911
|
);
|
|
55278
56912
|
const projects = guessedRoots.flatMap(
|
|
55279
56913
|
(root) => discoverProjectsUnderRoot(root)
|
|
@@ -55325,25 +56959,25 @@ function looksLikeProject(path2) {
|
|
|
55325
56959
|
"pnpm-lock.yaml",
|
|
55326
56960
|
"yarn.lock",
|
|
55327
56961
|
"package-lock.json"
|
|
55328
|
-
].some((name) => existsSync17(
|
|
56962
|
+
].some((name) => existsSync17(join26(path2, name)));
|
|
55329
56963
|
}
|
|
55330
56964
|
function detectProjectLanguage(path2) {
|
|
55331
|
-
if (existsSync17(
|
|
56965
|
+
if (existsSync17(join26(path2, "pyproject.toml")) || existsSync17(join26(path2, "requirements.txt"))) {
|
|
55332
56966
|
return "Python";
|
|
55333
56967
|
}
|
|
55334
|
-
if (existsSync17(
|
|
56968
|
+
if (existsSync17(join26(path2, "Cargo.toml"))) {
|
|
55335
56969
|
return "Rust";
|
|
55336
56970
|
}
|
|
55337
|
-
if (existsSync17(
|
|
56971
|
+
if (existsSync17(join26(path2, "go.mod"))) {
|
|
55338
56972
|
return "Go";
|
|
55339
56973
|
}
|
|
55340
|
-
if (existsSync17(
|
|
56974
|
+
if (existsSync17(join26(path2, "mix.exs"))) {
|
|
55341
56975
|
return "Elixir";
|
|
55342
56976
|
}
|
|
55343
|
-
if (existsSync17(
|
|
56977
|
+
if (existsSync17(join26(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
|
|
55344
56978
|
return "TypeScript";
|
|
55345
56979
|
}
|
|
55346
|
-
if (existsSync17(
|
|
56980
|
+
if (existsSync17(join26(path2, "package.json"))) {
|
|
55347
56981
|
return "JavaScript";
|
|
55348
56982
|
}
|
|
55349
56983
|
return "Project";
|
|
@@ -55351,7 +56985,7 @@ function detectProjectLanguage(path2) {
|
|
|
55351
56985
|
function packageJsonMentionsTypeScript(path2) {
|
|
55352
56986
|
try {
|
|
55353
56987
|
const packageJson2 = JSON.parse(
|
|
55354
|
-
|
|
56988
|
+
readFileSync23(join26(path2, "package.json"), "utf8")
|
|
55355
56989
|
);
|
|
55356
56990
|
return packageJson2.dependencies?.typescript !== void 0 || packageJson2.devDependencies?.typescript !== void 0;
|
|
55357
56991
|
} catch {
|
|
@@ -55359,11 +56993,11 @@ function packageJsonMentionsTypeScript(path2) {
|
|
|
55359
56993
|
}
|
|
55360
56994
|
}
|
|
55361
56995
|
function hasGitMetadata(path2) {
|
|
55362
|
-
return existsSync17(
|
|
56996
|
+
return existsSync17(join26(path2, ".git"));
|
|
55363
56997
|
}
|
|
55364
56998
|
function childDirectories(root) {
|
|
55365
56999
|
try {
|
|
55366
|
-
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
57000
|
+
return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join26(root, entry.name));
|
|
55367
57001
|
} catch {
|
|
55368
57002
|
return [];
|
|
55369
57003
|
}
|
|
@@ -55389,10 +57023,10 @@ function directoryExists(path2) {
|
|
|
55389
57023
|
}
|
|
55390
57024
|
function expandHomePath(path2) {
|
|
55391
57025
|
if (path2 === "~") {
|
|
55392
|
-
return
|
|
57026
|
+
return homedir18();
|
|
55393
57027
|
}
|
|
55394
57028
|
if (path2.startsWith("~/")) {
|
|
55395
|
-
return
|
|
57029
|
+
return join26(homedir18(), path2.slice(2));
|
|
55396
57030
|
}
|
|
55397
57031
|
return resolve10(path2);
|
|
55398
57032
|
}
|
|
@@ -55483,8 +57117,8 @@ init_runner();
|
|
|
55483
57117
|
init_runnerLockTakeover();
|
|
55484
57118
|
init_claudeCodeSession();
|
|
55485
57119
|
init_authCache();
|
|
55486
|
-
import { existsSync as existsSync18, readFileSync as
|
|
55487
|
-
import { homedir as
|
|
57120
|
+
import { existsSync as existsSync18, readFileSync as readFileSync24, realpathSync as realpathSync7 } from "node:fs";
|
|
57121
|
+
import { homedir as homedir19 } from "node:os";
|
|
55488
57122
|
import { resolve as resolve11 } from "node:path";
|
|
55489
57123
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
55490
57124
|
|
|
@@ -55579,9 +57213,9 @@ init_kandanTls();
|
|
|
55579
57213
|
init_protocol();
|
|
55580
57214
|
init_json();
|
|
55581
57215
|
init_defaultUrls();
|
|
55582
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
55583
|
-
import { dirname as
|
|
55584
|
-
import { homedir as
|
|
57216
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "node:fs";
|
|
57217
|
+
import { dirname as dirname16, join as join23 } from "node:path";
|
|
57218
|
+
import { homedir as homedir16 } from "node:os";
|
|
55585
57219
|
async function runAgentCliCommand(args, deps = {
|
|
55586
57220
|
fetchImpl: fetch,
|
|
55587
57221
|
stdout: process.stdout,
|
|
@@ -56289,7 +57923,7 @@ function agentTokenFile(flags) {
|
|
|
56289
57923
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
56290
57924
|
}
|
|
56291
57925
|
function defaultAgentTokenFilePath() {
|
|
56292
|
-
return
|
|
57926
|
+
return join23(homedir16(), ".linzumi", "agent-token.json");
|
|
56293
57927
|
}
|
|
56294
57928
|
function normalizedApiUrl(apiUrl) {
|
|
56295
57929
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -56298,10 +57932,10 @@ function authorizationHeaders(token) {
|
|
|
56298
57932
|
return { authorization: `Bearer ${token}` };
|
|
56299
57933
|
}
|
|
56300
57934
|
function readOptionalTextFile(path2) {
|
|
56301
|
-
return existsSync15(path2) ?
|
|
57935
|
+
return existsSync15(path2) ? readFileSync20(path2, "utf8") : void 0;
|
|
56302
57936
|
}
|
|
56303
57937
|
function writeTextFile(path2, content) {
|
|
56304
|
-
|
|
57938
|
+
mkdirSync15(dirname16(path2), { recursive: true });
|
|
56305
57939
|
writeFileSync13(path2, content);
|
|
56306
57940
|
}
|
|
56307
57941
|
function readStoredAgentTokenFile(path2, readTextFile = readOptionalTextFile) {
|
|
@@ -56390,26 +58024,26 @@ init_helloLinzumiProject();
|
|
|
56390
58024
|
init_runnerLogger();
|
|
56391
58025
|
import {
|
|
56392
58026
|
existsSync as existsSync16,
|
|
56393
|
-
closeSync as
|
|
56394
|
-
mkdirSync as
|
|
56395
|
-
openSync as
|
|
56396
|
-
readFileSync as
|
|
58027
|
+
closeSync as closeSync5,
|
|
58028
|
+
mkdirSync as mkdirSync16,
|
|
58029
|
+
openSync as openSync6,
|
|
58030
|
+
readFileSync as readFileSync21,
|
|
56397
58031
|
watch,
|
|
56398
58032
|
writeFileSync as writeFileSync14
|
|
56399
58033
|
} from "node:fs";
|
|
56400
|
-
import { homedir as
|
|
56401
|
-
import { dirname as
|
|
58034
|
+
import { homedir as homedir17 } from "node:os";
|
|
58035
|
+
import { dirname as dirname17, join as join24, resolve as resolve9 } from "node:path";
|
|
56402
58036
|
import { execFileSync, spawn as spawn10 } from "node:child_process";
|
|
56403
58037
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
56404
58038
|
var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
|
|
56405
58039
|
function commanderStatusDir() {
|
|
56406
|
-
return
|
|
58040
|
+
return join24(homedir17(), ".linzumi", "commanders");
|
|
56407
58041
|
}
|
|
56408
58042
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
56409
|
-
return
|
|
58043
|
+
return join24(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
56410
58044
|
}
|
|
56411
58045
|
function defaultCommanderLogFile(runnerId) {
|
|
56412
|
-
return
|
|
58046
|
+
return join24(homedir17(), ".linzumi", "logs", `${safeRunnerId(runnerId)}.log`);
|
|
56413
58047
|
}
|
|
56414
58048
|
function commanderLogIsConnected(log2) {
|
|
56415
58049
|
return connectedMarkers.some((marker) => log2.includes(marker));
|
|
@@ -56430,10 +58064,10 @@ function startCommanderDaemon(options) {
|
|
|
56430
58064
|
"--log-file",
|
|
56431
58065
|
logFile
|
|
56432
58066
|
];
|
|
56433
|
-
|
|
56434
|
-
|
|
56435
|
-
const out =
|
|
56436
|
-
const err =
|
|
58067
|
+
mkdirSync16(statusDir, { recursive: true });
|
|
58068
|
+
mkdirSync16(dirname17(logFile), { recursive: true });
|
|
58069
|
+
const out = openSync6(logFile, "a");
|
|
58070
|
+
const err = openSync6(logFile, "a");
|
|
56437
58071
|
writeCliAuditEvent(
|
|
56438
58072
|
"process.spawn",
|
|
56439
58073
|
{
|
|
@@ -56460,8 +58094,8 @@ function startCommanderDaemon(options) {
|
|
|
56460
58094
|
},
|
|
56461
58095
|
{ sessionId: options.runnerId }
|
|
56462
58096
|
);
|
|
56463
|
-
|
|
56464
|
-
|
|
58097
|
+
closeSync5(out);
|
|
58098
|
+
closeSync5(err);
|
|
56465
58099
|
child.unref();
|
|
56466
58100
|
if (child.pid === void 0) {
|
|
56467
58101
|
throw new Error("commander daemon did not report a pid");
|
|
@@ -56486,12 +58120,12 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
|
|
|
56486
58120
|
if (!existsSync16(statusFile)) {
|
|
56487
58121
|
return { status: "missing", runnerId, statusFile };
|
|
56488
58122
|
}
|
|
56489
|
-
const record = parseRecord2(
|
|
58123
|
+
const record = parseRecord2(readFileSync21(statusFile, "utf8"));
|
|
56490
58124
|
return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
|
|
56491
58125
|
}
|
|
56492
58126
|
async function waitForCommanderDaemon(options) {
|
|
56493
58127
|
const now = options.now ?? (() => Date.now());
|
|
56494
|
-
const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ?
|
|
58128
|
+
const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync21(path2, "utf8") : void 0);
|
|
56495
58129
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
56496
58130
|
const deadline = now() + options.timeoutMs;
|
|
56497
58131
|
while (now() <= deadline) {
|
|
@@ -69384,7 +71018,7 @@ init_userFacingErrors();
|
|
|
69384
71018
|
init_version();
|
|
69385
71019
|
import { chmodSync as chmodSync3, mkdtempSync as mkdtempSync5, rmSync as rmSync6 } from "node:fs";
|
|
69386
71020
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
69387
|
-
import { basename as basename9, dirname as
|
|
71021
|
+
import { basename as basename9, dirname as dirname18, join as join25 } from "node:path";
|
|
69388
71022
|
var configEnvKey = "LINZUMI_REMOTE_CODEX_HARNESS_CONFIG_B64";
|
|
69389
71023
|
var remoteHarnessEnvironmentId = "linzumi-remote-codex-harness";
|
|
69390
71024
|
async function runRemoteCodexHarnessWorkerFromEnv(env = process.env) {
|
|
@@ -69617,18 +71251,18 @@ function resolveLinzumiCliEntrypoint(entrypoint) {
|
|
|
69617
71251
|
case "remote-codex-harness-worker.js":
|
|
69618
71252
|
case "remote-codex-harness-worker.mjs":
|
|
69619
71253
|
case "remote-codex-harness-worker.cjs":
|
|
69620
|
-
return
|
|
71254
|
+
return join25(dirname18(entrypoint), "linzumi.js");
|
|
69621
71255
|
case "remoteCodexHarnessWorkerEntrypoint.ts":
|
|
69622
71256
|
case "remoteCodexHarnessWorkerEntrypoint.js":
|
|
69623
|
-
return
|
|
71257
|
+
return join25(dirname18(entrypoint), "index.ts");
|
|
69624
71258
|
default:
|
|
69625
71259
|
return entrypoint;
|
|
69626
71260
|
}
|
|
69627
71261
|
}
|
|
69628
71262
|
function writeEphemeralMcpAuthFile2(options) {
|
|
69629
|
-
const directory = mkdtempSync5(
|
|
71263
|
+
const directory = mkdtempSync5(join25(tmpdir4(), "linzumi-mcp-auth-"));
|
|
69630
71264
|
chmodSync3(directory, 448);
|
|
69631
|
-
const path2 =
|
|
71265
|
+
const path2 = join25(directory, "auth.json");
|
|
69632
71266
|
try {
|
|
69633
71267
|
writeCachedLocalRunnerToken({
|
|
69634
71268
|
kandanUrl: options.kandanUrl,
|
|
@@ -70579,7 +72213,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
70579
72213
|
};
|
|
70580
72214
|
}
|
|
70581
72215
|
function readAgentTokenTextFile(path2) {
|
|
70582
|
-
return existsSync18(path2) ?
|
|
72216
|
+
return existsSync18(path2) ? readFileSync24(path2, "utf8") : void 0;
|
|
70583
72217
|
}
|
|
70584
72218
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
70585
72219
|
const unsupportedFlags = [
|
|
@@ -70898,10 +72532,10 @@ function rejectStartTargetingFlags(values) {
|
|
|
70898
72532
|
}
|
|
70899
72533
|
function resolveUserPath(pathValue) {
|
|
70900
72534
|
if (pathValue === "~") {
|
|
70901
|
-
return
|
|
72535
|
+
return homedir19();
|
|
70902
72536
|
}
|
|
70903
72537
|
if (pathValue.startsWith("~/")) {
|
|
70904
|
-
return resolve11(
|
|
72538
|
+
return resolve11(homedir19(), pathValue.slice(2));
|
|
70905
72539
|
}
|
|
70906
72540
|
return resolve11(pathValue);
|
|
70907
72541
|
}
|