@linzumi/cli 0.0.106-beta → 0.0.108-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1868 -171
  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,
@@ -15076,6 +15156,15 @@ import { createServer } from "node:net";
15076
15156
  import { homedir as homedir8 } from "node:os";
15077
15157
  import { join as join10 } from "node:path";
15078
15158
  import { WebSocket as NodeWebSocket } from "ws";
15159
+ function pingWebSocketIfSupported(websocket) {
15160
+ const candidate = websocket;
15161
+ if (typeof candidate.ping === "function" && candidate.readyState === WebSocket.OPEN) {
15162
+ try {
15163
+ candidate.ping();
15164
+ } catch {
15165
+ }
15166
+ }
15167
+ }
15079
15168
  async function chooseLoopbackPort() {
15080
15169
  return new Promise((resolve12, reject) => {
15081
15170
  const server = createServer();
@@ -15363,7 +15452,13 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
15363
15452
  pending.forEach((pendingRequest) => pendingRequest.reject(error));
15364
15453
  pending.clear();
15365
15454
  };
15455
+ const keepalive = setInterval(
15456
+ () => pingWebSocketIfSupported(websocket),
15457
+ codexAppServerKeepaliveIntervalMs
15458
+ );
15459
+ keepalive.unref?.();
15366
15460
  const handleConnectionClosed = (message) => {
15461
+ clearInterval(keepalive);
15367
15462
  rejectPending(message);
15368
15463
  if (state.closeError !== void 0) {
15369
15464
  return;
@@ -15443,7 +15538,10 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
15443
15538
  }
15444
15539
  closeCallbacks.add(callback);
15445
15540
  },
15446
- close: () => websocket.close()
15541
+ close: () => {
15542
+ clearInterval(keepalive);
15543
+ websocket.close();
15544
+ }
15447
15545
  };
15448
15546
  }
15449
15547
  function parseCodexAppServerFrame(data) {
@@ -15653,7 +15751,7 @@ function readyzUrlForWebsocket(websocketUrl) {
15653
15751
  parsed.hash = "";
15654
15752
  return parsed.toString();
15655
15753
  }
15656
- var codexAppServerWatchdogPollMs, blockedCodexAppServerEnvKeys, codexDisableAppsConfigArgs;
15754
+ var codexAppServerWatchdogPollMs, codexAppServerKeepaliveIntervalMs, blockedCodexAppServerEnvKeys, codexDisableAppsConfigArgs;
15657
15755
  var init_codexAppServer = __esm({
15658
15756
  "src/codexAppServer.ts"() {
15659
15757
  "use strict";
@@ -15663,6 +15761,14 @@ var init_codexAppServer = __esm({
15663
15761
  init_engineChildReaper();
15664
15762
  init_engineParentDeathWatchdog();
15665
15763
  codexAppServerWatchdogPollMs = 2e3;
15764
+ codexAppServerKeepaliveIntervalMs = (() => {
15765
+ const raw = process.env.LINZUMI_CODEX_WS_KEEPALIVE_MS;
15766
+ if (raw === void 0) {
15767
+ return 2e4;
15768
+ }
15769
+ const parsed = Number.parseInt(raw, 10);
15770
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 2e4;
15771
+ })();
15666
15772
  blockedCodexAppServerEnvKeys = [
15667
15773
  "LINZUMI_MCP_ACCESS_TOKEN",
15668
15774
  "LINZUMI_MCP_OWNER_USERNAME"
@@ -19972,7 +20078,7 @@ var linzumiCliVersion, linzumiCliVersionText;
19972
20078
  var init_version = __esm({
19973
20079
  "src/version.ts"() {
19974
20080
  "use strict";
19975
- linzumiCliVersion = "0.0.106-beta";
20081
+ linzumiCliVersion = "0.0.108-beta";
19976
20082
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
19977
20083
  }
19978
20084
  });
@@ -22702,6 +22808,9 @@ var init_authCache = __esm({
22702
22808
  });
22703
22809
 
22704
22810
  // src/threadCodexWorkerIpc.ts
22811
+ function isThreadCodexWorkerSessionClosedMessage(message) {
22812
+ return isJsonObject(message) && message.type === "linzumi_thread_codex_worker_session_closed";
22813
+ }
22705
22814
  function bindThreadCodexWorkerIpc(codex, ipc = process) {
22706
22815
  const pendingServerRequests = /* @__PURE__ */ new Map();
22707
22816
  const state = { nextServerRequestId: 1, sendDropLogged: false };
@@ -23557,33 +23666,901 @@ var init_remoteCodexSandboxRunner = __esm({
23557
23666
  }
23558
23667
  });
23559
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
+
23560
24534
  // src/runner.ts
23561
24535
  import { spawn as spawn9, spawnSync as spawnSync5 } from "node:child_process";
23562
- import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
24536
+ import { createHash as createHash7, randomUUID as randomUUID4 } from "node:crypto";
23563
24537
  import {
23564
24538
  chmodSync as chmodSync2,
24539
+ closeSync as closeSync4,
23565
24540
  existsSync as existsSync13,
24541
+ fsyncSync as fsyncSync2,
23566
24542
  lstatSync,
23567
- mkdirSync as mkdirSync13,
24543
+ mkdirSync as mkdirSync14,
23568
24544
  mkdtempSync as mkdtempSync4,
24545
+ openSync as openSync5,
23569
24546
  readdirSync as readdirSync4,
23570
- readFileSync as readFileSync17,
24547
+ readFileSync as readFileSync18,
23571
24548
  realpathSync as realpathSync6,
23572
- renameSync as renameSync4,
24549
+ renameSync as renameSync5,
23573
24550
  rmSync as rmSync5,
23574
24551
  statSync as statSync3,
23575
24552
  writeFileSync as writeFileSync12
23576
24553
  } from "node:fs";
23577
24554
  import { readFile as readFile2 } from "node:fs/promises";
23578
24555
  import { createServer as createServer3 } from "node:http";
23579
- 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";
23580
24557
  import { createInterface } from "node:readline";
23581
24558
  import {
23582
24559
  basename as basename8,
23583
- dirname as dirname14,
24560
+ dirname as dirname15,
23584
24561
  extname as extname2,
23585
24562
  isAbsolute as isAbsolute5,
23586
- join as join21,
24563
+ join as join22,
23587
24564
  resolve as resolve8
23588
24565
  } from "node:path";
23589
24566
  async function runLocalCodexRunner(options) {
@@ -23594,7 +24571,11 @@ async function runLocalCodexRunner(options) {
23594
24571
  removeHandlers: void 0
23595
24572
  };
23596
24573
  const close = () => closeCleanupStack(cleanup);
23597
- cleanup.removeHandlers = installCleanupHandlers(close);
24574
+ const runnerDrainHooks = {
24575
+ stopAccepting: () => void 0,
24576
+ flushInboundQueues: () => void 0
24577
+ };
24578
+ cleanup.removeHandlers = installCleanupHandlers(close, runnerDrainHooks, log2);
23598
24579
  log2("runner.starting", {
23599
24580
  runnerId: options.runnerId,
23600
24581
  cwd: options.cwd,
@@ -23638,7 +24619,13 @@ async function runLocalCodexRunner(options) {
23638
24619
  runnerId: options.runnerId
23639
24620
  });
23640
24621
  }
23641
- return await openLocalCodexRunner(options, log2, cleanup, close);
24622
+ return await openLocalCodexRunner(
24623
+ options,
24624
+ log2,
24625
+ cleanup,
24626
+ close,
24627
+ runnerDrainHooks
24628
+ );
23642
24629
  } catch (error) {
23643
24630
  await close().catch(() => void 0);
23644
24631
  throw error;
@@ -23654,26 +24641,113 @@ async function runThreadCodexWorker(options) {
23654
24641
  const log2 = makeRunnerLogger(options);
23655
24642
  const started = await startOwnedCodexAppServer(options);
23656
24643
  const codex = await connectCodexAppServer(started.url);
24644
+ const kandanThreadId = options.threadProcess.kandanThreadId;
23657
24645
  const stop = once(() => {
23658
24646
  codex.close();
23659
24647
  started.stop();
23660
24648
  log2.close();
23661
24649
  });
23662
24650
  bindThreadCodexWorkerIpc(codex);
24651
+ codex.onClose?.((error) => {
24652
+ log2("runner.thread_codex_worker_session_closed", {
24653
+ kandanThreadId,
24654
+ reason: error.message
24655
+ });
24656
+ if (process.send !== void 0 && process.connected !== false) {
24657
+ try {
24658
+ process.send({
24659
+ type: "linzumi_thread_codex_worker_session_closed",
24660
+ kandanThreadId,
24661
+ reason: error.message
24662
+ });
24663
+ } catch {
24664
+ }
24665
+ }
24666
+ stop();
24667
+ });
23663
24668
  process.send({
23664
24669
  type: "linzumi_thread_codex_worker_ready",
23665
- kandanThreadId: options.threadProcess.kandanThreadId,
24670
+ kandanThreadId,
23666
24671
  commanderManagedPorts: commanderManagedPortsForStartedCodex(started)
23667
24672
  });
23668
24673
  await waitForThreadCodexWorkerStop(started.process);
23669
24674
  stop();
23670
24675
  }
23671
- 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) {
23672
24739
  const agentProviders = availableRunnerAgentProviders(options);
23673
24740
  const localCodexAppServerAvailable = hasLocalCodexAppServerCapability(options);
23674
24741
  const dependencyStatusRef = {
23675
24742
  value: options.dependencyStatus
23676
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
+ });
23677
24751
  const refreshDependencyStatus = async () => {
23678
24752
  if (dependencyStatusRef.value === void 0) {
23679
24753
  return;
@@ -23699,6 +24773,27 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23699
24773
  });
23700
24774
  }
23701
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
+ };
23702
24797
  const allowedForwardPorts = options.allowedForwardPorts ?? [];
23703
24798
  const liveForwardPorts = new Set(allowedForwardPorts);
23704
24799
  const managedForwardPorts = /* @__PURE__ */ new Set();
@@ -23852,6 +24947,16 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23852
24947
  // would silently drop it (and run against the user's personal account).
23853
24948
  // Old servers ignore the extra key.
23854
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,
23855
24960
  defaultAgentProvider: "codex",
23856
24961
  startInstance: allowedCwds.value.length > 0,
23857
24962
  durableClaudeSessionStore: true,
@@ -23900,6 +25005,123 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
23900
25005
  runnerThreadSessionRegistryPath(options.runnerId),
23901
25006
  log2
23902
25007
  );
25008
+ const appliedSourceSeqFileStore = createControlCursorFileStore(
25009
+ appliedSourceSeqStorePath(options.runnerId),
25010
+ log2,
25011
+ // POWER-LOSS DURABLE (SEAM 1): this watermark is the SOLE fence that stops a
25012
+ // confirmed-dispatched turn from being re-dispatched on restart, and it is
25013
+ // paired with the fsynced durable inbound queue. fsync it on every flush so
25014
+ // a power loss cannot lose a dispatch-confirm advance while the queue ack
25015
+ // survived (which would double-execute the turn on reopen).
25016
+ { fsyncOnWrite: true }
25017
+ );
25018
+ cleanup.actions.push(() => appliedSourceSeqFileStore.flush());
25019
+ const appliedSourceSeqStore = {
25020
+ initial: (key) => appliedSourceSeqFileStore.initial(key),
25021
+ onAdvance: (key, sourceSeq) => appliedSourceSeqFileStore.onAdvance(key, sourceSeq),
25022
+ flush: () => appliedSourceSeqFileStore.flush()
25023
+ };
25024
+ const commitLiveWorkerAcceptedSourceSeq = (args) => {
25025
+ if (args.sourceSeq === void 0) {
25026
+ return;
25027
+ }
25028
+ const watermarkKey = threadSourceSeqWatermarkKey(
25029
+ inboundQueueWorkspace(),
25030
+ args.channel,
25031
+ args.threadId
25032
+ );
25033
+ const advanced = commitLiveAcceptedSourceSeq({
25034
+ store: appliedSourceSeqStore,
25035
+ watermarkKey,
25036
+ sourceSeq: args.sourceSeq
25037
+ });
25038
+ if (advanced) {
25039
+ log2("runner.live_worker_source_seq_committed", {
25040
+ kandanThreadId: args.threadId,
25041
+ channel: args.channel,
25042
+ sourceSeq: args.sourceSeq
25043
+ });
25044
+ }
25045
+ capturedLiveFollowUpEvents.delete(
25046
+ capturedFollowUpKey(args.channel, args.threadId, args.sourceSeq)
25047
+ );
25048
+ };
25049
+ const liveSourceSeqAlreadyApplied = (args) => {
25050
+ if (args.sourceSeq === void 0) {
25051
+ return false;
25052
+ }
25053
+ const watermarkKey = threadSourceSeqWatermarkKey(
25054
+ inboundQueueWorkspace(),
25055
+ args.channel,
25056
+ args.threadId
25057
+ );
25058
+ const applied = appliedSourceSeqStore.initial(watermarkKey);
25059
+ return !shouldApplySourceSeq(applied, args.sourceSeq);
25060
+ };
25061
+ const threadReconcilerLock = createThreadReconcilerLock();
25062
+ const leaseEpochRegistry = createLeaseEpochRegistry();
25063
+ const durableInboundQueues = /* @__PURE__ */ new Map();
25064
+ const latestResumeReconcileByThread = /* @__PURE__ */ new Map();
25065
+ let acceptingInbound = true;
25066
+ const inboundQueueWorkspace = () => runnerWorkspaceSlug(options) ?? "default";
25067
+ const durableInboundQueueFor = (channel, kandanThreadId) => {
25068
+ const workspace = inboundQueueWorkspace();
25069
+ const cacheKey = `${workspace}:${channel}:${kandanThreadId}`;
25070
+ const existing = durableInboundQueues.get(cacheKey);
25071
+ if (existing !== void 0) {
25072
+ return existing;
25073
+ }
25074
+ const queue = createDurableInboundThreadQueue(
25075
+ durableInboundQueuePath(workspace, channel, kandanThreadId),
25076
+ log2
25077
+ );
25078
+ durableInboundQueues.set(cacheKey, queue);
25079
+ return queue;
25080
+ };
25081
+ const capturedLiveFollowUpEvents = /* @__PURE__ */ new Map();
25082
+ const capturedFollowUpKey = (channel, threadId, seq2) => `${inboundQueueWorkspace()}:${channel}:${threadId}:${seq2}`;
25083
+ const captureLiveFollowUpEvent = (channel, threadId, event) => {
25084
+ capturedLiveFollowUpEvents.set(
25085
+ capturedFollowUpKey(channel, threadId, event.seq),
25086
+ event
25087
+ );
25088
+ };
25089
+ const reEnqueueUndeliveredFollowUps = (args) => {
25090
+ const queue = durableInboundQueueFor(args.channel, args.threadId);
25091
+ for (const seq2 of args.sourceSeqs) {
25092
+ const key = capturedFollowUpKey(args.channel, args.threadId, seq2);
25093
+ const event = capturedLiveFollowUpEvents.get(key);
25094
+ if (event === void 0) {
25095
+ log2("runner.live_follow_up_recapture_missing", {
25096
+ kandanThreadId: args.threadId,
25097
+ channel: args.channel,
25098
+ sourceSeq: seq2
25099
+ });
25100
+ continue;
25101
+ }
25102
+ const enqueued = queue.enqueue(
25103
+ seq2,
25104
+ "replay",
25105
+ serializeKandanChatEventForQueue(event),
25106
+ Date.now()
25107
+ );
25108
+ log2("runner.live_follow_up_re_enqueued_on_close", {
25109
+ kandanThreadId: args.threadId,
25110
+ channel: args.channel,
25111
+ sourceSeq: seq2,
25112
+ enqueued: enqueued.accepted
25113
+ });
25114
+ capturedLiveFollowUpEvents.delete(key);
25115
+ }
25116
+ };
25117
+ runnerDrainHooks.stopAccepting = () => {
25118
+ acceptingInbound = false;
25119
+ };
25120
+ runnerDrainHooks.flushInboundQueues = () => {
25121
+ for (const queue of durableInboundQueues.values()) {
25122
+ queue.compact();
25123
+ }
25124
+ };
23903
25125
  const controlEpochStore = createRunnerControlEpochStore(
23904
25126
  controlCursorStore,
23905
25127
  appliedStartTurnStore,
@@ -24741,6 +25963,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24741
25963
  codex,
24742
25964
  topic,
24743
25965
  instanceId,
25966
+ // SEAM 2: source the held lease epoch for the stream-write fence echo.
25967
+ leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
24744
25968
  options: {
24745
25969
  kandanUrl: options.kandanUrl,
24746
25970
  token: options.token,
@@ -24777,6 +26001,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
24777
26001
  // session is attached (the durable thread's worker fully died). Late-
24778
26002
  // bound to dodge the const's temporal dead zone (defined below).
24779
26003
  onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
26004
+ // BLOCKER B: codex live dispatch-confirm watermark commit on the main
26005
+ // channel session too, off the same persisted authority the keystone
26006
+ // gates on.
26007
+ onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
24780
26008
  channelSession: options.channelSession
24781
26009
  },
24782
26010
  log: log2
@@ -25127,6 +26355,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25127
26355
  codex: sessionCodex,
25128
26356
  topic,
25129
26357
  instanceId,
26358
+ // SEAM 2: dynamic per-thread session also echoes the held lease epoch.
26359
+ leaseEpochFor: (threadId) => leaseEpochRegistry.current(threadId),
25130
26360
  options: {
25131
26361
  kandanUrl: options.kandanUrl,
25132
26362
  token: options.token,
@@ -25141,6 +26371,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25141
26371
  // it out of the const's temporal dead zone while still resolving the
25142
26372
  // live handler when an unrouted chat event actually arrives.
25143
26373
  onUnroutedThreadMessage: (event) => handleUnroutedThreadMessage(event),
26374
+ // BLOCKER B: codex live dispatch-confirm watermark commit (the
26375
+ // production-default double-exec fix). Fires from drainKandanMessageQueue
26376
+ // when turn/start replies, off the SAME persisted authority the keystone
26377
+ // gates on, so a codex worker-death + replay is gated 'ignored'.
26378
+ onLiveSourceSeqAccepted: commitLiveWorkerAcceptedSourceSeq,
25144
26379
  pipelinePersistDir: commanderOutboxPersistDir(),
25145
26380
  enablePortForwardWatch: true,
25146
26381
  initialForwardPorts: allowedForwardPorts,
@@ -25519,7 +26754,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25519
26754
  control,
25520
26755
  log2,
25521
26756
  onStartedThread,
25522
- void 0
26757
+ void 0,
26758
+ (id) => leaseEpochRegistry.current(id),
26759
+ commitLiveWorkerAcceptedSourceSeq,
26760
+ captureLiveFollowUpEvent,
26761
+ reEnqueueUndeliveredFollowUps,
26762
+ liveSourceSeqAlreadyApplied
25523
26763
  )
25524
26764
  );
25525
26765
  }
@@ -25562,6 +26802,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25562
26802
  attachments: event.attachments,
25563
26803
  boundStartTimeout: true
25564
26804
  });
26805
+ return "dispatched";
25565
26806
  } catch (error) {
25566
26807
  log2("runner.spawn_on_miss_failed", {
25567
26808
  kandanThreadId: event.threadId ?? null,
@@ -25575,6 +26816,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25575
26816
  `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25576
26817
  record.codexThreadId
25577
26818
  );
26819
+ return "transient_failed";
25578
26820
  }
25579
26821
  };
25580
26822
  const handleUnroutedThreadMessage = async (event) => {
@@ -25605,6 +26847,246 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25605
26847
  if (record === void 0) {
25606
26848
  return "ignored";
25607
26849
  }
26850
+ const queueChannel = record.channel;
26851
+ const watermarkKey = threadSourceSeqWatermarkKey(
26852
+ inboundQueueWorkspace(),
26853
+ queueChannel,
26854
+ kandanThreadId
26855
+ );
26856
+ const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
26857
+ if (!acceptingInbound) {
26858
+ const enqueued = queue.enqueue(
26859
+ event.seq,
26860
+ event.localRunnerEventType === void 0 ? "live" : "replay",
26861
+ serializeKandanChatEventForQueue(event),
26862
+ Date.now()
26863
+ );
26864
+ log2("runner.spawn_on_miss_drain_in_progress", {
26865
+ kandanThreadId,
26866
+ seq: event.seq,
26867
+ enqueued: enqueued.accepted
26868
+ });
26869
+ return "spawned";
26870
+ }
26871
+ const outcome = await receiveResumeMessage({
26872
+ queue,
26873
+ store: appliedSourceSeqStore,
26874
+ lock: threadReconcilerLock,
26875
+ leases: leaseEpochRegistry,
26876
+ threadId: kandanThreadId,
26877
+ watermarkKey,
26878
+ sourceSeq: event.seq,
26879
+ kind: event.localRunnerEventType === void 0 ? "live" : "replay",
26880
+ payload: serializeKandanChatEventForQueue(event),
26881
+ leaseEpoch: readLeaseEpoch(event),
26882
+ // Blocker #2: the keystone drains the queue HEAD in source_seq order, so
26883
+ // the dispatch is keyed by (sourceSeq, payload) - it may run a LOWER
26884
+ // pending seq before this arrived one. Reconstruct the event from the
26885
+ // head's payload, exactly as the boot/rejoin drain does.
26886
+ dispatch: async (sourceSeq, payload) => {
26887
+ const drained = deserializeKandanChatEventFromQueue(payload);
26888
+ if (drained === void 0) {
26889
+ log2("runner.spawn_on_miss_bad_payload", {
26890
+ kandanThreadId,
26891
+ seq: sourceSeq
26892
+ });
26893
+ return "permanent_failed";
26894
+ }
26895
+ return await dispatchResumeForOwnedThread(drained, record);
26896
+ },
26897
+ // BLOCKER #4: pass the server's per-thread processed floor (the most-recent
26898
+ // resume_reconcile rows) so the live-miss path reconciles
26899
+ // highestProcessedSourceSeq into the watermark BEFORE dispatching - a
26900
+ // completed-but-not-locally-acked turn that replays is gated 'ignored', not
26901
+ // re-run. Empty until the server SEAM 1 half ships; then the floor is
26902
+ // authoritative. The live-miss path leaves onlyDispatchUnattempted at its
26903
+ // default (false): the arrived message is attempts===0 and the blind
26904
+ // post-completion fence is owned by the boot/rejoin recovery drain, not this
26905
+ // on-receipt path (changing it here would alter the existing live behavior).
26906
+ serverRows: latestResumeReconcileByThread.get(kandanThreadId) ?? [],
26907
+ // Blocker #3: surface a LOUD terminal `failed` state on any dead-letter.
26908
+ onDeadLetter: deadLetterPublisherFor(record),
26909
+ log: log2
26910
+ });
26911
+ return outcome === "ignored" ? "ignored" : "spawned";
26912
+ };
26913
+ const drainOwnedThreadQueue = async (record, serverRows = []) => {
26914
+ if (!acceptingInbound) {
26915
+ return;
26916
+ }
26917
+ const kandanThreadId = record.kandanThreadId;
26918
+ const queueChannel = record.channel;
26919
+ const watermarkKey = threadSourceSeqWatermarkKey(
26920
+ inboundQueueWorkspace(),
26921
+ queueChannel,
26922
+ kandanThreadId
26923
+ );
26924
+ const queue = durableInboundQueueFor(queueChannel, kandanThreadId);
26925
+ await drainResumeQueue({
26926
+ queue,
26927
+ store: appliedSourceSeqStore,
26928
+ lock: threadReconcilerLock,
26929
+ threadId: kandanThreadId,
26930
+ watermarkKey,
26931
+ serverRows,
26932
+ log: log2,
26933
+ // Until the SERVER message_state reconcile half (SEAM 1 server) ships, the
26934
+ // drain runs with serverRows=[] in production. In that mode a post-
26935
+ // completion-kill (turn confirmed-dispatched, then SIGKILL before the
26936
+ // watermark flush + ack) leaves an attempted-but-unacked entry. Blindly
26937
+ // re-dispatching it would double-execute the turn (real edits/commits), so
26938
+ // gate the drain to UNATTEMPTED entries only: an attempted entry is dead-
26939
+ // lettered with a loud `failed` state rather than re-run. Once serverRows
26940
+ // is populated the reconcile fences already-run seqs and this gate is moot.
26941
+ onlyDispatchUnattempted: serverRows.length === 0,
26942
+ // Blocker #3: surface a LOUD terminal `failed` state on any dead-letter
26943
+ // (exhausted retries, a withheld post-completion-kill entry, or a permanent
26944
+ // failure) so a persistently-unbindable thread is never silently dropped.
26945
+ onDeadLetter: deadLetterPublisherFor(record),
26946
+ // A torn tail with no server reconcile cannot be re-derived on this branch
26947
+ // (no server outbox/replay). Publish a loud "please resend" terminal state
26948
+ // for the thread instead of silently dropping the lost tail.
26949
+ onTornTailUnrecoverable: async (threadId) => {
26950
+ const control = rehydrationReconnectControl(record);
26951
+ await publishSpawnOnMissMessageState(
26952
+ control,
26953
+ "failed",
26954
+ "a message may have been lost during an unexpected shutdown (power loss). Please resend your last message to continue this job.",
26955
+ record.codexThreadId
26956
+ );
26957
+ log2("runner.inbound_torn_tail_resend_published", {
26958
+ kandanThreadId: threadId
26959
+ });
26960
+ },
26961
+ dispatch: async (sourceSeq, payload) => {
26962
+ const event = deserializeKandanChatEventFromQueue(payload);
26963
+ if (event === void 0) {
26964
+ log2("runner.inbound_drain_bad_payload", {
26965
+ kandanThreadId,
26966
+ seq: sourceSeq
26967
+ });
26968
+ return "permanent_failed";
26969
+ }
26970
+ return await dispatchResumeForOwnedThread(event, record);
26971
+ }
26972
+ });
26973
+ };
26974
+ const consumeResumeReconcile = (reconcileValue) => {
26975
+ const byThread = /* @__PURE__ */ new Map();
26976
+ const entries = parseResumeReconcileEntries(reconcileValue);
26977
+ for (const entry of entries) {
26978
+ if (entry.leaseEpoch !== void 0) {
26979
+ const adopted = leaseEpochRegistry.adopt(entry.threadId, entry.leaseEpoch);
26980
+ if (adopted) {
26981
+ log2("runner.resume_reconcile_lease_adopted", {
26982
+ kandanThreadId: entry.threadId,
26983
+ leaseEpoch: entry.leaseEpoch
26984
+ });
26985
+ }
26986
+ }
26987
+ const rows = resumeReconcileServerRows(entry);
26988
+ byThread.set(entry.threadId, rows);
26989
+ latestResumeReconcileByThread.set(entry.threadId, rows);
26990
+ }
26991
+ if (entries.length > 0) {
26992
+ log2("runner.resume_reconcile_consumed", { threadCount: entries.length });
26993
+ }
26994
+ return byThread;
26995
+ };
26996
+ const drainOrphanedThreadQueues = async () => {
26997
+ const dir = durableInboundQueueDir();
26998
+ let files;
26999
+ try {
27000
+ files = readdirSync4(dir).filter((name) => name.endsWith(".log"));
27001
+ } catch (error) {
27002
+ if (error?.code === "ENOENT") {
27003
+ return;
27004
+ }
27005
+ log2("runner.inbound_orphan_scan_failed", {
27006
+ message: error instanceof Error ? error.message : String(error)
27007
+ });
27008
+ return;
27009
+ }
27010
+ const workspace = inboundQueueWorkspace();
27011
+ const owned = new Set(
27012
+ threadSessionRegistry.list().map(
27013
+ (record) => durableInboundQueuePath(
27014
+ workspace,
27015
+ record.channel,
27016
+ record.kandanThreadId
27017
+ )
27018
+ )
27019
+ );
27020
+ for (const name of files) {
27021
+ const path2 = join22(dir, name);
27022
+ if (owned.has(path2)) {
27023
+ continue;
27024
+ }
27025
+ try {
27026
+ const queue = createDurableInboundThreadQueue(path2, log2);
27027
+ const stranded = queue.entries();
27028
+ if (stranded.length === 0) {
27029
+ continue;
27030
+ }
27031
+ for (const entry of stranded) {
27032
+ queue.deadLetter(entry.sourceSeq);
27033
+ }
27034
+ queue.compact();
27035
+ log2("runner.inbound_orphan_dead_lettered", {
27036
+ path: path2,
27037
+ strandedCount: stranded.length,
27038
+ strandedSeqs: stranded.map((e) => e.sourceSeq)
27039
+ });
27040
+ } catch (error) {
27041
+ log2("runner.inbound_orphan_drain_failed", {
27042
+ path: path2,
27043
+ message: error instanceof Error ? error.message : String(error)
27044
+ });
27045
+ }
27046
+ }
27047
+ };
27048
+ const drainAllOwnedThreadQueues = async (reconcileByThread) => {
27049
+ if (!acceptingInbound) {
27050
+ return;
27051
+ }
27052
+ const records = threadSessionRegistry.list();
27053
+ await Promise.all(
27054
+ records.map(
27055
+ (record) => drainOwnedThreadQueue(
27056
+ record,
27057
+ reconcileByThread?.get(record.kandanThreadId) ?? []
27058
+ ).catch((error) => {
27059
+ log2("runner.inbound_drain_failed", {
27060
+ kandanThreadId: record.kandanThreadId,
27061
+ message: error instanceof Error ? error.message : String(error)
27062
+ });
27063
+ })
27064
+ )
27065
+ );
27066
+ await drainOrphanedThreadQueues();
27067
+ };
27068
+ const bootReconcile = consumeResumeReconcile(
27069
+ objectValue(joinResponse)?.resume_reconcile
27070
+ );
27071
+ const bootDrain = threadSessionRehydration.then(() => drainAllOwnedThreadQueues(bootReconcile)).catch((error) => {
27072
+ log2("runner.inbound_boot_drain_failed", {
27073
+ message: error instanceof Error ? error.message : String(error)
27074
+ });
27075
+ });
27076
+ cleanup.actions.push(() => bootDrain);
27077
+ kandan.onReconnect((rejoinReplies) => {
27078
+ const rejoinReply = rejoinReplies.get(topic);
27079
+ const reconcile = consumeResumeReconcile(
27080
+ rejoinReply === void 0 ? void 0 : rejoinReply.resume_reconcile
27081
+ );
27082
+ void drainAllOwnedThreadQueues(reconcile).catch((error) => {
27083
+ log2("runner.inbound_rejoin_drain_failed", {
27084
+ message: error instanceof Error ? error.message : String(error)
27085
+ });
27086
+ });
27087
+ });
27088
+ const dispatchResumeForOwnedThread = async (event, record) => {
27089
+ const kandanThreadId = record.kandanThreadId;
25608
27090
  const baseControl = rehydrationReconnectControl(record);
25609
27091
  const providerBinding = threadModelProviderBindings.get(kandanThreadId);
25610
27092
  const reconnectControl = {
@@ -25634,7 +27116,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25634
27116
  `could not resume the thread's coding session: working directory ${record.cwd} is not an allowed root (${cwd.reason})`,
25635
27117
  record.codexThreadId
25636
27118
  );
25637
- return "spawned";
27119
+ return "permanent_failed";
25638
27120
  }
25639
27121
  const resolvedCwd = cwd.cwd;
25640
27122
  const inFlightSpawn = spawnOnMissInFlight.get(kandanThreadId);
@@ -25654,14 +27136,13 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25654
27136
  `could not resume the thread's coding session: no live session after spawn`,
25655
27137
  record.codexThreadId
25656
27138
  );
25657
- return "spawned";
27139
+ return "transient_failed";
25658
27140
  }
25659
- await replaySpawnOnMissMessageOnLiveSession(
27141
+ return await replaySpawnOnMissMessageOnLiveSession(
25660
27142
  event,
25661
27143
  record,
25662
27144
  reconnectControl
25663
27145
  );
25664
- return "spawned";
25665
27146
  }
25666
27147
  log2("runner.spawn_on_miss_resuming", {
25667
27148
  kandanThreadId,
@@ -25682,12 +27163,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25682
27163
  );
25683
27164
  if (threadStartDelegatedToSibling(startResult)) {
25684
27165
  spawnSettle("spawned");
25685
- await replaySpawnOnMissMessageOnLiveSession(
27166
+ return await replaySpawnOnMissMessageOnLiveSession(
25686
27167
  event,
25687
27168
  record,
25688
27169
  reconnectControl
25689
27170
  );
25690
- return "spawned";
25691
27171
  }
25692
27172
  if (startResult === void 0 || startResult.ok === false) {
25693
27173
  spawnSettle("no_session");
@@ -25704,10 +27184,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25704
27184
  `could not resume the thread's coding session: ${startError}`,
25705
27185
  record.codexThreadId
25706
27186
  );
25707
- return "spawned";
27187
+ return "transient_failed";
25708
27188
  }
25709
27189
  spawnSettle("spawned");
25710
- return "spawned";
27190
+ return "dispatched";
25711
27191
  } catch (error) {
25712
27192
  spawnSettle("no_session");
25713
27193
  log2("runner.spawn_on_miss_failed", {
@@ -25722,7 +27202,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25722
27202
  `could not resume the thread's coding session: ${error instanceof Error ? error.message : String(error)}`,
25723
27203
  record.codexThreadId
25724
27204
  );
25725
- return "spawned";
27205
+ return "transient_failed";
25726
27206
  } finally {
25727
27207
  spawnSettle("spawned");
25728
27208
  if (spawnOnMissInFlight.get(kandanThreadId) === spawnPromise) {
@@ -25748,6 +27228,26 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
25748
27228
  });
25749
27229
  }
25750
27230
  };
27231
+ const deadLetterPublisherFor = (record) => {
27232
+ return async ({ sourceSeq, reason, attempts, alreadyPublished }) => {
27233
+ log2("runner.inbound_dead_lettered", {
27234
+ kandanThreadId: record.kandanThreadId,
27235
+ seq: sourceSeq,
27236
+ reason,
27237
+ attempts,
27238
+ alreadyPublished
27239
+ });
27240
+ if (alreadyPublished) {
27241
+ return;
27242
+ }
27243
+ await publishSpawnOnMissMessageState(
27244
+ rehydrationReconnectControl(record),
27245
+ "failed",
27246
+ deadLetterFailedReason(reason),
27247
+ record.codexThreadId
27248
+ );
27249
+ };
27250
+ };
25751
27251
  const heartbeatPayload = () => ({
25752
27252
  instanceId,
25753
27253
  clientId,
@@ -26238,7 +27738,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
26238
27738
  control,
26239
27739
  log2,
26240
27740
  attachThreadSession,
26241
- shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0
27741
+ shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0,
27742
+ (id) => leaseEpochRegistry.current(id),
27743
+ commitLiveWorkerAcceptedSourceSeq,
27744
+ captureLiveFollowUpEvent,
27745
+ reEnqueueUndeliveredFollowUps,
27746
+ liveSourceSeqAlreadyApplied
26242
27747
  );
26243
27748
  }),
26244
27749
  commitStartTurnWatermark
@@ -26269,7 +27774,10 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
26269
27774
  pendingControls,
26270
27775
  controlDispatcher,
26271
27776
  dispatchControl,
26272
- refreshDependencyStatus
27777
+ async () => {
27778
+ await refreshDependencyStatus();
27779
+ await refreshCodexAuthStatus();
27780
+ }
26273
27781
  );
26274
27782
  return { instanceId, codexUrl, close };
26275
27783
  }
@@ -26283,28 +27791,34 @@ function commanderOutboxPersistDir() {
26283
27791
  if (override !== void 0 && override !== "") {
26284
27792
  return override;
26285
27793
  }
26286
- return join21(homedir14(), ".linzumi", "commander-outbox");
27794
+ return join22(homedir15(), ".linzumi", "commander-outbox");
27795
+ }
27796
+ function controlCursorsDir() {
27797
+ const override = process.env.LINZUMI_CONTROL_CURSOR_DIR?.trim();
27798
+ if (override !== void 0 && override !== "") {
27799
+ return override;
27800
+ }
27801
+ return join22(homedir15(), ".linzumi", "control-cursors");
26287
27802
  }
26288
27803
  function controlCursorStorePath(runnerId) {
26289
27804
  const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
26290
- const digest = createHash6("sha256").update(runnerId).digest("hex").slice(0, 8);
27805
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
26291
27806
  const stem = sanitized === "" ? "runner" : sanitized;
26292
- return join21(
26293
- homedir14(),
26294
- ".linzumi",
26295
- "control-cursors",
26296
- `${stem}.${digest}.json`
26297
- );
27807
+ return join22(controlCursorsDir(), `${stem}.${digest}.json`);
26298
27808
  }
26299
27809
  function appliedStartTurnStorePath(runnerId) {
26300
27810
  const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
26301
- const digest = createHash6("sha256").update(runnerId).digest("hex").slice(0, 8);
27811
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
26302
27812
  const stem = sanitized === "" ? "runner" : sanitized;
26303
- return join21(
26304
- homedir14(),
26305
- ".linzumi",
26306
- "control-cursors",
26307
- `${stem}.${digest}.start-turn.json`
27813
+ return join22(controlCursorsDir(), `${stem}.${digest}.start-turn.json`);
27814
+ }
27815
+ function appliedSourceSeqStorePath(runnerId) {
27816
+ const sanitized = runnerId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
27817
+ const digest = createHash7("sha256").update(runnerId).digest("hex").slice(0, 8);
27818
+ const stem = sanitized === "" ? "runner" : sanitized;
27819
+ return join22(
27820
+ controlCursorsDir(),
27821
+ `${stem}.${digest}.source-seq.json`
26308
27822
  );
26309
27823
  }
26310
27824
  function startTurnWatermarkKey(topic, threadId) {
@@ -26426,6 +27940,7 @@ function createConnectionStalenessTracker(log2, now = Date.now, thresholdMs = co
26426
27940
  }
26427
27941
  function createControlCursorFileStore(path2, log2, options) {
26428
27942
  const discardSeqsOnFirstEpochAdoption = options?.discardSeqsOnFirstEpochAdoption === true;
27943
+ const fsyncOnWrite = options?.fsyncOnWrite === true;
26429
27944
  const { cursors, epoch: persistedEpoch } = readControlCursorFile(path2, log2);
26430
27945
  let epoch = persistedEpoch;
26431
27946
  let dirty = false;
@@ -26436,18 +27951,32 @@ function createControlCursorFileStore(path2, log2, options) {
26436
27951
  }
26437
27952
  dirty = false;
26438
27953
  try {
26439
- mkdirSync13(dirname14(path2), { recursive: true });
27954
+ mkdirSync14(dirname15(path2), { recursive: true });
26440
27955
  const tmpPath = `${path2}.tmp`;
26441
- writeFileSync12(
26442
- tmpPath,
26443
- `${JSON.stringify({
26444
- ...epoch === void 0 ? {} : { [controlCursorEpochKey]: epoch },
26445
- ...Object.fromEntries(cursors)
26446
- })}
26447
- `,
26448
- "utf8"
26449
- );
26450
- renameSync4(tmpPath, path2);
27956
+ const contents = `${JSON.stringify({
27957
+ ...epoch === void 0 ? {} : { [controlCursorEpochKey]: epoch },
27958
+ ...Object.fromEntries(cursors)
27959
+ })}
27960
+ `;
27961
+ if (fsyncOnWrite) {
27962
+ const fileFd = openSync5(tmpPath, "w");
27963
+ try {
27964
+ writeFileSync12(fileFd, contents, "utf8");
27965
+ fsyncSync2(fileFd);
27966
+ } finally {
27967
+ closeSync4(fileFd);
27968
+ }
27969
+ renameSync5(tmpPath, path2);
27970
+ const dirFd = openSync5(dirname15(path2), "r");
27971
+ try {
27972
+ fsyncSync2(dirFd);
27973
+ } finally {
27974
+ closeSync4(dirFd);
27975
+ }
27976
+ } else {
27977
+ writeFileSync12(tmpPath, contents, "utf8");
27978
+ renameSync5(tmpPath, path2);
27979
+ }
26451
27980
  } catch (error) {
26452
27981
  log2("control_cursor_store.write_failed", {
26453
27982
  path: path2,
@@ -26546,7 +28075,7 @@ function readControlCursorFile(path2, log2) {
26546
28075
  const cursors = /* @__PURE__ */ new Map();
26547
28076
  let raw;
26548
28077
  try {
26549
- raw = readFileSync17(path2, "utf8");
28078
+ raw = readFileSync18(path2, "utf8");
26550
28079
  } catch (error) {
26551
28080
  if (!isErrnoCode2(error, "ENOENT")) {
26552
28081
  log2("control_cursor_store.read_failed", {
@@ -26891,21 +28420,47 @@ function makeRunnerLogger(options) {
26891
28420
  consoleReporter
26892
28421
  );
26893
28422
  }
26894
- function installCleanupHandlers(close) {
26895
- const closeAndExit = () => {
26896
- void close().catch(() => void 0).finally(() => process.exit(0));
26897
- };
28423
+ function installCleanupHandlers(close, drainHooks, log2) {
28424
+ const uninstallDrain = installDrainOnSigterm({
28425
+ steps: {
28426
+ stopAccepting: () => {
28427
+ drainHooks.stopAccepting();
28428
+ },
28429
+ // SCOPE NOTE (BET1, partial): checkpointing an in-flight turn on a LIVE
28430
+ // worker (persisting enough codex turn state to resume it) is NOT
28431
+ // implemented in this branch - that turn still relies on the server's
28432
+ // rejoin replay. What IS durable is every durably-owned message that was
28433
+ // ENQUEUED into the per-thread inbound queue (the respawn-window / spawn-on-
28434
+ // miss path): flushInboundQueues fsyncs those below, and the boot/rejoin
28435
+ // drain replays them. Left as an explicit no-op (not silently absent) so the
28436
+ // gap is visible rather than implied-handled.
28437
+ checkpointCurrentTurn: () => void 0,
28438
+ flushInboundQueues: () => {
28439
+ drainHooks.flushInboundQueues();
28440
+ },
28441
+ // The final flush also performs the connection teardown via close(): the
28442
+ // cleanup stack runs after kandan.close() so no advance races the final
28443
+ // cursor/watermark write.
28444
+ flushOutbox: () => close().catch(() => void 0)
28445
+ },
28446
+ deadlineMs: runnerDrainExitDeadlineMs,
28447
+ log: (event, fields) => log2(event, fields),
28448
+ // SIGHUP is a drain-AND-EXIT signal too: a detached child of the Electron
28449
+ // shell that receives a parent hangup must drain its inbound queue + cursor
28450
+ // stores and then EXIT, never linger as a defunct lock-holder that does no
28451
+ // work (the prior close()-without-exit handler left a zombie that stopped
28452
+ // accepting, tore down its connection, yet never exited - stranding prior
28453
+ // jobs and holding the runner lock). installDrainOnSigterm's handler exits
28454
+ // after the drain (deadline-bounded), so routing SIGHUP through it fixes the
28455
+ // orphan-zombie regression.
28456
+ signals: ["SIGTERM", "SIGINT", "SIGHUP"]
28457
+ });
26898
28458
  const closeOnExit = () => {
26899
28459
  void close().catch(() => void 0);
26900
28460
  };
26901
- process.once("SIGINT", closeAndExit);
26902
- process.once("SIGTERM", closeAndExit);
26903
- process.once("SIGHUP", closeAndExit);
26904
28461
  process.once("exit", closeOnExit);
26905
28462
  return () => {
26906
- process.off("SIGINT", closeAndExit);
26907
- process.off("SIGTERM", closeAndExit);
26908
- process.off("SIGHUP", closeAndExit);
28463
+ uninstallDrain();
26909
28464
  process.off("exit", closeOnExit);
26910
28465
  };
26911
28466
  }
@@ -27208,7 +28763,7 @@ async function applyCodexUnavailableControl(kandan, topic, instanceId, control,
27208
28763
  }
27209
28764
  throw new Error(error);
27210
28765
  }
27211
- async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart) {
28766
+ async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log2, onStartedThread, onThreadProcessStart, leaseEpochFor, onLiveSourceSeqAccepted, captureLiveFollowUpEvent, onUndeliveredFollowUpsAtClose, liveSourceSeqAlreadyApplied) {
27212
28767
  if (codex === void 0) {
27213
28768
  switch (control.type) {
27214
28769
  case "remote_codex_exec":
@@ -27401,6 +28956,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27401
28956
  ensureClaudeCodeForwardPortSession,
27402
28957
  disposeClaudeCodeForwardPortSession,
27403
28958
  resumeSessionId: normalizedWorkDescription(control.resumeSessionId),
28959
+ leaseEpochFor,
28960
+ onLiveSourceSeqAccepted,
28961
+ onUndeliveredFollowUpsAtClose,
27404
28962
  setStartupStage: (stage) => {
27405
28963
  startupStage = stage;
27406
28964
  }
@@ -27603,6 +29161,51 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27603
29161
  message
27604
29162
  };
27605
29163
  }
29164
+ const followUpThreadId = optionalThreadControlField(
29165
+ control,
29166
+ "threadId"
29167
+ );
29168
+ const followUpChannel = optionalThreadControlField(
29169
+ control,
29170
+ "channel"
29171
+ );
29172
+ if (sourceSeq !== void 0 && followUpThreadId !== void 0 && followUpChannel !== void 0) {
29173
+ captureLiveFollowUpEvent?.(followUpChannel, followUpThreadId, {
29174
+ seq: sourceSeq,
29175
+ type: "thread.message",
29176
+ actorKind: void 0,
29177
+ actorSlug: void 0,
29178
+ actorUserId: void 0,
29179
+ threadId: followUpThreadId,
29180
+ threadTitle: void 0,
29181
+ replyToSeq: integerValue(control.rootSeq),
29182
+ localRunnerEventType: void 0,
29183
+ body: workDescription,
29184
+ attachments
29185
+ });
29186
+ }
29187
+ if (followUpChannel !== void 0 && followUpThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
29188
+ channel: followUpChannel,
29189
+ threadId: followUpThreadId,
29190
+ sourceSeq
29191
+ }) === true) {
29192
+ log2("claude_code.message_duplicate_ignored", {
29193
+ linzumi_thread_id: followUpThreadId,
29194
+ claude_session_id: codexThreadId,
29195
+ body_preview: claudeConsoleBodyPreview(workDescription),
29196
+ source_seq: sourceSeq ?? null
29197
+ });
29198
+ return {
29199
+ instanceId,
29200
+ controlType: control.type,
29201
+ agentProvider: "claude-code",
29202
+ cwd: cwd.cwd,
29203
+ matchedRoot: cwd.matchedRoot,
29204
+ codexThreadId,
29205
+ queuedInput: true,
29206
+ duplicate: true
29207
+ };
29208
+ }
27606
29209
  try {
27607
29210
  const enqueueResult = activeSession.enqueueInput({
27608
29211
  content: contentResult.content,
@@ -27707,6 +29310,9 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27707
29310
  pendingClaudeCodeApprovals,
27708
29311
  ensureClaudeCodeForwardPortSession,
27709
29312
  disposeClaudeCodeForwardPortSession,
29313
+ leaseEpochFor,
29314
+ onLiveSourceSeqAccepted,
29315
+ onUndeliveredFollowUpsAtClose,
27710
29316
  setStartupStage: (stage) => {
27711
29317
  startupStage = stage;
27712
29318
  }
@@ -27750,6 +29356,30 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
27750
29356
  if (startedThreadSession === void 0 || sourceSeq === void 0) {
27751
29357
  throw new Error("cannot reconnect a Kandan thread without a session");
27752
29358
  }
29359
+ const reconnectChannel = optionalThreadControlField(control, "channel");
29360
+ const reconnectThreadId = optionalThreadControlField(
29361
+ control,
29362
+ "threadId"
29363
+ );
29364
+ if (reconnectChannel !== void 0 && reconnectThreadId !== void 0 && liveSourceSeqAlreadyApplied?.({
29365
+ channel: reconnectChannel,
29366
+ threadId: reconnectThreadId,
29367
+ sourceSeq
29368
+ }) === true) {
29369
+ log2("kandan.reconnect_thread_seq_already_applied", {
29370
+ linzumi_thread_id: reconnectThreadId,
29371
+ codex_thread_id: codexThreadId,
29372
+ source_seq: sourceSeq
29373
+ });
29374
+ return {
29375
+ instanceId,
29376
+ controlType: control.type,
29377
+ cwd: cwd.cwd,
29378
+ matchedRoot: cwd.matchedRoot,
29379
+ codexThreadId,
29380
+ duplicate: true
29381
+ };
29382
+ }
27753
29383
  await resumeCodexThreadForReconnect(
27754
29384
  codex,
27755
29385
  codexThreadId,
@@ -28496,11 +30126,13 @@ function claudeCodeTurnStallThresholdMs(env) {
28496
30126
  return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultClaudeCodeTurnStallThresholdMs;
28497
30127
  }
28498
30128
  function createClaudeCodeInputQueue(input) {
30129
+ const onSourceSeqDelivered = input.onSourceSeqDelivered;
28499
30130
  const pendingMessages = [
28500
30131
  claudeCodeUserInputMessage(input.content)
28501
30132
  ];
28502
30133
  const pendingSourceSeqs = [input.sourceSeq];
28503
30134
  const deferredFollowUps = [];
30135
+ const deliveredSourceSeqs = /* @__PURE__ */ new Set();
28504
30136
  const waiters = [];
28505
30137
  const state = {
28506
30138
  closed: false
@@ -28516,6 +30148,10 @@ function createClaudeCodeInputQueue(input) {
28516
30148
  };
28517
30149
  const deliverNow = (message, sourceSeq) => {
28518
30150
  pendingSourceSeqs.push(sourceSeq);
30151
+ if (sourceSeq !== void 0 && onSourceSeqDelivered !== void 0 && !deliveredSourceSeqs.has(sourceSeq)) {
30152
+ deliveredSourceSeqs.add(sourceSeq);
30153
+ onSourceSeqDelivered(sourceSeq);
30154
+ }
28519
30155
  const waiter = waiters.shift();
28520
30156
  if (waiter === void 0) {
28521
30157
  pendingMessages.push(message);
@@ -28584,6 +30220,17 @@ function createClaudeCodeInputQueue(input) {
28584
30220
  },
28585
30221
  enqueue,
28586
30222
  enqueueSteer,
30223
+ // BLOCKER A (don't drop deferred follow-ups on a kill): the parked,
30224
+ // never-delivered follow-ups (a turn was in flight at close, so close()'s
30225
+ // drain is intentionally skipped to avoid folding them into the active turn).
30226
+ // Their watermark was NOT committed (commit fires only at deliverNow), so the
30227
+ // runner must re-enqueue them into the DURABLE inbound queue; a kill-before-
30228
+ // run then replays + runs them exactly once. Drains the parked list so a
30229
+ // double-call cannot double-enqueue. Returns the un-run seqs+messages.
30230
+ drainUndeliveredFollowUps: () => {
30231
+ const drained = deferredFollowUps.splice(0);
30232
+ return drained;
30233
+ },
28587
30234
  completeTurn: () => {
28588
30235
  const completed = pendingSourceSeqs.shift();
28589
30236
  if (!state.closed && pendingSourceSeqs.length === 0) {
@@ -28810,7 +30457,21 @@ async function startClaudeCodeProviderInstance(args) {
28810
30457
  }
28811
30458
  const inputQueue = createClaudeCodeInputQueue({
28812
30459
  content: initialContentResult.content,
28813
- sourceSeq
30460
+ sourceSeq,
30461
+ // BLOCKER A: commit the persisted dedup watermark at DISPATCH (deliverNow),
30462
+ // not at enqueue-accept. enqueueInput used to commit unconditionally right
30463
+ // after inputQueue.enqueue, but enqueue PARKS a mid-turn follow-up in
30464
+ // deferredFollowUps (a bare return) and a close-in-flight drops it - so a
30465
+ // never-run seq was marked applied and its replay was gated 'ignored' (the
30466
+ // drop). Committing only when the seq is actually delivered onto the live
30467
+ // stream means a parked-but-unrun seq is never marked applied.
30468
+ onSourceSeqDelivered: (deliveredSeq) => {
30469
+ args.onLiveSourceSeqAccepted?.({
30470
+ channel,
30471
+ threadId,
30472
+ sourceSeq: deliveredSeq
30473
+ });
30474
+ }
28814
30475
  });
28815
30476
  const developerPrompt = normalizedWorkDescription(
28816
30477
  args.control.developerPrompt
@@ -28919,7 +30580,8 @@ async function startClaudeCodeProviderInstance(args) {
28919
30580
  channelSlug: channel,
28920
30581
  kandanThreadId: threadId,
28921
30582
  codexThreadId: activeSessionId ?? args.resumeSessionId,
28922
- rootSeq
30583
+ rootSeq,
30584
+ leaseEpoch: args.leaseEpochFor?.(threadId)
28923
30585
  }),
28924
30586
  push: async (event, payload, _timeoutMs, onSent) => {
28925
30587
  const reply = await args.kandan.push(args.topic, event, payload, onSent);
@@ -28967,7 +30629,7 @@ async function startClaudeCodeProviderInstance(args) {
28967
30629
  log: args.log
28968
30630
  });
28969
30631
  const liveBashCapture = args.options.claudeCodeRunner === void 0 && claudeLiveBashCaptureEnabled(process.env) ? createClaudeCodeLiveBashCapture({
28970
- captureDir: join21(
30632
+ captureDir: join22(
28971
30633
  tmpdir3(),
28972
30634
  `linzumi-claude-live-bash-${args.instanceId}`
28973
30635
  ),
@@ -29086,6 +30748,7 @@ async function startClaudeCodeProviderInstance(args) {
29086
30748
  linzumi_thread_id: threadId,
29087
30749
  claude_session_id: event.sessionId
29088
30750
  });
30751
+ args.onLiveSourceSeqAccepted?.({ channel, threadId, sourceSeq });
29089
30752
  args.activeClaudeCodeSessions.set(event.sessionId, {
29090
30753
  abortController,
29091
30754
  workspace,
@@ -29294,6 +30957,15 @@ async function startClaudeCodeProviderInstance(args) {
29294
30957
  onTranscriptEvent
29295
30958
  });
29296
30959
  } finally {
30960
+ const undeliveredFollowUps = inputQueue.drainUndeliveredFollowUps();
30961
+ const undeliveredSeqs = undeliveredFollowUps.map((followUp) => followUp.sourceSeq).filter((seq) => seq !== void 0);
30962
+ if (undeliveredSeqs.length > 0 && args.onUndeliveredFollowUpsAtClose !== void 0) {
30963
+ args.onUndeliveredFollowUpsAtClose({
30964
+ channel,
30965
+ threadId,
30966
+ sourceSeqs: undeliveredSeqs
30967
+ });
30968
+ }
29297
30969
  inputQueue.close();
29298
30970
  mcpAuthCleanup?.();
29299
30971
  liveBashCapture?.close();
@@ -29817,15 +31489,15 @@ function rehydrationControlSnapshot(args) {
29817
31489
  ...runtimeSettings.fast === void 0 ? {} : { fast: runtimeSettings.fast }
29818
31490
  };
29819
31491
  }
29820
- function kandanChatAttachmentsToWire(attachments) {
29821
- return attachments.map((attachment) => ({
29822
- ...attachment.id === void 0 ? {} : { id: attachment.id },
29823
- ...attachment.kind === void 0 ? {} : { kind: attachment.kind },
29824
- ...attachment.fileName === void 0 ? {} : { file_name: attachment.fileName },
29825
- ...attachment.contentType === void 0 ? {} : { content_type: attachment.contentType },
29826
- ...attachment.sizeBytes === void 0 ? {} : { size_bytes: attachment.sizeBytes },
29827
- ...attachment.url === void 0 ? {} : { url: attachment.url }
29828
- }));
31492
+ function deadLetterFailedReason(reason) {
31493
+ switch (reason) {
31494
+ case "exhausted":
31495
+ return "couldn't resume this job after several attempts; please resend your last message to continue.";
31496
+ case "withheld":
31497
+ return "this message may have already run before an unexpected shutdown; it was not re-run to avoid duplicating work. Please resend if it did not complete.";
31498
+ case "permanent":
31499
+ return "couldn't resume this job's coding session.";
31500
+ }
29829
31501
  }
29830
31502
  function rehydrationReconnectControl(record) {
29831
31503
  const snapshot = record.control;
@@ -30038,6 +31710,9 @@ function threadRunnerEntryLiveness(entry) {
30038
31710
  if (ipcClosed) {
30039
31711
  return "dead";
30040
31712
  }
31713
+ if (handle.sessionClosed?.() === true) {
31714
+ return "dead";
31715
+ }
30041
31716
  return processIsAlive2(handle.processPid) ? "live" : "dead";
30042
31717
  }
30043
31718
  async function closeThreadRunnerEntry(entry) {
@@ -30095,6 +31770,18 @@ async function spawnLocalThreadRunnerProcess(options) {
30095
31770
  });
30096
31771
  const exitListeners = /* @__PURE__ */ new Set();
30097
31772
  const exitState = { exited: false };
31773
+ const sessionState = { closed: false };
31774
+ child.on("message", (message) => {
31775
+ if (isThreadCodexWorkerSessionClosedMessage(message)) {
31776
+ sessionState.closed = true;
31777
+ writeCliAuditEvent("runner.thread_runner_session_detached", {
31778
+ purpose: "linzumi.thread_runner",
31779
+ kandanThreadId: message.kandanThreadId,
31780
+ reason: message.reason,
31781
+ pid: child.pid
31782
+ });
31783
+ }
31784
+ });
30098
31785
  child.once("exit", (code, signal) => {
30099
31786
  writeCliAuditEvent("process.exit", {
30100
31787
  command: process.execPath,
@@ -30170,6 +31857,7 @@ async function spawnLocalThreadRunnerProcess(options) {
30170
31857
  codex: connectThreadCodexWorkerIpc(child),
30171
31858
  processPid: child.pid,
30172
31859
  commanderManagedPorts: ready2.commanderManagedPorts,
31860
+ sessionClosed: () => sessionState.closed,
30173
31861
  onExit: (listener) => {
30174
31862
  if (exitState.exited) {
30175
31863
  queueMicrotask(listener);
@@ -30857,8 +32545,8 @@ function onboardingConversationOfficeImport(response) {
30857
32545
  return objectValue(metadata?.office_channel_import);
30858
32546
  }
30859
32547
  function onboardingConversationImportClientMessageId(importKey, itemKey) {
30860
- const importHash = createHash6("sha256").update(importKey).digest("hex");
30861
- const itemHash = createHash6("sha256").update(itemKey).digest("hex");
32548
+ const importHash = createHash7("sha256").update(importKey).digest("hex");
32549
+ const itemHash = createHash7("sha256").update(itemKey).digest("hex");
30862
32550
  return `onboarding-import:${importHash.slice(0, 32)}:${itemHash.slice(0, 24)}`;
30863
32551
  }
30864
32552
  async function uploadLocalConversationAttachments(args) {
@@ -30956,7 +32644,7 @@ async function localConversationAttachmentFile(candidatePath, attachment) {
30956
32644
  };
30957
32645
  }
30958
32646
  case "path": {
30959
- const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(dirname14(candidatePath), attachment.path);
32647
+ const path2 = isAbsolute5(attachment.path) ? attachment.path : resolve8(dirname15(candidatePath), attachment.path);
30960
32648
  try {
30961
32649
  const bytes = await readFile2(path2);
30962
32650
  return {
@@ -31411,9 +33099,9 @@ function mcpOwnerUsername(options) {
31411
33099
  return options.channelSession?.listenUser ?? identityFromAccessToken(options.token).actorUsername;
31412
33100
  }
31413
33101
  function writeEphemeralMcpAuthFile(options) {
31414
- const directory = mkdtempSync4(join21(tmpdir3(), "linzumi-mcp-auth-"));
33102
+ const directory = mkdtempSync4(join22(tmpdir3(), "linzumi-mcp-auth-"));
31415
33103
  chmodSync2(directory, 448);
31416
- const path2 = join21(directory, "auth.json");
33104
+ const path2 = join22(directory, "auth.json");
31417
33105
  writeCachedLocalRunnerToken({
31418
33106
  kandanUrl: options.kandanUrl,
31419
33107
  accessToken: options.token,
@@ -31489,7 +33177,7 @@ function configuredAllowedCwds(values, options = {}) {
31489
33177
  const absolutePath = resolve8(expandUserPath(value));
31490
33178
  try {
31491
33179
  if (options.createMissing === true) {
31492
- mkdirSync13(absolutePath, { recursive: true });
33180
+ mkdirSync14(absolutePath, { recursive: true });
31493
33181
  }
31494
33182
  const realPath = realpathSync6(absolutePath);
31495
33183
  allowedCwds.push(
@@ -31526,7 +33214,7 @@ function allowedCwdProjects(allowedCwds) {
31526
33214
  });
31527
33215
  }
31528
33216
  function isGitProjectDirectory(cwd) {
31529
- const gitPath = join21(cwd, ".git");
33217
+ const gitPath = join22(cwd, ".git");
31530
33218
  try {
31531
33219
  const gitPathStats = statSync3(gitPath);
31532
33220
  return gitPathStats.isDirectory() || gitPathStats.isFile();
@@ -31549,9 +33237,9 @@ function browseRunnerDirectory(control, options) {
31549
33237
  error: "not_directory"
31550
33238
  };
31551
33239
  }
31552
- const parent = dirname14(currentPath);
33240
+ const parent = dirname15(currentPath);
31553
33241
  const entries = readdirSync4(currentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => visibleRunnerDirectoryEntryName(entry.name)).map((entry) => {
31554
- const path2 = join21(currentPath, entry.name);
33242
+ const path2 = join22(currentPath, entry.name);
31555
33243
  return {
31556
33244
  name: entry.name,
31557
33245
  path: path2,
@@ -31592,7 +33280,7 @@ function projectDirectoryName(name) {
31592
33280
  function availableProjectDirectoryName(projectsRoot, baseName, suffix = 0) {
31593
33281
  for (let nextSuffix = suffix; ; nextSuffix += 1) {
31594
33282
  const candidate = nextSuffix === 0 ? baseName : `${baseName}-${nextSuffix}`;
31595
- if (!projectPathExists(join21(projectsRoot, candidate))) {
33283
+ if (!projectPathExists(join22(projectsRoot, candidate))) {
31596
33284
  return candidate;
31597
33285
  }
31598
33286
  }
@@ -31620,9 +33308,9 @@ function createRunnerProject(control, options, allowedCwds) {
31620
33308
  error: "invalid_project_template"
31621
33309
  };
31622
33310
  }
31623
- const projectsRoot = join21(currentHomeDirectory(), "linzumi");
33311
+ const projectsRoot = join22(currentHomeDirectory(), "linzumi");
31624
33312
  const resolvedProjectDirName = template === "hello_linzumi_demo" ? availableProjectDirectoryName(projectsRoot, projectDirName) : projectDirName;
31625
- const projectPath = join21(projectsRoot, resolvedProjectDirName);
33313
+ const projectPath = join22(projectsRoot, resolvedProjectDirName);
31626
33314
  let createdProjectPath = false;
31627
33315
  try {
31628
33316
  if (template !== "hello_linzumi_demo" && projectPathExists(projectPath)) {
@@ -31634,7 +33322,7 @@ function createRunnerProject(control, options, allowedCwds) {
31634
33322
  error: "project_directory_exists"
31635
33323
  };
31636
33324
  }
31637
- mkdirSync13(projectsRoot, { recursive: true });
33325
+ mkdirSync14(projectsRoot, { recursive: true });
31638
33326
  if (template === "hello_linzumi_demo") {
31639
33327
  createdProjectPath = true;
31640
33328
  createHelloLinzumiProject({
@@ -31642,7 +33330,7 @@ function createRunnerProject(control, options, allowedCwds) {
31642
33330
  name: resolvedProjectDirName
31643
33331
  });
31644
33332
  } else {
31645
- mkdirSync13(projectPath, { recursive: false });
33333
+ mkdirSync14(projectPath, { recursive: false });
31646
33334
  createdProjectPath = true;
31647
33335
  }
31648
33336
  const git = spawnSync5("git", ["init"], {
@@ -31755,7 +33443,7 @@ async function suggestRunnerTasks(control, options, allowedCwds) {
31755
33443
  };
31756
33444
  }
31757
33445
  }
31758
- var THREAD_RUNNER_READY_TIMEOUT_MS, THREAD_RUNNER_REAPER_INTERVAL_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
33446
+ var THREAD_RUNNER_READY_TIMEOUT_MS, THREAD_RUNNER_REAPER_INTERVAL_MS, onboardingDiscoveryAgentStartKeys, onboardingConversationImportKeys, onboardingConversationDiscoveryReportLimit, CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS, staleStartInstanceWindowMs, connectionStaleThresholdMs, connectionUnrecoverableAfterMs, controlCursorWriteDebounceMs, controlCursorEpochKey, codexTurnTerminalNotificationMethods, runnerDrainExitDeadlineMs, claudeSessionStoreSequenceHighWater, claudeCodeLlmProxySessionHeader, onboardingConversationTitleFirstMessages, onboardingConversationTitleLastMessages, onboardingConversationTitleBodyMaxLength, onboardingConversationImportMessageMaxLength, onboardingConversationImportCandidateLimit, onboardingConversationImportProgressInterval;
31759
33447
  var init_runner = __esm({
31760
33448
  "src/runner.ts"() {
31761
33449
  "use strict";
@@ -31763,6 +33451,7 @@ var init_runner = __esm({
31763
33451
  init_runnerThreadSessionRegistry();
31764
33452
  init_outboxJournal();
31765
33453
  init_channelSessionSupport();
33454
+ init_channelSessionSupport();
31766
33455
  init_commanderAttachments();
31767
33456
  init_claudeCodePipeline();
31768
33457
  init_claudeCodePlanMirror();
@@ -31805,11 +33494,18 @@ var init_runner = __esm({
31805
33494
  init_signupTaskSuggestions();
31806
33495
  init_userFacingErrors();
31807
33496
  init_remoteCodexSandboxRunner();
33497
+ init_durableInboundQueue();
33498
+ init_appliedSourceSeqWatermark();
33499
+ init_threadReconcilerLock();
33500
+ init_runnerLifecycleGuards();
33501
+ init_leaseEpochGuard();
33502
+ init_resumeKeystone();
31808
33503
  THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
31809
33504
  THREAD_RUNNER_REAPER_INTERVAL_MS = 3e4;
31810
33505
  onboardingDiscoveryAgentStartKeys = /* @__PURE__ */ new Set();
31811
33506
  onboardingConversationImportKeys = /* @__PURE__ */ new Set();
31812
33507
  onboardingConversationDiscoveryReportLimit = 100;
33508
+ CODEX_AUTH_STATUS_PROBE_TIMEOUT_MS = 3e3;
31813
33509
  staleStartInstanceWindowMs = 6e4;
31814
33510
  connectionStaleThresholdMs = 12e4;
31815
33511
  connectionUnrecoverableAfterMs = 10 * 60 * 1e3;
@@ -31822,6 +33518,7 @@ var init_runner = __esm({
31822
33518
  "turn/canceled",
31823
33519
  "turn/cancelled"
31824
33520
  ]);
33521
+ runnerDrainExitDeadlineMs = 8e3;
31825
33522
  claudeSessionStoreSequenceHighWater = /* @__PURE__ */ new Map();
31826
33523
  claudeCodeLlmProxySessionHeader = "x-linzumi-llm-proxy-token";
31827
33524
  onboardingConversationTitleFirstMessages = 4;
@@ -31834,7 +33531,7 @@ var init_runner = __esm({
31834
33531
  });
31835
33532
 
31836
33533
  // src/kandanTls.ts
31837
- import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
33534
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
31838
33535
  import { Agent } from "undici";
31839
33536
  import WsWebSocket from "ws";
31840
33537
  function kandanTlsTrustFromEnv() {
@@ -31848,7 +33545,7 @@ function kandanTlsTrustFromCaFile(caFile) {
31848
33545
  if (!existsSync14(trimmed)) {
31849
33546
  throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
31850
33547
  }
31851
- const ca = readFileSync18(trimmed, "utf8");
33548
+ const ca = readFileSync19(trimmed, "utf8");
31852
33549
  return {
31853
33550
  caFile: trimmed,
31854
33551
  ca,
@@ -50632,7 +52329,7 @@ var init_RemoveFileError = __esm({
50632
52329
 
50633
52330
  // ../../node_modules/@inquirer/external-editor/dist/esm/index.js
50634
52331
  import { spawn as spawn11, spawnSync as spawnSync6 } from "child_process";
50635
- import { readFileSync as readFileSync21, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
52332
+ import { readFileSync as readFileSync22, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
50636
52333
  import path from "node:path";
50637
52334
  import os from "node:os";
50638
52335
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -50756,7 +52453,7 @@ var init_esm5 = __esm({
50756
52453
  }
50757
52454
  readTemporaryFile() {
50758
52455
  try {
50759
- const tempFileBuffer = readFileSync21(this.tempFile);
52456
+ const tempFileBuffer = readFileSync22(this.tempFile);
50760
52457
  if (tempFileBuffer.length === 0) {
50761
52458
  this.text = "";
50762
52459
  } else {
@@ -52125,17 +53822,17 @@ import { spawn as spawn12, spawnSync as spawnSync7 } from "node:child_process";
52125
53822
  import {
52126
53823
  existsSync as existsSync17,
52127
53824
  constants as fsConstants,
52128
- mkdirSync as mkdirSync16,
53825
+ mkdirSync as mkdirSync17,
52129
53826
  mkdtempSync as mkdtempSync6,
52130
- readFileSync as readFileSync22,
53827
+ readFileSync as readFileSync23,
52131
53828
  readdirSync as readdirSync5,
52132
53829
  rmSync as rmSync7,
52133
53830
  statSync as statSync4,
52134
53831
  writeFileSync as writeFileSync16
52135
53832
  } from "node:fs";
52136
53833
  import { access } from "node:fs/promises";
52137
- import { homedir as homedir17, tmpdir as tmpdir5 } from "node:os";
52138
- import { delimiter as delimiter3, dirname as dirname18, join as join25, resolve as resolve10 } from "node:path";
53834
+ import { homedir as homedir18, tmpdir as tmpdir5 } from "node:os";
53835
+ import { delimiter as delimiter3, dirname as dirname19, join as join26, resolve as resolve10 } from "node:path";
52139
53836
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
52140
53837
  import { emitKeypressEvents } from "node:readline";
52141
53838
  function signupHelpText() {
@@ -52256,7 +53953,7 @@ function defaultSignupDraftStore(serviceUrl) {
52256
53953
  }
52257
53954
  let parsed;
52258
53955
  try {
52259
- parsed = JSON.parse(readFileSync22(path2, "utf8"));
53956
+ parsed = JSON.parse(readFileSync23(path2, "utf8"));
52260
53957
  } catch (_error) {
52261
53958
  return void 0;
52262
53959
  }
@@ -52266,7 +53963,7 @@ function defaultSignupDraftStore(serviceUrl) {
52266
53963
  return comparableServiceUrl(parsed.serviceUrl) === comparableServiceUrl(serviceUrl) ? parsed : void 0;
52267
53964
  },
52268
53965
  write: (draft) => {
52269
- mkdirSync16(dirname18(path2), { recursive: true });
53966
+ mkdirSync17(dirname19(path2), { recursive: true });
52270
53967
  writeFileSync16(path2, `${JSON.stringify(draft, null, 2)}
52271
53968
  `, {
52272
53969
  encoding: "utf8",
@@ -52279,8 +53976,8 @@ function defaultSignupDraftStore(serviceUrl) {
52279
53976
  };
52280
53977
  }
52281
53978
  function defaultSignupDraftPath(serviceUrl) {
52282
- return join25(
52283
- dirname18(localConfigPath()),
53979
+ return join26(
53980
+ dirname19(localConfigPath()),
52284
53981
  `signup-draft.${localConfigScopeFileStem(serviceUrl)}.json`
52285
53982
  );
52286
53983
  }
@@ -52314,7 +54011,7 @@ function defaultSignupTaskCacheStore(serviceUrl) {
52314
54011
  }
52315
54012
  }
52316
54013
  };
52317
- mkdirSync16(dirname18(path2), { recursive: true });
54014
+ mkdirSync17(dirname19(path2), { recursive: true });
52318
54015
  writeFileSync16(path2, `${JSON.stringify(next, null, 2)}
52319
54016
  `, {
52320
54017
  encoding: "utf8",
@@ -52324,8 +54021,8 @@ function defaultSignupTaskCacheStore(serviceUrl) {
52324
54021
  };
52325
54022
  }
52326
54023
  function defaultSignupTaskCachePath(serviceUrl) {
52327
- return join25(
52328
- dirname18(localConfigPath()),
54024
+ return join26(
54025
+ dirname19(localConfigPath()),
52329
54026
  `signup-task-cache.${localConfigScopeFileStem(serviceUrl)}.json`
52330
54027
  );
52331
54028
  }
@@ -52335,7 +54032,7 @@ function readSignupTaskCache(path2) {
52335
54032
  }
52336
54033
  let parsed;
52337
54034
  try {
52338
- parsed = JSON.parse(readFileSync22(path2, "utf8"));
54035
+ parsed = JSON.parse(readFileSync23(path2, "utf8"));
52339
54036
  } catch (_error) {
52340
54037
  return { version: 1, entries: {} };
52341
54038
  }
@@ -54220,7 +55917,7 @@ function autocompletePathSuggestions(pathInput) {
54220
55917
  try {
54221
55918
  const showHidden = prefix.startsWith(".");
54222
55919
  const currentPath = directoryExists(normalizedInput) ? [normalizedInput.replace(/\/$/, "") || "/"] : [];
54223
- const childPaths = readdirSync5(directoryPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => showHidden || !entry.name.startsWith(".")).map((entry) => join25(directoryPath, entry.name)).map((path2) => ({
55920
+ const childPaths = readdirSync5(directoryPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => showHidden || !entry.name.startsWith(".")).map((entry) => join26(directoryPath, entry.name)).map((path2) => ({
54224
55921
  path: path2,
54225
55922
  score: fuzzyPathSegmentScore(path2.split("/").at(-1) ?? path2, prefix)
54226
55923
  })).filter((candidate) => candidate.score !== void 0).sort((left, right) => {
@@ -54466,7 +56163,7 @@ async function runSignupPreflights(runtime) {
54466
56163
  function defaultPreflightRuntime() {
54467
56164
  return {
54468
56165
  cwd: process.cwd(),
54469
- homeDir: homedir17(),
56166
+ homeDir: homedir18(),
54470
56167
  probeTool,
54471
56168
  readGitEmail,
54472
56169
  discoverCodeRoots,
@@ -54611,9 +56308,9 @@ async function suggestTasksWithCodex(projectPath, retryPolicy) {
54611
56308
  );
54612
56309
  }
54613
56310
  async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolicy) {
54614
- const tempRoot = mkdtempSync6(join25(tmpdir5(), "linzumi-signup-codex-tasks-"));
54615
- const schemaPath = join25(tempRoot, "task-suggestions.schema.json");
54616
- const outputPath = join25(tempRoot, "task-suggestions.json");
56311
+ const tempRoot = mkdtempSync6(join26(tmpdir5(), "linzumi-signup-codex-tasks-"));
56312
+ const schemaPath = join26(tempRoot, "task-suggestions.schema.json");
56313
+ const outputPath = join26(tempRoot, "task-suggestions.json");
54617
56314
  const model = process.env.LINZUMI_SIGNUP_TASK_MODEL ?? "gpt-5.4-mini";
54618
56315
  const prompt = taskSuggestionPrompt2(previousResponse);
54619
56316
  const codexCommand = await resolveSignupCodexCommand();
@@ -54638,7 +56335,7 @@ async function runCodexTaskSuggestion2(projectPath, previousResponse, retryPolic
54638
56335
  ),
54639
56336
  retryPolicy
54640
56337
  );
54641
- return readFileSync22(outputPath, "utf8");
56338
+ return readFileSync23(outputPath, "utf8");
54642
56339
  } finally {
54643
56340
  rmSync7(tempRoot, { recursive: true, force: true });
54644
56341
  }
@@ -54675,7 +56372,7 @@ function codexTaskSuggestionProcess2(args) {
54675
56372
  function signupCodexTaskSuggestionProcessForTest(args) {
54676
56373
  return codexTaskSuggestionProcess2(args);
54677
56374
  }
54678
- async function resolveSignupCodexCommand(env = process.env, homeDir = homedir17(), executableExists = fileIsExecutable) {
56375
+ async function resolveSignupCodexCommand(env = process.env, homeDir = homedir18(), executableExists = fileIsExecutable) {
54679
56376
  const override = firstConfiguredValue([
54680
56377
  env.LINZUMI_SIGNUP_CODEX_BIN,
54681
56378
  env.LINZUMI_CODEX_BIN
@@ -54730,7 +56427,7 @@ function resolveHomePath(path2, homeDir) {
54730
56427
  return homeDir;
54731
56428
  }
54732
56429
  if (path2.startsWith("~/")) {
54733
- return join25(homeDir, path2.slice(2));
56430
+ return join26(homeDir, path2.slice(2));
54734
56431
  }
54735
56432
  return resolve10(path2);
54736
56433
  }
@@ -54743,9 +56440,9 @@ function commandLooksPathLike(command) {
54743
56440
  }
54744
56441
  function homeManagedCodexCandidates(homeDir) {
54745
56442
  return [
54746
- join25(homeDir, ".volta", "bin", "codex"),
54747
- join25(homeDir, ".local", "bin", "codex"),
54748
- join25(homeDir, "bin", "codex")
56443
+ join26(homeDir, ".volta", "bin", "codex"),
56444
+ join26(homeDir, ".local", "bin", "codex"),
56445
+ join26(homeDir, "bin", "codex")
54749
56446
  ];
54750
56447
  }
54751
56448
  async function firstExecutablePath(paths, executableExists) {
@@ -54760,7 +56457,7 @@ function commandPathCandidates(path2) {
54760
56457
  if (path2 === void 0 || path2.trim() === "") {
54761
56458
  return [];
54762
56459
  }
54763
- return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) => join25(entry, "codex"));
56460
+ return path2.split(delimiter3).filter((entry) => entry.trim() !== "").map((entry) => join26(entry, "codex"));
54764
56461
  }
54765
56462
  async function fileIsExecutable(path2) {
54766
56463
  try {
@@ -54993,11 +56690,11 @@ function codexPreflightLocation(command, homeDir) {
54993
56690
  switch (command) {
54994
56691
  case "codex":
54995
56692
  return "on PATH";
54996
- case join25(homeDir, ".volta", "bin", "codex"):
56693
+ case join26(homeDir, ".volta", "bin", "codex"):
54997
56694
  return "via Volta";
54998
- case join25(homeDir, ".local", "bin", "codex"):
56695
+ case join26(homeDir, ".local", "bin", "codex"):
54999
56696
  return "via ~/.local/bin";
55000
- case join25(homeDir, "bin", "codex"):
56697
+ case join26(homeDir, "bin", "codex"):
55001
56698
  return "via ~/bin";
55002
56699
  default:
55003
56700
  return "from configured path";
@@ -55156,7 +56853,7 @@ function probeToolWithArgs(command, args, cwd) {
55156
56853
  }
55157
56854
  async function discoverCodeRoots(homeDir) {
55158
56855
  const candidates = ["src", "code", "projects"].map(
55159
- (name) => join25(homeDir, name)
56856
+ (name) => join26(homeDir, name)
55160
56857
  );
55161
56858
  return candidates.filter((path2) => existsSync17(path2)).flatMap((path2) => discoveredProjectNames(path2));
55162
56859
  }
@@ -55210,7 +56907,7 @@ function discoverProjectsFromCurrentDirectory(cwd) {
55210
56907
  }
55211
56908
  function discoverProjectsFromGuessedRoots(homeDir) {
55212
56909
  const guessedRoots = ["src", "code", "projects"].map(
55213
- (name) => join25(homeDir, name)
56910
+ (name) => join26(homeDir, name)
55214
56911
  );
55215
56912
  const projects = guessedRoots.flatMap(
55216
56913
  (root) => discoverProjectsUnderRoot(root)
@@ -55262,25 +56959,25 @@ function looksLikeProject(path2) {
55262
56959
  "pnpm-lock.yaml",
55263
56960
  "yarn.lock",
55264
56961
  "package-lock.json"
55265
- ].some((name) => existsSync17(join25(path2, name)));
56962
+ ].some((name) => existsSync17(join26(path2, name)));
55266
56963
  }
55267
56964
  function detectProjectLanguage(path2) {
55268
- if (existsSync17(join25(path2, "pyproject.toml")) || existsSync17(join25(path2, "requirements.txt"))) {
56965
+ if (existsSync17(join26(path2, "pyproject.toml")) || existsSync17(join26(path2, "requirements.txt"))) {
55269
56966
  return "Python";
55270
56967
  }
55271
- if (existsSync17(join25(path2, "Cargo.toml"))) {
56968
+ if (existsSync17(join26(path2, "Cargo.toml"))) {
55272
56969
  return "Rust";
55273
56970
  }
55274
- if (existsSync17(join25(path2, "go.mod"))) {
56971
+ if (existsSync17(join26(path2, "go.mod"))) {
55275
56972
  return "Go";
55276
56973
  }
55277
- if (existsSync17(join25(path2, "mix.exs"))) {
56974
+ if (existsSync17(join26(path2, "mix.exs"))) {
55278
56975
  return "Elixir";
55279
56976
  }
55280
- if (existsSync17(join25(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
56977
+ if (existsSync17(join26(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
55281
56978
  return "TypeScript";
55282
56979
  }
55283
- if (existsSync17(join25(path2, "package.json"))) {
56980
+ if (existsSync17(join26(path2, "package.json"))) {
55284
56981
  return "JavaScript";
55285
56982
  }
55286
56983
  return "Project";
@@ -55288,7 +56985,7 @@ function detectProjectLanguage(path2) {
55288
56985
  function packageJsonMentionsTypeScript(path2) {
55289
56986
  try {
55290
56987
  const packageJson2 = JSON.parse(
55291
- readFileSync22(join25(path2, "package.json"), "utf8")
56988
+ readFileSync23(join26(path2, "package.json"), "utf8")
55292
56989
  );
55293
56990
  return packageJson2.dependencies?.typescript !== void 0 || packageJson2.devDependencies?.typescript !== void 0;
55294
56991
  } catch {
@@ -55296,11 +56993,11 @@ function packageJsonMentionsTypeScript(path2) {
55296
56993
  }
55297
56994
  }
55298
56995
  function hasGitMetadata(path2) {
55299
- return existsSync17(join25(path2, ".git"));
56996
+ return existsSync17(join26(path2, ".git"));
55300
56997
  }
55301
56998
  function childDirectories(root) {
55302
56999
  try {
55303
- return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join25(root, entry.name));
57000
+ return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join26(root, entry.name));
55304
57001
  } catch {
55305
57002
  return [];
55306
57003
  }
@@ -55326,10 +57023,10 @@ function directoryExists(path2) {
55326
57023
  }
55327
57024
  function expandHomePath(path2) {
55328
57025
  if (path2 === "~") {
55329
- return homedir17();
57026
+ return homedir18();
55330
57027
  }
55331
57028
  if (path2.startsWith("~/")) {
55332
- return join25(homedir17(), path2.slice(2));
57029
+ return join26(homedir18(), path2.slice(2));
55333
57030
  }
55334
57031
  return resolve10(path2);
55335
57032
  }
@@ -55420,8 +57117,8 @@ init_runner();
55420
57117
  init_runnerLockTakeover();
55421
57118
  init_claudeCodeSession();
55422
57119
  init_authCache();
55423
- import { existsSync as existsSync18, readFileSync as readFileSync23, realpathSync as realpathSync7 } from "node:fs";
55424
- import { homedir as homedir18 } from "node:os";
57120
+ import { existsSync as existsSync18, readFileSync as readFileSync24, realpathSync as realpathSync7 } from "node:fs";
57121
+ import { homedir as homedir19 } from "node:os";
55425
57122
  import { resolve as resolve11 } from "node:path";
55426
57123
  import { fileURLToPath as fileURLToPath4 } from "node:url";
55427
57124
 
@@ -55516,9 +57213,9 @@ init_kandanTls();
55516
57213
  init_protocol();
55517
57214
  init_json();
55518
57215
  init_defaultUrls();
55519
- import { existsSync as existsSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
55520
- import { dirname as dirname15, join as join22 } from "node:path";
55521
- import { homedir as homedir15 } from "node:os";
57216
+ import { existsSync as existsSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "node:fs";
57217
+ import { dirname as dirname16, join as join23 } from "node:path";
57218
+ import { homedir as homedir16 } from "node:os";
55522
57219
  async function runAgentCliCommand(args, deps = {
55523
57220
  fetchImpl: fetch,
55524
57221
  stdout: process.stdout,
@@ -56226,7 +57923,7 @@ function agentTokenFile(flags) {
56226
57923
  return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
56227
57924
  }
56228
57925
  function defaultAgentTokenFilePath() {
56229
- return join22(homedir15(), ".linzumi", "agent-token.json");
57926
+ return join23(homedir16(), ".linzumi", "agent-token.json");
56230
57927
  }
56231
57928
  function normalizedApiUrl(apiUrl) {
56232
57929
  return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
@@ -56235,10 +57932,10 @@ function authorizationHeaders(token) {
56235
57932
  return { authorization: `Bearer ${token}` };
56236
57933
  }
56237
57934
  function readOptionalTextFile(path2) {
56238
- return existsSync15(path2) ? readFileSync19(path2, "utf8") : void 0;
57935
+ return existsSync15(path2) ? readFileSync20(path2, "utf8") : void 0;
56239
57936
  }
56240
57937
  function writeTextFile(path2, content) {
56241
- mkdirSync14(dirname15(path2), { recursive: true });
57938
+ mkdirSync15(dirname16(path2), { recursive: true });
56242
57939
  writeFileSync13(path2, content);
56243
57940
  }
56244
57941
  function readStoredAgentTokenFile(path2, readTextFile = readOptionalTextFile) {
@@ -56327,26 +58024,26 @@ init_helloLinzumiProject();
56327
58024
  init_runnerLogger();
56328
58025
  import {
56329
58026
  existsSync as existsSync16,
56330
- closeSync as closeSync3,
56331
- mkdirSync as mkdirSync15,
56332
- openSync as openSync4,
56333
- readFileSync as readFileSync20,
58027
+ closeSync as closeSync5,
58028
+ mkdirSync as mkdirSync16,
58029
+ openSync as openSync6,
58030
+ readFileSync as readFileSync21,
56334
58031
  watch,
56335
58032
  writeFileSync as writeFileSync14
56336
58033
  } from "node:fs";
56337
- import { homedir as homedir16 } from "node:os";
56338
- import { dirname as dirname16, join as join23, resolve as resolve9 } from "node:path";
58034
+ import { homedir as homedir17 } from "node:os";
58035
+ import { dirname as dirname17, join as join24, resolve as resolve9 } from "node:path";
56339
58036
  import { execFileSync, spawn as spawn10 } from "node:child_process";
56340
58037
  import { fileURLToPath as fileURLToPath3 } from "node:url";
56341
58038
  var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
56342
58039
  function commanderStatusDir() {
56343
- return join23(homedir16(), ".linzumi", "commanders");
58040
+ return join24(homedir17(), ".linzumi", "commanders");
56344
58041
  }
56345
58042
  function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
56346
- return join23(statusDir, `${safeRunnerId(runnerId)}.json`);
58043
+ return join24(statusDir, `${safeRunnerId(runnerId)}.json`);
56347
58044
  }
56348
58045
  function defaultCommanderLogFile(runnerId) {
56349
- return join23(homedir16(), ".linzumi", "logs", `${safeRunnerId(runnerId)}.log`);
58046
+ return join24(homedir17(), ".linzumi", "logs", `${safeRunnerId(runnerId)}.log`);
56350
58047
  }
56351
58048
  function commanderLogIsConnected(log2) {
56352
58049
  return connectedMarkers.some((marker) => log2.includes(marker));
@@ -56367,10 +58064,10 @@ function startCommanderDaemon(options) {
56367
58064
  "--log-file",
56368
58065
  logFile
56369
58066
  ];
56370
- mkdirSync15(statusDir, { recursive: true });
56371
- mkdirSync15(dirname16(logFile), { recursive: true });
56372
- const out = openSync4(logFile, "a");
56373
- const err = openSync4(logFile, "a");
58067
+ mkdirSync16(statusDir, { recursive: true });
58068
+ mkdirSync16(dirname17(logFile), { recursive: true });
58069
+ const out = openSync6(logFile, "a");
58070
+ const err = openSync6(logFile, "a");
56374
58071
  writeCliAuditEvent(
56375
58072
  "process.spawn",
56376
58073
  {
@@ -56397,8 +58094,8 @@ function startCommanderDaemon(options) {
56397
58094
  },
56398
58095
  { sessionId: options.runnerId }
56399
58096
  );
56400
- closeSync3(out);
56401
- closeSync3(err);
58097
+ closeSync5(out);
58098
+ closeSync5(err);
56402
58099
  child.unref();
56403
58100
  if (child.pid === void 0) {
56404
58101
  throw new Error("commander daemon did not report a pid");
@@ -56423,12 +58120,12 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
56423
58120
  if (!existsSync16(statusFile)) {
56424
58121
  return { status: "missing", runnerId, statusFile };
56425
58122
  }
56426
- const record = parseRecord2(readFileSync20(statusFile, "utf8"));
58123
+ const record = parseRecord2(readFileSync21(statusFile, "utf8"));
56427
58124
  return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
56428
58125
  }
56429
58126
  async function waitForCommanderDaemon(options) {
56430
58127
  const now = options.now ?? (() => Date.now());
56431
- const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync20(path2, "utf8") : void 0);
58128
+ const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync21(path2, "utf8") : void 0);
56432
58129
  const statusImpl = options.statusImpl ?? commanderDaemonStatus;
56433
58130
  const deadline = now() + options.timeoutMs;
56434
58131
  while (now() <= deadline) {
@@ -69321,7 +71018,7 @@ init_userFacingErrors();
69321
71018
  init_version();
69322
71019
  import { chmodSync as chmodSync3, mkdtempSync as mkdtempSync5, rmSync as rmSync6 } from "node:fs";
69323
71020
  import { tmpdir as tmpdir4 } from "node:os";
69324
- import { basename as basename9, dirname as dirname17, join as join24 } from "node:path";
71021
+ import { basename as basename9, dirname as dirname18, join as join25 } from "node:path";
69325
71022
  var configEnvKey = "LINZUMI_REMOTE_CODEX_HARNESS_CONFIG_B64";
69326
71023
  var remoteHarnessEnvironmentId = "linzumi-remote-codex-harness";
69327
71024
  async function runRemoteCodexHarnessWorkerFromEnv(env = process.env) {
@@ -69554,18 +71251,18 @@ function resolveLinzumiCliEntrypoint(entrypoint) {
69554
71251
  case "remote-codex-harness-worker.js":
69555
71252
  case "remote-codex-harness-worker.mjs":
69556
71253
  case "remote-codex-harness-worker.cjs":
69557
- return join24(dirname17(entrypoint), "linzumi.js");
71254
+ return join25(dirname18(entrypoint), "linzumi.js");
69558
71255
  case "remoteCodexHarnessWorkerEntrypoint.ts":
69559
71256
  case "remoteCodexHarnessWorkerEntrypoint.js":
69560
- return join24(dirname17(entrypoint), "index.ts");
71257
+ return join25(dirname18(entrypoint), "index.ts");
69561
71258
  default:
69562
71259
  return entrypoint;
69563
71260
  }
69564
71261
  }
69565
71262
  function writeEphemeralMcpAuthFile2(options) {
69566
- const directory = mkdtempSync5(join24(tmpdir4(), "linzumi-mcp-auth-"));
71263
+ const directory = mkdtempSync5(join25(tmpdir4(), "linzumi-mcp-auth-"));
69567
71264
  chmodSync3(directory, 448);
69568
- const path2 = join24(directory, "auth.json");
71265
+ const path2 = join25(directory, "auth.json");
69569
71266
  try {
69570
71267
  writeCachedLocalRunnerToken({
69571
71268
  kandanUrl: options.kandanUrl,
@@ -70516,7 +72213,7 @@ async function parseAgentRunnerArgs(args, deps = {
70516
72213
  };
70517
72214
  }
70518
72215
  function readAgentTokenTextFile(path2) {
70519
- return existsSync18(path2) ? readFileSync23(path2, "utf8") : void 0;
72216
+ return existsSync18(path2) ? readFileSync24(path2, "utf8") : void 0;
70520
72217
  }
70521
72218
  function rejectAgentRunnerTargetingFlags(values) {
70522
72219
  const unsupportedFlags = [
@@ -70835,10 +72532,10 @@ function rejectStartTargetingFlags(values) {
70835
72532
  }
70836
72533
  function resolveUserPath(pathValue) {
70837
72534
  if (pathValue === "~") {
70838
- return homedir18();
72535
+ return homedir19();
70839
72536
  }
70840
72537
  if (pathValue.startsWith("~/")) {
70841
- return resolve11(homedir18(), pathValue.slice(2));
72538
+ return resolve11(homedir19(), pathValue.slice(2));
70842
72539
  }
70843
72540
  return resolve11(pathValue);
70844
72541
  }