@linzumi/cli 0.0.74-beta → 0.0.76-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -934,6 +934,7 @@ function codexThreadRuntimeOverrides(options) {
934
934
  ...session.model === void 0 ? {} : { model: session.model },
935
935
  ...session.reasoningEffort === void 0 ? {} : { reasoningEffort: session.reasoningEffort },
936
936
  ...options.fast === true ? { serviceTier: "fast" } : {},
937
+ ...session.codexEnvironmentId === void 0 ? {} : { environmentId: session.codexEnvironmentId },
937
938
  approvalPolicy: codexApprovalPolicyForRequest(
938
939
  session.approvalPolicy,
939
940
  session.sandbox
@@ -948,6 +949,7 @@ function codexTurnRuntimeOverrides(options) {
948
949
  ...session.model === void 0 ? {} : { model: session.model },
949
950
  ...session.reasoningEffort === void 0 ? {} : { effort: session.reasoningEffort },
950
951
  ...options.fast === true ? { serviceTier: "fast" } : {},
952
+ ...session.codexEnvironmentId === void 0 ? {} : { environmentId: session.codexEnvironmentId },
951
953
  approvalPolicy: codexApprovalPolicyForRequest(
952
954
  session.approvalPolicy,
953
955
  session.sandbox
@@ -3822,6 +3824,14 @@ async function attachChannelSession(args) {
3822
3824
  case "turn/cancelled":
3823
3825
  case "turn/failed":
3824
3826
  if (turnId !== void 0) {
3827
+ clearInterruptedThreadOnlyCompletionForExplicitTerminal(
3828
+ state,
3829
+ threadId,
3830
+ turnId
3831
+ );
3832
+ if (turnTerminalNotificationIsStale(args, state, turnId)) {
3833
+ break;
3834
+ }
3825
3835
  void failActiveCodexTurn(
3826
3836
  args,
3827
3837
  state,
@@ -3837,6 +3847,16 @@ async function attachChannelSession(args) {
3837
3847
  }
3838
3848
  break;
3839
3849
  case "turn/completed": {
3850
+ if (turnId !== void 0) {
3851
+ clearInterruptedThreadOnlyCompletionForExplicitTerminal(
3852
+ state,
3853
+ threadId,
3854
+ turnId
3855
+ );
3856
+ }
3857
+ if (turnId !== void 0 && turnTerminalNotificationIsStale(args, state, turnId)) {
3858
+ break;
3859
+ }
3840
3860
  const completedTurnId = turnId ?? inferThreadOnlyCompletedTurnId(args, state, threadId);
3841
3861
  if (completedTurnId !== void 0) {
3842
3862
  enqueueWebSearchProgressCompletion(args, state, completedTurnId);
@@ -4023,7 +4043,9 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
4023
4043
  queue: createPendingKandanMessageQueue(),
4024
4044
  forwardedTurnIds: /* @__PURE__ */ new Set(),
4025
4045
  forwardingTurnIds: /* @__PURE__ */ new Set(),
4046
+ completionForwardingTurnIds: /* @__PURE__ */ new Set(),
4026
4047
  recoveredCompletingTurnIds: /* @__PURE__ */ new Set(),
4048
+ pendingInterruptedThreadOnlyCompletions: [],
4027
4049
  retryableTurnIds: /* @__PURE__ */ new Set(),
4028
4050
  completedAssistantItemKeys: /* @__PURE__ */ new Set(),
4029
4051
  completedAssistantItemTurns: createBoundedCache(maxForwardedTurnIds),
@@ -4143,6 +4165,10 @@ async function bindChannelSession(args, state, payloadContext) {
4143
4165
  if (state.rootSeq !== void 0) {
4144
4166
  state.minSeq = Math.max(state.minSeq, state.rootSeq);
4145
4167
  }
4168
+ } else if (session.startCodexThread === true) {
4169
+ state.codexThreadId = await startCodexThread(args.codex, args.options);
4170
+ await hydrateKandanThreadTitleFromThreadContext(args, state);
4171
+ await bindCurrentCodexThread(args, state);
4146
4172
  } else if (state.codexThreadId !== void 0) {
4147
4173
  await hydrateKandanThreadTitleFromThreadContext(args, state);
4148
4174
  await bindCurrentCodexThread(args, state);
@@ -4193,6 +4219,55 @@ async function recoverCompletingCodexTurn(args, state, reason) {
4193
4219
  reason
4194
4220
  });
4195
4221
  }
4222
+ async function recoverInterruptedCodexTurn(args, state, threadId, turnId, queuedSeq) {
4223
+ interruptCodexTurnBestEffort(args, threadId, turnId);
4224
+ state.turn = { status: "idle" };
4225
+ clearActiveProcessingState(state, queuedSeq);
4226
+ rememberForwardedTurnId(state, turnId);
4227
+ state.pendingInterruptedThreadOnlyCompletions.push({
4228
+ threadId,
4229
+ interruptedTurnId: turnId,
4230
+ interruptedQueuedSeq: queuedSeq
4231
+ });
4232
+ trimBoundedArray(
4233
+ state.pendingInterruptedThreadOnlyCompletions,
4234
+ maxForwardedTurnIds
4235
+ );
4236
+ forgetRetryableTurnId(state, turnId);
4237
+ forgetLocalTuiTurnId(state, turnId);
4238
+ forgetTurnReplyTarget(state, turnId);
4239
+ rejectPendingApprovalRequestsForTurn(
4240
+ state,
4241
+ turnId,
4242
+ new Error("Codex turn interrupted")
4243
+ );
4244
+ if (state.kandanThreadId !== void 0) {
4245
+ await publishMessageState(
4246
+ args,
4247
+ state.kandanThreadId,
4248
+ queuedSeq,
4249
+ {
4250
+ status: "processed"
4251
+ },
4252
+ void 0,
4253
+ void 0,
4254
+ state.codexThreadId
4255
+ );
4256
+ }
4257
+ await stopCodexTyping(args, state);
4258
+ }
4259
+ function interruptCodexTurnBestEffort(args, threadId, turnId) {
4260
+ void args.codex.request("turn/interrupt", {
4261
+ threadId,
4262
+ turnId
4263
+ }).catch((error) => {
4264
+ args.log("codex.turn_interrupt_failed", {
4265
+ thread_id: threadId,
4266
+ turn_id: turnId,
4267
+ message: error instanceof Error ? error.message : String(error)
4268
+ });
4269
+ });
4270
+ }
4196
4271
  async function handleChannelSessionControl(args, state, payloadContext, control) {
4197
4272
  if (control.type === "update_session_settings") {
4198
4273
  return updateSessionSettings(args, state, payloadContext, control);
@@ -4236,23 +4311,12 @@ async function handleChannelSessionControl(args, state, payloadContext, control)
4236
4311
  }
4237
4312
  switch (state.turn.status) {
4238
4313
  case "active":
4239
- const interruptedActiveSeq = state.turn.queuedSeq;
4240
- await args.codex.request("turn/interrupt", {
4241
- threadId: state.codexThreadId,
4242
- turnId: state.turn.turnId
4243
- });
4244
- state.turn = { status: "idle" };
4245
- clearActiveProcessingState(state, interruptedActiveSeq);
4246
- await publishMessageState(
4314
+ await recoverInterruptedCodexTurn(
4247
4315
  args,
4248
- state.kandanThreadId,
4249
- interruptedActiveSeq,
4250
- {
4251
- status: "processed"
4252
- },
4253
- void 0,
4254
- void 0,
4255
- state.codexThreadId
4316
+ state,
4317
+ state.codexThreadId,
4318
+ state.turn.turnId,
4319
+ state.turn.queuedSeq
4256
4320
  );
4257
4321
  break;
4258
4322
  case "starting":
@@ -5525,25 +5589,13 @@ async function drainKandanMessageQueue(args, state, payloadContext) {
5525
5589
  rememberTurnReplyTarget(state, turnId, next.seq);
5526
5590
  args.log("codex.turn_started", { turn_id: turnId });
5527
5591
  if (interruptAfterStart) {
5528
- await args.codex.request("turn/interrupt", {
5529
- threadId: codexThreadId,
5530
- turnId
5531
- });
5532
- state.turn = { status: "idle" };
5533
- clearActiveProcessingState(state, next.seq);
5534
- if (state.kandanThreadId !== void 0) {
5535
- await publishMessageState(
5536
- args,
5537
- state.kandanThreadId,
5538
- next.seq,
5539
- {
5540
- status: "processed"
5541
- },
5542
- void 0,
5543
- void 0,
5544
- state.codexThreadId
5545
- );
5546
- }
5592
+ await recoverInterruptedCodexTurn(
5593
+ args,
5594
+ state,
5595
+ codexThreadId,
5596
+ turnId,
5597
+ next.seq
5598
+ );
5547
5599
  await drainKandanMessageQueue(args, state, payloadContext);
5548
5600
  }
5549
5601
  } catch (error) {
@@ -5783,43 +5835,44 @@ async function forwardCompletedCodexTurn(args, state, turnId, payloadContext) {
5783
5835
  if (isLocalTuiTurn(state, turnId)) {
5784
5836
  ensureKandanThreadForLocalTuiTurn(state);
5785
5837
  }
5786
- if (state.kandanThreadId === void 0 || state.codexThreadId === void 0 || state.forwardedTurnIds.has(turnId) || state.forwardingTurnIds.has(turnId) || !turnCanForward(state, turnId)) {
5838
+ if (state.kandanThreadId === void 0 || state.codexThreadId === void 0 || state.forwardedTurnIds.has(turnId) || state.forwardingTurnIds.has(turnId) || state.completionForwardingTurnIds.has(turnId) || !turnCanForward(state, turnId)) {
5787
5839
  return;
5788
5840
  }
5789
5841
  const completingQueuedSeq = completingQueuedSeqForTurn(state.turn, turnId);
5790
5842
  const completingActiveTurn = completingQueuedSeq !== void 0;
5791
5843
  const completingLocalTuiTurn = isLocalTuiTurn(state, turnId);
5792
- if (completingQueuedSeq !== void 0) {
5793
- state.turn = {
5794
- status: "completing",
5795
- turnId,
5796
- queuedSeq: completingQueuedSeq
5797
- };
5798
- }
5799
- await waitForPendingTuiInputMirror(state, turnId);
5800
- if (completingTurnWasRecovered(state, turnId)) {
5801
- return;
5802
- }
5803
- await waitForPendingCompletedAssistantItemForwards(state, turnId);
5804
- if (completingTurnWasRecovered(state, turnId)) {
5805
- return;
5806
- }
5807
- if (completingQueuedSeq !== void 0) {
5808
- await waitForPendingCompletedAssistantItemSourceForwards(
5809
- state,
5810
- completingQueuedSeq
5811
- );
5812
- }
5813
- if (completingTurnWasRecovered(state, turnId)) {
5814
- return;
5815
- }
5816
- await waitForStreamingForwardChains(args, state, payloadContext);
5817
- if (completingTurnWasRecovered(state, turnId)) {
5818
- return;
5819
- }
5820
- rememberForwardingTurnId(state, turnId);
5821
- forgetRetryableTurnId(state, turnId);
5844
+ rememberCompletionForwardingTurnId(state, turnId);
5822
5845
  try {
5846
+ if (completingQueuedSeq !== void 0) {
5847
+ state.turn = {
5848
+ status: "completing",
5849
+ turnId,
5850
+ queuedSeq: completingQueuedSeq
5851
+ };
5852
+ }
5853
+ await waitForPendingTuiInputMirror(state, turnId);
5854
+ if (completingTurnWasRecovered(state, turnId)) {
5855
+ return;
5856
+ }
5857
+ await waitForPendingCompletedAssistantItemForwards(state, turnId);
5858
+ if (completingTurnWasRecovered(state, turnId)) {
5859
+ return;
5860
+ }
5861
+ if (completingQueuedSeq !== void 0) {
5862
+ await waitForPendingCompletedAssistantItemSourceForwards(
5863
+ state,
5864
+ completingQueuedSeq
5865
+ );
5866
+ }
5867
+ if (completingTurnWasRecovered(state, turnId)) {
5868
+ return;
5869
+ }
5870
+ await waitForStreamingForwardChains(args, state, payloadContext);
5871
+ if (completingTurnWasRecovered(state, turnId)) {
5872
+ return;
5873
+ }
5874
+ rememberForwardingTurnId(state, turnId);
5875
+ forgetRetryableTurnId(state, turnId);
5823
5876
  const read = await args.codex.request("thread/read", {
5824
5877
  threadId: state.codexThreadId,
5825
5878
  includeTurns: true
@@ -5967,6 +6020,7 @@ async function forwardCompletedCodexTurn(args, state, turnId, payloadContext) {
5967
6020
  rememberRetryableTurnId(state, turnId);
5968
6021
  throw error;
5969
6022
  } finally {
6023
+ forgetCompletionForwardingTurnId(state, turnId);
5970
6024
  forgetForwardingTurnId(state, turnId);
5971
6025
  if (completingActiveTurn && state.turn.status === "completing" && state.turn.turnId === turnId) {
5972
6026
  state.turn = { status: "idle" };
@@ -6157,7 +6211,10 @@ function completedMessagesWithSessionLog(readMessages, sessionLogMessages) {
6157
6211
  }
6158
6212
  nextReadIndex += 1;
6159
6213
  }
6160
- mergedMessages.push(readMessages[entry.readIndex]);
6214
+ const readMessage = readMessages[entry.readIndex];
6215
+ if (readMessage !== void 0) {
6216
+ mergedMessages.push(readMessage);
6217
+ }
6161
6218
  nextReadIndex = Math.max(nextReadIndex, entry.readIndex + 1);
6162
6219
  break;
6163
6220
  case "session":
@@ -8004,6 +8061,16 @@ function inferThreadOnlyCompletedTurnId(args, state, threadId) {
8004
8061
  switch (state.turn.status) {
8005
8062
  case "active":
8006
8063
  case "completing":
8064
+ clearInterruptedThreadOnlyCompletionsBeforeQueuedSeq(
8065
+ state,
8066
+ threadId,
8067
+ state.turn.queuedSeq
8068
+ );
8069
+ rejectPendingApprovalRequestsForTurn(
8070
+ state,
8071
+ state.turn.turnId,
8072
+ new Error("Codex turn completed with thread-only notification")
8073
+ );
8007
8074
  args.log("codex.turn_completion_inferred", {
8008
8075
  thread_id: threadId,
8009
8076
  turn_id: state.turn.turnId,
@@ -8012,6 +8079,24 @@ function inferThreadOnlyCompletedTurnId(args, state, threadId) {
8012
8079
  });
8013
8080
  return state.turn.turnId;
8014
8081
  case "idle":
8082
+ case "starting":
8083
+ break;
8084
+ }
8085
+ const pending = consumeAmbiguousInterruptedThreadOnlyCompletion(
8086
+ state,
8087
+ threadId
8088
+ );
8089
+ if (pending !== void 0) {
8090
+ args.log("codex.thread_only_completion_after_interrupt_ignored", {
8091
+ thread_id: threadId,
8092
+ interrupted_turn_id: pending.interruptedTurnId,
8093
+ interrupted_queued_seq: pending.interruptedQueuedSeq,
8094
+ turn_status: state.turn.status
8095
+ });
8096
+ return void 0;
8097
+ }
8098
+ switch (state.turn.status) {
8099
+ case "idle":
8015
8100
  case "starting":
8016
8101
  args.log("codex.turn_completion_without_turn_id", {
8017
8102
  thread_id: threadId,
@@ -8021,6 +8106,46 @@ function inferThreadOnlyCompletedTurnId(args, state, threadId) {
8021
8106
  return void 0;
8022
8107
  }
8023
8108
  }
8109
+ function consumeAmbiguousInterruptedThreadOnlyCompletion(state, threadId) {
8110
+ const index = state.pendingInterruptedThreadOnlyCompletions.findIndex(
8111
+ (pending2) => pending2.threadId === threadId
8112
+ );
8113
+ if (index < 0) {
8114
+ return void 0;
8115
+ }
8116
+ const [pending] = state.pendingInterruptedThreadOnlyCompletions.splice(
8117
+ index,
8118
+ 1
8119
+ );
8120
+ return pending;
8121
+ }
8122
+ function clearInterruptedThreadOnlyCompletionForExplicitTerminal(state, threadId, turnId) {
8123
+ state.pendingInterruptedThreadOnlyCompletions = state.pendingInterruptedThreadOnlyCompletions.filter((pending) => {
8124
+ if (threadId !== void 0 && pending.threadId !== threadId) {
8125
+ return true;
8126
+ }
8127
+ if (pending.interruptedTurnId === turnId) {
8128
+ return false;
8129
+ }
8130
+ return true;
8131
+ });
8132
+ }
8133
+ function clearInterruptedThreadOnlyCompletionsBeforeQueuedSeq(state, threadId, queuedSeq) {
8134
+ state.pendingInterruptedThreadOnlyCompletions = state.pendingInterruptedThreadOnlyCompletions.filter(
8135
+ (pending) => pending.threadId !== threadId || pending.interruptedQueuedSeq >= queuedSeq
8136
+ );
8137
+ }
8138
+ function turnTerminalNotificationIsStale(args, state, turnId) {
8139
+ if (!state.forwardedTurnIds.has(turnId)) {
8140
+ return false;
8141
+ }
8142
+ args.log("codex.stale_turn_terminal_ignored", {
8143
+ turn_id: turnId,
8144
+ active_turn_id: activeTurnId(state.turn) ?? null,
8145
+ turn_status: state.turn.status
8146
+ });
8147
+ return true;
8148
+ }
8024
8149
  function codexNotificationActiveTurnFallback(method, state, params, threadId) {
8025
8150
  if (threadId === void 0 || state.codexThreadId !== threadId) {
8026
8151
  return void 0;
@@ -8212,6 +8337,9 @@ function rememberTurnReplyTarget(state, turnId, replyToSeq) {
8212
8337
  replyToSeq
8213
8338
  });
8214
8339
  }
8340
+ function forgetTurnReplyTarget(state, turnId) {
8341
+ forgetBoundedCacheValue(state.turnReplyTargets, turnId);
8342
+ }
8215
8343
  function sourceMessageSeqForTurn(state, turnId) {
8216
8344
  return getBoundedCacheValue(state.turnReplyTargets, turnId)?.replyToSeq;
8217
8345
  }
@@ -8282,6 +8410,16 @@ function rememberForwardingTurnId(state, turnId) {
8282
8410
  function forgetForwardingTurnId(state, turnId) {
8283
8411
  state.forwardingTurnIds.delete(turnId);
8284
8412
  }
8413
+ function rememberCompletionForwardingTurnId(state, turnId) {
8414
+ rememberBoundedStringSet(
8415
+ state.completionForwardingTurnIds,
8416
+ turnId,
8417
+ maxForwardedTurnIds
8418
+ );
8419
+ }
8420
+ function forgetCompletionForwardingTurnId(state, turnId) {
8421
+ state.completionForwardingTurnIds.delete(turnId);
8422
+ }
8285
8423
  function rememberRecoveredCompletingTurnId(state, turnId) {
8286
8424
  rememberBoundedStringSet(
8287
8425
  state.recoveredCompletingTurnIds,
@@ -8321,6 +8459,11 @@ function trimBoundedMap(values, maxSize) {
8321
8459
  values.delete(oldest);
8322
8460
  }
8323
8461
  }
8462
+ function trimBoundedArray(values, maxSize) {
8463
+ while (values.length > maxSize) {
8464
+ values.shift();
8465
+ }
8466
+ }
8324
8467
  async function stopCodexTyping(args, state) {
8325
8468
  stopCodexTypingHeartbeat(state);
8326
8469
  if (state.kandanThreadId === void 0) {
@@ -9743,6 +9886,7 @@ import {
9743
9886
  spawn
9744
9887
  } from "node:child_process";
9745
9888
  import { createServer } from "node:net";
9889
+ import { WebSocket as NodeWebSocket } from "ws";
9746
9890
  async function chooseLoopbackPort() {
9747
9891
  return new Promise((resolve11, reject) => {
9748
9892
  const server = createServer();
@@ -9783,7 +9927,7 @@ async function startCodexAppServerAttempt(codexBin, cwd, options, attempt) {
9783
9927
  });
9784
9928
  const child = spawn(codexBin, args, {
9785
9929
  cwd,
9786
- env: codexAppServerEnv(options.env),
9930
+ env: codexAppServerEnv(options.env, options.inheritEnv ?? true),
9787
9931
  stdio,
9788
9932
  detached: true
9789
9933
  });
@@ -9870,8 +10014,8 @@ function drainCodexAppServerOutput(child, streamName, onText = void 0) {
9870
10014
  });
9871
10015
  });
9872
10016
  }
9873
- function codexAppServerEnv(overrides) {
9874
- const env = { ...process.env };
10017
+ function codexAppServerEnv(overrides, inheritEnv) {
10018
+ const env = inheritEnv ? { ...process.env } : {};
9875
10019
  for (const key of blockedCodexAppServerEnvKeys) {
9876
10020
  delete env[key];
9877
10021
  }
@@ -9943,8 +10087,11 @@ function codexConfigArgs(options) {
9943
10087
  )
9944
10088
  ];
9945
10089
  }
9946
- async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new WebSocket(url)) {
9947
- const websocket = socketFactory(websocketUrl);
10090
+ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new NodeWebSocket(url)) {
10091
+ const websocket = await openCodexAppServerWebSocket(
10092
+ websocketUrl,
10093
+ socketFactory
10094
+ );
9948
10095
  const pending = /* @__PURE__ */ new Map();
9949
10096
  const notificationCallbacks = /* @__PURE__ */ new Set();
9950
10097
  const requestCallbacks = /* @__PURE__ */ new Set();
@@ -9994,7 +10141,6 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
9994
10141
  }
9995
10142
  }
9996
10143
  });
9997
- await waitForOpen(websocket);
9998
10144
  await initialize(websocket, pending);
9999
10145
  const request = (method, params) => {
10000
10146
  const id = state.nextId;
@@ -10018,6 +10164,27 @@ async function connectCodexAppServer(websocketUrl, socketFactory = (url) => new
10018
10164
  close: () => websocket.close()
10019
10165
  };
10020
10166
  }
10167
+ async function openCodexAppServerWebSocket(websocketUrl, socketFactory, timeoutMs = 2e3) {
10168
+ const deadline = Date.now() + timeoutMs;
10169
+ let lastError;
10170
+ while (Date.now() <= deadline) {
10171
+ const websocket = socketFactory(websocketUrl);
10172
+ try {
10173
+ await waitForOpen(websocket);
10174
+ return websocket;
10175
+ } catch (error) {
10176
+ lastError = error instanceof Error ? error : new Error(String(error));
10177
+ closeWebSocketAfterFailedOpen(websocket);
10178
+ }
10179
+ }
10180
+ throw lastError ?? new Error("websocket open failed");
10181
+ }
10182
+ function closeWebSocketAfterFailedOpen(websocket) {
10183
+ try {
10184
+ websocket.close();
10185
+ } catch (_error) {
10186
+ }
10187
+ }
10021
10188
  async function respondToServerRequest(websocket, request, callbacks) {
10022
10189
  try {
10023
10190
  if (callbacks.size === 0) {
@@ -10253,21 +10420,62 @@ function codexNotificationConsoleLogPayload(method, params) {
10253
10420
  };
10254
10421
  }
10255
10422
  function codexNotificationConsoleStats(method, params) {
10423
+ const creditExhaustion = creditExhaustionStats(method, params);
10256
10424
  switch (method) {
10257
10425
  case "thread/tokenUsage/updated":
10258
10426
  return {
10259
- token_usage_summary: tokenUsageSummary(params) ?? "seen (no details)"
10427
+ token_usage_summary: tokenUsageSummary(params) ?? "seen (no details)",
10428
+ ...creditExhaustion
10260
10429
  };
10261
10430
  case "account/rateLimits/updated":
10262
10431
  return {
10263
- rate_limit_summary: rateLimitSummary(params) ?? "seen (no details)"
10432
+ rate_limit_summary: rateLimitSummary(params) ?? "seen (no details)",
10433
+ ...creditExhaustion
10264
10434
  };
10265
10435
  case "warning":
10266
10436
  case "error":
10267
- return codexNotificationDiagnosticDetails(method, params);
10437
+ return {
10438
+ ...codexNotificationDiagnosticDetails(method, params),
10439
+ ...creditExhaustion
10440
+ };
10268
10441
  default:
10269
- return {};
10442
+ return creditExhaustion;
10443
+ }
10444
+ }
10445
+ function creditExhaustionStats(method, params) {
10446
+ if (!creditExhaustionDetected(method, params)) {
10447
+ return {};
10448
+ }
10449
+ return {
10450
+ credit_exhaustion_detected: true,
10451
+ credit_exhaustion_summary: "OpenAI credits exhausted. Add credits or upgrade the account to continue Codex jobs."
10452
+ };
10453
+ }
10454
+ function creditExhaustionDetected(method, params) {
10455
+ const haystack = [method, ...diagnosticStrings(params, 0)].join("\n").toLowerCase();
10456
+ return creditExhaustionMatchers.some((matcher) => haystack.includes(matcher));
10457
+ }
10458
+ function diagnosticStrings(value, depth) {
10459
+ if (depth > 4) {
10460
+ return [];
10461
+ }
10462
+ if (typeof value === "string") {
10463
+ return [value];
10464
+ }
10465
+ if (typeof value === "number" || typeof value === "boolean") {
10466
+ return [String(value)];
10467
+ }
10468
+ if (Array.isArray(value)) {
10469
+ return value.flatMap((entry) => diagnosticStrings(entry, depth + 1));
10470
+ }
10471
+ const object = objectValue4(value);
10472
+ if (object === void 0) {
10473
+ return [];
10270
10474
  }
10475
+ return Object.entries(object).flatMap(([key, entry]) => [
10476
+ key,
10477
+ ...diagnosticStrings(entry, depth + 1)
10478
+ ]);
10271
10479
  }
10272
10480
  function tokenUsageSummary(params) {
10273
10481
  const usage = objectValue4(params.tokenUsage) ?? objectValue4(params.token_usage) ?? objectValue4(params.usage) ?? params;
@@ -10359,11 +10567,19 @@ function integerValue2(value) {
10359
10567
  const parsed = Number.parseInt(value, 10);
10360
10568
  return Number.isSafeInteger(parsed) && parsed.toString() === value.trim() ? parsed : void 0;
10361
10569
  }
10570
+ var creditExhaustionMatchers;
10362
10571
  var init_codexNotificationConsoleStats = __esm({
10363
10572
  "src/codexNotificationConsoleStats.ts"() {
10364
10573
  "use strict";
10365
10574
  init_codexNotificationDiagnostics();
10366
10575
  init_protocol();
10576
+ creditExhaustionMatchers = [
10577
+ "insufficient_quota",
10578
+ "quota_exceeded",
10579
+ "billing_hard_limit",
10580
+ "billing hard limit",
10581
+ "credit_exhausted"
10582
+ ];
10367
10583
  }
10368
10584
  });
10369
10585
 
@@ -11266,6 +11482,263 @@ var init_localConfig = __esm({
11266
11482
  }
11267
11483
  });
11268
11484
 
11485
+ // src/remoteCodexExecutionContext.ts
11486
+ async function handleRemoteCodexExecControl(control, options) {
11487
+ const requestId = normalizedString(control.requestId) ?? null;
11488
+ const cwd = resolveAllowedCwd(control.cwd, options.allowedCwds);
11489
+ if (!cwd.ok) {
11490
+ return {
11491
+ instanceId: options.instanceId,
11492
+ controlType: control.type,
11493
+ requestId,
11494
+ ok: false,
11495
+ error: cwd.reason
11496
+ };
11497
+ }
11498
+ const command = normalizedString(control.command);
11499
+ if (command === void 0) {
11500
+ return {
11501
+ instanceId: options.instanceId,
11502
+ controlType: control.type,
11503
+ requestId,
11504
+ ok: false,
11505
+ error: "command_required"
11506
+ };
11507
+ }
11508
+ const args = normalizedStringList(control.args);
11509
+ if (args === void 0) {
11510
+ return {
11511
+ instanceId: options.instanceId,
11512
+ controlType: control.type,
11513
+ requestId,
11514
+ ok: false,
11515
+ error: "invalid_args"
11516
+ };
11517
+ }
11518
+ const extraEnv = normalizedEnvOverrideMap(control.env);
11519
+ if (extraEnv === void 0) {
11520
+ return {
11521
+ instanceId: options.instanceId,
11522
+ controlType: control.type,
11523
+ requestId,
11524
+ ok: false,
11525
+ error: "invalid_env"
11526
+ };
11527
+ }
11528
+ const timeoutMs = normalizedTimeoutMs(control.timeoutMs);
11529
+ if (timeoutMs === void 0) {
11530
+ return {
11531
+ instanceId: options.instanceId,
11532
+ controlType: control.type,
11533
+ requestId,
11534
+ ok: false,
11535
+ error: "invalid_timeout"
11536
+ };
11537
+ }
11538
+ if (options.sandboxRunner === void 0) {
11539
+ return {
11540
+ instanceId: options.instanceId,
11541
+ controlType: control.type,
11542
+ requestId,
11543
+ ok: false,
11544
+ cwd: cwd.cwd,
11545
+ matchedRoot: cwd.matchedRoot,
11546
+ command,
11547
+ args,
11548
+ error: "remote_codex_exec_sandbox_unavailable"
11549
+ };
11550
+ }
11551
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11552
+ try {
11553
+ const result = await runSandboxRunnerWithTimeout(
11554
+ options.sandboxRunner,
11555
+ {
11556
+ command,
11557
+ args,
11558
+ cwd: cwd.cwd,
11559
+ env: remoteExecEnv(options.inheritedEnv ?? process.env, extraEnv),
11560
+ stdin: typeof control.stdin === "string" ? control.stdin : void 0,
11561
+ timeoutMs
11562
+ },
11563
+ options.abortSignal
11564
+ );
11565
+ return {
11566
+ instanceId: options.instanceId,
11567
+ controlType: control.type,
11568
+ requestId,
11569
+ ok: true,
11570
+ cwd: cwd.cwd,
11571
+ matchedRoot: cwd.matchedRoot,
11572
+ command,
11573
+ args,
11574
+ startedAt,
11575
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
11576
+ exitCode: result.exitCode,
11577
+ signal: result.signal,
11578
+ stdout: result.stdout,
11579
+ stderr: result.stderr,
11580
+ stdoutTruncated: result.stdoutTruncated,
11581
+ stderrTruncated: result.stderrTruncated,
11582
+ timedOut: result.timedOut
11583
+ };
11584
+ } catch (error) {
11585
+ return {
11586
+ instanceId: options.instanceId,
11587
+ controlType: control.type,
11588
+ requestId,
11589
+ ok: false,
11590
+ cwd: cwd.cwd,
11591
+ matchedRoot: cwd.matchedRoot,
11592
+ command,
11593
+ args,
11594
+ error: error instanceof Error ? `remote_codex_exec_failed:${error.message}` : "remote_codex_exec_failed"
11595
+ };
11596
+ }
11597
+ }
11598
+ function runSandboxRunnerWithTimeout(sandboxRunner, request, parentAbortSignal) {
11599
+ const abortController = new AbortController();
11600
+ const timeoutResult = {
11601
+ exitCode: null,
11602
+ signal: null,
11603
+ stdout: "",
11604
+ stderr: "remote_codex_exec_timed_out",
11605
+ stdoutTruncated: false,
11606
+ stderrTruncated: false,
11607
+ timedOut: true
11608
+ };
11609
+ const requestWithSignal = {
11610
+ ...request,
11611
+ abortSignal: abortController.signal
11612
+ };
11613
+ let timeout;
11614
+ let timedOut = false;
11615
+ const onParentAbort = () => {
11616
+ abortController.abort(parentAbortSignal?.reason);
11617
+ };
11618
+ if (parentAbortSignal?.aborted === true) {
11619
+ onParentAbort();
11620
+ } else {
11621
+ parentAbortSignal?.addEventListener("abort", onParentAbort, {
11622
+ once: true
11623
+ });
11624
+ }
11625
+ const runnerPromise = sandboxRunner(requestWithSignal).catch((error) => {
11626
+ if (timedOut) {
11627
+ return timeoutResult;
11628
+ }
11629
+ throw error;
11630
+ });
11631
+ const timeoutPromise = new Promise((resolve11) => {
11632
+ timeout = setTimeout(() => {
11633
+ timedOut = true;
11634
+ resolve11(timeoutResult);
11635
+ abortController.abort();
11636
+ }, request.timeoutMs);
11637
+ });
11638
+ return Promise.race([runnerPromise, timeoutPromise]).finally(() => {
11639
+ if (timeout !== void 0) {
11640
+ clearTimeout(timeout);
11641
+ }
11642
+ parentAbortSignal?.removeEventListener("abort", onParentAbort);
11643
+ });
11644
+ }
11645
+ function remoteExecEnv(inheritedEnv, extraEnv) {
11646
+ return {
11647
+ ...inheritedEnv.PATH === void 0 ? {} : { PATH: inheritedEnv.PATH },
11648
+ ...extraEnv,
11649
+ LINZUMI_REMOTE_CODEX_EXEC: "1"
11650
+ };
11651
+ }
11652
+ function normalizedString(value) {
11653
+ if (typeof value !== "string") {
11654
+ return void 0;
11655
+ }
11656
+ const trimmed = value.trim();
11657
+ return trimmed === "" ? void 0 : trimmed;
11658
+ }
11659
+ function normalizedStringList(value) {
11660
+ if (value === void 0) {
11661
+ return [];
11662
+ }
11663
+ if (!Array.isArray(value)) {
11664
+ return void 0;
11665
+ }
11666
+ const values = value.flatMap(
11667
+ (entry) => typeof entry === "string" ? [entry] : []
11668
+ );
11669
+ return values.length === value.length ? values : void 0;
11670
+ }
11671
+ function normalizedEnvOverrideMap(value) {
11672
+ if (value === void 0) {
11673
+ return {};
11674
+ }
11675
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
11676
+ return void 0;
11677
+ }
11678
+ const entries = Object.entries(value).flatMap(([key, entry]) => {
11679
+ if (ignoredEnvOverrideKeys.has(key) && typeof entry === "string") {
11680
+ return [];
11681
+ }
11682
+ if (typeof entry !== "string" || !validEnvKey(key) || reservedEnvKeys.has(key) || deniedEnvKeys.has(key) || deniedEnvPrefixes.some((prefix) => key.startsWith(prefix))) {
11683
+ return [];
11684
+ }
11685
+ return [[key, entry]];
11686
+ });
11687
+ const expectedEntries = Object.entries(value).filter(
11688
+ ([key, entry]) => !(ignoredEnvOverrideKeys.has(key) && typeof entry === "string")
11689
+ );
11690
+ return entries.length === expectedEntries.length ? Object.fromEntries(entries) : void 0;
11691
+ }
11692
+ function validEnvKey(key) {
11693
+ return /^[A-Z_][A-Z0-9_]*$/.test(key);
11694
+ }
11695
+ function normalizedTimeoutMs(value) {
11696
+ if (value === void 0) {
11697
+ return defaultTimeoutMs;
11698
+ }
11699
+ if (!Number.isInteger(value) || value <= 0 || value > maxTimeoutMs) {
11700
+ return void 0;
11701
+ }
11702
+ return value;
11703
+ }
11704
+ var defaultTimeoutMs, maxTimeoutMs, reservedEnvKeys, ignoredEnvOverrideKeys, deniedEnvKeys, deniedEnvPrefixes;
11705
+ var init_remoteCodexExecutionContext = __esm({
11706
+ "src/remoteCodexExecutionContext.ts"() {
11707
+ "use strict";
11708
+ init_localCapabilities();
11709
+ defaultTimeoutMs = 12e4;
11710
+ maxTimeoutMs = 3e5;
11711
+ reservedEnvKeys = /* @__PURE__ */ new Set([
11712
+ "HOME",
11713
+ "LINZUMI_REMOTE_CODEX_EXEC",
11714
+ "NODE_OPTIONS",
11715
+ "SHELL",
11716
+ "TMPDIR"
11717
+ ]);
11718
+ ignoredEnvOverrideKeys = /* @__PURE__ */ new Set(["PATH"]);
11719
+ deniedEnvKeys = /* @__PURE__ */ new Set([
11720
+ "BASH_ENV",
11721
+ "ENV",
11722
+ "NODE_PATH",
11723
+ "PERL5LIB",
11724
+ "PYTHONHOME",
11725
+ "PYTHONPATH",
11726
+ "RUBYLIB",
11727
+ "RUBYOPT",
11728
+ "ZDOTDIR"
11729
+ ]);
11730
+ deniedEnvPrefixes = [
11731
+ "AWS_",
11732
+ "DYLD_",
11733
+ "GIT_",
11734
+ "LD_",
11735
+ "NPM_CONFIG_",
11736
+ "OPENAI_",
11737
+ "SSH_"
11738
+ ];
11739
+ }
11740
+ });
11741
+
11269
11742
  // src/helloLinzumiProject.ts
11270
11743
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
11271
11744
  import { dirname as dirname3, join as join7, resolve as resolve5 } from "node:path";
@@ -13778,14 +14251,17 @@ function codeServerDependencyStatus(args) {
13778
14251
  }) : probeTool(args.codeServerBin, args.cwd);
13779
14252
  }
13780
14253
  function assertStartDependencies(status) {
13781
- if (!status.node.available) {
14254
+ assertRunnerConnectionDependencies(status);
14255
+ if (!status.codex.available) {
13782
14256
  throw new Error(
13783
- "Node.js is not available. Install Node.js 20+, then rerun the bootstrap command."
14257
+ `Codex is not available at ${status.codex.command}. Install Codex or pass --codex-bin <path>.`
13784
14258
  );
13785
14259
  }
13786
- if (!status.codex.available) {
14260
+ }
14261
+ function assertRunnerConnectionDependencies(status) {
14262
+ if (!status.node.available) {
13787
14263
  throw new Error(
13788
- `Codex is not available at ${status.codex.command}. Install Codex or pass --codex-bin <path>.`
14264
+ "Node.js is not available. Install Node.js 20+, then rerun the bootstrap command."
13789
14265
  );
13790
14266
  }
13791
14267
  if (status.editorRuntime?.status === "unavailable" || status.codeServer?.available === false && status.codeServer.reason !== "not_configured") {
@@ -14157,7 +14633,7 @@ var linzumiCliVersion, linzumiCliVersionText;
14157
14633
  var init_version = __esm({
14158
14634
  "src/version.ts"() {
14159
14635
  "use strict";
14160
- linzumiCliVersion = "0.0.74-beta";
14636
+ linzumiCliVersion = "0.0.76-beta";
14161
14637
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
14162
14638
  }
14163
14639
  });
@@ -14175,11 +14651,7 @@ import {
14175
14651
  import { dirname as dirname7, join as join11 } from "node:path";
14176
14652
  function runnerLockPath(machineId, configPath = localConfigPath(), linzumiUrl) {
14177
14653
  const lockName = linzumiUrl === void 0 ? encodeURIComponent(machineId) : localConfigScopeFileStem(linzumiUrl);
14178
- return join11(
14179
- dirname7(configPath),
14180
- "runners",
14181
- `${lockName}.lock`
14182
- );
14654
+ return join11(dirname7(configPath), "runners", `${lockName}.lock`);
14183
14655
  }
14184
14656
  function acquireRunnerLock(options) {
14185
14657
  const path2 = runnerLockPath(
@@ -14196,6 +14668,7 @@ function acquireRunnerLock(options) {
14196
14668
  cwd: options.cwd,
14197
14669
  workspace: options.workspace,
14198
14670
  ...options.linzumiUrl === void 0 ? {} : { linzumiUrl: kandanHttpBaseUrl(options.linzumiUrl) },
14671
+ ...options.launchSource === void 0 ? {} : { launchSource: options.launchSource },
14199
14672
  startedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
14200
14673
  cliVersion: options.cliVersion ?? linzumiCliVersion
14201
14674
  };
@@ -14482,12 +14955,16 @@ function updateRunnerConsoleDashboard(state, event, payload, nowMs) {
14482
14955
  case "codex.notification": {
14483
14956
  const tokenUsage = stringValue4(payload.token_usage_summary);
14484
14957
  const rateLimit = stringValue4(payload.rate_limit_summary);
14958
+ const creditExhaustion = stringValue4(payload.credit_exhaustion_summary);
14485
14959
  if (tokenUsage !== void 0) {
14486
14960
  state.tokenUsage = tokenUsage;
14487
14961
  }
14488
14962
  if (rateLimit !== void 0) {
14489
14963
  state.rateLimit = mergeRateLimitSummary(state.rateLimit, rateLimit);
14490
14964
  }
14965
+ if (creditExhaustion !== void 0) {
14966
+ state.creditExhaustion = creditExhaustion;
14967
+ }
14491
14968
  const method = stringValue4(payload.method);
14492
14969
  const metadata = objectValue5(payload.metadata);
14493
14970
  const metadataHasJobSignal = stringValue4(metadata?.threadId) !== void 0 || stringValue4(metadata?.turnId) !== void 0 || stringValue4(metadata?.itemId) !== void 0;
@@ -14499,8 +14976,9 @@ function updateRunnerConsoleDashboard(state, event, payload, nowMs) {
14499
14976
  if (job !== void 0) {
14500
14977
  job.codexThreadId = codexThreadId ?? job.codexThreadId;
14501
14978
  job.tokenUsage = tokenUsage ?? job.tokenUsage;
14979
+ job.creditExhaustion = creditExhaustion ?? job.creditExhaustion;
14502
14980
  job.latestCodexEventId = codexEventId(payload);
14503
- job.eventType = method ?? event;
14981
+ job.eventType = payload.credit_exhaustion_detected === true ? "credit_exhausted" : method ?? event;
14504
14982
  job.turnId = stringValue4(metadata?.turnId) ?? job.turnId;
14505
14983
  job.updatedAtMs = nowMs;
14506
14984
  }
@@ -14529,7 +15007,7 @@ function updateRunnerConsoleDashboard(state, event, payload, nowMs) {
14529
15007
  rememberRawLine(state, event, payload, previousJobKey);
14530
15008
  }
14531
15009
  function renderRunnerConsoleDashboard(state, nowMs) {
14532
- if (state.jobs.size === 0 && state.tokenUsage === void 0 && state.rateLimit === void 0) {
15010
+ if (state.jobs.size === 0 && state.tokenUsage === void 0 && state.rateLimit === void 0 && state.creditExhaustion === void 0) {
14533
15011
  return void 0;
14534
15012
  }
14535
15013
  const jobs = dashboardJobs(state);
@@ -14566,6 +15044,7 @@ function renderDashboardTable(state, jobs, nowMs) {
14566
15044
  "Linzumi Commander",
14567
15045
  "",
14568
15046
  `Jobs: ${jobs.length} Last update: ${timeAgo(state.lastUpdateAtMs, nowMs)}`,
15047
+ ...creditExhaustionBanner(state.creditExhaustion),
14569
15048
  `Overall Token Usage: ${state.tokenUsage ?? "?"}`,
14570
15049
  `Account rate limits: ${state.rateLimit ?? "?"}`,
14571
15050
  "Controls: up/down select | enter raw job | r raw stream | esc table",
@@ -14574,6 +15053,12 @@ function renderDashboardTable(state, jobs, nowMs) {
14574
15053
  ""
14575
15054
  ].join("\n");
14576
15055
  }
15056
+ function creditExhaustionBanner(summary) {
15057
+ if (summary === void 0) {
15058
+ return [];
15059
+ }
15060
+ return [`*** ACTION REQUIRED: ${summary} ***`];
15061
+ }
14577
15062
  function dashboardTableModel(state, jobs, nowMs) {
14578
15063
  const selectedJobKey = selectedDashboardJobKey(state, jobs);
14579
15064
  const rows = jobs.length === 0 ? [dashboardTableHeader(), emptyJobRow()] : [
@@ -14639,7 +15124,9 @@ function formatRunnerConsoleEvent(event, payload) {
14639
15124
  case "codex.turn_started":
14640
15125
  return `Codex turn started: id=${text(payload.turn_id)}`;
14641
15126
  case "codex.notification": {
15127
+ const creditExhaustion = stringValue4(payload.credit_exhaustion_summary);
14642
15128
  const summary = [
15129
+ creditExhaustion === void 0 ? void 0 : `URGENT ${creditExhaustion}`,
14643
15130
  stringValue4(payload.token_usage_summary),
14644
15131
  stringValue4(payload.rate_limit_summary),
14645
15132
  stringValue4(payload.diagnostic_summary)
@@ -15142,6 +15629,7 @@ function tuiHeaderContent(state, jobCount, nowMs) {
15142
15629
  return [
15143
15630
  "Linzumi Commander",
15144
15631
  `Jobs: ${jobCount} Last update: ${timeAgo(state.lastUpdateAtMs, nowMs)}`,
15632
+ ...creditExhaustionBanner(state.creditExhaustion),
15145
15633
  `Overall Token Usage: ${state.tokenUsage ?? "?"}`,
15146
15634
  `Account rate limits: ${state.rateLimit ?? "?"}`,
15147
15635
  "Controls: click row/enter raw job | r raw stream | esc/back table | mouse wheel scroll"
@@ -15200,6 +15688,7 @@ function dashboardJobForPayload(state, payload, nowMs) {
15200
15688
  lastAssistantAtMs: void 0,
15201
15689
  latestCodexEventId: void 0,
15202
15690
  eventType: void 0,
15691
+ creditExhaustion: void 0,
15203
15692
  queueDepth: void 0,
15204
15693
  turnId: void 0,
15205
15694
  updatedAtMs: nowMs
@@ -15262,6 +15751,7 @@ function dashboardJobForKey(state, key, nowMs) {
15262
15751
  lastAssistantAtMs: void 0,
15263
15752
  latestCodexEventId: void 0,
15264
15753
  eventType: void 0,
15754
+ creditExhaustion: void 0,
15265
15755
  queueDepth: void 0,
15266
15756
  turnId: void 0,
15267
15757
  updatedAtMs: nowMs
@@ -15410,6 +15900,7 @@ var init_runnerConsoleReporter = __esm({
15410
15900
  selectedJobKey: void 0,
15411
15901
  tokenUsage: void 0,
15412
15902
  rateLimit: void 0,
15903
+ creditExhaustion: void 0,
15413
15904
  lastUpdateAtMs: void 0
15414
15905
  };
15415
15906
  maxRawLines = 500;
@@ -16135,8 +16626,281 @@ var init_signupTaskSuggestions = __esm({
16135
16626
  }
16136
16627
  });
16137
16628
 
16629
+ // src/remoteCodexSandboxRunner.ts
16630
+ import { spawn as spawn7 } from "node:child_process";
16631
+ import { existsSync as existsSync9, realpathSync as realpathSync5 } from "node:fs";
16632
+ import { dirname as dirname9, isAbsolute as isAbsolute3 } from "node:path";
16633
+ function createConfiguredRemoteCodexSandboxRunner(args) {
16634
+ const kind = normalizedSandboxKind(args.env.LINZUMI_REMOTE_CODEX_SANDBOX);
16635
+ if (kind === void 0) {
16636
+ return void 0;
16637
+ }
16638
+ const sandboxBin = requiredSandboxBin(kind, args.env);
16639
+ switch (kind) {
16640
+ case "bubblewrap":
16641
+ if (args.platform !== "linux") {
16642
+ throw new Error("bubblewrap remote Codex sandbox requires linux");
16643
+ }
16644
+ break;
16645
+ case "macos-seatbelt":
16646
+ if (args.platform !== "darwin") {
16647
+ throw new Error("macos-seatbelt remote Codex sandbox requires darwin");
16648
+ }
16649
+ break;
16650
+ }
16651
+ if (!existsSync9(sandboxBin)) {
16652
+ throw new Error(`remote Codex sandbox binary not found: ${sandboxBin}`);
16653
+ }
16654
+ return createRemoteCodexSandboxRunner({
16655
+ kind,
16656
+ sandboxBin
16657
+ });
16658
+ }
16659
+ function createRemoteCodexSandboxRunner(config, deps = {}) {
16660
+ const resolvedDeps = {
16661
+ platform: deps.platform ?? process.platform,
16662
+ exists: deps.exists ?? existsSync9,
16663
+ spawnProcess: deps.spawnProcess ?? spawn7
16664
+ };
16665
+ return (request) => runSandboxInvocation(
16666
+ buildRemoteCodexSandboxInvocation(config, request, resolvedDeps),
16667
+ request,
16668
+ resolvedDeps.spawnProcess
16669
+ );
16670
+ }
16671
+ function buildRemoteCodexSandboxInvocation(config, request, deps) {
16672
+ switch (config.kind) {
16673
+ case "bubblewrap":
16674
+ return {
16675
+ command: config.sandboxBin,
16676
+ args: bubblewrapArgs(config.sandboxBin, request, deps.exists),
16677
+ env: {},
16678
+ cwd: request.cwd
16679
+ };
16680
+ case "macos-seatbelt":
16681
+ return {
16682
+ command: config.sandboxBin,
16683
+ args: [
16684
+ "-p",
16685
+ macosSeatbeltProfile(request, deps.exists),
16686
+ "--",
16687
+ request.command,
16688
+ ...request.args
16689
+ ],
16690
+ env: request.env,
16691
+ cwd: request.cwd
16692
+ };
16693
+ }
16694
+ }
16695
+ function runSandboxInvocation(invocation, request, spawnProcess) {
16696
+ return new Promise((resolve11, reject) => {
16697
+ const child = spawnProcess(invocation.command, invocation.args, {
16698
+ cwd: invocation.cwd,
16699
+ env: invocation.env,
16700
+ shell: false,
16701
+ stdio: "pipe"
16702
+ });
16703
+ const stdout = createBoundedOutputBuffer();
16704
+ const stderr = createBoundedOutputBuffer();
16705
+ let settled = false;
16706
+ const killChild = () => {
16707
+ child.kill("SIGKILL");
16708
+ };
16709
+ child.stdout.on("data", (chunk) => stdout.append(chunk));
16710
+ child.stderr.on("data", (chunk) => stderr.append(chunk));
16711
+ child.on("error", (error) => {
16712
+ if (!settled) {
16713
+ settled = true;
16714
+ reject(error);
16715
+ }
16716
+ });
16717
+ child.on("close", (exitCode, signal) => {
16718
+ if (!settled) {
16719
+ settled = true;
16720
+ resolve11({
16721
+ exitCode,
16722
+ signal,
16723
+ stdout: stdout.value(),
16724
+ stderr: stderr.value(),
16725
+ stdoutTruncated: stdout.truncated(),
16726
+ stderrTruncated: stderr.truncated(),
16727
+ timedOut: false
16728
+ });
16729
+ }
16730
+ });
16731
+ if (request.abortSignal.aborted) {
16732
+ killChild();
16733
+ } else {
16734
+ request.abortSignal.addEventListener("abort", killChild, { once: true });
16735
+ }
16736
+ child.stdin.end(request.stdin ?? "");
16737
+ });
16738
+ }
16739
+ function bubblewrapArgs(sandboxBin, request, exists) {
16740
+ const envArgs = Object.entries(request.env).flatMap(([key, value]) => [
16741
+ "--setenv",
16742
+ key,
16743
+ value
16744
+ ]);
16745
+ const readOnlyBindArgs = uniqueStrings2([
16746
+ ...linuxReadOnlyRoots,
16747
+ ...isAbsolute3(request.command) ? [dirname9(request.command)] : [],
16748
+ dirname9(sandboxBin)
16749
+ ]).flatMap((path2) => exists(path2) ? ["--ro-bind", path2, path2] : []);
16750
+ const cwdParentDirs = parentDirs(request.cwd).flatMap((path2) => [
16751
+ "--dir",
16752
+ path2
16753
+ ]);
16754
+ return [
16755
+ "--unshare-all",
16756
+ "--die-with-parent",
16757
+ "--new-session",
16758
+ "--clearenv",
16759
+ ...envArgs,
16760
+ "--tmpfs",
16761
+ "/tmp",
16762
+ "--proc",
16763
+ "/proc",
16764
+ "--dev",
16765
+ "/dev",
16766
+ ...cwdParentDirs,
16767
+ ...readOnlyBindArgs,
16768
+ "--bind",
16769
+ request.cwd,
16770
+ request.cwd,
16771
+ "--chdir",
16772
+ request.cwd,
16773
+ "--",
16774
+ request.command,
16775
+ ...request.args
16776
+ ];
16777
+ }
16778
+ function macosSeatbeltProfile(request, exists) {
16779
+ const readableRoots = uniqueStrings2([
16780
+ ...macosReadOnlyRoots,
16781
+ ...isAbsolute3(request.command) ? [dirname9(request.command)] : []
16782
+ ]).filter(exists);
16783
+ const readableRules = readableRoots.map((path2) => `(allow file-read* (subpath ${sandboxString(path2)}))`).join("\n");
16784
+ const writableTempRules = macosWritableTempRoots.filter(exists).flatMap((path2) => [
16785
+ `(allow file-read* (subpath ${sandboxString(path2)}))`,
16786
+ `(allow file-write* (subpath ${sandboxString(path2)}))`
16787
+ ]).join("\n");
16788
+ const cwdRules = existingPathAliases(request.cwd).flatMap((path2) => [
16789
+ `(allow file-read* (subpath ${sandboxString(path2)}))`,
16790
+ `(allow file-write* (subpath ${sandboxString(path2)}))`
16791
+ ]).join("\n");
16792
+ return [
16793
+ "(version 1)",
16794
+ "(deny default)",
16795
+ "(allow process*)",
16796
+ "(allow signal (target self))",
16797
+ "(allow sysctl-read)",
16798
+ "(allow file-read-metadata)",
16799
+ '(allow file-read-data (literal "/"))',
16800
+ '(allow file-read* (literal "/dev/null"))',
16801
+ '(allow file-read* (literal "/dev/random"))',
16802
+ '(allow file-read* (literal "/dev/urandom"))',
16803
+ readableRules,
16804
+ writableTempRules,
16805
+ cwdRules
16806
+ ].join("\n");
16807
+ }
16808
+ function createBoundedOutputBuffer() {
16809
+ let bytes = 0;
16810
+ let output = "";
16811
+ let isTruncated = false;
16812
+ return {
16813
+ append: (chunk) => {
16814
+ const text2 = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
16815
+ const remaining = maxCapturedOutputBytes - bytes;
16816
+ if (remaining <= 0) {
16817
+ isTruncated = true;
16818
+ return;
16819
+ }
16820
+ const buffer = Buffer.from(text2, "utf8");
16821
+ if (buffer.byteLength <= remaining) {
16822
+ bytes += buffer.byteLength;
16823
+ output += text2;
16824
+ return;
16825
+ }
16826
+ bytes = maxCapturedOutputBytes;
16827
+ output += buffer.subarray(0, remaining).toString("utf8");
16828
+ isTruncated = true;
16829
+ },
16830
+ value: () => output,
16831
+ truncated: () => isTruncated
16832
+ };
16833
+ }
16834
+ function normalizedSandboxKind(value) {
16835
+ switch (value) {
16836
+ case void 0:
16837
+ case "":
16838
+ return void 0;
16839
+ case "bubblewrap":
16840
+ case "macos-seatbelt":
16841
+ return value;
16842
+ default:
16843
+ throw new Error(`unsupported remote Codex sandbox: ${value}`);
16844
+ }
16845
+ }
16846
+ function requiredSandboxBin(kind, env) {
16847
+ const value = env.LINZUMI_REMOTE_CODEX_SANDBOX_BIN;
16848
+ if (value === void 0 || value.trim() === "") {
16849
+ throw new Error(`LINZUMI_REMOTE_CODEX_SANDBOX_BIN is required for ${kind}`);
16850
+ }
16851
+ if (!isAbsolute3(value)) {
16852
+ throw new Error("LINZUMI_REMOTE_CODEX_SANDBOX_BIN must be absolute");
16853
+ }
16854
+ return value;
16855
+ }
16856
+ function parentDirs(path2) {
16857
+ const parts = path2.split("/").filter((part) => part !== "");
16858
+ const parents = parts.slice(0, -1);
16859
+ return parents.map(
16860
+ (_part, index) => `/${parents.slice(0, index + 1).join("/")}`
16861
+ );
16862
+ }
16863
+ function uniqueStrings2(values) {
16864
+ return Array.from(new Set(values));
16865
+ }
16866
+ function existingPathAliases(path2) {
16867
+ return uniqueStrings2([path2, realpathSync5(path2)]);
16868
+ }
16869
+ function sandboxString(value) {
16870
+ return JSON.stringify(value);
16871
+ }
16872
+ var maxCapturedOutputBytes, linuxReadOnlyRoots, macosReadOnlyRoots, macosWritableTempRoots;
16873
+ var init_remoteCodexSandboxRunner = __esm({
16874
+ "src/remoteCodexSandboxRunner.ts"() {
16875
+ "use strict";
16876
+ maxCapturedOutputBytes = 512 * 1024;
16877
+ linuxReadOnlyRoots = [
16878
+ "/bin",
16879
+ "/etc/pki",
16880
+ "/etc/ssl",
16881
+ "/lib",
16882
+ "/lib64",
16883
+ "/nix/store",
16884
+ "/usr",
16885
+ "/usr/local"
16886
+ ];
16887
+ macosReadOnlyRoots = [
16888
+ "/System",
16889
+ "/Library",
16890
+ "/etc",
16891
+ "/private/etc",
16892
+ "/usr",
16893
+ "/bin",
16894
+ "/sbin",
16895
+ "/opt/homebrew",
16896
+ "/nix/store"
16897
+ ];
16898
+ macosWritableTempRoots = ["/tmp", "/private/tmp"];
16899
+ }
16900
+ });
16901
+
16138
16902
  // src/runner.ts
16139
- import { spawn as spawn7, spawnSync as spawnSync4 } from "node:child_process";
16903
+ import { spawn as spawn8, spawnSync as spawnSync4 } from "node:child_process";
16140
16904
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
16141
16905
  import {
16142
16906
  chmodSync as chmodSync2,
@@ -16144,13 +16908,13 @@ import {
16144
16908
  mkdirSync as mkdirSync9,
16145
16909
  mkdtempSync as mkdtempSync4,
16146
16910
  readdirSync as readdirSync2,
16147
- realpathSync as realpathSync5,
16911
+ realpathSync as realpathSync6,
16148
16912
  rmSync as rmSync4,
16149
16913
  statSync
16150
16914
  } from "node:fs";
16151
16915
  import { createServer as createServer3 } from "node:http";
16152
16916
  import { hostname as hostname2, tmpdir as tmpdir3 } from "node:os";
16153
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join14, resolve as resolve7 } from "node:path";
16917
+ import { dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve7 } from "node:path";
16154
16918
  async function runLocalCodexRunner(options) {
16155
16919
  const log = makeRunnerLogger(options);
16156
16920
  const cleanup = {
@@ -16173,6 +16937,7 @@ async function runLocalCodexRunner(options) {
16173
16937
  cwd: options.cwd,
16174
16938
  workspace: runnerWorkspaceSlug(options) ?? null,
16175
16939
  linzumiUrl: options.kandanUrl,
16940
+ launchSource: options.launchSource,
16176
16941
  configPath: options.runnerLockConfigPath
16177
16942
  });
16178
16943
  cleanup.actions.push(() => runnerLock.release());
@@ -16214,6 +16979,7 @@ async function runThreadCodexWorker(options) {
16214
16979
  }
16215
16980
  async function openLocalCodexRunner(options, log, cleanup, close) {
16216
16981
  const agentProviders = availableRunnerAgentProviders(options);
16982
+ const localCodexAppServerAvailable = hasLocalCodexAppServerCapability(options);
16217
16983
  const allowedForwardPorts = options.allowedForwardPorts ?? [];
16218
16984
  const liveForwardPorts = new Set(allowedForwardPorts);
16219
16985
  const managedForwardPorts = /* @__PURE__ */ new Set();
@@ -16274,6 +17040,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16274
17040
  });
16275
17041
  const startupAllowedCwds = normalizeAllowedCwds(options.allowedCwds);
16276
17042
  const allowedCwds = { value: [...startupAllowedCwds] };
17043
+ const remoteCodexSandboxRunner = options.remoteCodexSandboxRunner ?? createConfiguredRemoteCodexSandboxRunner({
17044
+ env: process.env,
17045
+ platform: process.platform
17046
+ });
17047
+ const activeRemoteCodexExecRequests = /* @__PURE__ */ new Map();
16277
17048
  const missingAllowedCwds = {
16278
17049
  value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
16279
17050
  };
@@ -16290,6 +17061,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16290
17061
  queuedCandidates: /* @__PURE__ */ new Map()
16291
17062
  };
16292
17063
  cleanup.actions.push(() => {
17064
+ for (const controller of activeRemoteCodexExecRequests.values()) {
17065
+ controller.abort(new Error("local runner stopped"));
17066
+ }
17067
+ activeRemoteCodexExecRequests.clear();
16293
17068
  if (localEditorState.value.status === "running") {
16294
17069
  localEditorState.value.process.kill("SIGINT");
16295
17070
  localEditorState.value.collaboration?.process.kill("SIGINT");
@@ -16314,8 +17089,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16314
17089
  };
16315
17090
  });
16316
17091
  const capabilitiesPayload = () => ({
16317
- codexAppServer: true,
16318
- codexRemoteTui: true,
17092
+ codexAppServer: localCodexAppServerAvailable,
17093
+ codexRemoteTui: localCodexAppServerAvailable,
16319
17094
  agentProviders,
16320
17095
  defaultAgentProvider: "codex",
16321
17096
  startInstance: allowedCwds.value.length > 0,
@@ -16331,6 +17106,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16331
17106
  portForwarding: liveForwardPorts.size > 0,
16332
17107
  tcpForwarding: true,
16333
17108
  linzumiMcp: true,
17109
+ remoteCodexExecution: remoteCodexSandboxRunner !== void 0 && allowedCwds.value.length > 0,
17110
+ remoteCodexExecutionStatus: remoteCodexSandboxRunner === void 0 ? "sandbox_unavailable" : allowedCwds.value.length > 0 ? "ready" : "cwd_unavailable",
16334
17111
  allowedPorts: Array.from(liveForwardPorts).sort(
16335
17112
  (left, right) => left - right
16336
17113
  ),
@@ -16380,18 +17157,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16380
17157
  const replacedRunners = replacementRunnerSummaries(
16381
17158
  objectValue(joinResponse)?.replaced_runners
16382
17159
  );
16383
- const started = options.codexUrl === void 0 ? await startOwnedCodexAppServer(options) : void 0;
17160
+ const started = options.codexUrl === void 0 && localCodexAppServerAvailable ? await startOwnedCodexAppServer(options) : void 0;
16384
17161
  if (started !== void 0) {
16385
17162
  cleanup.actions.push(() => {
16386
17163
  started.stop();
16387
17164
  });
16388
17165
  }
16389
17166
  const codexUrl = options.codexUrl ?? started?.url;
16390
- if (codexUrl === void 0) {
16391
- throw new Error("missing codex app-server websocket URL");
16392
- }
16393
17167
  if (started !== void 0) {
16394
- const appServerPort = explicitUrlPort(codexUrl);
17168
+ const appServerPort = explicitUrlPort(started.url);
16395
17169
  if (appServerPort !== void 0) {
16396
17170
  markCommanderManagedForwardPort(appServerPort, {
16397
17171
  processName: "Codex app-server",
@@ -17081,15 +17855,20 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17081
17855
  };
17082
17856
  void state.exited.then(handleExitAfterInitialStatus);
17083
17857
  };
17084
- const codex = await connectCodexAppServer(codexUrl);
17085
- cleanup.actions.push(() => codex.close());
17858
+ const codex = codexUrl === void 0 ? void 0 : await connectCodexAppServer(codexUrl);
17859
+ if (codex !== void 0) {
17860
+ cleanup.actions.push(() => codex.close());
17861
+ }
17862
+ if (options.channelSession !== void 0 && codex === void 0) {
17863
+ throw new Error("channel session requires a Codex app-server connection");
17864
+ }
17086
17865
  const seq = { value: 0 };
17087
17866
  const discoveredCodexThreads = { value: [] };
17088
17867
  const runtimeDefaults = runnerRuntimeDefaults(options);
17089
17868
  const instancePayload = {
17090
17869
  instanceId,
17091
17870
  clientId,
17092
- codexUrl,
17871
+ ...codexUrl === void 0 ? {} : { codexUrl },
17093
17872
  tuiLaunched: options.launchTui,
17094
17873
  cwd: options.cwd,
17095
17874
  hostname: runnerHost,
@@ -17108,7 +17887,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17108
17887
  workspace: runnerWorkspaceSlug(options) ?? null,
17109
17888
  version: linzumiCliVersion,
17110
17889
  instanceId,
17111
- codexUrl,
17890
+ codexUrl: codexUrl ?? null,
17112
17891
  replacedRunners
17113
17892
  });
17114
17893
  const channelSession = options.channelSession === void 0 ? void 0 : await attachChannelSession({
@@ -17422,6 +18201,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17422
18201
  options,
17423
18202
  agentProviders,
17424
18203
  allowedCwds,
18204
+ remoteCodexSandboxRunner,
18205
+ activeRemoteCodexExecRequests,
17425
18206
  activeClaudeCodeSessions,
17426
18207
  pendingClaudeCodeApprovals,
17427
18208
  ensureClaudeCodeForwardPortSession,
@@ -17444,7 +18225,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17444
18225
  const heartbeatPayload = () => ({
17445
18226
  instanceId,
17446
18227
  clientId,
17447
- codexUrl,
18228
+ ...codexUrl === void 0 ? {} : { codexUrl },
17448
18229
  cwd: options.cwd,
17449
18230
  hostname: runnerHost,
17450
18231
  workspace: runnerWorkspaceSlug(options) ?? null,
@@ -17476,10 +18257,13 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17476
18257
  );
17477
18258
  cleanup.actions.push(() => forwardTcp.close());
17478
18259
  const channelCodexThreadId = channelSession?.currentCodexThreadId();
17479
- if (options.launchTui && channelCodexThreadId !== void 0) {
18260
+ if (options.launchTui && codex === void 0) {
18261
+ throw new Error("Codex TUI requires a Codex app-server connection");
18262
+ }
18263
+ if (options.launchTui && codex !== void 0 && channelCodexThreadId !== void 0) {
17480
18264
  await prepareCodexThreadForTuiResume(codex, channelCodexThreadId);
17481
18265
  }
17482
- const tui = options.launchTui ? launchCodexTui(
18266
+ const tui = options.launchTui && codexUrl !== void 0 ? launchCodexTui(
17483
18267
  options.codexBin,
17484
18268
  codexUrl,
17485
18269
  options.cwd,
@@ -17492,7 +18276,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17492
18276
  tui.kill("SIGINT");
17493
18277
  });
17494
18278
  }
17495
- codex.onNotification((notification) => {
18279
+ codex?.onNotification((notification) => {
17496
18280
  seq.value += 1;
17497
18281
  const params = notification.params ?? {};
17498
18282
  const logPayload = codexNotificationConsoleLogPayload(
@@ -17747,6 +18531,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17747
18531
  options,
17748
18532
  agentProviders,
17749
18533
  allowedCwds,
18534
+ remoteCodexSandboxRunner,
18535
+ activeRemoteCodexExecRequests,
17750
18536
  activeClaudeCodeSessions,
17751
18537
  pendingClaudeCodeApprovals,
17752
18538
  ensureClaudeCodeForwardPortSession,
@@ -17831,6 +18617,8 @@ function controlThreadId(control) {
17831
18617
  case "forward_tcp_open":
17832
18618
  case "forward_tcp_send":
17833
18619
  case "forward_tcp_close":
18620
+ case "remote_codex_exec":
18621
+ case "remote_codex_exec_cancel":
17834
18622
  case "update_thread_interaction_access": {
17835
18623
  const threadId = stringValue(control.threadId)?.trim();
17836
18624
  return threadId === void 0 || threadId === "" ? void 0 : threadId;
@@ -17940,9 +18728,15 @@ function replacementRunnerSummaries(value) {
17940
18728
  });
17941
18729
  }
17942
18730
  function makeRunnerLogger(options) {
18731
+ const consoleReporter = options.launchTui && options.consoleReporter === void 0 ? void 0 : (event, payload) => {
18732
+ if (!options.launchTui) {
18733
+ reportRunnerConsoleEvent(event, payload);
18734
+ }
18735
+ options.consoleReporter?.(event, payload);
18736
+ };
17943
18737
  return createRunnerLogger(
17944
18738
  options.logFile ?? defaultRunnerLogFile(),
17945
- options.launchTui ? void 0 : reportRunnerConsoleEvent
18739
+ consoleReporter
17946
18740
  );
17947
18741
  }
17948
18742
  function installCleanupHandlers(close) {
@@ -17971,7 +18765,7 @@ function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
17971
18765
  cwd,
17972
18766
  purpose: "codex.tui"
17973
18767
  });
17974
- const child = spawn7(codexBin, args, {
18768
+ const child = spawn8(codexBin, args, {
17975
18769
  cwd,
17976
18770
  env: process.env,
17977
18771
  stdio: "inherit"
@@ -18098,7 +18892,29 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
18098
18892
  );
18099
18893
  }
18100
18894
  }
18101
- async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread, onThreadProcessStart) {
18895
+ async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread, onThreadProcessStart) {
18896
+ if (codex === void 0) {
18897
+ switch (control.type) {
18898
+ case "remote_codex_exec":
18899
+ case "remote_codex_exec_cancel":
18900
+ return await applyRemoteCodexExecControl(
18901
+ control,
18902
+ instanceId,
18903
+ allowedCwds.value,
18904
+ remoteCodexSandboxRunner,
18905
+ activeRemoteCodexExecRequests,
18906
+ log
18907
+ );
18908
+ default:
18909
+ return {
18910
+ instanceId,
18911
+ controlType: control.type,
18912
+ requestId: "requestId" in control ? stringValue(control.requestId) ?? null : null,
18913
+ ok: false,
18914
+ error: "agent_provider_unavailable:codex"
18915
+ };
18916
+ }
18917
+ }
18102
18918
  switch (control.type) {
18103
18919
  case "start_instance": {
18104
18920
  const cwd = resolveAllowedCwd(control.cwd, allowedCwds.value);
@@ -18597,6 +19413,26 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
18597
19413
  case "suggest_tasks": {
18598
19414
  return suggestRunnerTasks(control, options, allowedCwds.value);
18599
19415
  }
19416
+ case "remote_codex_exec": {
19417
+ return await applyRemoteCodexExecControl(
19418
+ control,
19419
+ instanceId,
19420
+ allowedCwds.value,
19421
+ remoteCodexSandboxRunner,
19422
+ activeRemoteCodexExecRequests,
19423
+ log
19424
+ );
19425
+ }
19426
+ case "remote_codex_exec_cancel": {
19427
+ return await applyRemoteCodexExecControl(
19428
+ control,
19429
+ instanceId,
19430
+ allowedCwds.value,
19431
+ remoteCodexSandboxRunner,
19432
+ activeRemoteCodexExecRequests,
19433
+ log
19434
+ );
19435
+ }
18600
19436
  case "start_turn": {
18601
19437
  const response = await codex.request("turn/start", {
18602
19438
  threadId: control.threadId,
@@ -18769,6 +19605,55 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
18769
19605
  return { instanceId, controlType: control.type, skipped: true };
18770
19606
  }
18771
19607
  }
19608
+ async function applyRemoteCodexExecControl(control, instanceId, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, log) {
19609
+ switch (control.type) {
19610
+ case "remote_codex_exec": {
19611
+ const requestId = stringValue(control.requestId)?.trim();
19612
+ const abortController = new AbortController();
19613
+ if (requestId !== void 0 && requestId !== "") {
19614
+ activeRemoteCodexExecRequests.set(requestId, abortController);
19615
+ }
19616
+ const result = await handleRemoteCodexExecControl(control, {
19617
+ instanceId,
19618
+ allowedCwds,
19619
+ sandboxRunner: remoteCodexSandboxRunner,
19620
+ abortSignal: abortController.signal
19621
+ });
19622
+ if (requestId !== void 0 && requestId !== "") {
19623
+ activeRemoteCodexExecRequests.delete(requestId);
19624
+ }
19625
+ log("remote_codex_exec.handled", {
19626
+ requestId: stringValue(control.requestId) ?? null,
19627
+ ok: result.ok === true,
19628
+ error: typeof result.error === "string" ? result.error : null
19629
+ });
19630
+ return result;
19631
+ }
19632
+ case "remote_codex_exec_cancel": {
19633
+ const requestId = stringValue(control.requestId)?.trim();
19634
+ if (requestId === void 0 || requestId === "") {
19635
+ return {
19636
+ instanceId,
19637
+ controlType: control.type,
19638
+ ok: false,
19639
+ error: "request_id_required"
19640
+ };
19641
+ }
19642
+ const activeRequest = activeRemoteCodexExecRequests.get(requestId);
19643
+ if (activeRequest !== void 0) {
19644
+ activeRequest.abort(new Error("remote_codex_exec_cancelled"));
19645
+ activeRemoteCodexExecRequests.delete(requestId);
19646
+ }
19647
+ return {
19648
+ instanceId,
19649
+ controlType: control.type,
19650
+ requestId,
19651
+ ok: true,
19652
+ cancelled: activeRequest !== void 0
19653
+ };
19654
+ }
19655
+ }
19656
+ }
18772
19657
  function startInstanceFailureReason(stage, error) {
18773
19658
  const detail = truncateFailureDetail(
18774
19659
  error instanceof Error ? error.message : String(error)
@@ -18901,6 +19786,18 @@ function availableRunnerAgentProviders(options) {
18901
19786
  return ["codex"];
18902
19787
  }
18903
19788
  }
19789
+ function hasLocalCodexAppServerCapability(options) {
19790
+ if (options.codexUrl !== void 0) {
19791
+ return true;
19792
+ }
19793
+ switch (options.dependencyStatus?.codex.available) {
19794
+ case false:
19795
+ return false;
19796
+ case true:
19797
+ case void 0:
19798
+ return true;
19799
+ }
19800
+ }
18904
19801
  function startInstanceAgentProvider(control) {
18905
19802
  const value = stringValue(control.agentProvider)?.trim();
18906
19803
  switch (value) {
@@ -20362,7 +21259,7 @@ async function spawnLocalThreadRunnerProcess(options) {
20362
21259
  cwd: options.cwd,
20363
21260
  purpose: "linzumi.thread_runner"
20364
21261
  });
20365
- const child = spawn7(process.execPath, nodeArgs, {
21262
+ const child = spawn8(process.execPath, nodeArgs, {
20366
21263
  cwd: options.cwd,
20367
21264
  env,
20368
21265
  stdio: ["inherit", "inherit", "inherit", "ipc"]
@@ -20477,7 +21374,7 @@ function threadRunnerScriptPath(scriptPath, cwd) {
20477
21374
  "cannot fork thread runner without current CLI script path"
20478
21375
  );
20479
21376
  }
20480
- return isAbsolute3(scriptPath) ? scriptPath : resolve7(cwd, scriptPath);
21377
+ return isAbsolute4(scriptPath) ? scriptPath : resolve7(cwd, scriptPath);
20481
21378
  }
20482
21379
  function threadRunnerLoaderExecArgv(execArgv, index = 0) {
20483
21380
  const current = execArgv[index];
@@ -20812,7 +21709,7 @@ function configuredAllowedCwds(values, options = {}) {
20812
21709
  if (options.createMissing === true) {
20813
21710
  mkdirSync9(absolutePath, { recursive: true });
20814
21711
  }
20815
- const realPath = realpathSync5(absolutePath);
21712
+ const realPath = realpathSync6(absolutePath);
20816
21713
  allowedCwds.push(
20817
21714
  ...realPath === absolutePath ? [realPath] : [realPath, absolutePath]
20818
21715
  );
@@ -20859,7 +21756,7 @@ function browseRunnerDirectory(control, options) {
20859
21756
  const requestId = stringValue(control.requestId) ?? null;
20860
21757
  const requestedPath = stringValue(control.path) ?? currentHomeDirectory();
20861
21758
  try {
20862
- const currentPath = realpathSync5(resolve7(expandUserPath(requestedPath)));
21759
+ const currentPath = realpathSync6(resolve7(expandUserPath(requestedPath)));
20863
21760
  const stats = statSync(currentPath);
20864
21761
  if (!stats.isDirectory()) {
20865
21762
  return {
@@ -20870,7 +21767,7 @@ function browseRunnerDirectory(control, options) {
20870
21767
  error: "not_directory"
20871
21768
  };
20872
21769
  }
20873
- const parent = dirname9(currentPath);
21770
+ const parent = dirname10(currentPath);
20874
21771
  const entries = readdirSync2(currentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => visibleRunnerDirectoryEntryName(entry.name)).map((entry) => {
20875
21772
  const path2 = join14(currentPath, entry.name);
20876
21773
  return {
@@ -20982,7 +21879,7 @@ function createRunnerProject(control, options, allowedCwds) {
20982
21879
  error: cleanupError === void 0 ? `git_init_failed:${gitFailure}` : `git_init_cleanup_failed:${cleanupError}`
20983
21880
  };
20984
21881
  }
20985
- const projectRealPath = realpathSync5(projectPath);
21882
+ const projectRealPath = realpathSync6(projectPath);
20986
21883
  const persistedAllowedCwds = addAllowedCwdForLinzumiUrl(
20987
21884
  projectRealPath,
20988
21885
  options.kandanUrl
@@ -21090,6 +21987,7 @@ var init_runner = __esm({
21090
21987
  init_linzumiContext();
21091
21988
  init_localCapabilities();
21092
21989
  init_localConfig();
21990
+ init_remoteCodexExecutionContext();
21093
21991
  init_helloLinzumiProject();
21094
21992
  init_localForwarding();
21095
21993
  init_localEditor();
@@ -21109,6 +22007,7 @@ var init_runner = __esm({
21109
22007
  init_threadCodexWorkerIpc();
21110
22008
  init_signupTaskSuggestions();
21111
22009
  init_userFacingErrors();
22010
+ init_remoteCodexSandboxRunner();
21112
22011
  THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
21113
22012
  claudeSessionStoreSequenceHighWater = /* @__PURE__ */ new Map();
21114
22013
  ClaudeCodeOutputPostError = class extends Error {
@@ -21122,9 +22021,9 @@ var init_runner = __esm({
21122
22021
  });
21123
22022
 
21124
22023
  // src/kandanTls.ts
21125
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "node:fs";
22024
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
21126
22025
  import { Agent } from "undici";
21127
- import { WebSocket as WsWebSocket } from "ws";
22026
+ import WsWebSocket from "ws";
21128
22027
  function kandanTlsTrustFromEnv() {
21129
22028
  return kandanTlsTrustFromCaFile(process.env.KANDAN_TLS_CA_FILE);
21130
22029
  }
@@ -21133,7 +22032,7 @@ function kandanTlsTrustFromCaFile(caFile) {
21133
22032
  return void 0;
21134
22033
  }
21135
22034
  const trimmed = caFile.trim();
21136
- if (!existsSync9(trimmed)) {
22035
+ if (!existsSync10(trimmed)) {
21137
22036
  throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
21138
22037
  }
21139
22038
  const ca = readFileSync11(trimmed, "utf8");
@@ -39919,7 +40818,7 @@ var init_RemoveFileError = __esm({
39919
40818
  });
39920
40819
 
39921
40820
  // ../../node_modules/@inquirer/external-editor/dist/esm/index.js
39922
- import { spawn as spawn9, spawnSync as spawnSync5 } from "child_process";
40821
+ import { spawn as spawn10, spawnSync as spawnSync5 } from "child_process";
39923
40822
  import { readFileSync as readFileSync14, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "fs";
39924
40823
  import path from "node:path";
39925
40824
  import os from "node:os";
@@ -40075,7 +40974,7 @@ var init_esm5 = __esm({
40075
40974
  }
40076
40975
  launchEditorAsync(callback) {
40077
40976
  try {
40078
- const editorProcess = spawn9(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
40977
+ const editorProcess = spawn10(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
40079
40978
  editorProcess.on("exit", (code) => {
40080
40979
  this.lastExitStatus = code;
40081
40980
  setImmediate(callback);
@@ -41409,9 +42308,9 @@ __export(signupFlow_exports, {
41409
42308
  signupTaskSuggestionWaitMessageForTest: () => signupTaskSuggestionWaitMessageForTest,
41410
42309
  toggleProjectPickerSelectionForTest: () => toggleProjectPickerSelectionForTest
41411
42310
  });
41412
- import { spawn as spawn10, spawnSync as spawnSync6 } from "node:child_process";
42311
+ import { spawn as spawn11, spawnSync as spawnSync6 } from "node:child_process";
41413
42312
  import {
41414
- existsSync as existsSync12,
42313
+ existsSync as existsSync13,
41415
42314
  constants as fsConstants,
41416
42315
  mkdirSync as mkdirSync12,
41417
42316
  mkdtempSync as mkdtempSync5,
@@ -41423,7 +42322,7 @@ import {
41423
42322
  } from "node:fs";
41424
42323
  import { access } from "node:fs/promises";
41425
42324
  import { homedir as homedir12, tmpdir as tmpdir4 } from "node:os";
41426
- import { delimiter as delimiter3, dirname as dirname12, join as join17, resolve as resolve9 } from "node:path";
42325
+ import { delimiter as delimiter3, dirname as dirname13, join as join17, resolve as resolve9 } from "node:path";
41427
42326
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
41428
42327
  import { emitKeypressEvents } from "node:readline";
41429
42328
  function signupHelpText() {
@@ -41522,7 +42421,7 @@ function defaultSignupDraftStore(serviceUrl) {
41522
42421
  const path2 = defaultSignupDraftPath(serviceUrl);
41523
42422
  return {
41524
42423
  read: () => {
41525
- if (!existsSync12(path2)) {
42424
+ if (!existsSync13(path2)) {
41526
42425
  return void 0;
41527
42426
  }
41528
42427
  let parsed;
@@ -41537,7 +42436,7 @@ function defaultSignupDraftStore(serviceUrl) {
41537
42436
  return comparableServiceUrl(parsed.serviceUrl) === comparableServiceUrl(serviceUrl) ? parsed : void 0;
41538
42437
  },
41539
42438
  write: (draft) => {
41540
- mkdirSync12(dirname12(path2), { recursive: true });
42439
+ mkdirSync12(dirname13(path2), { recursive: true });
41541
42440
  writeFileSync11(path2, `${JSON.stringify(draft, null, 2)}
41542
42441
  `, {
41543
42442
  encoding: "utf8",
@@ -41551,7 +42450,7 @@ function defaultSignupDraftStore(serviceUrl) {
41551
42450
  }
41552
42451
  function defaultSignupDraftPath(serviceUrl) {
41553
42452
  return join17(
41554
- dirname12(localConfigPath()),
42453
+ dirname13(localConfigPath()),
41555
42454
  `signup-draft.${localConfigScopeFileStem(serviceUrl)}.json`
41556
42455
  );
41557
42456
  }
@@ -41585,7 +42484,7 @@ function defaultSignupTaskCacheStore(serviceUrl) {
41585
42484
  }
41586
42485
  }
41587
42486
  };
41588
- mkdirSync12(dirname12(path2), { recursive: true });
42487
+ mkdirSync12(dirname13(path2), { recursive: true });
41589
42488
  writeFileSync11(path2, `${JSON.stringify(next, null, 2)}
41590
42489
  `, {
41591
42490
  encoding: "utf8",
@@ -41596,12 +42495,12 @@ function defaultSignupTaskCacheStore(serviceUrl) {
41596
42495
  }
41597
42496
  function defaultSignupTaskCachePath(serviceUrl) {
41598
42497
  return join17(
41599
- dirname12(localConfigPath()),
42498
+ dirname13(localConfigPath()),
41600
42499
  `signup-task-cache.${localConfigScopeFileStem(serviceUrl)}.json`
41601
42500
  );
41602
42501
  }
41603
42502
  function readSignupTaskCache(path2) {
41604
- if (!existsSync12(path2)) {
42503
+ if (!existsSync13(path2)) {
41605
42504
  return { version: 1, entries: {} };
41606
42505
  }
41607
42506
  let parsed;
@@ -42916,7 +43815,7 @@ async function openUrlInBrowser(url) {
42916
43815
  }
42917
43816
  const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
42918
43817
  await new Promise((resolve11, reject) => {
42919
- const child = spawn10(command, args, { stdio: "ignore", detached: true });
43818
+ const child = spawn11(command, args, { stdio: "ignore", detached: true });
42920
43819
  child.once("spawn", () => {
42921
43820
  child.unref();
42922
43821
  resolve11();
@@ -44114,7 +45013,7 @@ function parseTaskSuggestionResponse2(response) {
44114
45013
  }
44115
45014
  function runProcess3(args) {
44116
45015
  return new Promise((resolveProcess, rejectProcess) => {
44117
- const child = spawn10(args.command, [...args.args], {
45016
+ const child = spawn11(args.command, [...args.args], {
44118
45017
  cwd: args.cwd,
44119
45018
  stdio: ["pipe", "ignore", "pipe"]
44120
45019
  });
@@ -44156,7 +45055,7 @@ function runProcess3(args) {
44156
45055
  }
44157
45056
  function runProcessCapture(args) {
44158
45057
  return new Promise((resolveProcess) => {
44159
- const child = spawn10(args.command, [...args.args], {
45058
+ const child = spawn11(args.command, [...args.args], {
44160
45059
  cwd: args.cwd,
44161
45060
  stdio: ["ignore", "pipe", "ignore"]
44162
45061
  });
@@ -44374,7 +45273,7 @@ function spawnSyncGitOutput(args, cwd) {
44374
45273
  }
44375
45274
  function probeToolWithArgs(command, args, cwd) {
44376
45275
  return new Promise((resolve11) => {
44377
- const child = spawn10(command, [...args], {
45276
+ const child = spawn11(command, [...args], {
44378
45277
  cwd,
44379
45278
  stdio: ["ignore", "pipe", "pipe"]
44380
45279
  });
@@ -44412,7 +45311,7 @@ async function discoverCodeRoots(homeDir) {
44412
45311
  const candidates = ["src", "code", "projects"].map(
44413
45312
  (name) => join17(homeDir, name)
44414
45313
  );
44415
- return candidates.filter((path2) => existsSync12(path2)).flatMap((path2) => discoveredProjectNames(path2));
45314
+ return candidates.filter((path2) => existsSync13(path2)).flatMap((path2) => discoveredProjectNames(path2));
44416
45315
  }
44417
45316
  function discoveredProjectNames(root) {
44418
45317
  try {
@@ -44516,25 +45415,25 @@ function looksLikeProject(path2) {
44516
45415
  "pnpm-lock.yaml",
44517
45416
  "yarn.lock",
44518
45417
  "package-lock.json"
44519
- ].some((name) => existsSync12(join17(path2, name)));
45418
+ ].some((name) => existsSync13(join17(path2, name)));
44520
45419
  }
44521
45420
  function detectProjectLanguage(path2) {
44522
- if (existsSync12(join17(path2, "pyproject.toml")) || existsSync12(join17(path2, "requirements.txt"))) {
45421
+ if (existsSync13(join17(path2, "pyproject.toml")) || existsSync13(join17(path2, "requirements.txt"))) {
44523
45422
  return "Python";
44524
45423
  }
44525
- if (existsSync12(join17(path2, "Cargo.toml"))) {
45424
+ if (existsSync13(join17(path2, "Cargo.toml"))) {
44526
45425
  return "Rust";
44527
45426
  }
44528
- if (existsSync12(join17(path2, "go.mod"))) {
45427
+ if (existsSync13(join17(path2, "go.mod"))) {
44529
45428
  return "Go";
44530
45429
  }
44531
- if (existsSync12(join17(path2, "mix.exs"))) {
45430
+ if (existsSync13(join17(path2, "mix.exs"))) {
44532
45431
  return "Elixir";
44533
45432
  }
44534
- if (existsSync12(join17(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
45433
+ if (existsSync13(join17(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
44535
45434
  return "TypeScript";
44536
45435
  }
44537
- if (existsSync12(join17(path2, "package.json"))) {
45436
+ if (existsSync13(join17(path2, "package.json"))) {
44538
45437
  return "JavaScript";
44539
45438
  }
44540
45439
  return "Project";
@@ -44550,7 +45449,7 @@ function packageJsonMentionsTypeScript(path2) {
44550
45449
  }
44551
45450
  }
44552
45451
  function hasGitMetadata(path2) {
44553
- return existsSync12(join17(path2, ".git"));
45452
+ return existsSync13(join17(path2, ".git"));
44554
45453
  }
44555
45454
  function childDirectories(root) {
44556
45455
  try {
@@ -44672,7 +45571,7 @@ secure mission control for all your agents on your computers
44672
45571
  init_runner();
44673
45572
  init_claudeCodeSession();
44674
45573
  init_authCache();
44675
- import { existsSync as existsSync13, readFileSync as readFileSync16, realpathSync as realpathSync6 } from "node:fs";
45574
+ import { existsSync as existsSync14, readFileSync as readFileSync16, realpathSync as realpathSync7 } from "node:fs";
44676
45575
  import { homedir as homedir13 } from "node:os";
44677
45576
  import { resolve as resolve10 } from "node:path";
44678
45577
  import { fileURLToPath as fileURLToPath4 } from "node:url";
@@ -44734,8 +45633,8 @@ init_kandanTls();
44734
45633
  init_protocol();
44735
45634
  init_json();
44736
45635
  init_defaultUrls();
44737
- import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
44738
- import { dirname as dirname10, join as join15 } from "node:path";
45636
+ import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
45637
+ import { dirname as dirname11, join as join15 } from "node:path";
44739
45638
  import { homedir as homedir10 } from "node:os";
44740
45639
  async function runAgentCliCommand(args, deps = {
44741
45640
  fetchImpl: fetch,
@@ -45453,10 +46352,10 @@ function authorizationHeaders(token) {
45453
46352
  return { authorization: `Bearer ${token}` };
45454
46353
  }
45455
46354
  function readOptionalTextFile(path2) {
45456
- return existsSync10(path2) ? readFileSync12(path2, "utf8") : void 0;
46355
+ return existsSync11(path2) ? readFileSync12(path2, "utf8") : void 0;
45457
46356
  }
45458
46357
  function writeTextFile(path2, content) {
45459
- mkdirSync10(dirname10(path2), { recursive: true });
46358
+ mkdirSync10(dirname11(path2), { recursive: true });
45460
46359
  writeFileSync8(path2, content);
45461
46360
  }
45462
46361
  function readStoredAgentTokenFile(path2, readTextFile = readOptionalTextFile) {
@@ -45544,7 +46443,7 @@ init_helloLinzumiProject();
45544
46443
  // src/commanderDaemon.ts
45545
46444
  init_runnerLogger();
45546
46445
  import {
45547
- existsSync as existsSync11,
46446
+ existsSync as existsSync12,
45548
46447
  closeSync as closeSync2,
45549
46448
  mkdirSync as mkdirSync11,
45550
46449
  openSync as openSync3,
@@ -45553,8 +46452,8 @@ import {
45553
46452
  writeFileSync as writeFileSync9
45554
46453
  } from "node:fs";
45555
46454
  import { homedir as homedir11 } from "node:os";
45556
- import { dirname as dirname11, join as join16, resolve as resolve8 } from "node:path";
45557
- import { execFileSync, spawn as spawn8 } from "node:child_process";
46455
+ import { dirname as dirname12, join as join16, resolve as resolve8 } from "node:path";
46456
+ import { execFileSync, spawn as spawn9 } from "node:child_process";
45558
46457
  import { fileURLToPath as fileURLToPath3 } from "node:url";
45559
46458
  var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
45560
46459
  function commanderStatusDir() {
@@ -45586,7 +46485,7 @@ function startCommanderDaemon(options) {
45586
46485
  logFile
45587
46486
  ];
45588
46487
  mkdirSync11(statusDir, { recursive: true });
45589
- mkdirSync11(dirname11(logFile), { recursive: true });
46488
+ mkdirSync11(dirname12(logFile), { recursive: true });
45590
46489
  const out = openSync3(logFile, "a");
45591
46490
  const err = openSync3(logFile, "a");
45592
46491
  writeCliAuditEvent(
@@ -45599,7 +46498,7 @@ function startCommanderDaemon(options) {
45599
46498
  },
45600
46499
  { sessionId: options.runnerId }
45601
46500
  );
45602
- const child = (options.spawnImpl ?? spawn8)(nodeBin, command, {
46501
+ const child = (options.spawnImpl ?? spawn9)(nodeBin, command, {
45603
46502
  detached: true,
45604
46503
  stdio: ["ignore", out, err],
45605
46504
  env: process.env
@@ -45638,7 +46537,7 @@ function startCommanderDaemon(options) {
45638
46537
  }
45639
46538
  function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
45640
46539
  const statusFile = commanderStatusFile(runnerId, statusDir);
45641
- if (!existsSync11(statusFile)) {
46540
+ if (!existsSync12(statusFile)) {
45642
46541
  return { status: "missing", runnerId, statusFile };
45643
46542
  }
45644
46543
  const record = parseRecord(readFileSync13(statusFile, "utf8"));
@@ -45646,7 +46545,7 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
45646
46545
  }
45647
46546
  async function waitForCommanderDaemon(options) {
45648
46547
  const now = options.now ?? (() => Date.now());
45649
- const readTextFile = options.readTextFile ?? ((path2) => existsSync11(path2) ? readFileSync13(path2, "utf8") : void 0);
46548
+ const readTextFile = options.readTextFile ?? ((path2) => existsSync12(path2) ? readFileSync13(path2, "utf8") : void 0);
45650
46549
  const statusImpl = options.statusImpl ?? commanderDaemonStatus;
45651
46550
  const deadline = now() + options.timeoutMs;
45652
46551
  while (now() <= deadline) {
@@ -57610,6 +58509,8 @@ function createLinzumiMcpApiClient(options) {
57610
58509
  sendChannelMessage: (params) => request("POST", `${apiPrefix}/channel-message`, params),
57611
58510
  prepareMessageUploads: (params) => request("POST", `${apiPrefix}/message-uploads/prepare`, params),
57612
58511
  attachMessageFiles: (params) => request("POST", `${apiPrefix}/message-files/attach`, params),
58512
+ prepareCustomEmoji: (params) => request("POST", `${apiPrefix}/custom-emoji/prepare`, params),
58513
+ renameCustomEmoji: (params) => request("POST", `${apiPrefix}/custom-emoji/rename`, params),
57613
58514
  sendThreadReply: (params) => request("POST", `${apiPrefix}/thread-reply`, params),
57614
58515
  sendDm: (params) => request("POST", `${apiPrefix}/dm`, params),
57615
58516
  dmOwner: (params) => request("POST", `${apiPrefix}/dm-owner`, params),
@@ -57620,6 +58521,17 @@ function createLinzumiMcpApiClient(options) {
57620
58521
  // src/mcpServer.ts
57621
58522
  init_mcpConfig();
57622
58523
  init_oauth();
58524
+ init_protocol();
58525
+ var maxCustomEmojiNameLength = 64;
58526
+ function normalizeCustomEmojiNameForSchema(value) {
58527
+ return value.trim().replace(/^:+/, "").replace(/:+$/, "").toLowerCase();
58528
+ }
58529
+ var customEmojiNameSchema = external_exports2.string().min(1).refine(
58530
+ (value) => normalizeCustomEmojiNameForSchema(value).length <= maxCustomEmojiNameLength,
58531
+ {
58532
+ message: `Custom emoji shortcode name must be at most ${maxCustomEmojiNameLength} characters after trimming surrounding colons.`
58533
+ }
58534
+ );
57623
58535
  var mcpFlagDefinitions = /* @__PURE__ */ new Map([
57624
58536
  ["api-url", { kind: "value" }],
57625
58537
  ["token", { kind: "value" }],
@@ -57680,6 +58592,10 @@ Tools:
57680
58592
  linzumi_dm_owner Send a plain-text DM to the configured owner username.
57681
58593
  linzumi_get_vault_values
57682
58594
  Request approved named vault values for a thread.
58595
+ linzumi_upload_custom_emoji
58596
+ Upload a local image as a workspace custom emoji.
58597
+ linzumi_rename_custom_emoji
58598
+ Rename an existing workspace custom emoji.
57683
58599
 
57684
58600
  Auth:
57685
58601
  Uses --token, LINZUMI_MCP_ACCESS_TOKEN, or cached local runner OAuth from linzumi auth.
@@ -57801,6 +58717,43 @@ async function runMcpServer(args) {
57801
58717
  {},
57802
58718
  async (params) => mcpJsonResult(await client.listVaultSecrets(params))
57803
58719
  );
58720
+ server.tool(
58721
+ "linzumi_upload_custom_emoji",
58722
+ "Upload a local image as a workspace custom emoji. For generated emoji art, create a transparent PNG with a simple centered subject, crisp edges, strong contrast, minimal tiny details, and enough outline/highlight separation to read at 16-24px on both dark and light backgrounds. Use lowercase shortcode names without colons unless preserving a user-provided :name:.",
58723
+ {
58724
+ name: customEmojiNameSchema.describe(
58725
+ "Custom emoji shortcode name, with or without surrounding colons."
58726
+ ),
58727
+ file: external_exports2.string().min(1).describe(
58728
+ "Local PNG/WebP/GIF/JPEG file path to upload. Prefer transparent PNG for generated emoji."
58729
+ ),
58730
+ workspace: external_exports2.string().optional().describe("Workspace slug. Omit to use the authenticated token scope.")
58731
+ },
58732
+ async (params) => mcpJsonResult(
58733
+ await uploadCustomEmojiWithClient({
58734
+ client,
58735
+ cwd,
58736
+ kandanUrl,
58737
+ name: params.name,
58738
+ file: params.file,
58739
+ workspace: params.workspace
58740
+ })
58741
+ )
58742
+ );
58743
+ server.tool(
58744
+ "linzumi_rename_custom_emoji",
58745
+ "Rename an existing workspace custom emoji. The new name may include surrounding colons; Linzumi stores the normalized shortcode without colons.",
58746
+ {
58747
+ emoji_id: external_exports2.union([external_exports2.string(), external_exports2.number().int().positive()]).describe(
58748
+ "Workspace custom emoji id returned by linzumi_upload_custom_emoji or workspace emoji listings."
58749
+ ),
58750
+ name: customEmojiNameSchema.describe(
58751
+ "New shortcode name, with or without surrounding colons."
58752
+ ),
58753
+ workspace: external_exports2.string().optional().describe("Workspace slug. Omit to use the authenticated token scope.")
58754
+ },
58755
+ async (params) => mcpJsonResult(await client.renameCustomEmoji(params))
58756
+ );
57804
58757
  server.tool(
57805
58758
  "linzumi_dm_owner",
57806
58759
  "Send a plain-text DM to the configured Commander owner. Requires dm.write and a visible owner username.",
@@ -57936,6 +58889,69 @@ async function uploadFilesWithClient(args) {
57936
58889
  uploaded_file_ids: uploadedFileIds
57937
58890
  });
57938
58891
  }
58892
+ async function uploadCustomEmojiWithClient(args) {
58893
+ const file = await commanderUploadFileForPath(args.cwd, args.file);
58894
+ const prepare = await args.client.prepareCustomEmoji({
58895
+ ...args.workspace === void 0 ? {} : { workspace: args.workspace },
58896
+ name: args.name,
58897
+ file_name: file.fileName,
58898
+ content_type: file.contentType,
58899
+ size_bytes: file.sizeBytes
58900
+ });
58901
+ const upload = objectValue(prepare.upload);
58902
+ const uploadUrl = stringValue(upload?.upload_url);
58903
+ const uploadMethod = stringValue(upload?.upload_method) ?? "PUT";
58904
+ if (uploadUrl === void 0) {
58905
+ throw new Error("Linzumi custom emoji prepare response missing upload_url");
58906
+ }
58907
+ const bytes = await readFile2(file.path);
58908
+ const uploadBody = bytes.buffer.slice(
58909
+ bytes.byteOffset,
58910
+ bytes.byteOffset + bytes.byteLength
58911
+ );
58912
+ const response = await fetch(
58913
+ resolveLinzumiUploadUrl(args.kandanUrl, uploadUrl),
58914
+ {
58915
+ method: uploadMethod,
58916
+ headers: { "content-type": file.contentType },
58917
+ body: uploadBody
58918
+ }
58919
+ );
58920
+ if (!response.ok) {
58921
+ const fallbackError = `${response.status} ${response.statusText}`;
58922
+ const responseText = await response.text();
58923
+ const trimmedResponseText = responseText.trim();
58924
+ const error = trimmedResponseText === "" ? fallbackError : (() => {
58925
+ try {
58926
+ const parsedError = JSON.parse(trimmedResponseText);
58927
+ return isJsonObject(parsedError) && typeof parsedError.error === "string" ? parsedError.error : trimmedResponseText;
58928
+ } catch {
58929
+ return trimmedResponseText;
58930
+ }
58931
+ })();
58932
+ throw new Error(`Linzumi custom emoji upload failed: ${error}`);
58933
+ }
58934
+ const parsed = await response.json();
58935
+ if (!isJsonObject(parsed)) {
58936
+ throw new Error("Linzumi custom emoji upload returned non-object JSON");
58937
+ }
58938
+ const emoji = objectValue(parsed.custom_emoji);
58939
+ if (emoji === void 0) {
58940
+ throw new Error(
58941
+ "Linzumi custom emoji upload response missing custom_emoji"
58942
+ );
58943
+ }
58944
+ return {
58945
+ ok: true,
58946
+ workspace: prepare.workspace,
58947
+ emoji,
58948
+ asset_id: stringValue(parsed.asset_id),
58949
+ id: integerValue(emoji?.id),
58950
+ name: stringValue(emoji?.name),
58951
+ shortcode: stringValue(emoji?.shortcode),
58952
+ image_url: stringValue(emoji?.image_url)
58953
+ };
58954
+ }
57939
58955
  function targetWithDefaultThread(target, defaultThreadId) {
57940
58956
  const existingThreadId = stringValue(target.thread_id);
57941
58957
  if (existingThreadId !== void 0 || defaultThreadId === void 0) {
@@ -58124,6 +59140,338 @@ function required(values, key) {
58124
59140
  return value;
58125
59141
  }
58126
59142
 
59143
+ // src/remoteCodexHarnessWorker.ts
59144
+ init_channelSession();
59145
+ init_codexAppServer();
59146
+ init_phoenix();
59147
+ init_runnerLogger();
59148
+ init_userFacingErrors();
59149
+ init_version();
59150
+ var configEnvKey = "LINZUMI_REMOTE_CODEX_HARNESS_CONFIG_B64";
59151
+ var remoteHarnessEnvironmentId = "linzumi-remote-codex-harness";
59152
+ async function runRemoteCodexHarnessWorkerFromEnv(env = process.env) {
59153
+ await runRemoteCodexHarnessWorker(remoteCodexHarnessWorkerConfigFromEnv(env));
59154
+ }
59155
+ async function runRemoteCodexHarnessWorker(config, deps = {}) {
59156
+ const resolvedDeps = {
59157
+ startCodexAppServer,
59158
+ connectCodexAppServer,
59159
+ connectPhoenixClient,
59160
+ attachChannelSession,
59161
+ waitUntilShutdown: defaultWaitUntilShutdown,
59162
+ writeStdout: (chunk) => process.stdout.write(chunk),
59163
+ log: writeCliAuditEvent,
59164
+ ...deps
59165
+ };
59166
+ const started = await resolvedDeps.startCodexAppServer(
59167
+ config.codexBin,
59168
+ config.workerCwd,
59169
+ {
59170
+ model: config.model,
59171
+ reasoningEffort: config.reasoningEffort,
59172
+ fast: config.fast,
59173
+ inheritEnv: false,
59174
+ env: remoteHarnessCodexEnv(process.env, config.execServerUrl)
59175
+ }
59176
+ );
59177
+ let kandan;
59178
+ let codex;
59179
+ let session;
59180
+ const cleanup = async () => {
59181
+ await session?.close();
59182
+ codex?.close();
59183
+ kandan?.close();
59184
+ started.stop();
59185
+ };
59186
+ const cleanupOnce = onceAsync(cleanup);
59187
+ try {
59188
+ codex = await resolvedDeps.connectCodexAppServer(started.url);
59189
+ await registerExecServerEnvironment(codex, config.execServerUrl);
59190
+ kandan = await resolvedDeps.connectPhoenixClient(
59191
+ config.kandanUrl,
59192
+ config.token
59193
+ );
59194
+ const runnerTopic = `local_runner:${config.runnerId}`;
59195
+ const runnerJoinPayload = () => remoteHarnessRunnerJoinPayload(config);
59196
+ const pendingControls = [];
59197
+ const controlDispatcher = { value: void 0 };
59198
+ kandan.onControl((control) => {
59199
+ const dispatcher = controlDispatcher.value;
59200
+ if (dispatcher === void 0) {
59201
+ pendingControls.push(control);
59202
+ return;
59203
+ }
59204
+ dispatcher(control);
59205
+ });
59206
+ await kandan.join(runnerTopic, runnerJoinPayload(), {
59207
+ rejoinPayload: runnerJoinPayload
59208
+ });
59209
+ session = await resolvedDeps.attachChannelSession({
59210
+ kandan,
59211
+ codex,
59212
+ topic: runnerTopic,
59213
+ instanceId: config.instanceId,
59214
+ options: {
59215
+ kandanUrl: config.kandanUrl,
59216
+ token: config.token,
59217
+ runnerId: config.runnerId,
59218
+ cwd: config.cwd,
59219
+ codexBin: config.codexBin,
59220
+ fast: config.fast,
59221
+ launchTui: false,
59222
+ enablePortForwardWatch: false,
59223
+ channelSession: {
59224
+ workspaceSlug: config.workspaceSlug,
59225
+ channelSlug: config.channelSlug,
59226
+ kandanThreadId: config.kandanThreadId,
59227
+ rootSeq: config.rootSeq,
59228
+ startCodexThread: true,
59229
+ linzumiContext: config.linzumiContext,
59230
+ listenUser: config.listenUser,
59231
+ model: config.model,
59232
+ reasoningEffort: config.reasoningEffort,
59233
+ sandbox: config.sandbox,
59234
+ approvalPolicy: config.approvalPolicy,
59235
+ codexEnvironmentId: remoteHarnessEnvironmentId,
59236
+ allowPortForwardingByDefault: config.allowPortForwardingByDefault
59237
+ }
59238
+ },
59239
+ log: resolvedDeps.log
59240
+ });
59241
+ kandan.onReconnect(() => session?.handleKandanReconnect());
59242
+ const dispatchControl = (control) => {
59243
+ const liveSession = session;
59244
+ const liveKandan = kandan;
59245
+ if (liveSession === void 0 || liveKandan === void 0) {
59246
+ pendingControls.push(control);
59247
+ return;
59248
+ }
59249
+ void liveSession.handleControl(control).then(
59250
+ (response) => response === void 0 ? void 0 : liveKandan.push(runnerTopic, "codex_response", response)
59251
+ ).catch(
59252
+ (error) => liveKandan.push(runnerTopic, "codex_error", {
59253
+ instanceId: config.instanceId,
59254
+ message: runnerActionErrorMessage(error)
59255
+ })
59256
+ ).catch((error) => {
59257
+ resolvedDeps.log("remote_codex_harness.control_push_failed", {
59258
+ message: error instanceof Error ? error.message : String(error)
59259
+ });
59260
+ });
59261
+ };
59262
+ controlDispatcher.value = dispatchControl;
59263
+ pendingControls.splice(0).forEach(dispatchControl);
59264
+ codex.onNotification((notification) => {
59265
+ const params = notification.params ?? {};
59266
+ session?.handleCodexNotification(notification.method, params);
59267
+ });
59268
+ await session.startThreadMessageTurn({
59269
+ seq: config.sourceSeq,
59270
+ body: config.workDescription
59271
+ });
59272
+ resolvedDeps.writeStdout(
59273
+ `LINZUMI_REMOTE_CODEX_HARNESS_READY ${config.readyToken}
59274
+ `
59275
+ );
59276
+ const stopReason = await Promise.race([
59277
+ resolvedDeps.waitUntilShutdown(cleanupOnce).then(() => ({ type: "shutdown" })),
59278
+ waitForCodexAppServerExit(started.process).then((exit) => ({
59279
+ type: "codex_exit",
59280
+ exit
59281
+ }))
59282
+ ]);
59283
+ if (stopReason.type === "codex_exit") {
59284
+ resolvedDeps.log("remote_codex_harness.codex_app_server_exited", {
59285
+ code: stopReason.exit.code,
59286
+ signal: stopReason.exit.signal
59287
+ });
59288
+ await cleanupOnce();
59289
+ }
59290
+ } catch (error) {
59291
+ await cleanupOnce();
59292
+ throw error;
59293
+ }
59294
+ }
59295
+ function remoteCodexHarnessWorkerConfigFromEnv(env) {
59296
+ const encoded = env[configEnvKey];
59297
+ if (encoded === void 0 || encoded.trim() === "") {
59298
+ throw new Error(`${configEnvKey} is required`);
59299
+ }
59300
+ return remoteCodexHarnessWorkerConfigFromJson(
59301
+ JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"))
59302
+ );
59303
+ }
59304
+ function remoteCodexHarnessWorkerConfigFromJson(value) {
59305
+ const input = requireObject(value);
59306
+ const config = {
59307
+ kandanUrl: requiredString2(input, "kandanUrl"),
59308
+ token: requiredString2(input, "token"),
59309
+ runnerId: requiredString2(input, "runnerId"),
59310
+ instanceId: requiredString2(input, "instanceId"),
59311
+ workspaceSlug: requiredString2(input, "workspaceSlug"),
59312
+ channelSlug: requiredString2(input, "channelSlug"),
59313
+ kandanThreadId: requiredString2(input, "kandanThreadId"),
59314
+ rootSeq: requiredPositiveInteger(input, "rootSeq"),
59315
+ sourceSeq: requiredPositiveInteger(input, "sourceSeq"),
59316
+ cwd: requiredString2(input, "cwd"),
59317
+ workerCwd: requiredString2(input, "workerCwd"),
59318
+ workDescription: requiredString2(input, "workDescription"),
59319
+ readyToken: requiredString2(input, "readyToken"),
59320
+ codexBin: requiredString2(input, "codexBin"),
59321
+ execServerUrl: requiredString2(input, "execServerUrl"),
59322
+ listenUser: requiredString2(input, "listenUser"),
59323
+ ...optionalStringField(input, "model"),
59324
+ ...optionalStringField(input, "reasoningEffort"),
59325
+ ...optionalStringField(input, "approvalPolicy"),
59326
+ ...optionalStringField(input, "sandbox"),
59327
+ ...optionalBooleanField(input, "fast"),
59328
+ ...optionalBooleanField(input, "allowPortForwardingByDefault"),
59329
+ ...optionalObjectField(input, "linzumiContext")
59330
+ };
59331
+ return config;
59332
+ }
59333
+ async function registerExecServerEnvironment(codex, execServerUrl) {
59334
+ const response = await codex.request("environment/add", {
59335
+ environmentId: remoteHarnessEnvironmentId,
59336
+ execServerUrl
59337
+ });
59338
+ assertJsonRpcOk(response, "environment/add");
59339
+ }
59340
+ function assertJsonRpcOk(response, method) {
59341
+ if ("error" in response) {
59342
+ throw new Error(`${method} failed: ${response.error.message}`);
59343
+ }
59344
+ }
59345
+ function remoteHarnessCodexEnv(sourceEnv, execServerUrl) {
59346
+ const env = {
59347
+ CODEX_EXEC_SERVER_URL: execServerUrl,
59348
+ LINZUMI_REMOTE_CODEX_HARNESS: "1"
59349
+ };
59350
+ for (const key of remoteHarnessCodexEnvAllowlist) {
59351
+ const value = sourceEnv[key];
59352
+ if (value !== void 0 && value.trim() !== "") {
59353
+ env[key] = value;
59354
+ }
59355
+ }
59356
+ return env;
59357
+ }
59358
+ var remoteHarnessCodexEnvAllowlist = [
59359
+ "PATH",
59360
+ "HOME",
59361
+ "TMPDIR",
59362
+ "SHELL",
59363
+ "USER",
59364
+ "LOGNAME",
59365
+ "CODEX_HOME",
59366
+ "XDG_CONFIG_HOME",
59367
+ "XDG_DATA_HOME",
59368
+ "SSL_CERT_FILE",
59369
+ "NIX_SSL_CERT_FILE",
59370
+ "NODE_EXTRA_CA_CERTS"
59371
+ ];
59372
+ function remoteHarnessRunnerJoinPayload(config) {
59373
+ return {
59374
+ clientName: "kandan-remote-codex-harness-worker",
59375
+ clientId: config.instanceId,
59376
+ connectionMode: "session_worker",
59377
+ runnerPid: process.pid,
59378
+ version: linzumiCliVersion,
59379
+ cwd: config.cwd,
59380
+ workspace: config.workspaceSlug,
59381
+ channel: config.channelSlug,
59382
+ capabilities: {
59383
+ codexAppServer: true,
59384
+ defaultAgentProvider: "codex",
59385
+ agentProviders: ["codex"],
59386
+ remoteCodexExecution: false,
59387
+ allowedCwdSuggestions: [],
59388
+ allowedCwdProjects: [],
59389
+ portForwarding: false,
59390
+ tcpForwarding: false,
59391
+ linzumiMcp: true
59392
+ }
59393
+ };
59394
+ }
59395
+ function defaultWaitUntilShutdown(cleanup) {
59396
+ return new Promise((resolve11, reject) => {
59397
+ const finish = () => {
59398
+ cleanup().then(resolve11, reject);
59399
+ };
59400
+ process.once("SIGINT", finish);
59401
+ process.once("SIGTERM", finish);
59402
+ });
59403
+ }
59404
+ function onceAsync(fn) {
59405
+ let promise;
59406
+ return () => {
59407
+ promise = promise ?? fn();
59408
+ return promise;
59409
+ };
59410
+ }
59411
+ function waitForCodexAppServerExit(child) {
59412
+ if (child.exitCode !== null || child.signalCode !== null) {
59413
+ return Promise.resolve({
59414
+ code: child.exitCode,
59415
+ signal: child.signalCode
59416
+ });
59417
+ }
59418
+ return new Promise((resolve11) => {
59419
+ child.once("exit", (code, signal) => {
59420
+ resolve11({ code, signal });
59421
+ });
59422
+ });
59423
+ }
59424
+ function requireObject(value) {
59425
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
59426
+ throw new Error("remote Codex harness worker config must be an object");
59427
+ }
59428
+ return value;
59429
+ }
59430
+ function requiredString2(input, key) {
59431
+ const value = input[key];
59432
+ if (typeof value !== "string" || value.trim() === "") {
59433
+ throw new Error(`remote Codex harness worker config missing ${key}`);
59434
+ }
59435
+ return value;
59436
+ }
59437
+ function requiredPositiveInteger(input, key) {
59438
+ const value = input[key];
59439
+ if (!Number.isInteger(value) || value <= 0) {
59440
+ throw new Error(`remote Codex harness worker config missing ${key}`);
59441
+ }
59442
+ return value;
59443
+ }
59444
+ function optionalStringField(input, key) {
59445
+ const value = input[key];
59446
+ if (value === void 0) {
59447
+ return {};
59448
+ }
59449
+ if (typeof value !== "string" || value.trim() === "") {
59450
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59451
+ }
59452
+ return { [key]: value };
59453
+ }
59454
+ function optionalBooleanField(input, key) {
59455
+ const value = input[key];
59456
+ if (value === void 0) {
59457
+ return {};
59458
+ }
59459
+ if (typeof value !== "boolean") {
59460
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59461
+ }
59462
+ return { [key]: value };
59463
+ }
59464
+ function optionalObjectField(input, key) {
59465
+ const value = input[key];
59466
+ if (value === void 0) {
59467
+ return {};
59468
+ }
59469
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
59470
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59471
+ }
59472
+ return { [key]: value };
59473
+ }
59474
+
58127
59475
  // src/index.ts
58128
59476
  init_userFacingErrors();
58129
59477
  var flagDefinitions = /* @__PURE__ */ new Map([
@@ -58250,6 +59598,9 @@ async function main(args) {
58250
59598
  case "commanderDaemon":
58251
59599
  await runCommanderDaemonCommand(parsed.args);
58252
59600
  return;
59601
+ case "remoteCodexHarnessWorker":
59602
+ await runRemoteCodexHarnessWorkerFromEnv();
59603
+ return;
58253
59604
  case "agentRunner": {
58254
59605
  const options = await parseAgentRunnerArgs(parsed.args);
58255
59606
  await runLocalCodexRunner(withLocalMachineId(options));
@@ -58301,6 +59652,8 @@ function parseCommand(args) {
58301
59652
  return ["daemon", "status", "wait", "stop"].includes(rest[0] ?? "") ? { command: "commanderDaemon", args: rest } : { command: "agentRunner", args: rest };
58302
59653
  case "commander-daemon":
58303
59654
  return { command: "commanderDaemon", args: ["daemon", ...rest] };
59655
+ case "remote-codex-harness-worker":
59656
+ return { command: "remoteCodexHarnessWorker", args: rest };
58304
59657
  case "commander-status":
58305
59658
  return { command: "commanderDaemon", args: ["status", ...rest] };
58306
59659
  case "commander-wait":
@@ -58397,7 +59750,7 @@ function runPathsCommand(args) {
58397
59750
  if (pathValue === void 0 || pathValue.trim() === "") {
58398
59751
  throw new Error("missing path for linzumi paths add");
58399
59752
  }
58400
- const trustedPath = realpathSync6(resolve10(expandUserPath(pathValue)));
59753
+ const trustedPath = realpathSync7(resolve10(expandUserPath(pathValue)));
58401
59754
  if (linzumiUrl === void 0) {
58402
59755
  addAllowedCwd(pathValue);
58403
59756
  } else {
@@ -58606,7 +59959,7 @@ async function parseStartRunnerArgs(args, deps = {
58606
59959
  codexBin: requestedCodexBin,
58607
59960
  codeServerBin: customCodeServerBin
58608
59961
  });
58609
- assertStartDependencies(initialDependencyStatus);
59962
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58610
59963
  const codexBin = initialDependencyStatus.codex.command;
58611
59964
  const explicitToken = stringValue7(values, "token");
58612
59965
  const authFilePath = stringValue7(values, "auth-file");
@@ -58652,7 +60005,7 @@ async function parseStartRunnerArgs(args, deps = {
58652
60005
  codeServerBin: editorRuntime.codeServerBin,
58653
60006
  editorRuntime: editorRuntime.status
58654
60007
  });
58655
- assertStartDependencies(dependencyStatus);
60008
+ assertRunnerConnectionDependencies(dependencyStatus);
58656
60009
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58657
60010
  return {
58658
60011
  kandanUrl,
@@ -58664,6 +60017,7 @@ async function parseStartRunnerArgs(args, deps = {
58664
60017
  cwd,
58665
60018
  codexBin,
58666
60019
  codexUrl: stringValue7(values, "codex-url"),
60020
+ launchSource: electronAutoConnectLaunchSource(),
58667
60021
  launchTui: values.get("launch-tui") === true,
58668
60022
  fast: values.get("fast") === true,
58669
60023
  logFile: stringValue7(values, "log-file"),
@@ -58741,7 +60095,7 @@ async function parseAgentRunnerArgs(args, deps = {
58741
60095
  codexBin: requestedCodexBin,
58742
60096
  codeServerBin: customCodeServerBin
58743
60097
  });
58744
- assertStartDependencies(initialDependencyStatus);
60098
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58745
60099
  const codexBin = initialDependencyStatus.codex.command;
58746
60100
  const editorRuntime = await deps.resolveEditorRuntime({
58747
60101
  kandanUrl,
@@ -58755,7 +60109,7 @@ async function parseAgentRunnerArgs(args, deps = {
58755
60109
  codeServerBin: editorRuntime.codeServerBin,
58756
60110
  editorRuntime: editorRuntime.status
58757
60111
  });
58758
- assertStartDependencies(dependencyStatus);
60112
+ assertRunnerConnectionDependencies(dependencyStatus);
58759
60113
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58760
60114
  return {
58761
60115
  kandanUrl,
@@ -58767,6 +60121,7 @@ async function parseAgentRunnerArgs(args, deps = {
58767
60121
  cwd,
58768
60122
  codexBin,
58769
60123
  codexUrl: stringValue7(values, "codex-url"),
60124
+ launchSource: electronAutoConnectLaunchSource(),
58770
60125
  launchTui: values.get("launch-tui") === true,
58771
60126
  fast: values.get("fast") === true,
58772
60127
  logFile: stringValue7(values, "log-file"),
@@ -58785,7 +60140,7 @@ async function parseAgentRunnerArgs(args, deps = {
58785
60140
  };
58786
60141
  }
58787
60142
  function readAgentTokenTextFile(path2) {
58788
- return existsSync13(path2) ? readFileSync16(path2, "utf8") : void 0;
60143
+ return existsSync14(path2) ? readFileSync16(path2, "utf8") : void 0;
58789
60144
  }
58790
60145
  function rejectAgentRunnerTargetingFlags(values) {
58791
60146
  const unsupportedFlags = [
@@ -58901,7 +60256,7 @@ async function parseRunnerArgs(args, deps = {
58901
60256
  codexBin: requestedCodexBin,
58902
60257
  codeServerBin: customCodeServerBin
58903
60258
  });
58904
- assertStartDependencies(initialDependencyStatus);
60259
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58905
60260
  const codexBin = initialDependencyStatus.codex.command;
58906
60261
  const explicitToken = stringValue7(values, "token");
58907
60262
  const token = await deps.resolveToken({
@@ -58928,7 +60283,7 @@ async function parseRunnerArgs(args, deps = {
58928
60283
  codeServerBin: editorRuntime.codeServerBin,
58929
60284
  editorRuntime: editorRuntime.status
58930
60285
  });
58931
- assertStartDependencies(dependencyStatus);
60286
+ assertRunnerConnectionDependencies(dependencyStatus);
58932
60287
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58933
60288
  return {
58934
60289
  kandanUrl,
@@ -58939,6 +60294,7 @@ async function parseRunnerArgs(args, deps = {
58939
60294
  cwd,
58940
60295
  codexBin,
58941
60296
  codexUrl: stringValue7(values, "codex-url"),
60297
+ launchSource: electronAutoConnectLaunchSource(),
58942
60298
  launchTui: values.get("launch-tui") === true,
58943
60299
  fast: values.get("fast") === true,
58944
60300
  logFile: stringValue7(values, "log-file"),
@@ -58968,6 +60324,9 @@ function runnerRuntimeDefaultsFromValues(values) {
58968
60324
  allowPortForwardingByDefault: true
58969
60325
  };
58970
60326
  }
60327
+ function electronAutoConnectLaunchSource() {
60328
+ return process.env.LINZUMI_ELECTRON_AUTO_CONNECT_RUNNER === "1" ? "electron_auto_connect" : void 0;
60329
+ }
58971
60330
  function kandanUrlValue(values) {
58972
60331
  const apiUrl = stringValue7(values, "api-url");
58973
60332
  const linzumiUrl = stringValue7(values, "linzumi-url");