@linzumi/cli 0.0.74-beta → 0.0.75-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.75-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,272 @@ 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 } 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
+ return [
16789
+ "(version 1)",
16790
+ "(deny default)",
16791
+ "(allow process*)",
16792
+ "(allow signal (target self))",
16793
+ "(allow sysctl-read)",
16794
+ "(allow file-read-metadata)",
16795
+ '(allow file-read* (literal "/dev/null"))',
16796
+ '(allow file-read* (literal "/dev/random"))',
16797
+ '(allow file-read* (literal "/dev/urandom"))',
16798
+ readableRules,
16799
+ writableTempRules,
16800
+ `(allow file-read* (subpath ${sandboxString(request.cwd)}))`,
16801
+ `(allow file-write* (subpath ${sandboxString(request.cwd)}))`
16802
+ ].join("\n");
16803
+ }
16804
+ function createBoundedOutputBuffer() {
16805
+ let bytes = 0;
16806
+ let output = "";
16807
+ let isTruncated = false;
16808
+ return {
16809
+ append: (chunk) => {
16810
+ const text2 = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
16811
+ const remaining = maxCapturedOutputBytes - bytes;
16812
+ if (remaining <= 0) {
16813
+ isTruncated = true;
16814
+ return;
16815
+ }
16816
+ const buffer = Buffer.from(text2, "utf8");
16817
+ if (buffer.byteLength <= remaining) {
16818
+ bytes += buffer.byteLength;
16819
+ output += text2;
16820
+ return;
16821
+ }
16822
+ bytes = maxCapturedOutputBytes;
16823
+ output += buffer.subarray(0, remaining).toString("utf8");
16824
+ isTruncated = true;
16825
+ },
16826
+ value: () => output,
16827
+ truncated: () => isTruncated
16828
+ };
16829
+ }
16830
+ function normalizedSandboxKind(value) {
16831
+ switch (value) {
16832
+ case void 0:
16833
+ case "":
16834
+ return void 0;
16835
+ case "bubblewrap":
16836
+ case "macos-seatbelt":
16837
+ return value;
16838
+ default:
16839
+ throw new Error(`unsupported remote Codex sandbox: ${value}`);
16840
+ }
16841
+ }
16842
+ function requiredSandboxBin(kind, env) {
16843
+ const value = env.LINZUMI_REMOTE_CODEX_SANDBOX_BIN;
16844
+ if (value === void 0 || value.trim() === "") {
16845
+ throw new Error(`LINZUMI_REMOTE_CODEX_SANDBOX_BIN is required for ${kind}`);
16846
+ }
16847
+ if (!isAbsolute3(value)) {
16848
+ throw new Error("LINZUMI_REMOTE_CODEX_SANDBOX_BIN must be absolute");
16849
+ }
16850
+ return value;
16851
+ }
16852
+ function parentDirs(path2) {
16853
+ const parts = path2.split("/").filter((part) => part !== "");
16854
+ const parents = parts.slice(0, -1);
16855
+ return parents.map(
16856
+ (_part, index) => `/${parents.slice(0, index + 1).join("/")}`
16857
+ );
16858
+ }
16859
+ function uniqueStrings2(values) {
16860
+ return Array.from(new Set(values));
16861
+ }
16862
+ function sandboxString(value) {
16863
+ return JSON.stringify(value);
16864
+ }
16865
+ var maxCapturedOutputBytes, linuxReadOnlyRoots, macosReadOnlyRoots, macosWritableTempRoots;
16866
+ var init_remoteCodexSandboxRunner = __esm({
16867
+ "src/remoteCodexSandboxRunner.ts"() {
16868
+ "use strict";
16869
+ maxCapturedOutputBytes = 512 * 1024;
16870
+ linuxReadOnlyRoots = [
16871
+ "/bin",
16872
+ "/etc/pki",
16873
+ "/etc/ssl",
16874
+ "/lib",
16875
+ "/lib64",
16876
+ "/nix/store",
16877
+ "/usr",
16878
+ "/usr/local"
16879
+ ];
16880
+ macosReadOnlyRoots = [
16881
+ "/System",
16882
+ "/Library",
16883
+ "/usr",
16884
+ "/bin",
16885
+ "/sbin",
16886
+ "/opt/homebrew",
16887
+ "/nix/store"
16888
+ ];
16889
+ macosWritableTempRoots = ["/tmp", "/private/tmp"];
16890
+ }
16891
+ });
16892
+
16138
16893
  // src/runner.ts
16139
- import { spawn as spawn7, spawnSync as spawnSync4 } from "node:child_process";
16894
+ import { spawn as spawn8, spawnSync as spawnSync4 } from "node:child_process";
16140
16895
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
16141
16896
  import {
16142
16897
  chmodSync as chmodSync2,
@@ -16150,7 +16905,7 @@ import {
16150
16905
  } from "node:fs";
16151
16906
  import { createServer as createServer3 } from "node:http";
16152
16907
  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";
16908
+ import { dirname as dirname10, isAbsolute as isAbsolute4, join as join14, resolve as resolve7 } from "node:path";
16154
16909
  async function runLocalCodexRunner(options) {
16155
16910
  const log = makeRunnerLogger(options);
16156
16911
  const cleanup = {
@@ -16173,6 +16928,7 @@ async function runLocalCodexRunner(options) {
16173
16928
  cwd: options.cwd,
16174
16929
  workspace: runnerWorkspaceSlug(options) ?? null,
16175
16930
  linzumiUrl: options.kandanUrl,
16931
+ launchSource: options.launchSource,
16176
16932
  configPath: options.runnerLockConfigPath
16177
16933
  });
16178
16934
  cleanup.actions.push(() => runnerLock.release());
@@ -16214,6 +16970,7 @@ async function runThreadCodexWorker(options) {
16214
16970
  }
16215
16971
  async function openLocalCodexRunner(options, log, cleanup, close) {
16216
16972
  const agentProviders = availableRunnerAgentProviders(options);
16973
+ const localCodexAppServerAvailable = hasLocalCodexAppServerCapability(options);
16217
16974
  const allowedForwardPorts = options.allowedForwardPorts ?? [];
16218
16975
  const liveForwardPorts = new Set(allowedForwardPorts);
16219
16976
  const managedForwardPorts = /* @__PURE__ */ new Set();
@@ -16274,6 +17031,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16274
17031
  });
16275
17032
  const startupAllowedCwds = normalizeAllowedCwds(options.allowedCwds);
16276
17033
  const allowedCwds = { value: [...startupAllowedCwds] };
17034
+ const remoteCodexSandboxRunner = options.remoteCodexSandboxRunner ?? createConfiguredRemoteCodexSandboxRunner({
17035
+ env: process.env,
17036
+ platform: process.platform
17037
+ });
17038
+ const activeRemoteCodexExecRequests = /* @__PURE__ */ new Map();
16277
17039
  const missingAllowedCwds = {
16278
17040
  value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
16279
17041
  };
@@ -16290,6 +17052,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16290
17052
  queuedCandidates: /* @__PURE__ */ new Map()
16291
17053
  };
16292
17054
  cleanup.actions.push(() => {
17055
+ for (const controller of activeRemoteCodexExecRequests.values()) {
17056
+ controller.abort(new Error("local runner stopped"));
17057
+ }
17058
+ activeRemoteCodexExecRequests.clear();
16293
17059
  if (localEditorState.value.status === "running") {
16294
17060
  localEditorState.value.process.kill("SIGINT");
16295
17061
  localEditorState.value.collaboration?.process.kill("SIGINT");
@@ -16314,8 +17080,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16314
17080
  };
16315
17081
  });
16316
17082
  const capabilitiesPayload = () => ({
16317
- codexAppServer: true,
16318
- codexRemoteTui: true,
17083
+ codexAppServer: localCodexAppServerAvailable,
17084
+ codexRemoteTui: localCodexAppServerAvailable,
16319
17085
  agentProviders,
16320
17086
  defaultAgentProvider: "codex",
16321
17087
  startInstance: allowedCwds.value.length > 0,
@@ -16331,6 +17097,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16331
17097
  portForwarding: liveForwardPorts.size > 0,
16332
17098
  tcpForwarding: true,
16333
17099
  linzumiMcp: true,
17100
+ remoteCodexExecution: remoteCodexSandboxRunner !== void 0 && allowedCwds.value.length > 0,
17101
+ remoteCodexExecutionStatus: remoteCodexSandboxRunner === void 0 ? "sandbox_unavailable" : allowedCwds.value.length > 0 ? "ready" : "cwd_unavailable",
16334
17102
  allowedPorts: Array.from(liveForwardPorts).sort(
16335
17103
  (left, right) => left - right
16336
17104
  ),
@@ -16380,18 +17148,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
16380
17148
  const replacedRunners = replacementRunnerSummaries(
16381
17149
  objectValue(joinResponse)?.replaced_runners
16382
17150
  );
16383
- const started = options.codexUrl === void 0 ? await startOwnedCodexAppServer(options) : void 0;
17151
+ const started = options.codexUrl === void 0 && localCodexAppServerAvailable ? await startOwnedCodexAppServer(options) : void 0;
16384
17152
  if (started !== void 0) {
16385
17153
  cleanup.actions.push(() => {
16386
17154
  started.stop();
16387
17155
  });
16388
17156
  }
16389
17157
  const codexUrl = options.codexUrl ?? started?.url;
16390
- if (codexUrl === void 0) {
16391
- throw new Error("missing codex app-server websocket URL");
16392
- }
16393
17158
  if (started !== void 0) {
16394
- const appServerPort = explicitUrlPort(codexUrl);
17159
+ const appServerPort = explicitUrlPort(started.url);
16395
17160
  if (appServerPort !== void 0) {
16396
17161
  markCommanderManagedForwardPort(appServerPort, {
16397
17162
  processName: "Codex app-server",
@@ -17081,15 +17846,20 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17081
17846
  };
17082
17847
  void state.exited.then(handleExitAfterInitialStatus);
17083
17848
  };
17084
- const codex = await connectCodexAppServer(codexUrl);
17085
- cleanup.actions.push(() => codex.close());
17849
+ const codex = codexUrl === void 0 ? void 0 : await connectCodexAppServer(codexUrl);
17850
+ if (codex !== void 0) {
17851
+ cleanup.actions.push(() => codex.close());
17852
+ }
17853
+ if (options.channelSession !== void 0 && codex === void 0) {
17854
+ throw new Error("channel session requires a Codex app-server connection");
17855
+ }
17086
17856
  const seq = { value: 0 };
17087
17857
  const discoveredCodexThreads = { value: [] };
17088
17858
  const runtimeDefaults = runnerRuntimeDefaults(options);
17089
17859
  const instancePayload = {
17090
17860
  instanceId,
17091
17861
  clientId,
17092
- codexUrl,
17862
+ ...codexUrl === void 0 ? {} : { codexUrl },
17093
17863
  tuiLaunched: options.launchTui,
17094
17864
  cwd: options.cwd,
17095
17865
  hostname: runnerHost,
@@ -17108,7 +17878,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17108
17878
  workspace: runnerWorkspaceSlug(options) ?? null,
17109
17879
  version: linzumiCliVersion,
17110
17880
  instanceId,
17111
- codexUrl,
17881
+ codexUrl: codexUrl ?? null,
17112
17882
  replacedRunners
17113
17883
  });
17114
17884
  const channelSession = options.channelSession === void 0 ? void 0 : await attachChannelSession({
@@ -17422,6 +18192,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17422
18192
  options,
17423
18193
  agentProviders,
17424
18194
  allowedCwds,
18195
+ remoteCodexSandboxRunner,
18196
+ activeRemoteCodexExecRequests,
17425
18197
  activeClaudeCodeSessions,
17426
18198
  pendingClaudeCodeApprovals,
17427
18199
  ensureClaudeCodeForwardPortSession,
@@ -17444,7 +18216,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17444
18216
  const heartbeatPayload = () => ({
17445
18217
  instanceId,
17446
18218
  clientId,
17447
- codexUrl,
18219
+ ...codexUrl === void 0 ? {} : { codexUrl },
17448
18220
  cwd: options.cwd,
17449
18221
  hostname: runnerHost,
17450
18222
  workspace: runnerWorkspaceSlug(options) ?? null,
@@ -17476,10 +18248,13 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17476
18248
  );
17477
18249
  cleanup.actions.push(() => forwardTcp.close());
17478
18250
  const channelCodexThreadId = channelSession?.currentCodexThreadId();
17479
- if (options.launchTui && channelCodexThreadId !== void 0) {
18251
+ if (options.launchTui && codex === void 0) {
18252
+ throw new Error("Codex TUI requires a Codex app-server connection");
18253
+ }
18254
+ if (options.launchTui && codex !== void 0 && channelCodexThreadId !== void 0) {
17480
18255
  await prepareCodexThreadForTuiResume(codex, channelCodexThreadId);
17481
18256
  }
17482
- const tui = options.launchTui ? launchCodexTui(
18257
+ const tui = options.launchTui && codexUrl !== void 0 ? launchCodexTui(
17483
18258
  options.codexBin,
17484
18259
  codexUrl,
17485
18260
  options.cwd,
@@ -17492,7 +18267,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17492
18267
  tui.kill("SIGINT");
17493
18268
  });
17494
18269
  }
17495
- codex.onNotification((notification) => {
18270
+ codex?.onNotification((notification) => {
17496
18271
  seq.value += 1;
17497
18272
  const params = notification.params ?? {};
17498
18273
  const logPayload = codexNotificationConsoleLogPayload(
@@ -17747,6 +18522,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
17747
18522
  options,
17748
18523
  agentProviders,
17749
18524
  allowedCwds,
18525
+ remoteCodexSandboxRunner,
18526
+ activeRemoteCodexExecRequests,
17750
18527
  activeClaudeCodeSessions,
17751
18528
  pendingClaudeCodeApprovals,
17752
18529
  ensureClaudeCodeForwardPortSession,
@@ -17831,6 +18608,8 @@ function controlThreadId(control) {
17831
18608
  case "forward_tcp_open":
17832
18609
  case "forward_tcp_send":
17833
18610
  case "forward_tcp_close":
18611
+ case "remote_codex_exec":
18612
+ case "remote_codex_exec_cancel":
17834
18613
  case "update_thread_interaction_access": {
17835
18614
  const threadId = stringValue(control.threadId)?.trim();
17836
18615
  return threadId === void 0 || threadId === "" ? void 0 : threadId;
@@ -17940,9 +18719,15 @@ function replacementRunnerSummaries(value) {
17940
18719
  });
17941
18720
  }
17942
18721
  function makeRunnerLogger(options) {
18722
+ const consoleReporter = options.launchTui && options.consoleReporter === void 0 ? void 0 : (event, payload) => {
18723
+ if (!options.launchTui) {
18724
+ reportRunnerConsoleEvent(event, payload);
18725
+ }
18726
+ options.consoleReporter?.(event, payload);
18727
+ };
17943
18728
  return createRunnerLogger(
17944
18729
  options.logFile ?? defaultRunnerLogFile(),
17945
- options.launchTui ? void 0 : reportRunnerConsoleEvent
18730
+ consoleReporter
17946
18731
  );
17947
18732
  }
17948
18733
  function installCleanupHandlers(close) {
@@ -17971,7 +18756,7 @@ function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
17971
18756
  cwd,
17972
18757
  purpose: "codex.tui"
17973
18758
  });
17974
- const child = spawn7(codexBin, args, {
18759
+ const child = spawn8(codexBin, args, {
17975
18760
  cwd,
17976
18761
  env: process.env,
17977
18762
  stdio: "inherit"
@@ -18098,7 +18883,29 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
18098
18883
  );
18099
18884
  }
18100
18885
  }
18101
- async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread, onThreadProcessStart) {
18886
+ async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread, onThreadProcessStart) {
18887
+ if (codex === void 0) {
18888
+ switch (control.type) {
18889
+ case "remote_codex_exec":
18890
+ case "remote_codex_exec_cancel":
18891
+ return await applyRemoteCodexExecControl(
18892
+ control,
18893
+ instanceId,
18894
+ allowedCwds.value,
18895
+ remoteCodexSandboxRunner,
18896
+ activeRemoteCodexExecRequests,
18897
+ log
18898
+ );
18899
+ default:
18900
+ return {
18901
+ instanceId,
18902
+ controlType: control.type,
18903
+ requestId: "requestId" in control ? stringValue(control.requestId) ?? null : null,
18904
+ ok: false,
18905
+ error: "agent_provider_unavailable:codex"
18906
+ };
18907
+ }
18908
+ }
18102
18909
  switch (control.type) {
18103
18910
  case "start_instance": {
18104
18911
  const cwd = resolveAllowedCwd(control.cwd, allowedCwds.value);
@@ -18597,6 +19404,26 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
18597
19404
  case "suggest_tasks": {
18598
19405
  return suggestRunnerTasks(control, options, allowedCwds.value);
18599
19406
  }
19407
+ case "remote_codex_exec": {
19408
+ return await applyRemoteCodexExecControl(
19409
+ control,
19410
+ instanceId,
19411
+ allowedCwds.value,
19412
+ remoteCodexSandboxRunner,
19413
+ activeRemoteCodexExecRequests,
19414
+ log
19415
+ );
19416
+ }
19417
+ case "remote_codex_exec_cancel": {
19418
+ return await applyRemoteCodexExecControl(
19419
+ control,
19420
+ instanceId,
19421
+ allowedCwds.value,
19422
+ remoteCodexSandboxRunner,
19423
+ activeRemoteCodexExecRequests,
19424
+ log
19425
+ );
19426
+ }
18600
19427
  case "start_turn": {
18601
19428
  const response = await codex.request("turn/start", {
18602
19429
  threadId: control.threadId,
@@ -18769,6 +19596,55 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
18769
19596
  return { instanceId, controlType: control.type, skipped: true };
18770
19597
  }
18771
19598
  }
19599
+ async function applyRemoteCodexExecControl(control, instanceId, allowedCwds, remoteCodexSandboxRunner, activeRemoteCodexExecRequests, log) {
19600
+ switch (control.type) {
19601
+ case "remote_codex_exec": {
19602
+ const requestId = stringValue(control.requestId)?.trim();
19603
+ const abortController = new AbortController();
19604
+ if (requestId !== void 0 && requestId !== "") {
19605
+ activeRemoteCodexExecRequests.set(requestId, abortController);
19606
+ }
19607
+ const result = await handleRemoteCodexExecControl(control, {
19608
+ instanceId,
19609
+ allowedCwds,
19610
+ sandboxRunner: remoteCodexSandboxRunner,
19611
+ abortSignal: abortController.signal
19612
+ });
19613
+ if (requestId !== void 0 && requestId !== "") {
19614
+ activeRemoteCodexExecRequests.delete(requestId);
19615
+ }
19616
+ log("remote_codex_exec.handled", {
19617
+ requestId: stringValue(control.requestId) ?? null,
19618
+ ok: result.ok === true,
19619
+ error: typeof result.error === "string" ? result.error : null
19620
+ });
19621
+ return result;
19622
+ }
19623
+ case "remote_codex_exec_cancel": {
19624
+ const requestId = stringValue(control.requestId)?.trim();
19625
+ if (requestId === void 0 || requestId === "") {
19626
+ return {
19627
+ instanceId,
19628
+ controlType: control.type,
19629
+ ok: false,
19630
+ error: "request_id_required"
19631
+ };
19632
+ }
19633
+ const activeRequest = activeRemoteCodexExecRequests.get(requestId);
19634
+ if (activeRequest !== void 0) {
19635
+ activeRequest.abort(new Error("remote_codex_exec_cancelled"));
19636
+ activeRemoteCodexExecRequests.delete(requestId);
19637
+ }
19638
+ return {
19639
+ instanceId,
19640
+ controlType: control.type,
19641
+ requestId,
19642
+ ok: true,
19643
+ cancelled: activeRequest !== void 0
19644
+ };
19645
+ }
19646
+ }
19647
+ }
18772
19648
  function startInstanceFailureReason(stage, error) {
18773
19649
  const detail = truncateFailureDetail(
18774
19650
  error instanceof Error ? error.message : String(error)
@@ -18901,6 +19777,18 @@ function availableRunnerAgentProviders(options) {
18901
19777
  return ["codex"];
18902
19778
  }
18903
19779
  }
19780
+ function hasLocalCodexAppServerCapability(options) {
19781
+ if (options.codexUrl !== void 0) {
19782
+ return true;
19783
+ }
19784
+ switch (options.dependencyStatus?.codex.available) {
19785
+ case false:
19786
+ return false;
19787
+ case true:
19788
+ case void 0:
19789
+ return true;
19790
+ }
19791
+ }
18904
19792
  function startInstanceAgentProvider(control) {
18905
19793
  const value = stringValue(control.agentProvider)?.trim();
18906
19794
  switch (value) {
@@ -20362,7 +21250,7 @@ async function spawnLocalThreadRunnerProcess(options) {
20362
21250
  cwd: options.cwd,
20363
21251
  purpose: "linzumi.thread_runner"
20364
21252
  });
20365
- const child = spawn7(process.execPath, nodeArgs, {
21253
+ const child = spawn8(process.execPath, nodeArgs, {
20366
21254
  cwd: options.cwd,
20367
21255
  env,
20368
21256
  stdio: ["inherit", "inherit", "inherit", "ipc"]
@@ -20477,7 +21365,7 @@ function threadRunnerScriptPath(scriptPath, cwd) {
20477
21365
  "cannot fork thread runner without current CLI script path"
20478
21366
  );
20479
21367
  }
20480
- return isAbsolute3(scriptPath) ? scriptPath : resolve7(cwd, scriptPath);
21368
+ return isAbsolute4(scriptPath) ? scriptPath : resolve7(cwd, scriptPath);
20481
21369
  }
20482
21370
  function threadRunnerLoaderExecArgv(execArgv, index = 0) {
20483
21371
  const current = execArgv[index];
@@ -20870,7 +21758,7 @@ function browseRunnerDirectory(control, options) {
20870
21758
  error: "not_directory"
20871
21759
  };
20872
21760
  }
20873
- const parent = dirname9(currentPath);
21761
+ const parent = dirname10(currentPath);
20874
21762
  const entries = readdirSync2(currentPath, { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => visibleRunnerDirectoryEntryName(entry.name)).map((entry) => {
20875
21763
  const path2 = join14(currentPath, entry.name);
20876
21764
  return {
@@ -21090,6 +21978,7 @@ var init_runner = __esm({
21090
21978
  init_linzumiContext();
21091
21979
  init_localCapabilities();
21092
21980
  init_localConfig();
21981
+ init_remoteCodexExecutionContext();
21093
21982
  init_helloLinzumiProject();
21094
21983
  init_localForwarding();
21095
21984
  init_localEditor();
@@ -21109,6 +21998,7 @@ var init_runner = __esm({
21109
21998
  init_threadCodexWorkerIpc();
21110
21999
  init_signupTaskSuggestions();
21111
22000
  init_userFacingErrors();
22001
+ init_remoteCodexSandboxRunner();
21112
22002
  THREAD_RUNNER_READY_TIMEOUT_MS = 3e4;
21113
22003
  claudeSessionStoreSequenceHighWater = /* @__PURE__ */ new Map();
21114
22004
  ClaudeCodeOutputPostError = class extends Error {
@@ -21122,9 +22012,9 @@ var init_runner = __esm({
21122
22012
  });
21123
22013
 
21124
22014
  // src/kandanTls.ts
21125
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "node:fs";
22015
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
21126
22016
  import { Agent } from "undici";
21127
- import { WebSocket as WsWebSocket } from "ws";
22017
+ import WsWebSocket from "ws";
21128
22018
  function kandanTlsTrustFromEnv() {
21129
22019
  return kandanTlsTrustFromCaFile(process.env.KANDAN_TLS_CA_FILE);
21130
22020
  }
@@ -21133,7 +22023,7 @@ function kandanTlsTrustFromCaFile(caFile) {
21133
22023
  return void 0;
21134
22024
  }
21135
22025
  const trimmed = caFile.trim();
21136
- if (!existsSync9(trimmed)) {
22026
+ if (!existsSync10(trimmed)) {
21137
22027
  throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
21138
22028
  }
21139
22029
  const ca = readFileSync11(trimmed, "utf8");
@@ -39919,7 +40809,7 @@ var init_RemoveFileError = __esm({
39919
40809
  });
39920
40810
 
39921
40811
  // ../../node_modules/@inquirer/external-editor/dist/esm/index.js
39922
- import { spawn as spawn9, spawnSync as spawnSync5 } from "child_process";
40812
+ import { spawn as spawn10, spawnSync as spawnSync5 } from "child_process";
39923
40813
  import { readFileSync as readFileSync14, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "fs";
39924
40814
  import path from "node:path";
39925
40815
  import os from "node:os";
@@ -40075,7 +40965,7 @@ var init_esm5 = __esm({
40075
40965
  }
40076
40966
  launchEditorAsync(callback) {
40077
40967
  try {
40078
- const editorProcess = spawn9(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
40968
+ const editorProcess = spawn10(this.editor.bin, this.editor.args.concat([this.tempFile]), { stdio: "inherit" });
40079
40969
  editorProcess.on("exit", (code) => {
40080
40970
  this.lastExitStatus = code;
40081
40971
  setImmediate(callback);
@@ -41409,9 +42299,9 @@ __export(signupFlow_exports, {
41409
42299
  signupTaskSuggestionWaitMessageForTest: () => signupTaskSuggestionWaitMessageForTest,
41410
42300
  toggleProjectPickerSelectionForTest: () => toggleProjectPickerSelectionForTest
41411
42301
  });
41412
- import { spawn as spawn10, spawnSync as spawnSync6 } from "node:child_process";
42302
+ import { spawn as spawn11, spawnSync as spawnSync6 } from "node:child_process";
41413
42303
  import {
41414
- existsSync as existsSync12,
42304
+ existsSync as existsSync13,
41415
42305
  constants as fsConstants,
41416
42306
  mkdirSync as mkdirSync12,
41417
42307
  mkdtempSync as mkdtempSync5,
@@ -41423,7 +42313,7 @@ import {
41423
42313
  } from "node:fs";
41424
42314
  import { access } from "node:fs/promises";
41425
42315
  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";
42316
+ import { delimiter as delimiter3, dirname as dirname13, join as join17, resolve as resolve9 } from "node:path";
41427
42317
  import { stdin as defaultStdin, stdout as defaultStdout } from "node:process";
41428
42318
  import { emitKeypressEvents } from "node:readline";
41429
42319
  function signupHelpText() {
@@ -41522,7 +42412,7 @@ function defaultSignupDraftStore(serviceUrl) {
41522
42412
  const path2 = defaultSignupDraftPath(serviceUrl);
41523
42413
  return {
41524
42414
  read: () => {
41525
- if (!existsSync12(path2)) {
42415
+ if (!existsSync13(path2)) {
41526
42416
  return void 0;
41527
42417
  }
41528
42418
  let parsed;
@@ -41537,7 +42427,7 @@ function defaultSignupDraftStore(serviceUrl) {
41537
42427
  return comparableServiceUrl(parsed.serviceUrl) === comparableServiceUrl(serviceUrl) ? parsed : void 0;
41538
42428
  },
41539
42429
  write: (draft) => {
41540
- mkdirSync12(dirname12(path2), { recursive: true });
42430
+ mkdirSync12(dirname13(path2), { recursive: true });
41541
42431
  writeFileSync11(path2, `${JSON.stringify(draft, null, 2)}
41542
42432
  `, {
41543
42433
  encoding: "utf8",
@@ -41551,7 +42441,7 @@ function defaultSignupDraftStore(serviceUrl) {
41551
42441
  }
41552
42442
  function defaultSignupDraftPath(serviceUrl) {
41553
42443
  return join17(
41554
- dirname12(localConfigPath()),
42444
+ dirname13(localConfigPath()),
41555
42445
  `signup-draft.${localConfigScopeFileStem(serviceUrl)}.json`
41556
42446
  );
41557
42447
  }
@@ -41585,7 +42475,7 @@ function defaultSignupTaskCacheStore(serviceUrl) {
41585
42475
  }
41586
42476
  }
41587
42477
  };
41588
- mkdirSync12(dirname12(path2), { recursive: true });
42478
+ mkdirSync12(dirname13(path2), { recursive: true });
41589
42479
  writeFileSync11(path2, `${JSON.stringify(next, null, 2)}
41590
42480
  `, {
41591
42481
  encoding: "utf8",
@@ -41596,12 +42486,12 @@ function defaultSignupTaskCacheStore(serviceUrl) {
41596
42486
  }
41597
42487
  function defaultSignupTaskCachePath(serviceUrl) {
41598
42488
  return join17(
41599
- dirname12(localConfigPath()),
42489
+ dirname13(localConfigPath()),
41600
42490
  `signup-task-cache.${localConfigScopeFileStem(serviceUrl)}.json`
41601
42491
  );
41602
42492
  }
41603
42493
  function readSignupTaskCache(path2) {
41604
- if (!existsSync12(path2)) {
42494
+ if (!existsSync13(path2)) {
41605
42495
  return { version: 1, entries: {} };
41606
42496
  }
41607
42497
  let parsed;
@@ -42916,7 +43806,7 @@ async function openUrlInBrowser(url) {
42916
43806
  }
42917
43807
  const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
42918
43808
  await new Promise((resolve11, reject) => {
42919
- const child = spawn10(command, args, { stdio: "ignore", detached: true });
43809
+ const child = spawn11(command, args, { stdio: "ignore", detached: true });
42920
43810
  child.once("spawn", () => {
42921
43811
  child.unref();
42922
43812
  resolve11();
@@ -44114,7 +45004,7 @@ function parseTaskSuggestionResponse2(response) {
44114
45004
  }
44115
45005
  function runProcess3(args) {
44116
45006
  return new Promise((resolveProcess, rejectProcess) => {
44117
- const child = spawn10(args.command, [...args.args], {
45007
+ const child = spawn11(args.command, [...args.args], {
44118
45008
  cwd: args.cwd,
44119
45009
  stdio: ["pipe", "ignore", "pipe"]
44120
45010
  });
@@ -44156,7 +45046,7 @@ function runProcess3(args) {
44156
45046
  }
44157
45047
  function runProcessCapture(args) {
44158
45048
  return new Promise((resolveProcess) => {
44159
- const child = spawn10(args.command, [...args.args], {
45049
+ const child = spawn11(args.command, [...args.args], {
44160
45050
  cwd: args.cwd,
44161
45051
  stdio: ["ignore", "pipe", "ignore"]
44162
45052
  });
@@ -44374,7 +45264,7 @@ function spawnSyncGitOutput(args, cwd) {
44374
45264
  }
44375
45265
  function probeToolWithArgs(command, args, cwd) {
44376
45266
  return new Promise((resolve11) => {
44377
- const child = spawn10(command, [...args], {
45267
+ const child = spawn11(command, [...args], {
44378
45268
  cwd,
44379
45269
  stdio: ["ignore", "pipe", "pipe"]
44380
45270
  });
@@ -44412,7 +45302,7 @@ async function discoverCodeRoots(homeDir) {
44412
45302
  const candidates = ["src", "code", "projects"].map(
44413
45303
  (name) => join17(homeDir, name)
44414
45304
  );
44415
- return candidates.filter((path2) => existsSync12(path2)).flatMap((path2) => discoveredProjectNames(path2));
45305
+ return candidates.filter((path2) => existsSync13(path2)).flatMap((path2) => discoveredProjectNames(path2));
44416
45306
  }
44417
45307
  function discoveredProjectNames(root) {
44418
45308
  try {
@@ -44516,25 +45406,25 @@ function looksLikeProject(path2) {
44516
45406
  "pnpm-lock.yaml",
44517
45407
  "yarn.lock",
44518
45408
  "package-lock.json"
44519
- ].some((name) => existsSync12(join17(path2, name)));
45409
+ ].some((name) => existsSync13(join17(path2, name)));
44520
45410
  }
44521
45411
  function detectProjectLanguage(path2) {
44522
- if (existsSync12(join17(path2, "pyproject.toml")) || existsSync12(join17(path2, "requirements.txt"))) {
45412
+ if (existsSync13(join17(path2, "pyproject.toml")) || existsSync13(join17(path2, "requirements.txt"))) {
44523
45413
  return "Python";
44524
45414
  }
44525
- if (existsSync12(join17(path2, "Cargo.toml"))) {
45415
+ if (existsSync13(join17(path2, "Cargo.toml"))) {
44526
45416
  return "Rust";
44527
45417
  }
44528
- if (existsSync12(join17(path2, "go.mod"))) {
45418
+ if (existsSync13(join17(path2, "go.mod"))) {
44529
45419
  return "Go";
44530
45420
  }
44531
- if (existsSync12(join17(path2, "mix.exs"))) {
45421
+ if (existsSync13(join17(path2, "mix.exs"))) {
44532
45422
  return "Elixir";
44533
45423
  }
44534
- if (existsSync12(join17(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
45424
+ if (existsSync13(join17(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
44535
45425
  return "TypeScript";
44536
45426
  }
44537
- if (existsSync12(join17(path2, "package.json"))) {
45427
+ if (existsSync13(join17(path2, "package.json"))) {
44538
45428
  return "JavaScript";
44539
45429
  }
44540
45430
  return "Project";
@@ -44550,7 +45440,7 @@ function packageJsonMentionsTypeScript(path2) {
44550
45440
  }
44551
45441
  }
44552
45442
  function hasGitMetadata(path2) {
44553
- return existsSync12(join17(path2, ".git"));
45443
+ return existsSync13(join17(path2, ".git"));
44554
45444
  }
44555
45445
  function childDirectories(root) {
44556
45446
  try {
@@ -44672,7 +45562,7 @@ secure mission control for all your agents on your computers
44672
45562
  init_runner();
44673
45563
  init_claudeCodeSession();
44674
45564
  init_authCache();
44675
- import { existsSync as existsSync13, readFileSync as readFileSync16, realpathSync as realpathSync6 } from "node:fs";
45565
+ import { existsSync as existsSync14, readFileSync as readFileSync16, realpathSync as realpathSync6 } from "node:fs";
44676
45566
  import { homedir as homedir13 } from "node:os";
44677
45567
  import { resolve as resolve10 } from "node:path";
44678
45568
  import { fileURLToPath as fileURLToPath4 } from "node:url";
@@ -44734,8 +45624,8 @@ init_kandanTls();
44734
45624
  init_protocol();
44735
45625
  init_json();
44736
45626
  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";
45627
+ import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
45628
+ import { dirname as dirname11, join as join15 } from "node:path";
44739
45629
  import { homedir as homedir10 } from "node:os";
44740
45630
  async function runAgentCliCommand(args, deps = {
44741
45631
  fetchImpl: fetch,
@@ -45453,10 +46343,10 @@ function authorizationHeaders(token) {
45453
46343
  return { authorization: `Bearer ${token}` };
45454
46344
  }
45455
46345
  function readOptionalTextFile(path2) {
45456
- return existsSync10(path2) ? readFileSync12(path2, "utf8") : void 0;
46346
+ return existsSync11(path2) ? readFileSync12(path2, "utf8") : void 0;
45457
46347
  }
45458
46348
  function writeTextFile(path2, content) {
45459
- mkdirSync10(dirname10(path2), { recursive: true });
46349
+ mkdirSync10(dirname11(path2), { recursive: true });
45460
46350
  writeFileSync8(path2, content);
45461
46351
  }
45462
46352
  function readStoredAgentTokenFile(path2, readTextFile = readOptionalTextFile) {
@@ -45544,7 +46434,7 @@ init_helloLinzumiProject();
45544
46434
  // src/commanderDaemon.ts
45545
46435
  init_runnerLogger();
45546
46436
  import {
45547
- existsSync as existsSync11,
46437
+ existsSync as existsSync12,
45548
46438
  closeSync as closeSync2,
45549
46439
  mkdirSync as mkdirSync11,
45550
46440
  openSync as openSync3,
@@ -45553,8 +46443,8 @@ import {
45553
46443
  writeFileSync as writeFileSync9
45554
46444
  } from "node:fs";
45555
46445
  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";
46446
+ import { dirname as dirname12, join as join16, resolve as resolve8 } from "node:path";
46447
+ import { execFileSync, spawn as spawn9 } from "node:child_process";
45558
46448
  import { fileURLToPath as fileURLToPath3 } from "node:url";
45559
46449
  var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
45560
46450
  function commanderStatusDir() {
@@ -45586,7 +46476,7 @@ function startCommanderDaemon(options) {
45586
46476
  logFile
45587
46477
  ];
45588
46478
  mkdirSync11(statusDir, { recursive: true });
45589
- mkdirSync11(dirname11(logFile), { recursive: true });
46479
+ mkdirSync11(dirname12(logFile), { recursive: true });
45590
46480
  const out = openSync3(logFile, "a");
45591
46481
  const err = openSync3(logFile, "a");
45592
46482
  writeCliAuditEvent(
@@ -45599,7 +46489,7 @@ function startCommanderDaemon(options) {
45599
46489
  },
45600
46490
  { sessionId: options.runnerId }
45601
46491
  );
45602
- const child = (options.spawnImpl ?? spawn8)(nodeBin, command, {
46492
+ const child = (options.spawnImpl ?? spawn9)(nodeBin, command, {
45603
46493
  detached: true,
45604
46494
  stdio: ["ignore", out, err],
45605
46495
  env: process.env
@@ -45638,7 +46528,7 @@ function startCommanderDaemon(options) {
45638
46528
  }
45639
46529
  function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
45640
46530
  const statusFile = commanderStatusFile(runnerId, statusDir);
45641
- if (!existsSync11(statusFile)) {
46531
+ if (!existsSync12(statusFile)) {
45642
46532
  return { status: "missing", runnerId, statusFile };
45643
46533
  }
45644
46534
  const record = parseRecord(readFileSync13(statusFile, "utf8"));
@@ -45646,7 +46536,7 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
45646
46536
  }
45647
46537
  async function waitForCommanderDaemon(options) {
45648
46538
  const now = options.now ?? (() => Date.now());
45649
- const readTextFile = options.readTextFile ?? ((path2) => existsSync11(path2) ? readFileSync13(path2, "utf8") : void 0);
46539
+ const readTextFile = options.readTextFile ?? ((path2) => existsSync12(path2) ? readFileSync13(path2, "utf8") : void 0);
45650
46540
  const statusImpl = options.statusImpl ?? commanderDaemonStatus;
45651
46541
  const deadline = now() + options.timeoutMs;
45652
46542
  while (now() <= deadline) {
@@ -57610,6 +58500,8 @@ function createLinzumiMcpApiClient(options) {
57610
58500
  sendChannelMessage: (params) => request("POST", `${apiPrefix}/channel-message`, params),
57611
58501
  prepareMessageUploads: (params) => request("POST", `${apiPrefix}/message-uploads/prepare`, params),
57612
58502
  attachMessageFiles: (params) => request("POST", `${apiPrefix}/message-files/attach`, params),
58503
+ prepareCustomEmoji: (params) => request("POST", `${apiPrefix}/custom-emoji/prepare`, params),
58504
+ renameCustomEmoji: (params) => request("POST", `${apiPrefix}/custom-emoji/rename`, params),
57613
58505
  sendThreadReply: (params) => request("POST", `${apiPrefix}/thread-reply`, params),
57614
58506
  sendDm: (params) => request("POST", `${apiPrefix}/dm`, params),
57615
58507
  dmOwner: (params) => request("POST", `${apiPrefix}/dm-owner`, params),
@@ -57620,6 +58512,17 @@ function createLinzumiMcpApiClient(options) {
57620
58512
  // src/mcpServer.ts
57621
58513
  init_mcpConfig();
57622
58514
  init_oauth();
58515
+ init_protocol();
58516
+ var maxCustomEmojiNameLength = 64;
58517
+ function normalizeCustomEmojiNameForSchema(value) {
58518
+ return value.trim().replace(/^:+/, "").replace(/:+$/, "").toLowerCase();
58519
+ }
58520
+ var customEmojiNameSchema = external_exports2.string().min(1).refine(
58521
+ (value) => normalizeCustomEmojiNameForSchema(value).length <= maxCustomEmojiNameLength,
58522
+ {
58523
+ message: `Custom emoji shortcode name must be at most ${maxCustomEmojiNameLength} characters after trimming surrounding colons.`
58524
+ }
58525
+ );
57623
58526
  var mcpFlagDefinitions = /* @__PURE__ */ new Map([
57624
58527
  ["api-url", { kind: "value" }],
57625
58528
  ["token", { kind: "value" }],
@@ -57680,6 +58583,10 @@ Tools:
57680
58583
  linzumi_dm_owner Send a plain-text DM to the configured owner username.
57681
58584
  linzumi_get_vault_values
57682
58585
  Request approved named vault values for a thread.
58586
+ linzumi_upload_custom_emoji
58587
+ Upload a local image as a workspace custom emoji.
58588
+ linzumi_rename_custom_emoji
58589
+ Rename an existing workspace custom emoji.
57683
58590
 
57684
58591
  Auth:
57685
58592
  Uses --token, LINZUMI_MCP_ACCESS_TOKEN, or cached local runner OAuth from linzumi auth.
@@ -57801,6 +58708,43 @@ async function runMcpServer(args) {
57801
58708
  {},
57802
58709
  async (params) => mcpJsonResult(await client.listVaultSecrets(params))
57803
58710
  );
58711
+ server.tool(
58712
+ "linzumi_upload_custom_emoji",
58713
+ "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:.",
58714
+ {
58715
+ name: customEmojiNameSchema.describe(
58716
+ "Custom emoji shortcode name, with or without surrounding colons."
58717
+ ),
58718
+ file: external_exports2.string().min(1).describe(
58719
+ "Local PNG/WebP/GIF/JPEG file path to upload. Prefer transparent PNG for generated emoji."
58720
+ ),
58721
+ workspace: external_exports2.string().optional().describe("Workspace slug. Omit to use the authenticated token scope.")
58722
+ },
58723
+ async (params) => mcpJsonResult(
58724
+ await uploadCustomEmojiWithClient({
58725
+ client,
58726
+ cwd,
58727
+ kandanUrl,
58728
+ name: params.name,
58729
+ file: params.file,
58730
+ workspace: params.workspace
58731
+ })
58732
+ )
58733
+ );
58734
+ server.tool(
58735
+ "linzumi_rename_custom_emoji",
58736
+ "Rename an existing workspace custom emoji. The new name may include surrounding colons; Linzumi stores the normalized shortcode without colons.",
58737
+ {
58738
+ emoji_id: external_exports2.union([external_exports2.string(), external_exports2.number().int().positive()]).describe(
58739
+ "Workspace custom emoji id returned by linzumi_upload_custom_emoji or workspace emoji listings."
58740
+ ),
58741
+ name: customEmojiNameSchema.describe(
58742
+ "New shortcode name, with or without surrounding colons."
58743
+ ),
58744
+ workspace: external_exports2.string().optional().describe("Workspace slug. Omit to use the authenticated token scope.")
58745
+ },
58746
+ async (params) => mcpJsonResult(await client.renameCustomEmoji(params))
58747
+ );
57804
58748
  server.tool(
57805
58749
  "linzumi_dm_owner",
57806
58750
  "Send a plain-text DM to the configured Commander owner. Requires dm.write and a visible owner username.",
@@ -57936,6 +58880,69 @@ async function uploadFilesWithClient(args) {
57936
58880
  uploaded_file_ids: uploadedFileIds
57937
58881
  });
57938
58882
  }
58883
+ async function uploadCustomEmojiWithClient(args) {
58884
+ const file = await commanderUploadFileForPath(args.cwd, args.file);
58885
+ const prepare = await args.client.prepareCustomEmoji({
58886
+ ...args.workspace === void 0 ? {} : { workspace: args.workspace },
58887
+ name: args.name,
58888
+ file_name: file.fileName,
58889
+ content_type: file.contentType,
58890
+ size_bytes: file.sizeBytes
58891
+ });
58892
+ const upload = objectValue(prepare.upload);
58893
+ const uploadUrl = stringValue(upload?.upload_url);
58894
+ const uploadMethod = stringValue(upload?.upload_method) ?? "PUT";
58895
+ if (uploadUrl === void 0) {
58896
+ throw new Error("Linzumi custom emoji prepare response missing upload_url");
58897
+ }
58898
+ const bytes = await readFile2(file.path);
58899
+ const uploadBody = bytes.buffer.slice(
58900
+ bytes.byteOffset,
58901
+ bytes.byteOffset + bytes.byteLength
58902
+ );
58903
+ const response = await fetch(
58904
+ resolveLinzumiUploadUrl(args.kandanUrl, uploadUrl),
58905
+ {
58906
+ method: uploadMethod,
58907
+ headers: { "content-type": file.contentType },
58908
+ body: uploadBody
58909
+ }
58910
+ );
58911
+ if (!response.ok) {
58912
+ const fallbackError = `${response.status} ${response.statusText}`;
58913
+ const responseText = await response.text();
58914
+ const trimmedResponseText = responseText.trim();
58915
+ const error = trimmedResponseText === "" ? fallbackError : (() => {
58916
+ try {
58917
+ const parsedError = JSON.parse(trimmedResponseText);
58918
+ return isJsonObject(parsedError) && typeof parsedError.error === "string" ? parsedError.error : trimmedResponseText;
58919
+ } catch {
58920
+ return trimmedResponseText;
58921
+ }
58922
+ })();
58923
+ throw new Error(`Linzumi custom emoji upload failed: ${error}`);
58924
+ }
58925
+ const parsed = await response.json();
58926
+ if (!isJsonObject(parsed)) {
58927
+ throw new Error("Linzumi custom emoji upload returned non-object JSON");
58928
+ }
58929
+ const emoji = objectValue(parsed.custom_emoji);
58930
+ if (emoji === void 0) {
58931
+ throw new Error(
58932
+ "Linzumi custom emoji upload response missing custom_emoji"
58933
+ );
58934
+ }
58935
+ return {
58936
+ ok: true,
58937
+ workspace: prepare.workspace,
58938
+ emoji,
58939
+ asset_id: stringValue(parsed.asset_id),
58940
+ id: integerValue(emoji?.id),
58941
+ name: stringValue(emoji?.name),
58942
+ shortcode: stringValue(emoji?.shortcode),
58943
+ image_url: stringValue(emoji?.image_url)
58944
+ };
58945
+ }
57939
58946
  function targetWithDefaultThread(target, defaultThreadId) {
57940
58947
  const existingThreadId = stringValue(target.thread_id);
57941
58948
  if (existingThreadId !== void 0 || defaultThreadId === void 0) {
@@ -58124,6 +59131,338 @@ function required(values, key) {
58124
59131
  return value;
58125
59132
  }
58126
59133
 
59134
+ // src/remoteCodexHarnessWorker.ts
59135
+ init_channelSession();
59136
+ init_codexAppServer();
59137
+ init_phoenix();
59138
+ init_runnerLogger();
59139
+ init_userFacingErrors();
59140
+ init_version();
59141
+ var configEnvKey = "LINZUMI_REMOTE_CODEX_HARNESS_CONFIG_B64";
59142
+ var remoteHarnessEnvironmentId = "linzumi-remote-codex-harness";
59143
+ async function runRemoteCodexHarnessWorkerFromEnv(env = process.env) {
59144
+ await runRemoteCodexHarnessWorker(remoteCodexHarnessWorkerConfigFromEnv(env));
59145
+ }
59146
+ async function runRemoteCodexHarnessWorker(config, deps = {}) {
59147
+ const resolvedDeps = {
59148
+ startCodexAppServer,
59149
+ connectCodexAppServer,
59150
+ connectPhoenixClient,
59151
+ attachChannelSession,
59152
+ waitUntilShutdown: defaultWaitUntilShutdown,
59153
+ writeStdout: (chunk) => process.stdout.write(chunk),
59154
+ log: writeCliAuditEvent,
59155
+ ...deps
59156
+ };
59157
+ const started = await resolvedDeps.startCodexAppServer(
59158
+ config.codexBin,
59159
+ config.workerCwd,
59160
+ {
59161
+ model: config.model,
59162
+ reasoningEffort: config.reasoningEffort,
59163
+ fast: config.fast,
59164
+ inheritEnv: false,
59165
+ env: remoteHarnessCodexEnv(process.env, config.execServerUrl)
59166
+ }
59167
+ );
59168
+ let kandan;
59169
+ let codex;
59170
+ let session;
59171
+ const cleanup = async () => {
59172
+ await session?.close();
59173
+ codex?.close();
59174
+ kandan?.close();
59175
+ started.stop();
59176
+ };
59177
+ const cleanupOnce = onceAsync(cleanup);
59178
+ try {
59179
+ codex = await resolvedDeps.connectCodexAppServer(started.url);
59180
+ await registerExecServerEnvironment(codex, config.execServerUrl);
59181
+ kandan = await resolvedDeps.connectPhoenixClient(
59182
+ config.kandanUrl,
59183
+ config.token
59184
+ );
59185
+ const runnerTopic = `local_runner:${config.runnerId}`;
59186
+ const runnerJoinPayload = () => remoteHarnessRunnerJoinPayload(config);
59187
+ const pendingControls = [];
59188
+ const controlDispatcher = { value: void 0 };
59189
+ kandan.onControl((control) => {
59190
+ const dispatcher = controlDispatcher.value;
59191
+ if (dispatcher === void 0) {
59192
+ pendingControls.push(control);
59193
+ return;
59194
+ }
59195
+ dispatcher(control);
59196
+ });
59197
+ await kandan.join(runnerTopic, runnerJoinPayload(), {
59198
+ rejoinPayload: runnerJoinPayload
59199
+ });
59200
+ session = await resolvedDeps.attachChannelSession({
59201
+ kandan,
59202
+ codex,
59203
+ topic: runnerTopic,
59204
+ instanceId: config.instanceId,
59205
+ options: {
59206
+ kandanUrl: config.kandanUrl,
59207
+ token: config.token,
59208
+ runnerId: config.runnerId,
59209
+ cwd: config.cwd,
59210
+ codexBin: config.codexBin,
59211
+ fast: config.fast,
59212
+ launchTui: false,
59213
+ enablePortForwardWatch: false,
59214
+ channelSession: {
59215
+ workspaceSlug: config.workspaceSlug,
59216
+ channelSlug: config.channelSlug,
59217
+ kandanThreadId: config.kandanThreadId,
59218
+ rootSeq: config.rootSeq,
59219
+ startCodexThread: true,
59220
+ linzumiContext: config.linzumiContext,
59221
+ listenUser: config.listenUser,
59222
+ model: config.model,
59223
+ reasoningEffort: config.reasoningEffort,
59224
+ sandbox: config.sandbox,
59225
+ approvalPolicy: config.approvalPolicy,
59226
+ codexEnvironmentId: remoteHarnessEnvironmentId,
59227
+ allowPortForwardingByDefault: config.allowPortForwardingByDefault
59228
+ }
59229
+ },
59230
+ log: resolvedDeps.log
59231
+ });
59232
+ kandan.onReconnect(() => session?.handleKandanReconnect());
59233
+ const dispatchControl = (control) => {
59234
+ const liveSession = session;
59235
+ const liveKandan = kandan;
59236
+ if (liveSession === void 0 || liveKandan === void 0) {
59237
+ pendingControls.push(control);
59238
+ return;
59239
+ }
59240
+ void liveSession.handleControl(control).then(
59241
+ (response) => response === void 0 ? void 0 : liveKandan.push(runnerTopic, "codex_response", response)
59242
+ ).catch(
59243
+ (error) => liveKandan.push(runnerTopic, "codex_error", {
59244
+ instanceId: config.instanceId,
59245
+ message: runnerActionErrorMessage(error)
59246
+ })
59247
+ ).catch((error) => {
59248
+ resolvedDeps.log("remote_codex_harness.control_push_failed", {
59249
+ message: error instanceof Error ? error.message : String(error)
59250
+ });
59251
+ });
59252
+ };
59253
+ controlDispatcher.value = dispatchControl;
59254
+ pendingControls.splice(0).forEach(dispatchControl);
59255
+ codex.onNotification((notification) => {
59256
+ const params = notification.params ?? {};
59257
+ session?.handleCodexNotification(notification.method, params);
59258
+ });
59259
+ await session.startThreadMessageTurn({
59260
+ seq: config.sourceSeq,
59261
+ body: config.workDescription
59262
+ });
59263
+ resolvedDeps.writeStdout(
59264
+ `LINZUMI_REMOTE_CODEX_HARNESS_READY ${config.readyToken}
59265
+ `
59266
+ );
59267
+ const stopReason = await Promise.race([
59268
+ resolvedDeps.waitUntilShutdown(cleanupOnce).then(() => ({ type: "shutdown" })),
59269
+ waitForCodexAppServerExit(started.process).then((exit) => ({
59270
+ type: "codex_exit",
59271
+ exit
59272
+ }))
59273
+ ]);
59274
+ if (stopReason.type === "codex_exit") {
59275
+ resolvedDeps.log("remote_codex_harness.codex_app_server_exited", {
59276
+ code: stopReason.exit.code,
59277
+ signal: stopReason.exit.signal
59278
+ });
59279
+ await cleanupOnce();
59280
+ }
59281
+ } catch (error) {
59282
+ await cleanupOnce();
59283
+ throw error;
59284
+ }
59285
+ }
59286
+ function remoteCodexHarnessWorkerConfigFromEnv(env) {
59287
+ const encoded = env[configEnvKey];
59288
+ if (encoded === void 0 || encoded.trim() === "") {
59289
+ throw new Error(`${configEnvKey} is required`);
59290
+ }
59291
+ return remoteCodexHarnessWorkerConfigFromJson(
59292
+ JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"))
59293
+ );
59294
+ }
59295
+ function remoteCodexHarnessWorkerConfigFromJson(value) {
59296
+ const input = requireObject(value);
59297
+ const config = {
59298
+ kandanUrl: requiredString2(input, "kandanUrl"),
59299
+ token: requiredString2(input, "token"),
59300
+ runnerId: requiredString2(input, "runnerId"),
59301
+ instanceId: requiredString2(input, "instanceId"),
59302
+ workspaceSlug: requiredString2(input, "workspaceSlug"),
59303
+ channelSlug: requiredString2(input, "channelSlug"),
59304
+ kandanThreadId: requiredString2(input, "kandanThreadId"),
59305
+ rootSeq: requiredPositiveInteger(input, "rootSeq"),
59306
+ sourceSeq: requiredPositiveInteger(input, "sourceSeq"),
59307
+ cwd: requiredString2(input, "cwd"),
59308
+ workerCwd: requiredString2(input, "workerCwd"),
59309
+ workDescription: requiredString2(input, "workDescription"),
59310
+ readyToken: requiredString2(input, "readyToken"),
59311
+ codexBin: requiredString2(input, "codexBin"),
59312
+ execServerUrl: requiredString2(input, "execServerUrl"),
59313
+ listenUser: requiredString2(input, "listenUser"),
59314
+ ...optionalStringField(input, "model"),
59315
+ ...optionalStringField(input, "reasoningEffort"),
59316
+ ...optionalStringField(input, "approvalPolicy"),
59317
+ ...optionalStringField(input, "sandbox"),
59318
+ ...optionalBooleanField(input, "fast"),
59319
+ ...optionalBooleanField(input, "allowPortForwardingByDefault"),
59320
+ ...optionalObjectField(input, "linzumiContext")
59321
+ };
59322
+ return config;
59323
+ }
59324
+ async function registerExecServerEnvironment(codex, execServerUrl) {
59325
+ const response = await codex.request("environment/add", {
59326
+ environmentId: remoteHarnessEnvironmentId,
59327
+ execServerUrl
59328
+ });
59329
+ assertJsonRpcOk(response, "environment/add");
59330
+ }
59331
+ function assertJsonRpcOk(response, method) {
59332
+ if ("error" in response) {
59333
+ throw new Error(`${method} failed: ${response.error.message}`);
59334
+ }
59335
+ }
59336
+ function remoteHarnessCodexEnv(sourceEnv, execServerUrl) {
59337
+ const env = {
59338
+ CODEX_EXEC_SERVER_URL: execServerUrl,
59339
+ LINZUMI_REMOTE_CODEX_HARNESS: "1"
59340
+ };
59341
+ for (const key of remoteHarnessCodexEnvAllowlist) {
59342
+ const value = sourceEnv[key];
59343
+ if (value !== void 0 && value.trim() !== "") {
59344
+ env[key] = value;
59345
+ }
59346
+ }
59347
+ return env;
59348
+ }
59349
+ var remoteHarnessCodexEnvAllowlist = [
59350
+ "PATH",
59351
+ "HOME",
59352
+ "TMPDIR",
59353
+ "SHELL",
59354
+ "USER",
59355
+ "LOGNAME",
59356
+ "CODEX_HOME",
59357
+ "XDG_CONFIG_HOME",
59358
+ "XDG_DATA_HOME",
59359
+ "SSL_CERT_FILE",
59360
+ "NIX_SSL_CERT_FILE",
59361
+ "NODE_EXTRA_CA_CERTS"
59362
+ ];
59363
+ function remoteHarnessRunnerJoinPayload(config) {
59364
+ return {
59365
+ clientName: "kandan-remote-codex-harness-worker",
59366
+ clientId: config.instanceId,
59367
+ connectionMode: "session_worker",
59368
+ runnerPid: process.pid,
59369
+ version: linzumiCliVersion,
59370
+ cwd: config.cwd,
59371
+ workspace: config.workspaceSlug,
59372
+ channel: config.channelSlug,
59373
+ capabilities: {
59374
+ codexAppServer: true,
59375
+ defaultAgentProvider: "codex",
59376
+ agentProviders: ["codex"],
59377
+ remoteCodexExecution: false,
59378
+ allowedCwdSuggestions: [],
59379
+ allowedCwdProjects: [],
59380
+ portForwarding: false,
59381
+ tcpForwarding: false,
59382
+ linzumiMcp: true
59383
+ }
59384
+ };
59385
+ }
59386
+ function defaultWaitUntilShutdown(cleanup) {
59387
+ return new Promise((resolve11, reject) => {
59388
+ const finish = () => {
59389
+ cleanup().then(resolve11, reject);
59390
+ };
59391
+ process.once("SIGINT", finish);
59392
+ process.once("SIGTERM", finish);
59393
+ });
59394
+ }
59395
+ function onceAsync(fn) {
59396
+ let promise;
59397
+ return () => {
59398
+ promise = promise ?? fn();
59399
+ return promise;
59400
+ };
59401
+ }
59402
+ function waitForCodexAppServerExit(child) {
59403
+ if (child.exitCode !== null || child.signalCode !== null) {
59404
+ return Promise.resolve({
59405
+ code: child.exitCode,
59406
+ signal: child.signalCode
59407
+ });
59408
+ }
59409
+ return new Promise((resolve11) => {
59410
+ child.once("exit", (code, signal) => {
59411
+ resolve11({ code, signal });
59412
+ });
59413
+ });
59414
+ }
59415
+ function requireObject(value) {
59416
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
59417
+ throw new Error("remote Codex harness worker config must be an object");
59418
+ }
59419
+ return value;
59420
+ }
59421
+ function requiredString2(input, key) {
59422
+ const value = input[key];
59423
+ if (typeof value !== "string" || value.trim() === "") {
59424
+ throw new Error(`remote Codex harness worker config missing ${key}`);
59425
+ }
59426
+ return value;
59427
+ }
59428
+ function requiredPositiveInteger(input, key) {
59429
+ const value = input[key];
59430
+ if (!Number.isInteger(value) || value <= 0) {
59431
+ throw new Error(`remote Codex harness worker config missing ${key}`);
59432
+ }
59433
+ return value;
59434
+ }
59435
+ function optionalStringField(input, key) {
59436
+ const value = input[key];
59437
+ if (value === void 0) {
59438
+ return {};
59439
+ }
59440
+ if (typeof value !== "string" || value.trim() === "") {
59441
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59442
+ }
59443
+ return { [key]: value };
59444
+ }
59445
+ function optionalBooleanField(input, key) {
59446
+ const value = input[key];
59447
+ if (value === void 0) {
59448
+ return {};
59449
+ }
59450
+ if (typeof value !== "boolean") {
59451
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59452
+ }
59453
+ return { [key]: value };
59454
+ }
59455
+ function optionalObjectField(input, key) {
59456
+ const value = input[key];
59457
+ if (value === void 0) {
59458
+ return {};
59459
+ }
59460
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
59461
+ throw new Error(`remote Codex harness worker config invalid ${key}`);
59462
+ }
59463
+ return { [key]: value };
59464
+ }
59465
+
58127
59466
  // src/index.ts
58128
59467
  init_userFacingErrors();
58129
59468
  var flagDefinitions = /* @__PURE__ */ new Map([
@@ -58250,6 +59589,9 @@ async function main(args) {
58250
59589
  case "commanderDaemon":
58251
59590
  await runCommanderDaemonCommand(parsed.args);
58252
59591
  return;
59592
+ case "remoteCodexHarnessWorker":
59593
+ await runRemoteCodexHarnessWorkerFromEnv();
59594
+ return;
58253
59595
  case "agentRunner": {
58254
59596
  const options = await parseAgentRunnerArgs(parsed.args);
58255
59597
  await runLocalCodexRunner(withLocalMachineId(options));
@@ -58301,6 +59643,8 @@ function parseCommand(args) {
58301
59643
  return ["daemon", "status", "wait", "stop"].includes(rest[0] ?? "") ? { command: "commanderDaemon", args: rest } : { command: "agentRunner", args: rest };
58302
59644
  case "commander-daemon":
58303
59645
  return { command: "commanderDaemon", args: ["daemon", ...rest] };
59646
+ case "remote-codex-harness-worker":
59647
+ return { command: "remoteCodexHarnessWorker", args: rest };
58304
59648
  case "commander-status":
58305
59649
  return { command: "commanderDaemon", args: ["status", ...rest] };
58306
59650
  case "commander-wait":
@@ -58606,7 +59950,7 @@ async function parseStartRunnerArgs(args, deps = {
58606
59950
  codexBin: requestedCodexBin,
58607
59951
  codeServerBin: customCodeServerBin
58608
59952
  });
58609
- assertStartDependencies(initialDependencyStatus);
59953
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58610
59954
  const codexBin = initialDependencyStatus.codex.command;
58611
59955
  const explicitToken = stringValue7(values, "token");
58612
59956
  const authFilePath = stringValue7(values, "auth-file");
@@ -58652,7 +59996,7 @@ async function parseStartRunnerArgs(args, deps = {
58652
59996
  codeServerBin: editorRuntime.codeServerBin,
58653
59997
  editorRuntime: editorRuntime.status
58654
59998
  });
58655
- assertStartDependencies(dependencyStatus);
59999
+ assertRunnerConnectionDependencies(dependencyStatus);
58656
60000
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58657
60001
  return {
58658
60002
  kandanUrl,
@@ -58664,6 +60008,7 @@ async function parseStartRunnerArgs(args, deps = {
58664
60008
  cwd,
58665
60009
  codexBin,
58666
60010
  codexUrl: stringValue7(values, "codex-url"),
60011
+ launchSource: electronAutoConnectLaunchSource(),
58667
60012
  launchTui: values.get("launch-tui") === true,
58668
60013
  fast: values.get("fast") === true,
58669
60014
  logFile: stringValue7(values, "log-file"),
@@ -58741,7 +60086,7 @@ async function parseAgentRunnerArgs(args, deps = {
58741
60086
  codexBin: requestedCodexBin,
58742
60087
  codeServerBin: customCodeServerBin
58743
60088
  });
58744
- assertStartDependencies(initialDependencyStatus);
60089
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58745
60090
  const codexBin = initialDependencyStatus.codex.command;
58746
60091
  const editorRuntime = await deps.resolveEditorRuntime({
58747
60092
  kandanUrl,
@@ -58755,7 +60100,7 @@ async function parseAgentRunnerArgs(args, deps = {
58755
60100
  codeServerBin: editorRuntime.codeServerBin,
58756
60101
  editorRuntime: editorRuntime.status
58757
60102
  });
58758
- assertStartDependencies(dependencyStatus);
60103
+ assertRunnerConnectionDependencies(dependencyStatus);
58759
60104
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58760
60105
  return {
58761
60106
  kandanUrl,
@@ -58767,6 +60112,7 @@ async function parseAgentRunnerArgs(args, deps = {
58767
60112
  cwd,
58768
60113
  codexBin,
58769
60114
  codexUrl: stringValue7(values, "codex-url"),
60115
+ launchSource: electronAutoConnectLaunchSource(),
58770
60116
  launchTui: values.get("launch-tui") === true,
58771
60117
  fast: values.get("fast") === true,
58772
60118
  logFile: stringValue7(values, "log-file"),
@@ -58785,7 +60131,7 @@ async function parseAgentRunnerArgs(args, deps = {
58785
60131
  };
58786
60132
  }
58787
60133
  function readAgentTokenTextFile(path2) {
58788
- return existsSync13(path2) ? readFileSync16(path2, "utf8") : void 0;
60134
+ return existsSync14(path2) ? readFileSync16(path2, "utf8") : void 0;
58789
60135
  }
58790
60136
  function rejectAgentRunnerTargetingFlags(values) {
58791
60137
  const unsupportedFlags = [
@@ -58901,7 +60247,7 @@ async function parseRunnerArgs(args, deps = {
58901
60247
  codexBin: requestedCodexBin,
58902
60248
  codeServerBin: customCodeServerBin
58903
60249
  });
58904
- assertStartDependencies(initialDependencyStatus);
60250
+ assertRunnerConnectionDependencies(initialDependencyStatus);
58905
60251
  const codexBin = initialDependencyStatus.codex.command;
58906
60252
  const explicitToken = stringValue7(values, "token");
58907
60253
  const token = await deps.resolveToken({
@@ -58928,7 +60274,7 @@ async function parseRunnerArgs(args, deps = {
58928
60274
  codeServerBin: editorRuntime.codeServerBin,
58929
60275
  editorRuntime: editorRuntime.status
58930
60276
  });
58931
- assertStartDependencies(dependencyStatus);
60277
+ assertRunnerConnectionDependencies(dependencyStatus);
58932
60278
  const claudeCodeAvailable = await resolveClaudeCodeAvailability(deps, cwd);
58933
60279
  return {
58934
60280
  kandanUrl,
@@ -58939,6 +60285,7 @@ async function parseRunnerArgs(args, deps = {
58939
60285
  cwd,
58940
60286
  codexBin,
58941
60287
  codexUrl: stringValue7(values, "codex-url"),
60288
+ launchSource: electronAutoConnectLaunchSource(),
58942
60289
  launchTui: values.get("launch-tui") === true,
58943
60290
  fast: values.get("fast") === true,
58944
60291
  logFile: stringValue7(values, "log-file"),
@@ -58968,6 +60315,9 @@ function runnerRuntimeDefaultsFromValues(values) {
58968
60315
  allowPortForwardingByDefault: true
58969
60316
  };
58970
60317
  }
60318
+ function electronAutoConnectLaunchSource() {
60319
+ return process.env.LINZUMI_ELECTRON_AUTO_CONNECT_RUNNER === "1" ? "electron_auto_connect" : void 0;
60320
+ }
58971
60321
  function kandanUrlValue(values) {
58972
60322
  const apiUrl = stringValue7(values, "api-url");
58973
60323
  const linzumiUrl = stringValue7(values, "linzumi-url");