@linzumi/cli 0.0.107-beta → 0.0.109-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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1917 -175
  3. 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.107-beta";
20081
+ linzumiCliVersion = "0.0.109-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 createHash6, randomUUID as randomUUID4 } from "node:crypto";
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 mkdirSync13,
24543
+ mkdirSync as mkdirSync14,
23597
24544
  mkdtempSync as mkdtempSync4,
24545
+ openSync as openSync5,
23598
24546
  readdirSync as readdirSync4,
23599
- readFileSync as readFileSync17,
24547
+ readFileSync as readFileSync18,
23600
24548
  realpathSync as realpathSync6,
23601
- renameSync as renameSync4,
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 homedir14, hostname as hostname2, tmpdir as tmpdir3 } from "node:os";
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 dirname14,
24560
+ dirname as dirname15,
23613
24561
  extname as extname2,
23614
24562
  isAbsolute as isAbsolute5,
23615
- join as join21,
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
- cleanup.removeHandlers = installCleanupHandlers(close);
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(options, log2, cleanup, close);
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
- async function openLocalCodexRunner(options, log2, cleanup, close) {
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,124 @@ 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 resolveRunnerBoundChannel = () => options.channelSession?.channelSlug ?? threadSessionRegistry.list()[0]?.channel;
25068
+ const durableInboundQueueFor = (channel, kandanThreadId) => {
25069
+ const workspace = inboundQueueWorkspace();
25070
+ const cacheKey = `${workspace}:${channel}:${kandanThreadId}`;
25071
+ const existing = durableInboundQueues.get(cacheKey);
25072
+ if (existing !== void 0) {
25073
+ return existing;
25074
+ }
25075
+ const queue = createDurableInboundThreadQueue(
25076
+ durableInboundQueuePath(workspace, channel, kandanThreadId),
25077
+ log2
25078
+ );
25079
+ durableInboundQueues.set(cacheKey, queue);
25080
+ return queue;
25081
+ };
25082
+ const capturedLiveFollowUpEvents = /* @__PURE__ */ new Map();
25083
+ const capturedFollowUpKey = (channel, threadId, seq2) => `${inboundQueueWorkspace()}:${channel}:${threadId}:${seq2}`;
25084
+ const captureLiveFollowUpEvent = (channel, threadId, event) => {
25085
+ capturedLiveFollowUpEvents.set(
25086
+ capturedFollowUpKey(channel, threadId, event.seq),
25087
+ event
25088
+ );
25089
+ };
25090
+ const reEnqueueUndeliveredFollowUps = (args) => {
25091
+ const queue = durableInboundQueueFor(args.channel, args.threadId);
25092
+ for (const seq2 of args.sourceSeqs) {
25093
+ const key = capturedFollowUpKey(args.channel, args.threadId, seq2);
25094
+ const event = capturedLiveFollowUpEvents.get(key);
25095
+ if (event === void 0) {
25096
+ log2("runner.live_follow_up_recapture_missing", {
25097
+ kandanThreadId: args.threadId,
25098
+ channel: args.channel,
25099
+ sourceSeq: seq2
25100
+ });
25101
+ continue;
25102
+ }
25103
+ const enqueued = queue.enqueue(
25104
+ seq2,
25105
+ "replay",
25106
+ serializeKandanChatEventForQueue(event),
25107
+ Date.now()
25108
+ );
25109
+ log2("runner.live_follow_up_re_enqueued_on_close", {
25110
+ kandanThreadId: args.threadId,
25111
+ channel: args.channel,
25112
+ sourceSeq: seq2,
25113
+ enqueued: enqueued.accepted
25114
+ });
25115
+ capturedLiveFollowUpEvents.delete(key);
25116
+ }
25117
+ };
25118
+ runnerDrainHooks.stopAccepting = () => {
25119
+ acceptingInbound = false;
25120
+ };
25121
+ runnerDrainHooks.flushInboundQueues = () => {
25122
+ for (const queue of durableInboundQueues.values()) {
25123
+ queue.compact();
25124
+ }
25125
+ };
23950
25126
  const controlEpochStore = createRunnerControlEpochStore(
23951
25127
  controlCursorStore,
23952
25128
  appliedStartTurnStore,
@@ -24788,6 +25964,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24788
25964
  codex,
24789
25965
  topic,
24790
25966
  instanceId,
25967
+ // SEAM 2: source the held lease epoch for the stream-write fence echo.
25968
+ leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
24791
25969
  options: {
24792
25970
  kandanUrl: options.kandanUrl,
24793
25971
  token: options.token,
@@ -24824,6 +26002,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24824
26002
  // session is attached (the durable thread's worker fully died). Late-
24825
26003
  // bound to dodge the const's temporal dead zone (defined below).
24826
26004
  onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
26005
+ // BLOCKER B: codex live dispatch-confirm watermark commit on the main
26006
+ // channel session too, off the same persisted authority the keystone
26007
+ // gates on.
26008
+ onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
24827
26009
  channelSession: options.channelSession
24828
26010
  },
24829
26011
  log: log2
@@ -25174,6 +26356,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25174
26356
  codex: sessionCodex,
25175
26357
  topic,
25176
26358
  instanceId,
26359
+ // SEAM 2: dynamic per-thread session also echoes the held lease epoch.
26360
+ leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
25177
26361
  options: {
25178
26362
  kandanUrl: options.kandanUrl,
25179
26363
  token: options.token,
@@ -25188,6 +26372,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25188
26372
  // it out of the const's temporal dead zone while still resolving the
25189
26373
  // live handler when an unrouted chat event actually arrives.
25190
26374
  onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
26375
+ // BLOCKER B: codex live dispatch-confirm watermark commit (the
26376
+ // production-default double-exec fix). Fires from drainKandanMessageQueue
26377
+ // when turn/start replies, off the SAME persisted authority the keystone
26378
+ // gates on, so a codex worker-death + replay is gated 'ignored'.
26379
+ onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
25191
26380
  pipelinePersistDir: commanderOutboxPersistDir(),
25192
26381
  enablePortForwardWatch: true,
25193
26382
  initialForwardPorts: allowedForwardPorts,
@@ -25566,7 +26755,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25566
26755
  control,
25567
26756
  log2,
25568
26757
  onStartedThread,
25569
- void 0
26758
+ void 0,
26759
+ (id) => leaseEpochRegistry.current(id),
26760
+ commitLiveWorkerAcceptedSourceSeq,
26761
+ captureLiveFollowUpEvent,
26762
+ reEnqueueUndeliveredFollowUps,
26763
+ liveSourceSeqAlreadyApplied
25570
26764
  )
25571
26765
  );
25572
26766
  }
@@ -25609,6 +26803,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25609
26803
  attachments: event.attachments,
25610
26804
  boundStartTimeout: true
25611
26805
  });
26806
+ return "dispatched";
25612
26807
  } catch (error) {
25613
26808
  log2("runner.spawn_on_miss_failed", {
25614
26809
  kandanThreadId: event.threadId ?? null,
@@ -25622,6 +26817,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25622
26817
  `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25623
26818
  record.codexThreadId
25624
26819
  );
26820
+ return "transient_failed";
25625
26821
  }
25626
26822
  };
25627
26823
  const handleUnroutedThreadMessage = async (event) => {
@@ -25650,8 +26846,304 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25650
26846
  }
25651
26847
  const record = threadSessionRegistry.list().find((entry) => entry.kandanThreadId === kandanThreadId);
25652
26848
  if (record === void 0) {
25653
- return "ignored";
26849
+ const boundChannel = resolveRunnerBoundChannel();
26850
+ if (boundChannel === void 0 || !acceptingInbound) {
26851
+ return "ignored";
26852
+ }
26853
+ const queue2 = durableInboundQueueFor(boundChannel, kandanThreadId);
26854
+ const enqueued = queue2.enqueue(
26855
+ event.seq,
26856
+ event.localRunnerEventType === void 0 ? "live" : "replay",
26857
+ serializeKandanChatEventForQueue(event),
26858
+ Date.now()
26859
+ );
26860
+ log2("runner.spawn_on_miss_enqueued_without_local_record", {
26861
+ kandanThreadId,
26862
+ channel: boundChannel,
26863
+ seq: event.seq,
26864
+ enqueued: enqueued.accepted
26865
+ });
26866
+ await publishUnroutedDurableEnqueueState(boundChannel, event);
26867
+ return "spawned";
26868
+ }
26869
+ const queueChannel = record.channel;
26870
+ const watermarkKey = threadSourceSeqWatermarkKey(
26871
+ inboundQueueWorkspace(),
26872
+ queueChannel,
26873
+ kandanThreadId
26874
+ );
26875
+ const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
26876
+ if (!acceptingInbound) {
26877
+ const enqueued = queue.enqueue(
26878
+ event.seq,
26879
+ event.localRunnerEventType === void 0 ? "live" : "replay",
26880
+ serializeKandanChatEventForQueue(event),
26881
+ Date.now()
26882
+ );
26883
+ log2("runner.spawn_on_miss_drain_in_progress", {
26884
+ kandanThreadId,
26885
+ seq: event.seq,
26886
+ enqueued: enqueued.accepted
26887
+ });
26888
+ return "spawned";
26889
+ }
26890
+ const outcome = await receiveResumeMessage({
26891
+ queue,
26892
+ store: appliedSourceSeqStore,
26893
+ lock: threadReconcilerLock,
26894
+ leases: leaseEpochRegistry,
26895
+ threadId: kandanThreadId,
26896
+ watermarkKey,
26897
+ sourceSeq: event.seq,
26898
+ kind: event.localRunnerEventType === void 0 ? "live" : "replay",
26899
+ payload: serializeKandanChatEventForQueue(event),
26900
+ leaseEpoch: readLeaseEpoch(event),
26901
+ // Blocker #2: the keystone drains the queue HEAD in source_seq order, so
26902
+ // the dispatch is keyed by (sourceSeq, payload) - it may run a LOWER
26903
+ // pending seq before this arrived one. Reconstruct the event from the
26904
+ // head's payload, exactly as the boot/rejoin drain does.
26905
+ dispatch: async (sourceSeq, payload) => {
26906
+ const drained = deserializeKandanChatEventFromQueue(payload);
26907
+ if (drained === void 0) {
26908
+ log2("runner.spawn_on_miss_bad_payload", {
26909
+ kandanThreadId,
26910
+ seq: sourceSeq
26911
+ });
26912
+ return "permanent_failed";
26913
+ }
26914
+ return await dispatchResumeForOwnedThread(drained, record);
26915
+ },
26916
+ // BLOCKER #4: pass the server's per-thread processed floor (the most-recent
26917
+ // resume_reconcile rows) so the live-miss path reconciles
26918
+ // highestProcessedSourceSeq into the watermark BEFORE dispatching - a
26919
+ // completed-but-not-locally-acked turn that replays is gated 'ignored', not
26920
+ // re-run. Empty until the server SEAM 1 half ships; then the floor is
26921
+ // authoritative. The live-miss path leaves onlyDispatchUnattempted at its
26922
+ // default (false): the arrived message is attempts===0 and the blind
26923
+ // post-completion fence is owned by the boot/rejoin recovery drain, not this
26924
+ // on-receipt path (changing it here would alter the existing live behavior).
26925
+ serverRows: latestResumeReconcileByThread.get(kandanThreadId) ?? [],
26926
+ // Blocker #3: surface a LOUD terminal `failed` state on any dead-letter.
26927
+ onDeadLetter: deadLetterPublisherFor(record),
26928
+ log: log2
26929
+ });
26930
+ return outcome === "ignored" ? "ignored" : "spawned";
26931
+ };
26932
+ const drainOwnedThreadQueue = async (record, serverRows = []) => {
26933
+ if (!acceptingInbound) {
26934
+ return;
26935
+ }
26936
+ const kandanThreadId = record.kandanThreadId;
26937
+ const queueChannel = record.channel;
26938
+ const watermarkKey = threadSourceSeqWatermarkKey(
26939
+ inboundQueueWorkspace(),
26940
+ queueChannel,
26941
+ kandanThreadId
26942
+ );
26943
+ const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
26944
+ await drainResumeQueue({
26945
+ queue,
26946
+ store: appliedSourceSeqStore,
26947
+ lock: threadReconcilerLock,
26948
+ threadId: kandanThreadId,
26949
+ watermarkKey,
26950
+ serverRows,
26951
+ log: log2,
26952
+ // Until the SERVER message_state reconcile half (SEAM 1 server) ships, the
26953
+ // drain runs with serverRows=[] in production. In that mode a post-
26954
+ // completion-kill (turn confirmed-dispatched, then SIGKILL before the
26955
+ // watermark flush + ack) leaves an attempted-but-unacked entry. Blindly
26956
+ // re-dispatching it would double-execute the turn (real edits/commits), so
26957
+ // gate the drain to UNATTEMPTED entries only: an attempted entry is dead-
26958
+ // lettered with a loud `failed` state rather than re-run. Once serverRows
26959
+ // is populated the reconcile fences already-run seqs and this gate is moot.
26960
+ onlyDispatchUnattempted: serverRows.length === 0,
26961
+ // Blocker #3: surface a LOUD terminal `failed` state on any dead-letter
26962
+ // (exhausted retries, a withheld post-completion-kill entry, or a permanent
26963
+ // failure) so a persistently-unbindable thread is never silently dropped.
26964
+ onDeadLetter: deadLetterPublisherFor(record),
26965
+ // A torn tail with no server reconcile cannot be re-derived on this branch
26966
+ // (no server outbox/replay). Publish a loud "please resend" terminal state
26967
+ // for the thread instead of silently dropping the lost tail.
26968
+ onTornTailUnrecoverable: async (threadId) => {
26969
+ const control = rehydrationReconnectControl(record);
26970
+ await publishSpawnOnMissMessageState(
26971
+ control,
26972
+ "failed",
26973
+ "a message may have been lost during an unexpected shutdown (power loss). Please resend your last message to continue this job.",
26974
+ record.codexThreadId
26975
+ );
26976
+ log2("runner.inbound_torn_tail_resend_published", {
26977
+ kandanThreadId: threadId
26978
+ });
26979
+ },
26980
+ dispatch: async (sourceSeq, payload) => {
26981
+ const event = deserializeKandanChatEventFromQueue(payload);
26982
+ if (event === void 0) {
26983
+ log2("runner.inbound_drain_bad_payload", {
26984
+ kandanThreadId,
26985
+ seq: sourceSeq
26986
+ });
26987
+ return "permanent_failed";
26988
+ }
26989
+ return await dispatchResumeForOwnedThread(event, record);
26990
+ }
26991
+ });
26992
+ };
26993
+ const drainThreadQueueAfterSpawn = async (kandanThreadId, spawnResponse) => {
26994
+ if (spawnResponse !== void 0 && spawnResponse.ok === false) {
26995
+ return;
26996
+ }
26997
+ if (!acceptingInbound) {
26998
+ return;
26999
+ }
27000
+ const record = threadSessionRegistry.list().find((entry) => entry.kandanThreadId === kandanThreadId);
27001
+ if (record === void 0) {
27002
+ return;
27003
+ }
27004
+ const queue = durableInboundQueueFor(record.channel, kandanThreadId);
27005
+ if (queue.entries().length === 0) {
27006
+ return;
27007
+ }
27008
+ log2("runner.inbound_drain_after_spawn", {
27009
+ kandanThreadId,
27010
+ channel: record.channel,
27011
+ pending: queue.entries().length
27012
+ });
27013
+ await drainOwnedThreadQueue(
27014
+ record,
27015
+ latestResumeReconcileByThread.get(kandanThreadId) ?? []
27016
+ ).catch((error) => {
27017
+ log2("runner.inbound_drain_after_spawn_failed", {
27018
+ kandanThreadId,
27019
+ message: error instanceof Error ? error.message : String(error)
27020
+ });
27021
+ });
27022
+ };
27023
+ const startThreadRunnerProcessFromServerControl = async (control, cwd) => {
27024
+ const response = await startThreadRunnerProcess(control, cwd);
27025
+ const kandanThreadId = optionalThreadControlField(control, "threadId");
27026
+ if (kandanThreadId !== void 0) {
27027
+ await drainThreadQueueAfterSpawn(kandanThreadId, response);
27028
+ }
27029
+ return response;
27030
+ };
27031
+ const consumeResumeReconcile = (reconcileValue) => {
27032
+ const byThread = /* @__PURE__ */ new Map();
27033
+ const entries = parseResumeReconcileEntries(reconcileValue);
27034
+ for (const entry of entries) {
27035
+ if (entry.leaseEpoch !== void 0) {
27036
+ const adopted = leaseEpochRegistry.adopt(entry.threadId, entry.leaseEpoch);
27037
+ if (adopted) {
27038
+ log2("runner.resume_reconcile_lease_adopted", {
27039
+ kandanThreadId: entry.threadId,
27040
+ leaseEpoch: entry.leaseEpoch
27041
+ });
27042
+ }
27043
+ }
27044
+ const rows = resumeReconcileServerRows(entry);
27045
+ byThread.set(entry.threadId, rows);
27046
+ latestResumeReconcileByThread.set(entry.threadId, rows);
27047
+ }
27048
+ if (entries.length > 0) {
27049
+ log2("runner.resume_reconcile_consumed", { threadCount: entries.length });
27050
+ }
27051
+ return byThread;
27052
+ };
27053
+ const drainOrphanedThreadQueues = async () => {
27054
+ const dir = durableInboundQueueDir();
27055
+ let files;
27056
+ try {
27057
+ files = readdirSync4(dir).filter((name) => name.endsWith(".log"));
27058
+ } catch (error) {
27059
+ if (error?.code === "ENOENT") {
27060
+ return;
27061
+ }
27062
+ log2("runner.inbound_orphan_scan_failed", {
27063
+ message: error instanceof Error ? error.message : String(error)
27064
+ });
27065
+ return;
27066
+ }
27067
+ const workspace = inboundQueueWorkspace();
27068
+ const owned = new Set(
27069
+ threadSessionRegistry.list().map(
27070
+ (record) => durableInboundQueuePath(
27071
+ workspace,
27072
+ record.channel,
27073
+ record.kandanThreadId
27074
+ )
27075
+ )
27076
+ );
27077
+ for (const name of files) {
27078
+ const path2 = join22(dir, name);
27079
+ if (owned.has(path2)) {
27080
+ continue;
27081
+ }
27082
+ try {
27083
+ const queue = createDurableInboundThreadQueue(path2, log2);
27084
+ const stranded = queue.entries();
27085
+ if (stranded.length === 0) {
27086
+ continue;
27087
+ }
27088
+ for (const entry of stranded) {
27089
+ queue.deadLetter(entry.sourceSeq);
27090
+ }
27091
+ queue.compact();
27092
+ log2("runner.inbound_orphan_dead_lettered", {
27093
+ path: path2,
27094
+ strandedCount: stranded.length,
27095
+ strandedSeqs: stranded.map((e) => e.sourceSeq)
27096
+ });
27097
+ } catch (error) {
27098
+ log2("runner.inbound_orphan_drain_failed", {
27099
+ path: path2,
27100
+ message: error instanceof Error ? error.message : String(error)
27101
+ });
27102
+ }
25654
27103
  }
27104
+ };
27105
+ const drainAllOwnedThreadQueues = async (reconcileByThread) => {
27106
+ if (!acceptingInbound) {
27107
+ return;
27108
+ }
27109
+ const records = threadSessionRegistry.list();
27110
+ await Promise.all(
27111
+ records.map(
27112
+ (record) => drainOwnedThreadQueue(
27113
+ record,
27114
+ reconcileByThread?.get(record.kandanThreadId) ?? []
27115
+ ).catch((error) => {
27116
+ log2("runner.inbound_drain_failed", {
27117
+ kandanThreadId: record.kandanThreadId,
27118
+ message: error instanceof Error ? error.message : String(error)
27119
+ });
27120
+ })
27121
+ )
27122
+ );
27123
+ await drainOrphanedThreadQueues();
27124
+ };
27125
+ const bootReconcile = consumeResumeReconcile(
27126
+ objectValue(joinResponse)?.resume_reconcile
27127
+ );
27128
+ const bootDrain = threadSessionRehydration.then(() => drainAllOwnedThreadQueues(bootReconcile)).catch((error) => {
27129
+ log2("runner.inbound_boot_drain_failed", {
27130
+ message: error instanceof Error ? error.message : String(error)
27131
+ });
27132
+ });
27133
+ cleanup.actions.push(() => bootDrain);
27134
+ kandan.onReconnect((rejoinReplies) => {
27135
+ const rejoinReply = rejoinReplies.get(topic);
27136
+ const reconcile = consumeResumeReconcile(
27137
+ rejoinReply === void 0 ? void 0 : rejoinReply.resume_reconcile
27138
+ );
27139
+ void drainAllOwnedThreadQueues(reconcile).catch((error) => {
27140
+ log2("runner.inbound_rejoin_drain_failed", {
27141
+ message: error instanceof Error ? error.message : String(error)
27142
+ });
27143
+ });
27144
+ });
27145
+ const dispatchResumeForOwnedThread = async (event, record) => {
27146
+ const kandanThreadId = record.kandanThreadId;
25655
27147
  const baseControl = rehydrationReconnectControl(record);
25656
27148
  const providerBinding = threadModelProviderBindings.get(kandanThreadId);
25657
27149
  const reconnectControl = {
@@ -25681,7 +27173,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25681
27173
  `could not resume the thread's coding session: working directory ${record.cwd} is not an allowed root (${cwd.reason})`,
25682
27174
  record.codexThreadId
25683
27175
  );
25684
- return "spawned";
27176
+ return "permanent_failed";
25685
27177
  }
25686
27178
  const resolvedCwd = cwd.cwd;
25687
27179
  const inFlightSpawn = spawnOnMissInFlight.get(kandanThreadId);
@@ -25701,14 +27193,13 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25701
27193
  `could not resume the thread's coding session: no live session after spawn`,
25702
27194
  record.codexThreadId
25703
27195
  );
25704
- return "spawned";
27196
+ return "transient_failed";
25705
27197
  }
25706
- await replaySpawnOnMissMessageOnLiveSession(
27198
+ return await replaySpawnOnMissMessageOnLiveSession(
25707
27199
  event,
25708
27200
  record,
25709
27201
  reconnectControl
25710
27202
  );
25711
- return "spawned";
25712
27203
  }
25713
27204
  log2("runner.spawn_on_miss_resuming", {
25714
27205
  kandanThreadId,
@@ -25729,12 +27220,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25729
27220
  );
25730
27221
  if (threadStartDelegatedToSibling(startResult)) {
25731
27222
  spawnSettle("spawned");
25732
- await replaySpawnOnMissMessageOnLiveSession(
27223
+ return await replaySpawnOnMissMessageOnLiveSession(
25733
27224
  event,
25734
27225
  record,
25735
27226
  reconnectControl
25736
27227
  );
25737
- return "spawned";
25738
27228
  }
25739
27229
  if (startResult === void 0 || startResult.ok === false) {
25740
27230
  spawnSettle("no_session");
@@ -25751,10 +27241,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25751
27241
  `could not resume the thread's coding session: ${startError}`,
25752
27242
  record.codexThreadId
25753
27243
  );
25754
- return "spawned";
27244
+ return "transient_failed";
25755
27245
  }
25756
27246
  spawnSettle("spawned");
25757
- return "spawned";
27247
+ return "dispatched";
25758
27248
  } catch (error) {
25759
27249
  spawnSettle("no_session");
25760
27250
  log2("runner.spawn_on_miss_failed", {
@@ -25769,7 +27259,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25769
27259
  `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25770
27260
  record.codexThreadId
25771
27261
  );
25772
- return "spawned";
27262
+ return "transient_failed";
25773
27263
  } finally {
25774
27264
  spawnSettle("spawned");
25775
27265
  if (spawnOnMissInFlight.get(kandanThreadId) === spawnPromise) {
@@ -25795,6 +27285,49 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25795
27285
  });
25796
27286
  }
25797
27287
  };
27288
+ const publishUnroutedDurableEnqueueState = async (channel, event) => {
27289
+ if (event.threadId === void 0) {
27290
+ return;
27291
+ }
27292
+ const payload = {
27293
+ workspace: inboundQueueWorkspace(),
27294
+ channel,
27295
+ thread_id: event.threadId,
27296
+ instance_id: instanceId,
27297
+ seq: event.seq,
27298
+ status: "queued",
27299
+ reason: "queued for durable resume"
27300
+ };
27301
+ try {
27302
+ await kandan.push(topic, "message_state", payload);
27303
+ } catch (error) {
27304
+ log2("runner.spawn_on_miss_state_push_failed", {
27305
+ thread_id: event.threadId,
27306
+ status: "queued",
27307
+ message: error instanceof Error ? error.message : String(error)
27308
+ });
27309
+ }
27310
+ };
27311
+ const deadLetterPublisherFor = (record) => {
27312
+ return async ({ sourceSeq, reason, attempts, alreadyPublished }) => {
27313
+ log2("runner.inbound_dead_lettered", {
27314
+ kandanThreadId: record.kandanThreadId,
27315
+ seq: sourceSeq,
27316
+ reason,
27317
+ attempts,
27318
+ alreadyPublished
27319
+ });
27320
+ if (alreadyPublished) {
27321
+ return;
27322
+ }
27323
+ await publishSpawnOnMissMessageState(
27324
+ rehydrationReconnectControl(record),
27325
+ "failed",
27326
+ deadLetterFailedReason(reason),
27327
+ record.codexThreadId
27328
+ );
27329
+ };
27330
+ };
25798
27331
  const heartbeatPayload = () => ({
25799
27332
  instanceId,
25800
27333
  clientId,
@@ -26285,7 +27818,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
26285
27818
  control,
26286
27819
  log2,
26287
27820
  attachThreadSession,
26288
- shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0
27821
+ shouldUseThreadProcesses(options) ? startThreadRunnerProcessFromServerControl : void 0,
27822
+ (id) => leaseEpochRegistry.current(id),
27823
+ commitLiveWorkerAcceptedSourceSeq,
27824
+ captureLiveFollowUpEvent,
27825
+ reEnqueueUndeliveredFollowUps,
27826
+ liveSourceSeqAlreadyApplied
26289
27827
  );
26290
27828
  }),
26291
27829
  commitStartTurnWatermark
@@ -26316,7 +27854,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
26316
27854
  pendingControls,
26317
27855
  controlDispatcher,
26318
27856
  dispatchControl,
26319
- refreshDependencyStatus
27857
+ async () => {
27858
+ await refreshDependencyStatus();
27859
+ await refreshCodexAuthStatus();
27860
+ }
26320
27861
  );
26321
27862
  return { instanceId, codexUrl, close };
26322
27863
  }
@@ -26330,28 +27871,34 @@ function commanderOutboxPersistDir() {
26330
27871
  if (override !== void 0 && override !== "") {
26331
27872
  return override;
26332
27873
  }
26333
- return join21(homedir14(), ".linzumi", "commander-outbox");
27874
+ return join22(homedir15(), ".linzumi", "commander-outbox");
27875
+ }
27876
+ function controlCursorsDir() {
27877
+ const override = process.env.LINZUMI_CONTROL_CURSOR_DIR?.trim();
27878
+ if (override !== void 0 && override !== "") {
27879
+ return override;
27880
+ }
27881
+ return join22(homedir15(), ".linzumi", "control-cursors");
26334
27882
  }
26335
27883
  function controlCursorStorePath(runnerId) {
26336
27884
  const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
26337
- const digest = createHash6("sha256").update(runnerId).digest("hex").slice(0, 8);
27885
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
26338
27886
  const stem = sanitized === "" ? "runner" : sanitized;
26339
- return join21(
26340
- homedir14(),
26341
- ".linzumi",
26342
- "control-cursors",
26343
- `${stem}.${digest}.json`
26344
- );
27887
+ return join22(controlCursorsDir(), `${stem}.${digest}.json`);
26345
27888
  }
26346
27889
  function appliedStartTurnStorePath(runnerId) {
26347
27890
  const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
26348
- const digest = createHash6("sha256").update(runnerId).digest("hex").slice(0, 8);
27891
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
27892
+ const stem = sanitized === "" ? "runner" : sanitized;
27893
+ return join22(controlCursorsDir(), `${stem}.${digest}.start-turn.json`);
27894
+ }
27895
+ function appliedSourceSeqStorePath(runnerId) {
27896
+ const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
27897
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
26349
27898
  const stem = sanitized === "" ? "runner" : sanitized;
26350
- return join21(
26351
- homedir14(),
26352
- ".linzumi",
26353
- "control-cursors",
26354
- `${stem}.${digest}.start-turn.json`
27899
+ return join22(
27900
+ controlCursorsDir(),
27901
+ `${stem}.${digest}.source-seq.json`
26355
27902
  );
26356
27903
  }
26357
27904
  function startTurnWatermarkKey(topic, threadId) {
@@ -26473,6 +28020,7 @@ function createConnectionStalenessTracker(log2, now = Date.now, thresholdMs = co
26473
28020
  }
26474
28021
  function createControlCursorFileStore(path2, log2, options) {
26475
28022
  const discardSeqsOnFirstEpochAdoption = options?.discardSeqsOnFirstEpochAdoption === true;
28023
+ const fsyncOnWrite = options?.fsyncOnWrite === true;
26476
28024
  const { cursors, epoch: persistedEpoch } = readControlCursorFile(path2, log2);
26477
28025
  let epoch = persistedEpoch;
26478
28026
  let dirty = false;
@@ -26483,18 +28031,32 @@ function createControlCursorFileStore(path2, log2, options) {
26483
28031
  }
26484
28032
  dirty = false;
26485
28033
  try {
26486
- mkdirSync13(dirname14(path2), { recursive: true });
28034
+ mkdirSync14(dirname15(path2), { recursive: true });
26487
28035
  const tmpPath = `${path2}.tmp`;
26488
- writeFileSync12(
26489
- tmpPath,
26490
- `${JSON.stringify({
26491
- ...epoch === void 0 ? {} : { [controlCursorEpochKey]: epoch },
26492
- ...Object.fromEntries(cursors)
26493
- })}
26494
- `,
26495
- "utf8"
26496
- );
26497
- renameSync4(tmpPath, path2);
28036
+ const contents = `${JSON.stringify({
28037
+ ...epoch === void 0 ? {} : { [controlCursorEpochKey]: epoch },
28038
+ ...Object.fromEntries(cursors)
28039
+ })}
28040
+ `;
28041
+ if (fsyncOnWrite) {
28042
+ const fileFd = openSync5(tmpPath, "w");
28043
+ try {
28044
+ writeFileSync12(fileFd, contents, "utf8");
28045
+ fsyncSync2(fileFd);
28046
+ } finally {
28047
+ closeSync4(fileFd);
28048
+ }
28049
+ renameSync5(tmpPath, path2);
28050
+ const dirFd = openSync5(dirname15(path2), "r");
28051
+ try {
28052
+ fsyncSync2(dirFd);
28053
+ } finally {
28054
+ closeSync4(dirFd);
28055
+ }
28056
+ } else {
28057
+ writeFileSync12(tmpPath, contents, "utf8");
28058
+ renameSync5(tmpPath, path2);
28059
+ }
26498
28060
  } catch (error) {
26499
28061
  log2("control_cursor_store.write_failed", {
26500
28062
  path: path2,
@@ -26593,7 +28155,7 @@ function readControlCursorFile(path2, log2) {
26593
28155
  const cursors = /* @__PURE__ */ new Map();
26594
28156
  let raw;
26595
28157
  try {
26596
- raw = readFileSync17(path2, "utf8");
28158
+ raw = readFileSync18(path2, "utf8");
26597
28159
  } catch (error) {
26598
28160
  if (!isErrnoCode2(error, "ENOENT")) {
26599
28161
  log2("control_cursor_store.read_failed", {
@@ -26938,21 +28500,47 @@ function makeRunnerLogger(options) {
26938
28500
  consoleReporter
26939
28501
  );
26940
28502
  }
26941
- function installCleanupHandlers(close) {
26942
- const closeAndExit = () => {
26943
- void close().catch(() => void 0).finally(() => process.exit(0));
26944
- };
28503
+ function installCleanupHandlers(close, drainHooks, log2) {
28504
+ const uninstallDrain = installDrainOnSigterm({
28505
+ steps: {
28506
+ stopAccepting: () => {
28507
+ drainHooks.stopAccepting();
28508
+ },
28509
+ // SCOPE NOTE (BET1, partial): checkpointing an in-flight turn on a LIVE
28510
+ // worker (persisting enough codex turn state to resume it) is NOT
28511
+ // implemented in this branch - that turn still relies on the server's
28512
+ // rejoin replay. What IS durable is every durably-owned message that was
28513
+ // ENQUEUED into the per-thread inbound queue (the respawn-window / spawn-on-
28514
+ // miss path): flushInboundQueues fsyncs those below, and the boot/rejoin
28515
+ // drain replays them. Left as an explicit no-op (not silently absent) so the
28516
+ // gap is visible rather than implied-handled.
28517
+ checkpointCurrentTurn: () => void 0,
28518
+ flushInboundQueues: () => {
28519
+ drainHooks.flushInboundQueues();
28520
+ },
28521
+ // The final flush also performs the connection teardown via close(): the
28522
+ // cleanup stack runs after kandan.close() so no advance races the final
28523
+ // cursor/watermark write.
28524
+ flushOutbox: () => close().catch(() => void 0)
28525
+ },
28526
+ deadlineMs: runnerDrainExitDeadlineMs,
28527
+ log: (event, fields) => log2(event, fields),
28528
+ // SIGHUP is a drain-AND-EXIT signal too: a detached child of the Electron
28529
+ // shell that receives a parent hangup must drain its inbound queue + cursor
28530
+ // stores and then EXIT, never linger as a defunct lock-holder that does no
28531
+ // work (the prior close()-without-exit handler left a zombie that stopped
28532
+ // accepting, tore down its connection, yet never exited - stranding prior
28533
+ // jobs and holding the runner lock). installDrainOnSigterm's handler exits
28534
+ // after the drain (deadline-bounded), so routing SIGHUP through it fixes the
28535
+ // orphan-zombie regression.
28536
+ signals: ["SIGTERM", "SIGINT", "SIGHUP"]
28537
+ });
26945
28538
  const closeOnExit = () => {
26946
28539
  void close().catch(() => void 0);
26947
28540
  };
26948
- process.once("SIGINT", closeAndExit);
26949
- process.once("SIGTERM", closeAndExit);
26950
- process.once("SIGHUP", closeAndExit);
26951
28541
  process.once("exit", closeOnExit);
26952
28542
  return () => {
26953
- process.off("SIGINT", closeAndExit);
26954
- process.off("SIGTERM", closeAndExit);
26955
- process.off("SIGHUP", closeAndExit);
28543
+ uninstallDrain();
26956
28544
  process.off("exit", closeOnExit);
26957
28545
  };
26958
28546
  }
@@ -27255,7 +28843,7 @@ async function applyCodexUnavailableControl(kandan, topic, instanceId, control,
27255
28843
  }
27256
28844
  throw new Error(error);
27257
28845
  }
27258
- async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart) {
28846
+ 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
28847
  if (codex === void 0) {
27260
28848
  switch (control.type) {
27261
28849
  case "remote_codex_exec":
@@ -27448,6 +29036,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27448
29036
  ensureClaudeCodeForwardPortSession,
27449
29037
  disposeClaudeCodeForwardPortSession,
27450
29038
  resumeSessionId: normalizedWorkDescription(control.resumeSessionId),
29039
+ leaseEpochFor,
29040
+ onLiveSourceSeqAccepted,
29041
+ onUndeliveredFollowUpsAtClose,
27451
29042
  setStartupStage: (stage) => {
27452
29043
  startupStage = stage;
27453
29044
  }
@@ -27650,6 +29241,51 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27650
29241
  message
27651
29242
  };
27652
29243
  }
29244
+ const followUpThreadId = optionalThreadControlField(
29245
+ control,
29246
+ "threadId"
29247
+ );
29248
+ const followUpChannel = optionalThreadControlField(
29249
+ control,
29250
+ "channel"
29251
+ );
29252
+ if (sourceSeq !== void 0 && followUpThreadId !== void 0 && followUpChannel !== void 0) {
29253
+ captureLiveFollowUpEvent?.(followUpChannel, followUpThreadId, {
29254
+ seq: sourceSeq,
29255
+ type: "thread.message",
29256
+ actorKind: void 0,
29257
+ actorSlug: void 0,
29258
+ actorUserId: void 0,
29259
+ threadId: followUpThreadId,
29260
+ threadTitle: void 0,
29261
+ replyToSeq: integerValue(control.rootSeq),
29262
+ localRunnerEventType: void 0,
29263
+ body: workDescription,
29264
+ attachments
29265
+ });
29266
+ }
29267
+ if (followUpChannel !== void 0 && followUpThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
29268
+ channel: followUpChannel,
29269
+ threadId: followUpThreadId,
29270
+ sourceSeq
29271
+ }) === true) {
29272
+ log2("claude_code.message_duplicate_ignored", {
29273
+ linzumi_thread_id: followUpThreadId,
29274
+ claude_session_id: codexThreadId,
29275
+ body_preview: claudeConsoleBodyPreview(workDescription),
29276
+ source_seq: sourceSeq ?? null
29277
+ });
29278
+ return {
29279
+ instanceId,
29280
+ controlType: control.type,
29281
+ agentProvider: "claude-code",
29282
+ cwd: cwd.cwd,
29283
+ matchedRoot: cwd.matchedRoot,
29284
+ codexThreadId,
29285
+ queuedInput: true,
29286
+ duplicate: true
29287
+ };
29288
+ }
27653
29289
  try {
27654
29290
  const enqueueResult = activeSession.enqueueInput({
27655
29291
  content: contentResult.content,
@@ -27754,6 +29390,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27754
29390
  pendingClaudeCodeApprovals,
27755
29391
  ensureClaudeCodeForwardPortSession,
27756
29392
  disposeClaudeCodeForwardPortSession,
29393
+ leaseEpochFor,
29394
+ onLiveSourceSeqAccepted,
29395
+ onUndeliveredFollowUpsAtClose,
27757
29396
  setStartupStage: (stage) => {
27758
29397
  startupStage = stage;
27759
29398
  }
@@ -27797,6 +29436,30 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27797
29436
  if (startedThreadSession === void 0 || sourceSeq === void 0) {
27798
29437
  throw new Error("cannot reconnect a Kandan thread without a session");
27799
29438
  }
29439
+ const reconnectChannel = optionalThreadControlField(control, "channel");
29440
+ const reconnectThreadId = optionalThreadControlField(
29441
+ control,
29442
+ "threadId"
29443
+ );
29444
+ if (reconnectChannel !== void 0 && reconnectThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
29445
+ channel: reconnectChannel,
29446
+ threadId: reconnectThreadId,
29447
+ sourceSeq
29448
+ }) === true) {
29449
+ log2("kandan.reconnect_thread_seq_already_applied", {
29450
+ linzumi_thread_id: reconnectThreadId,
29451
+ codex_thread_id: codexThreadId,
29452
+ source_seq: sourceSeq
29453
+ });
29454
+ return {
29455
+ instanceId,
29456
+ controlType: control.type,
29457
+ cwd: cwd.cwd,
29458
+ matchedRoot: cwd.matchedRoot,
29459
+ codexThreadId,
29460
+ duplicate: true
29461
+ };
29462
+ }
27800
29463
  await resumeCodexThreadForReconnect(
27801
29464
  codex,
27802
29465
  codexThreadId,
@@ -28543,11 +30206,13 @@ function claudeCodeTurnStallThresholdMs(env) {
28543
30206
  return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultClaudeCodeTurnStallThresholdMs;
28544
30207
  }
28545
30208
  function createClaudeCodeInputQueue(input) {
30209
+ const onSourceSeqDelivered = input.onSourceSeqDelivered;
28546
30210
  const pendingMessages = [
28547
30211
  claudeCodeUserInputMessage(input.content)
28548
30212
  ];
28549
30213
  const pendingSourceSeqs = [input.sourceSeq];
28550
30214
  const deferredFollowUps = [];
30215
+ const deliveredSourceSeqs = /* @__PURE__ */ new Set();
28551
30216
  const waiters = [];
28552
30217
  const state = {
28553
30218
  closed: false
@@ -28563,6 +30228,10 @@ function createClaudeCodeInputQueue(input) {
28563
30228
  };
28564
30229
  const deliverNow = (message, sourceSeq) => {
28565
30230
  pendingSourceSeqs.push(sourceSeq);
30231
+ if (sourceSeq !== void 0 && onSourceSeqDelivered !== void 0 && !deliveredSourceSeqs.has(sourceSeq)) {
30232
+ deliveredSourceSeqs.add(sourceSeq);
30233
+ onSourceSeqDelivered(sourceSeq);
30234
+ }
28566
30235
  const waiter = waiters.shift();
28567
30236
  if (waiter === void 0) {
28568
30237
  pendingMessages.push(message);
@@ -28631,6 +30300,17 @@ function createClaudeCodeInputQueue(input) {
28631
30300
  },
28632
30301
  enqueue,
28633
30302
  enqueueSteer,
30303
+ // BLOCKER A (don't drop deferred follow-ups on a kill): the parked,
30304
+ // never-delivered follow-ups (a turn was in flight at close, so close()'s
30305
+ // drain is intentionally skipped to avoid folding them into the active turn).
30306
+ // Their watermark was NOT committed (commit fires only at deliverNow), so the
30307
+ // runner must re-enqueue them into the DURABLE inbound queue; a kill-before-
30308
+ // run then replays + runs them exactly once. Drains the parked list so a
30309
+ // double-call cannot double-enqueue. Returns the un-run seqs+messages.
30310
+ drainUndeliveredFollowUps: () => {
30311
+ const drained = deferredFollowUps.splice(0);
30312
+ return drained;
30313
+ },
28634
30314
  completeTurn: () => {
28635
30315
  const completed = pendingSourceSeqs.shift();
28636
30316
  if (!state.closed && pendingSourceSeqs.length === 0) {
@@ -28857,7 +30537,21 @@ async function startClaudeCodeProviderInstance(args) {
28857
30537
  }
28858
30538
  const inputQueue = createClaudeCodeInputQueue({
28859
30539
  content: initialContentResult.content,
28860
- sourceSeq
30540
+ sourceSeq,
30541
+ // BLOCKER A: commit the persisted dedup watermark at DISPATCH (deliverNow),
30542
+ // not at enqueue-accept. enqueueInput used to commit unconditionally right
30543
+ // after inputQueue.enqueue, but enqueue PARKS a mid-turn follow-up in
30544
+ // deferredFollowUps (a bare return) and a close-in-flight drops it - so a
30545
+ // never-run seq was marked applied and its replay was gated 'ignored' (the
30546
+ // drop). Committing only when the seq is actually delivered onto the live
30547
+ // stream means a parked-but-unrun seq is never marked applied.
30548
+ onSourceSeqDelivered: (deliveredSeq) => {
30549
+ args.onLiveSourceSeqAccepted?.({
30550
+ channel,
30551
+ threadId,
30552
+ sourceSeq: deliveredSeq
30553
+ });
30554
+ }
28861
30555
  });
28862
30556
  const developerPrompt = normalizedWorkDescription(
28863
30557
  args.control.developerPrompt
@@ -28966,7 +30660,8 @@ async function startClaudeCodeProviderInstance(args) {
28966
30660
  channelSlug: channel,
28967
30661
  kandanThreadId: threadId,
28968
30662
  codexThreadId: activeSessionId ?? args.resumeSessionId,
28969
- rootSeq
30663
+ rootSeq,
30664
+ leaseEpoch: args.leaseEpochFor?.(threadId)
28970
30665
  }),
28971
30666
  push: async (event, payload, _timeoutMs, onSent) => {
28972
30667
  const reply = await args.kandan.push(args.topic, event, payload, onSent);
@@ -29014,7 +30709,7 @@ async function startClaudeCodeProviderInstance(args) {
29014
30709
  log: args.log
29015
30710
  });
29016
30711
  const liveBashCapture = args.options.claudeCodeRunner === void 0 && claudeLiveBashCaptureEnabled(process.env) ? createClaudeCodeLiveBashCapture({
29017
- captureDir: join21(
30712
+ captureDir: join22(
29018
30713
  tmpdir3(),
29019
30714
  `linzumi-claude-live-bash-${args.instanceId}`
29020
30715
  ),
@@ -29133,6 +30828,7 @@ async function startClaudeCodeProviderInstance(args) {
29133
30828
  linzumi_thread_id: threadId,
29134
30829
  claude_session_id: event.sessionId
29135
30830
  });
30831
+ args.onLiveSourceSeqAccepted?.({ channel, threadId, sourceSeq });
29136
30832
  args.activeClaudeCodeSessions.set(event.sessionId, {
29137
30833
  abortController,
29138
30834
  workspace,
@@ -29341,6 +31037,15 @@ async function startClaudeCodeProviderInstance(args) {
29341
31037
  onTranscriptEvent
29342
31038
  });
29343
31039
  } finally {
31040
+ const undeliveredFollowUps = inputQueue.drainUndeliveredFollowUps();
31041
+ const undeliveredSeqs = undeliveredFollowUps.map((followUp) => followUp.sourceSeq).filter((seq) => seq !== void 0);
31042
+ if (undeliveredSeqs.length > 0 && args.onUndeliveredFollowUpsAtClose !== void 0) {
31043
+ args.onUndeliveredFollowUpsAtClose({
31044
+ channel,
31045
+ threadId,
31046
+ sourceSeqs: undeliveredSeqs
31047
+ });
31048
+ }
29344
31049
  inputQueue.close();
29345
31050
  mcpAuthCleanup?.();
29346
31051
  liveBashCapture?.close();
@@ -29701,7 +31406,8 @@ ${developerPrompt}`,
29701
31406
  metadata: {
29702
31407
  local_codex_runner: {
29703
31408
  event_type: "codex_start_instructions",
29704
- codex_thread_id: codexThreadId
31409
+ codex_thread_id: codexThreadId,
31410
+ instructions_body: developerPrompt
29705
31411
  }
29706
31412
  }
29707
31413
  },
@@ -29864,15 +31570,15 @@ function rehydrationControlSnapshot(args) {
29864
31570
  ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
29865
31571
  };
29866
31572
  }
29867
- function kandanChatAttachmentsToWire(attachments) {
29868
- return attachments.map((attachment) => ({
29869
- ...attachment.id === void 0 ? {} : { id: attachment.id },
29870
- ...attachment.kind === void 0 ? {} : { kind: attachment.kind },
29871
- ...attachment.fileName === void 0 ? {} : { file_name: attachment.fileName },
29872
- ...attachment.contentType === void 0 ? {} : { content_type: attachment.contentType },
29873
- ...attachment.sizeBytes === void 0 ? {} : { size_bytes: attachment.sizeBytes },
29874
- ...attachment.url === void 0 ? {} : { url: attachment.url }
29875
- }));
31573
+ function deadLetterFailedReason(reason) {
31574
+ switch (reason) {
31575
+ case "exhausted":
31576
+ return "couldn't resume this job after several attempts; please resend your last message to continue.";
31577
+ case "withheld":
31578
+ 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.";
31579
+ case "permanent":
31580
+ return "couldn't resume this job's coding session.";
31581
+ }
29876
31582
  }
29877
31583
  function rehydrationReconnectControl(record) {
29878
31584
  const snapshot = record.control;
@@ -30920,8 +32626,8 @@ function onboardingConversationOfficeImport(response) {
30920
32626
  return objectValue(metadata?.office_channel_import);
30921
32627
  }
30922
32628
  function onboardingConversationImportClientMessageId(importKey, itemKey) {
30923
- const importHash = createHash6("sha256").update(importKey).digest("hex");
30924
- const itemHash = createHash6("sha256").update(itemKey).digest("hex");
32629
+ const importHash = createHash7("sha256").update(importKey).digest("hex");
32630
+ const itemHash = createHash7("sha256").update(itemKey).digest("hex");
30925
32631
  return `onboarding-import:${importHash.slice(0, 32)}:${itemHash.slice(0, 24)}`;
30926
32632
  }
30927
32633
  async function uploadLocalConversationAttachments(args) {
@@ -31019,7 +32725,7 @@ async function localConversationAttachmentFile(candidatePath, attachment) {
31019
32725
  };
31020
32726
  }
31021
32727
  case "path": {
31022
- const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(dirname14(candidatePath), attachment.path);
32728
+ const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(dirname15(candidatePath), attachment.path);
31023
32729
  try {
31024
32730
  const bytes = await readFile2(path2);
31025
32731
  return {
@@ -31474,9 +33180,9 @@ function mcpOwnerUsername(options) {
31474
33180
  return options.channelSession?.listenUser ?? identityFromAccessToken(options.token).actorUsername;
31475
33181
  }
31476
33182
  function writeEphemeralMcpAuthFile(options) {
31477
- const directory = mkdtempSync4(join21(tmpdir3(), "linzumi-mcp-auth-"));
33183
+ const directory = mkdtempSync4(join22(tmpdir3(), "linzumi-mcp-auth-"));
31478
33184
  chmodSync2(directory, 448);
31479
- const path2 = join21(directory, "auth.json");
33185
+ const path2 = join22(directory, "auth.json");
31480
33186
  writeCachedLocalRunnerToken({
31481
33187
  kandanUrl: options.kandanUrl,
31482
33188
  accessToken: options.token,
@@ -31552,7 +33258,7 @@ function configuredAllowedCwds(values, options = {}) {
31552
33258
  const absolutePath = resolve8(expandUserPath(value));
31553
33259
  try {
31554
33260
  if (options.createMissing === true) {
31555
- mkdirSync13(absolutePath, { recursive: true });
33261
+ mkdirSync14(absolutePath, { recursive: true });
31556
33262
  }
31557
33263
  const realPath = realpathSync6(absolutePath);
31558
33264
  allowedCwds.push(
@@ -31589,7 +33295,7 @@ function allowedCwdProjects(allowedCwds) {
31589
33295
  });
31590
33296
  }
31591
33297
  function isGitProjectDirectory(cwd) {
31592
- const gitPath = join21(cwd, ".git");
33298
+ const gitPath = join22(cwd, ".git");
31593
33299
  try {
31594
33300
  const gitPathStats = statSync3(gitPath);
31595
33301
  return gitPathStats.isDirectory() || gitPathStats.isFile();
@@ -31612,9 +33318,9 @@ function browseRunnerDirectory(control, options) {
31612
33318
  error: "not_directory"
31613
33319
  };
31614
33320
  }
31615
- const parent = dirname14(currentPath);
33321
+ const parent = dirname15(currentPath);
31616
33322
  const entries = readdirSync4(currentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => visibleRunnerDirectoryEntryName(entry.name)).map((entry) => {
31617
- const path2 = join21(currentPath, entry.name);
33323
+ const path2 = join22(currentPath, entry.name);
31618
33324
  return {
31619
33325
  name: entry.name,
31620
33326
  path: path2,
@@ -31655,7 +33361,7 @@ function projectDirectoryName(name) {
31655
33361
  function availableProjectDirectoryName(projectsRoot, baseName, suffix = 0) {
31656
33362
  for (let nextSuffix = suffix; ; nextSuffix += 1) {
31657
33363
  const candidate = nextSuffix === 0 ? baseName : `${baseName}-${nextSuffix}`;
31658
- if (!projectPathExists(join21(projectsRoot, candidate))) {
33364
+ if (!projectPathExists(join22(projectsRoot, candidate))) {
31659
33365
  return candidate;
31660
33366
  }
31661
33367
  }
@@ -31683,9 +33389,9 @@ function createRunnerProject(control, options, allowedCwds) {
31683
33389
  error: "invalid_project_template"
31684
33390
  };
31685
33391
  }
31686
- const projectsRoot = join21(currentHomeDirectory(), "linzumi");
33392
+ const projectsRoot = join22(currentHomeDirectory(), "linzumi");
31687
33393
  const resolvedProjectDirName = template === "hello_linzumi_demo" ? availableProjectDirectoryName(projectsRoot, projectDirName) : projectDirName;
31688
- const projectPath = join21(projectsRoot, resolvedProjectDirName);
33394
+ const projectPath = join22(projectsRoot, resolvedProjectDirName);
31689
33395
  let createdProjectPath = false;
31690
33396
  try {
31691
33397
  if (template !== "hello_linzumi_demo" && projectPathExists(projectPath)) {
@@ -31697,7 +33403,7 @@ function createRunnerProject(control, options, allowedCwds) {
31697
33403
  error: "project_directory_exists"
31698
33404
  };
31699
33405
  }
31700
- mkdirSync13(projectsRoot, { recursive: true });
33406
+ mkdirSync14(projectsRoot, { recursive: true });
31701
33407
  if (template === "hello_linzumi_demo") {
31702
33408
  createdProjectPath = true;
31703
33409
  createHelloLinzumiProject({
@@ -31705,7 +33411,7 @@ function createRunnerProject(control, options, allowedCwds) {
31705
33411
  name: resolvedProjectDirName
31706
33412
  });
31707
33413
  } else {
31708
- mkdirSync13(projectPath, { recursive: false });
33414
+ mkdirSync14(projectPath, { recursive: false });
31709
33415
  createdProjectPath = true;
31710
33416
  }
31711
33417
  const git = spawnSync5("git", ["init"], {
@@ -31818,7 +33524,7 @@ async function suggestRunnerTasks(control, options, allowedCwds) {
31818
33524
  };
31819
33525
  }
31820
33526
  }
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;
33527
+ 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
33528
  var init_runner = __esm({
31823
33529
  "src/runner.ts"() {
31824
33530
  "use strict";
@@ -31826,6 +33532,7 @@ var init_runner = __esm({
31826
33532
  init_runnerThreadSessionRegistry();
31827
33533
  init_outboxJournal();
31828
33534
  init_channelSessionSupport();
33535
+ init_channelSessionSupport();
31829
33536
  init_commanderAttachments();
31830
33537
  init_claudeCodePipeline();
31831
33538
  init_claudeCodePlanMirror();
@@ -31868,11 +33575,18 @@ var init_runner = __esm({
31868
33575
  init_signupTaskSuggestions();
31869
33576
  init_userFacingErrors();
31870
33577
  init_remoteCodexSandboxRunner();
33578
+ init_durableInboundQueue();
33579
+ init_appliedSourceSeqWatermark();
33580
+ init_threadReconcilerLock();
33581
+ init_runnerLifecycleGuards();
33582
+ init_leaseEpochGuard();
33583
+ init_resumeKeystone();
31871
33584
  THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
31872
33585
  THREAD_RUNNER_REAPER_INTERVAL_MS = 3e4;
31873
33586
  onboardingDiscoveryAgentStartKeys = /* @__PURE__ */ new Set();
31874
33587
  onboardingConversationImportKeys = /* @__PURE__ */ new Set();
31875
33588
  onboardingConversationDiscoveryReportLimit = 100;
33589
+ CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS = 3e3;
31876
33590
  staleStartInstanceWindowMs = 6e4;
31877
33591
  connectionStaleThresholdMs = 12e4;
31878
33592
  connectionUnrecoverableAfterMs = 10 * 60 * 1e3;
@@ -31885,6 +33599,7 @@ var init_runner = __esm({
31885
33599
  "turn/canceled",
31886
33600
  "turn/cancelled"
31887
33601
  ]);
33602
+ runnerDrainExitDeadlineMs = 8e3;
31888
33603
  claudeSessionStoreSequenceHighWater = /* @__PURE__ */ new Map();
31889
33604
  claudeCodeLlmProxySessionHeader = "x-linzumi-llm-proxy-token";
31890
33605
  onboardingConversationTitleFirstMessages = 4;
@@ -31897,7 +33612,7 @@ var init_runner = __esm({
31897
33612
  });
31898
33613
 
31899
33614
  // src/kandanTls.ts
31900
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
33615
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
31901
33616
  import { Agent } from "undici";
31902
33617
  import WsWebSocket from "ws";
31903
33618
  function kandanTlsTrustFromEnv() {
@@ -31911,7 +33626,7 @@ function kandanTlsTrustFromCaFile(caFile) {
31911
33626
  if (!existsSync14(trimmed)) {
31912
33627
  throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
31913
33628
  }
31914
- const ca = readFileSync18(trimmed, "utf8");
33629
+ const ca = readFileSync19(trimmed, "utf8");
31915
33630
  return {
31916
33631
  caFile: trimmed,
31917
33632
  ca,
@@ -50695,7 +52410,7 @@ var init_RemoveFileError = __esm({
50695
52410
 
50696
52411
  // ../../node_modules/@inquirer/external-editor/dist/esm/index.js
50697
52412
  import { spawn as spawn11, spawnSync as spawnSync6 } from "child_process";
50698
- import { readFileSync as readFileSync21, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
52413
+ import { readFileSync as readFileSync22, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
50699
52414
  import path from "node:path";
50700
52415
  import os from "node:os";
50701
52416
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -50819,7 +52534,7 @@ var init_esm5 = __esm({
50819
52534
  }
50820
52535
  readTemporaryFile() {
50821
52536
  try {
50822
- const tempFileBuffer = readFileSync21(this.tempFile);
52537
+ const tempFileBuffer = readFileSync22(this.tempFile);
50823
52538
  if (tempFileBuffer.length === 0) {
50824
52539
  this.text = "";
50825
52540
  } else {
@@ -52188,17 +53903,17 @@ import { spawn as spawn12, spawnSync as spawnSync7 } from "node:child_process";
52188
53903
  import {
52189
53904
  existsSync as existsSync17,
52190
53905
  constants as fsConstants,
52191
- mkdirSync as mkdirSync16,
53906
+ mkdirSync as mkdirSync17,
52192
53907
  mkdtempSync as mkdtempSync6,
52193
- readFileSync as readFileSync22,
53908
+ readFileSync as readFileSync23,
52194
53909
  readdirSync as readdirSync5,
52195
53910
  rmSync as rmSync7,
52196
53911
  statSync as statSync4,
52197
53912
  writeFileSync as writeFileSync16
52198
53913
  } from "node:fs";
52199
53914
  import { access } from "node:fs/promises";
52200
- import { homedir as homedir17, tmpdir as tmpdir5 } from "node:os";
52201
- import { delimiter as delimiter3, dirname as dirname18, join as join25, resolve as resolve10 } from "node:path";
53915
+ import { homedir as homedir18, tmpdir as tmpdir5 } from "node:os";
53916
+ import { delimiter as delimiter3, dirname as dirname19, join as join26, resolve as resolve10 } from "node:path";
52202
53917
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
52203
53918
  import { emitKeypressEvents } from "node:readline";
52204
53919
  function signupHelpText() {
@@ -52319,7 +54034,7 @@ function defaultSignupDraftStore(serviceUrl) {
52319
54034
  }
52320
54035
  let parsed;
52321
54036
  try {
52322
- parsed = JSON.parse(readFileSync22(path2, "utf8"));
54037
+ parsed = JSON.parse(readFileSync23(path2, "utf8"));
52323
54038
  } catch (_error) {
52324
54039
  return void 0;
52325
54040
  }
@@ -52329,7 +54044,7 @@ function defaultSignupDraftStore(serviceUrl) {
52329
54044
  return comparableServiceUrl(parsed.serviceUrl) === comparableServiceUrl(serviceUrl) ? parsed : void 0;
52330
54045
  },
52331
54046
  write: (draft) => {
52332
- mkdirSync16(dirname18(path2), { recursive: true });
54047
+ mkdirSync17(dirname19(path2), { recursive: true });
52333
54048
  writeFileSync16(path2, `${JSON.stringify(draft, null, 2)}
52334
54049
  `, {
52335
54050
  encoding: "utf8",
@@ -52342,8 +54057,8 @@ function defaultSignupDraftStore(serviceUrl) {
52342
54057
  };
52343
54058
  }
52344
54059
  function defaultSignupDraftPath(serviceUrl) {
52345
- return join25(
52346
- dirname18(localConfigPath()),
54060
+ return join26(
54061
+ dirname19(localConfigPath()),
52347
54062
  `signup-draft.${localConfigScopeFileStem(serviceUrl)}.json`
52348
54063
  );
52349
54064
  }
@@ -52377,7 +54092,7 @@ function defaultSignupTaskCacheStore(serviceUrl) {
52377
54092
  }
52378
54093
  }
52379
54094
  };
52380
- mkdirSync16(dirname18(path2), { recursive: true });
54095
+ mkdirSync17(dirname19(path2), { recursive: true });
52381
54096
  writeFileSync16(path2, `${JSON.stringify(next, null, 2)}
52382
54097
  `, {
52383
54098
  encoding: "utf8",
@@ -52387,8 +54102,8 @@ function defaultSignupTaskCacheStore(serviceUrl) {
52387
54102
  };
52388
54103
  }
52389
54104
  function defaultSignupTaskCachePath(serviceUrl) {
52390
- return join25(
52391
- dirname18(localConfigPath()),
54105
+ return join26(
54106
+ dirname19(localConfigPath()),
52392
54107
  `signup-task-cache.${localConfigScopeFileStem(serviceUrl)}.json`
52393
54108
  );
52394
54109
  }
@@ -52398,7 +54113,7 @@ function readSignupTaskCache(path2) {
52398
54113
  }
52399
54114
  let parsed;
52400
54115
  try {
52401
- parsed = JSON.parse(readFileSync22(path2, "utf8"));
54116
+ parsed = JSON.parse(readFileSync23(path2, "utf8"));
52402
54117
  } catch (_error) {
52403
54118
  return { version: 1, entries: {} };
52404
54119
  }
@@ -54283,7 +55998,7 @@ function autocompletePathSuggestions(pathInput) {
54283
55998
  try {
54284
55999
  const showHidden = prefix.startsWith(".");
54285
56000
  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) => join25(directoryPath, entry.name)).map((path2) => ({
56001
+ 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
56002
  path: path2,
54288
56003
  score: fuzzyPathSegmentScore(path2.split("/").at(-1) ?? path2, prefix)
54289
56004
  })).filter((candidate) => candidate.score !== void 0).sort((left, right) => {
@@ -54529,7 +56244,7 @@ async function runSignupPreflights(runtime) {
54529
56244
  function defaultPreflightRuntime() {
54530
56245
  return {
54531
56246
  cwd: process.cwd(),
54532
- homeDir: homedir17(),
56247
+ homeDir: homedir18(),
54533
56248
  probeTool,
54534
56249
  readGitEmail,
54535
56250
  discoverCodeRoots,
@@ -54674,9 +56389,9 @@ async function suggestTasksWithCodex(projectPath, retryPolicy) {
54674
56389
  );
54675
56390
  }
54676
56391
  async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolicy) {
54677
- const tempRoot = mkdtempSync6(join25(tmpdir5(), "linzumi-signup-codex-tasks-"));
54678
- const schemaPath = join25(tempRoot, "task-suggestions.schema.json");
54679
- const outputPath = join25(tempRoot, "task-suggestions.json");
56392
+ const tempRoot = mkdtempSync6(join26(tmpdir5(), "linzumi-signup-codex-tasks-"));
56393
+ const schemaPath = join26(tempRoot, "task-suggestions.schema.json");
56394
+ const outputPath = join26(tempRoot, "task-suggestions.json");
54680
56395
  const model = process.env.LINZUMI_SIGNUP_TASK_MODEL ?? "gpt-5.4-mini";
54681
56396
  const prompt = taskSuggestionPrompt2(previousResponse);
54682
56397
  const codexCommand = await resolveSignupCodexCommand();
@@ -54701,7 +56416,7 @@ async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolic
54701
56416
  ),
54702
56417
  retryPolicy
54703
56418
  );
54704
- return readFileSync22(outputPath, "utf8");
56419
+ return readFileSync23(outputPath, "utf8");
54705
56420
  } finally {
54706
56421
  rmSync7(tempRoot, { recursive: true, force: true });
54707
56422
  }
@@ -54738,7 +56453,7 @@ function codexTaskSuggestionProcess2(args) {
54738
56453
  function signupCodexTaskSuggestionProcessForTest(args) {
54739
56454
  return codexTaskSuggestionProcess2(args);
54740
56455
  }
54741
- async function resolveSignupCodexCommand(env = process.env, homeDir = homedir17(), executableExists = fileIsExecutable) {
56456
+ async function resolveSignupCodexCommand(env = process.env, homeDir = homedir18(), executableExists = fileIsExecutable) {
54742
56457
  const override = firstConfiguredValue([
54743
56458
  env.LINZUMI_SIGNUP_CODEX_BIN,
54744
56459
  env.LINZUMI_CODEX_BIN
@@ -54793,7 +56508,7 @@ function resolveHomePath(path2, homeDir) {
54793
56508
  return homeDir;
54794
56509
  }
54795
56510
  if (path2.startsWith("~/")) {
54796
- return join25(homeDir, path2.slice(2));
56511
+ return join26(homeDir, path2.slice(2));
54797
56512
  }
54798
56513
  return resolve10(path2);
54799
56514
  }
@@ -54806,9 +56521,9 @@ function commandLooksPathLike(command) {
54806
56521
  }
54807
56522
  function homeManagedCodexCandidates(homeDir) {
54808
56523
  return [
54809
- join25(homeDir, ".volta", "bin", "codex"),
54810
- join25(homeDir, ".local", "bin", "codex"),
54811
- join25(homeDir, "bin", "codex")
56524
+ join26(homeDir, ".volta", "bin", "codex"),
56525
+ join26(homeDir, ".local", "bin", "codex"),
56526
+ join26(homeDir, "bin", "codex")
54812
56527
  ];
54813
56528
  }
54814
56529
  async function firstExecutablePath(paths, executableExists) {
@@ -54823,7 +56538,7 @@ function commandPathCandidates(path2) {
54823
56538
  if (path2 === void 0 || path2.trim() === "") {
54824
56539
  return [];
54825
56540
  }
54826
- return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) => join25(entry, "codex"));
56541
+ return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) => join26(entry, "codex"));
54827
56542
  }
54828
56543
  async function fileIsExecutable(path2) {
54829
56544
  try {
@@ -55056,11 +56771,11 @@ function codexPreflightLocation(command, homeDir) {
55056
56771
  switch (command) {
55057
56772
  case "codex":
55058
56773
  return "on PATH";
55059
- case join25(homeDir, ".volta", "bin", "codex"):
56774
+ case join26(homeDir, ".volta", "bin", "codex"):
55060
56775
  return "via Volta";
55061
- case join25(homeDir, ".local", "bin", "codex"):
56776
+ case join26(homeDir, ".local", "bin", "codex"):
55062
56777
  return "via ~/.local/bin";
55063
- case join25(homeDir, "bin", "codex"):
56778
+ case join26(homeDir, "bin", "codex"):
55064
56779
  return "via ~/bin";
55065
56780
  default:
55066
56781
  return "from configured path";
@@ -55219,7 +56934,7 @@ function probeToolWithArgs(command, args, cwd) {
55219
56934
  }
55220
56935
  async function discoverCodeRoots(homeDir) {
55221
56936
  const candidates = ["src", "code", "projects"].map(
55222
- (name) => join25(homeDir, name)
56937
+ (name) => join26(homeDir, name)
55223
56938
  );
55224
56939
  return candidates.filter((path2) => existsSync17(path2)).flatMap((path2) => discoveredProjectNames(path2));
55225
56940
  }
@@ -55273,7 +56988,7 @@ function discoverProjectsFromCurrentDirectory(cwd) {
55273
56988
  }
55274
56989
  function discoverProjectsFromGuessedRoots(homeDir) {
55275
56990
  const guessedRoots = ["src", "code", "projects"].map(
55276
- (name) => join25(homeDir, name)
56991
+ (name) => join26(homeDir, name)
55277
56992
  );
55278
56993
  const projects = guessedRoots.flatMap(
55279
56994
  (root) => discoverProjectsUnderRoot(root)
@@ -55325,25 +57040,25 @@ function looksLikeProject(path2) {
55325
57040
  "pnpm-lock.yaml",
55326
57041
  "yarn.lock",
55327
57042
  "package-lock.json"
55328
- ].some((name) => existsSync17(join25(path2, name)));
57043
+ ].some((name) => existsSync17(join26(path2, name)));
55329
57044
  }
55330
57045
  function detectProjectLanguage(path2) {
55331
- if (existsSync17(join25(path2, "pyproject.toml")) || existsSync17(join25(path2, "requirements.txt"))) {
57046
+ if (existsSync17(join26(path2, "pyproject.toml")) || existsSync17(join26(path2, "requirements.txt"))) {
55332
57047
  return "Python";
55333
57048
  }
55334
- if (existsSync17(join25(path2, "Cargo.toml"))) {
57049
+ if (existsSync17(join26(path2, "Cargo.toml"))) {
55335
57050
  return "Rust";
55336
57051
  }
55337
- if (existsSync17(join25(path2, "go.mod"))) {
57052
+ if (existsSync17(join26(path2, "go.mod"))) {
55338
57053
  return "Go";
55339
57054
  }
55340
- if (existsSync17(join25(path2, "mix.exs"))) {
57055
+ if (existsSync17(join26(path2, "mix.exs"))) {
55341
57056
  return "Elixir";
55342
57057
  }
55343
- if (existsSync17(join25(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
57058
+ if (existsSync17(join26(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
55344
57059
  return "TypeScript";
55345
57060
  }
55346
- if (existsSync17(join25(path2, "package.json"))) {
57061
+ if (existsSync17(join26(path2, "package.json"))) {
55347
57062
  return "JavaScript";
55348
57063
  }
55349
57064
  return "Project";
@@ -55351,7 +57066,7 @@ function detectProjectLanguage(path2) {
55351
57066
  function packageJsonMentionsTypeScript(path2) {
55352
57067
  try {
55353
57068
  const packageJson2 = JSON.parse(
55354
- readFileSync22(join25(path2, "package.json"), "utf8")
57069
+ readFileSync23(join26(path2, "package.json"), "utf8")
55355
57070
  );
55356
57071
  return packageJson2.dependencies?.typescript !== void 0 || packageJson2.devDependencies?.typescript !== void 0;
55357
57072
  } catch {
@@ -55359,11 +57074,11 @@ function packageJsonMentionsTypeScript(path2) {
55359
57074
  }
55360
57075
  }
55361
57076
  function hasGitMetadata(path2) {
55362
- return existsSync17(join25(path2, ".git"));
57077
+ return existsSync17(join26(path2, ".git"));
55363
57078
  }
55364
57079
  function childDirectories(root) {
55365
57080
  try {
55366
- return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join25(root, entry.name));
57081
+ return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join26(root, entry.name));
55367
57082
  } catch {
55368
57083
  return [];
55369
57084
  }
@@ -55389,10 +57104,10 @@ function directoryExists(path2) {
55389
57104
  }
55390
57105
  function expandHomePath(path2) {
55391
57106
  if (path2 === "~") {
55392
- return homedir17();
57107
+ return homedir18();
55393
57108
  }
55394
57109
  if (path2.startsWith("~/")) {
55395
- return join25(homedir17(), path2.slice(2));
57110
+ return join26(homedir18(), path2.slice(2));
55396
57111
  }
55397
57112
  return resolve10(path2);
55398
57113
  }
@@ -55483,8 +57198,8 @@ init_runner();
55483
57198
  init_runnerLockTakeover();
55484
57199
  init_claudeCodeSession();
55485
57200
  init_authCache();
55486
- import { existsSync as existsSync18, readFileSync as readFileSync23, realpathSync as realpathSync7 } from "node:fs";
55487
- import { homedir as homedir18 } from "node:os";
57201
+ import { existsSync as existsSync18, readFileSync as readFileSync24, realpathSync as realpathSync7 } from "node:fs";
57202
+ import { homedir as homedir19 } from "node:os";
55488
57203
  import { resolve as resolve11 } from "node:path";
55489
57204
  import { fileURLToPath as fileURLToPath4 } from "node:url";
55490
57205
 
@@ -55579,9 +57294,9 @@ init_kandanTls();
55579
57294
  init_protocol();
55580
57295
  init_json();
55581
57296
  init_defaultUrls();
55582
- import { existsSync as existsSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
55583
- import { dirname as dirname15, join as join22 } from "node:path";
55584
- import { homedir as homedir15 } from "node:os";
57297
+ import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "node:fs";
57298
+ import { dirname as dirname16, join as join23 } from "node:path";
57299
+ import { homedir as homedir16 } from "node:os";
55585
57300
  async function runAgentCliCommand(args, deps = {
55586
57301
  fetchImpl: fetch,
55587
57302
  stdout: process.stdout,
@@ -56289,7 +58004,7 @@ function agentTokenFile(flags) {
56289
58004
  return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
56290
58005
  }
56291
58006
  function defaultAgentTokenFilePath() {
56292
- return join22(homedir15(), ".linzumi", "agent-token.json");
58007
+ return join23(homedir16(), ".linzumi", "agent-token.json");
56293
58008
  }
56294
58009
  function normalizedApiUrl(apiUrl) {
56295
58010
  return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
@@ -56298,10 +58013,10 @@ function authorizationHeaders(token) {
56298
58013
  return { authorization: `Bearer ${token}` };
56299
58014
  }
56300
58015
  function readOptionalTextFile(path2) {
56301
- return existsSync15(path2) ? readFileSync19(path2, "utf8") : void 0;
58016
+ return existsSync15(path2) ? readFileSync20(path2, "utf8") : void 0;
56302
58017
  }
56303
58018
  function writeTextFile(path2, content) {
56304
- mkdirSync14(dirname15(path2), { recursive: true });
58019
+ mkdirSync15(dirname16(path2), { recursive: true });
56305
58020
  writeFileSync13(path2, content);
56306
58021
  }
56307
58022
  function readStoredAgentTokenFile(path2, readTextFile = readOptionalTextFile) {
@@ -56390,26 +58105,26 @@ init_helloLinzumiProject();
56390
58105
  init_runnerLogger();
56391
58106
  import {
56392
58107
  existsSync as existsSync16,
56393
- closeSync as closeSync3,
56394
- mkdirSync as mkdirSync15,
56395
- openSync as openSync4,
56396
- readFileSync as readFileSync20,
58108
+ closeSync as closeSync5,
58109
+ mkdirSync as mkdirSync16,
58110
+ openSync as openSync6,
58111
+ readFileSync as readFileSync21,
56397
58112
  watch,
56398
58113
  writeFileSync as writeFileSync14
56399
58114
  } from "node:fs";
56400
- import { homedir as homedir16 } from "node:os";
56401
- import { dirname as dirname16, join as join23, resolve as resolve9 } from "node:path";
58115
+ import { homedir as homedir17 } from "node:os";
58116
+ import { dirname as dirname17, join as join24, resolve as resolve9 } from "node:path";
56402
58117
  import { execFileSync, spawn as spawn10 } from "node:child_process";
56403
58118
  import { fileURLToPath as fileURLToPath3 } from "node:url";
56404
58119
  var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
56405
58120
  function commanderStatusDir() {
56406
- return join23(homedir16(), ".linzumi", "commanders");
58121
+ return join24(homedir17(), ".linzumi", "commanders");
56407
58122
  }
56408
58123
  function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
56409
- return join23(statusDir, `${safeRunnerId(runnerId)}.json`);
58124
+ return join24(statusDir, `${safeRunnerId(runnerId)}.json`);
56410
58125
  }
56411
58126
  function defaultCommanderLogFile(runnerId) {
56412
- return join23(homedir16(), ".linzumi", "logs", `${safeRunnerId(runnerId)}.log`);
58127
+ return join24(homedir17(), ".linzumi", "logs", `${safeRunnerId(runnerId)}.log`);
56413
58128
  }
56414
58129
  function commanderLogIsConnected(log2) {
56415
58130
  return connectedMarkers.some((marker) => log2.includes(marker));
@@ -56430,10 +58145,10 @@ function startCommanderDaemon(options) {
56430
58145
  "--log-file",
56431
58146
  logFile
56432
58147
  ];
56433
- mkdirSync15(statusDir, { recursive: true });
56434
- mkdirSync15(dirname16(logFile), { recursive: true });
56435
- const out = openSync4(logFile, "a");
56436
- const err = openSync4(logFile, "a");
58148
+ mkdirSync16(statusDir, { recursive: true });
58149
+ mkdirSync16(dirname17(logFile), { recursive: true });
58150
+ const out = openSync6(logFile, "a");
58151
+ const err = openSync6(logFile, "a");
56437
58152
  writeCliAuditEvent(
56438
58153
  "process.spawn",
56439
58154
  {
@@ -56460,8 +58175,8 @@ function startCommanderDaemon(options) {
56460
58175
  },
56461
58176
  { sessionId: options.runnerId }
56462
58177
  );
56463
- closeSync3(out);
56464
- closeSync3(err);
58178
+ closeSync5(out);
58179
+ closeSync5(err);
56465
58180
  child.unref();
56466
58181
  if (child.pid === void 0) {
56467
58182
  throw new Error("commander daemon did not report a pid");
@@ -56486,12 +58201,12 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
56486
58201
  if (!existsSync16(statusFile)) {
56487
58202
  return { status: "missing", runnerId, statusFile };
56488
58203
  }
56489
- const record = parseRecord2(readFileSync20(statusFile, "utf8"));
58204
+ const record = parseRecord2(readFileSync21(statusFile, "utf8"));
56490
58205
  return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
56491
58206
  }
56492
58207
  async function waitForCommanderDaemon(options) {
56493
58208
  const now = options.now ?? (() => Date.now());
56494
- const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync20(path2, "utf8") : void 0);
58209
+ const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync21(path2, "utf8") : void 0);
56495
58210
  const statusImpl = options.statusImpl ?? commanderDaemonStatus;
56496
58211
  const deadline = now() + options.timeoutMs;
56497
58212
  while (now() <= deadline) {
@@ -69287,22 +71002,49 @@ async function resolveMcpAuth(args) {
69287
71002
  mode: "personal-agent-delegation"
69288
71003
  };
69289
71004
  }
69290
- const token = args.explicitToken ?? readCachedLocalRunnerToken(args.kandanUrl, args.authFilePath)?.accessToken;
71005
+ const accessToken = await resolveMcpLocalRunnerToken({
71006
+ kandanUrl: args.kandanUrl,
71007
+ explicitToken: args.explicitToken,
71008
+ authFilePath: args.authFilePath,
71009
+ workspaceSlug: args.workspaceSlug,
71010
+ channelSlug: args.channelSlug
71011
+ });
71012
+ return { accessToken, mode: "local-runner" };
71013
+ }
71014
+ async function resolveMcpLocalRunnerToken(args, deps = {
71015
+ readCachedToken: readCachedLocalRunnerToken,
71016
+ validateTokenOutcome: validateLocalRunnerTokenOutcome,
71017
+ resolveToken: resolveLocalRunnerToken
71018
+ }) {
71019
+ const token = args.explicitToken ?? deps.readCachedToken(args.kandanUrl, args.authFilePath)?.accessToken;
69291
71020
  if (token === void 0) {
69292
71021
  throw new Error(
69293
71022
  "missing Linzumi MCP token; run linzumi auth or pass --token"
69294
71023
  );
69295
71024
  }
69296
- const valid = await validateLocalRunnerToken({
71025
+ const outcome = await deps.validateTokenOutcome({
69297
71026
  kandanUrl: args.kandanUrl,
69298
71027
  accessToken: token,
69299
71028
  workspaceSlug: args.workspaceSlug,
69300
71029
  channelSlug: args.channelSlug
69301
71030
  });
69302
- if (!valid) {
69303
- throw new Error("Linzumi MCP token was rejected by the server");
71031
+ if (outcome === "usable") {
71032
+ return token;
71033
+ }
71034
+ const _recoverable = outcome;
71035
+ void _recoverable;
71036
+ try {
71037
+ return await deps.resolveToken({
71038
+ kandanUrl: args.kandanUrl,
71039
+ workspaceSlug: args.workspaceSlug,
71040
+ channelSlug: args.channelSlug,
71041
+ authFilePath: args.authFilePath
71042
+ });
71043
+ } catch (error) {
71044
+ throw new Error(
71045
+ `Linzumi MCP token was rejected by the server and re-mint failed: ${error instanceof Error ? error.message : String(error)}`
71046
+ );
69304
71047
  }
69305
- return { accessToken: token, mode: "local-runner" };
69306
71048
  }
69307
71049
  function mcpJsonResult(value) {
69308
71050
  return {
@@ -69384,7 +71126,7 @@ init_userFacingErrors();
69384
71126
  init_version();
69385
71127
  import { chmodSync as chmodSync3, mkdtempSync as mkdtempSync5, rmSync as rmSync6 } from "node:fs";
69386
71128
  import { tmpdir as tmpdir4 } from "node:os";
69387
- import { basename as basename9, dirname as dirname17, join as join24 } from "node:path";
71129
+ import { basename as basename9, dirname as dirname18, join as join25 } from "node:path";
69388
71130
  var configEnvKey = "LINZUMI_REMOTE_CODEX_HARNESS_CONFIG_B64";
69389
71131
  var remoteHarnessEnvironmentId = "linzumi-remote-codex-harness";
69390
71132
  async function runRemoteCodexHarnessWorkerFromEnv(env = process.env) {
@@ -69617,18 +71359,18 @@ function resolveLinzumiCliEntrypoint(entrypoint) {
69617
71359
  case "remote-codex-harness-worker.js":
69618
71360
  case "remote-codex-harness-worker.mjs":
69619
71361
  case "remote-codex-harness-worker.cjs":
69620
- return join24(dirname17(entrypoint), "linzumi.js");
71362
+ return join25(dirname18(entrypoint), "linzumi.js");
69621
71363
  case "remoteCodexHarnessWorkerEntrypoint.ts":
69622
71364
  case "remoteCodexHarnessWorkerEntrypoint.js":
69623
- return join24(dirname17(entrypoint), "index.ts");
71365
+ return join25(dirname18(entrypoint), "index.ts");
69624
71366
  default:
69625
71367
  return entrypoint;
69626
71368
  }
69627
71369
  }
69628
71370
  function writeEphemeralMcpAuthFile2(options) {
69629
- const directory = mkdtempSync5(join24(tmpdir4(), "linzumi-mcp-auth-"));
71371
+ const directory = mkdtempSync5(join25(tmpdir4(), "linzumi-mcp-auth-"));
69630
71372
  chmodSync3(directory, 448);
69631
- const path2 = join24(directory, "auth.json");
71373
+ const path2 = join25(directory, "auth.json");
69632
71374
  try {
69633
71375
  writeCachedLocalRunnerToken({
69634
71376
  kandanUrl: options.kandanUrl,
@@ -70579,7 +72321,7 @@ async function parseAgentRunnerArgs(args, deps = {
70579
72321
  };
70580
72322
  }
70581
72323
  function readAgentTokenTextFile(path2) {
70582
- return existsSync18(path2) ? readFileSync23(path2, "utf8") : void 0;
72324
+ return existsSync18(path2) ? readFileSync24(path2, "utf8") : void 0;
70583
72325
  }
70584
72326
  function rejectAgentRunnerTargetingFlags(values) {
70585
72327
  const unsupportedFlags = [
@@ -70898,10 +72640,10 @@ function rejectStartTargetingFlags(values) {
70898
72640
  }
70899
72641
  function resolveUserPath(pathValue) {
70900
72642
  if (pathValue === "~") {
70901
- return homedir18();
72643
+ return homedir19();
70902
72644
  }
70903
72645
  if (pathValue.startsWith("~/")) {
70904
- return resolve11(homedir18(), pathValue.slice(2));
72646
+ return resolve11(homedir19(), pathValue.slice(2));
70905
72647
  }
70906
72648
  return resolve11(pathValue);
70907
72649
  }