@linzumi/cli 0.0.58-beta → 0.0.59-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +428 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -62,7 +62,7 @@ Install the CLI or run it with `npx`:
62
62
  ```bash
63
63
  npm install -g @linzumi/cli@latest
64
64
  npx -y @linzumi/cli@latest signup
65
- npx -y @linzumi/cli@0.0.58-beta --version
65
+ npx -y @linzumi/cli@0.0.59-beta --version
66
66
  linzumi --version
67
67
  ```
68
68
 
package/dist/index.js CHANGED
@@ -20847,7 +20847,7 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
20847
20847
  request.port,
20848
20848
  approvedTargetFromRequest(request)
20849
20849
  );
20850
- await publishForwardPortResolvedEvent(args, request, {
20850
+ await publishForwardPortResolvedEvent(args, state, request, {
20851
20851
  decision: "deny"
20852
20852
  });
20853
20853
  await publishMessageStateForPortForwardResult(
@@ -20874,7 +20874,7 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
20874
20874
  async function expirePendingPortForwardRequest(args, state, request, reason) {
20875
20875
  const failedReason = reason === "listener_exited" ? "port_forward_listener_exited" : "port_forward_listener_changed";
20876
20876
  state.pendingPortForwardRequests.delete(request.requestId);
20877
- await publishForwardPortResolvedEvent(args, request, {
20877
+ await publishForwardPortResolvedEvent(args, state, request, {
20878
20878
  decision: "expired",
20879
20879
  reason
20880
20880
  });
@@ -20906,7 +20906,7 @@ async function approvePortForwardRequest(args, state, request, options) {
20906
20906
  processName: processIdentity?.appName ?? null,
20907
20907
  processIconKey: processIdentity?.iconKey ?? null
20908
20908
  });
20909
- await publishForwardPortResolvedEvent(args, request, {
20909
+ await publishForwardPortResolvedEvent(args, state, request, {
20910
20910
  decision: "approve",
20911
20911
  capabilities
20912
20912
  });
@@ -21224,7 +21224,7 @@ async function publishForwardPortRequestedEvent(args, state, request, processIde
21224
21224
  args.log
21225
21225
  );
21226
21226
  }
21227
- async function publishForwardPortResolvedEvent(args, request, result) {
21227
+ async function publishForwardPortResolvedEvent(args, state, request, result) {
21228
21228
  await pushOptional(
21229
21229
  args.kandan,
21230
21230
  args.topic,
@@ -21235,6 +21235,9 @@ async function publishForwardPortResolvedEvent(args, request, result) {
21235
21235
  port: request.port,
21236
21236
  pid: request.pid,
21237
21237
  command: request.command,
21238
+ codexThreadId: state.codexThreadId ?? null,
21239
+ kandanThreadId: state.kandanThreadId ?? null,
21240
+ channelSlug: args.options.channelSession.channelSlug ?? null,
21238
21241
  ...request.cwd === void 0 ? {} : { cwd: request.cwd },
21239
21242
  sourceSeq: request.sourceSeq,
21240
21243
  decision: result.decision,
@@ -24718,6 +24721,7 @@ function linzumiMcpServerConfig(options) {
24718
24721
  name: "linzumi",
24719
24722
  command: options.command ?? "linzumi",
24720
24723
  args: [
24724
+ ...options.argsPrefix ?? [],
24721
24725
  "mcp",
24722
24726
  "server",
24723
24727
  "--api-url",
@@ -24738,6 +24742,23 @@ function codexMcpConfigArgs(config2) {
24738
24742
  `mcp_servers.${config2.name}.args=${tomlStringArray(config2.args)}`
24739
24743
  ];
24740
24744
  }
24745
+ function linzumiMcpCommandForProcess(processExecPath, scriptPath) {
24746
+ const trimmedScriptPath = scriptPath?.trim() ?? "";
24747
+ switch (trimmedScriptPath === "") {
24748
+ case true:
24749
+ return { command: "linzumi", argsPrefix: [] };
24750
+ case false:
24751
+ switch (trimmedScriptPath.endsWith(".ts") || trimmedScriptPath.endsWith(".js")) {
24752
+ case true:
24753
+ return {
24754
+ command: processExecPath,
24755
+ argsPrefix: [trimmedScriptPath]
24756
+ };
24757
+ case false:
24758
+ return { command: trimmedScriptPath, argsPrefix: [] };
24759
+ }
24760
+ }
24761
+ }
24741
24762
  function claudeCodeMcpConfigJson(config2) {
24742
24763
  return `${JSON.stringify(
24743
24764
  {
@@ -28273,7 +28294,7 @@ function realpathOrResolved(pathValue) {
28273
28294
  }
28274
28295
 
28275
28296
  // src/version.ts
28276
- var linzumiCliVersion = "0.0.58-beta";
28297
+ var linzumiCliVersion = "0.0.59-beta";
28277
28298
  var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
28278
28299
 
28279
28300
  // src/runnerLock.ts
@@ -28908,7 +28929,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
28908
28929
  );
28909
28930
  cleanup.actions.push(() => kandan.close());
28910
28931
  const topic = `local_runner:${options.runnerId}`;
28911
- const clientId = options.machineId ?? options.runnerId;
28932
+ const clientId = options.clientId ?? options.machineId ?? options.runnerId;
28912
28933
  const runnerHost = hostname2();
28913
28934
  const joinPayload = () => ({
28914
28935
  clientName: "kandan-local-codex-runner",
@@ -29690,6 +29711,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
29690
29711
  launchTui: options.launchTui,
29691
29712
  enablePortForwardWatch: true,
29692
29713
  initialForwardPorts: allowedForwardPorts,
29714
+ portForwardWatcher: started?.process.pid === void 0 ? void 0 : { rootPid: started.process.pid },
29693
29715
  suppressedForwardPorts,
29694
29716
  onForwardPortApproved: (port, attribution) => {
29695
29717
  liveForwardPorts.add(port);
@@ -29742,6 +29764,13 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
29742
29764
  )
29743
29765
  );
29744
29766
  });
29767
+ const threadRunnerProcesses = /* @__PURE__ */ new Map();
29768
+ cleanup.actions.push(async () => {
29769
+ await Promise.all(
29770
+ Array.from(threadRunnerProcesses.values(), closeThreadRunnerEntry)
29771
+ );
29772
+ threadRunnerProcesses.clear();
29773
+ });
29745
29774
  const attachThreadSession = async (control, cwd, codexThreadId) => {
29746
29775
  const workspaceSlug = optionalThreadControlField(control, "workspace");
29747
29776
  const channelSlug = optionalThreadControlField(control, "channel");
@@ -29776,6 +29805,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
29776
29805
  launchTui: false,
29777
29806
  enablePortForwardWatch: true,
29778
29807
  initialForwardPorts: allowedForwardPorts,
29808
+ portForwardWatcher: started?.process.pid === void 0 ? void 0 : { rootPid: started.process.pid },
29779
29809
  suppressedForwardPorts,
29780
29810
  onForwardPortApproved: (port, attribution) => {
29781
29811
  liveForwardPorts.add(port);
@@ -29810,6 +29840,78 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
29810
29840
  dynamicChannelSessions.set(kandanThreadId, session);
29811
29841
  return session;
29812
29842
  };
29843
+ const startThreadRunnerProcess = async (control, cwd) => {
29844
+ if (options.threadProcess?.role === "thread") {
29845
+ return void 0;
29846
+ }
29847
+ const workspaceSlug = optionalThreadControlField(control, "workspace");
29848
+ const channelSlug = optionalThreadControlField(control, "channel");
29849
+ const kandanThreadId = optionalThreadControlField(control, "threadId");
29850
+ if (workspaceSlug === void 0 || channelSlug === void 0 || kandanThreadId === void 0) {
29851
+ return void 0;
29852
+ }
29853
+ const existing = threadRunnerProcesses.get(kandanThreadId);
29854
+ if (existing !== void 0) {
29855
+ return {
29856
+ instanceId,
29857
+ controlType: control.type,
29858
+ ok: true,
29859
+ delegated: true,
29860
+ threadProcess: "already_started",
29861
+ kandanThreadId,
29862
+ cwd
29863
+ };
29864
+ }
29865
+ const spawnThreadRunner = options.spawnThreadRunner ?? spawnLocalThreadRunnerProcess;
29866
+ const startingEntry = {
29867
+ kind: "starting",
29868
+ promise: spawnThreadRunner(
29869
+ threadRunnerOptions({
29870
+ options,
29871
+ control,
29872
+ cwd,
29873
+ workspaceSlug,
29874
+ channelSlug,
29875
+ kandanThreadId
29876
+ })
29877
+ )
29878
+ };
29879
+ threadRunnerProcesses.set(kandanThreadId, startingEntry);
29880
+ let handle;
29881
+ try {
29882
+ handle = await startingEntry.promise;
29883
+ } catch (error51) {
29884
+ if (threadRunnerProcesses.get(kandanThreadId) === startingEntry) {
29885
+ threadRunnerProcesses.delete(kandanThreadId);
29886
+ }
29887
+ throw error51;
29888
+ }
29889
+ const runningEntry = {
29890
+ kind: "running",
29891
+ handle
29892
+ };
29893
+ if (threadRunnerProcesses.get(kandanThreadId) === startingEntry) {
29894
+ threadRunnerProcesses.set(kandanThreadId, runningEntry);
29895
+ }
29896
+ handle.onExit?.(() => {
29897
+ if (threadRunnerProcesses.get(kandanThreadId) === runningEntry) {
29898
+ threadRunnerProcesses.delete(kandanThreadId);
29899
+ }
29900
+ });
29901
+ log("runner.thread_process_started", {
29902
+ kandanThreadId,
29903
+ cwd
29904
+ });
29905
+ return {
29906
+ instanceId,
29907
+ controlType: control.type,
29908
+ ok: true,
29909
+ delegated: true,
29910
+ threadProcess: "started",
29911
+ kandanThreadId,
29912
+ cwd
29913
+ };
29914
+ };
29813
29915
  const heartbeatPayload = () => ({
29814
29916
  instanceId,
29815
29917
  clientId,
@@ -29890,6 +29992,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
29890
29992
  });
29891
29993
  const handleControl = (control) => {
29892
29994
  log("kandan.control", { control });
29995
+ if (!threadProcessOwnsControl(options, control)) {
29996
+ log("kandan.control_ignored", {
29997
+ reason: "thread_process_mismatch",
29998
+ controlType: control.type,
29999
+ controlThreadId: controlThreadId(control) ?? null,
30000
+ threadProcessId: options.threadProcess?.kandanThreadId ?? null
30001
+ });
30002
+ return;
30003
+ }
29893
30004
  if (control.type === "replace_runner") {
29894
30005
  log("runner.replaced", {
29895
30006
  runnerId: options.runnerId,
@@ -30099,7 +30210,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
30099
30210
  disposeClaudeCodeForwardPortSession,
30100
30211
  control,
30101
30212
  log,
30102
- attachThreadSession
30213
+ attachThreadSession,
30214
+ shouldUseThreadProcesses(options) ? startThreadRunnerProcess : void 0
30103
30215
  );
30104
30216
  }).then((response) => {
30105
30217
  return kandan.push(topic, "codex_response", response);
@@ -30116,6 +30228,12 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
30116
30228
  };
30117
30229
  controlDispatcher.value = handleControl;
30118
30230
  pendingControls.splice(0).forEach(handleControl);
30231
+ if (options.threadProcess?.role === "thread") {
30232
+ const initialControl = options.threadProcess.initialControl;
30233
+ if (initialControl !== void 0) {
30234
+ handleControl(initialControl);
30235
+ }
30236
+ }
30119
30237
  return { instanceId, codexUrl, close };
30120
30238
  }
30121
30239
  function controlTargetsInstance(control, instanceId) {
@@ -30129,6 +30247,60 @@ function controlTargetsInstance(control, instanceId) {
30129
30247
  function controlInstanceId(control) {
30130
30248
  return "instanceId" in control ? control.instanceId : void 0;
30131
30249
  }
30250
+ function shouldUseThreadProcesses(options) {
30251
+ if (options.threadProcess?.role === "thread") {
30252
+ return false;
30253
+ }
30254
+ return options.spawnThreadRunner !== void 0 || options.codexUrl === void 0;
30255
+ }
30256
+ function threadProcessOwnsControl(options, control) {
30257
+ const threadProcess = options.threadProcess;
30258
+ const threadId = controlThreadId(control);
30259
+ if (threadProcess?.role !== "thread") {
30260
+ if (shouldUseThreadProcesses(options) && threadId !== void 0 && threadChildOnlyControl(control)) {
30261
+ return false;
30262
+ }
30263
+ return true;
30264
+ }
30265
+ if (threadId !== void 0) {
30266
+ return threadId === threadProcess.kandanThreadId;
30267
+ }
30268
+ if ("instanceId" in control && control.instanceId !== void 0 && control.instanceId !== "") {
30269
+ return true;
30270
+ }
30271
+ switch (control.type) {
30272
+ case "replace_runner":
30273
+ return true;
30274
+ default:
30275
+ return false;
30276
+ }
30277
+ }
30278
+ function threadChildOnlyControl(control) {
30279
+ switch (control.type) {
30280
+ case "forward_http_request":
30281
+ case "forward_tcp_open":
30282
+ case "forward_tcp_send":
30283
+ case "forward_tcp_close":
30284
+ return true;
30285
+ default:
30286
+ return false;
30287
+ }
30288
+ }
30289
+ function controlThreadId(control) {
30290
+ switch (control.type) {
30291
+ case "start_instance":
30292
+ case "reconnect_thread":
30293
+ case "forward_http_request":
30294
+ case "forward_tcp_open":
30295
+ case "forward_tcp_send":
30296
+ case "forward_tcp_close": {
30297
+ const threadId = stringValue(control.threadId)?.trim();
30298
+ return threadId === void 0 || threadId === "" ? void 0 : threadId;
30299
+ }
30300
+ default:
30301
+ return void 0;
30302
+ }
30303
+ }
30132
30304
  async function resolveSessionControl(channelSession, dynamicChannelSessions, control) {
30133
30305
  const primaryHandled = await (channelSession?.handleControl(control) ?? Promise.resolve(void 0));
30134
30306
  if (primaryHandled !== void 0) {
@@ -30349,7 +30521,7 @@ async function resumeCodexThreadForReconnect(codex, codexThreadId, resumeOverrid
30349
30521
  );
30350
30522
  }
30351
30523
  }
30352
- async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread) {
30524
+ async function applyControl(codex, kandan, topic, instanceId, options, agentProviders, allowedCwds, activeClaudeCodeSessions, pendingClaudeCodeApprovals, ensureClaudeCodeForwardPortSession, disposeClaudeCodeForwardPortSession, control, log, onStartedThread, onThreadProcessStart) {
30353
30525
  switch (control.type) {
30354
30526
  case "start_instance": {
30355
30527
  const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
@@ -30427,6 +30599,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
30427
30599
  });
30428
30600
  }
30429
30601
  try {
30602
+ const delegated = await onThreadProcessStart?.(control, cwd.cwd);
30603
+ if (delegated !== void 0) {
30604
+ return delegated;
30605
+ }
30430
30606
  if (agentProvider === "claude-code") {
30431
30607
  startupStage = "starting_claude_code_session";
30432
30608
  const result2 = await startClaudeCodeProviderInstance({
@@ -30564,6 +30740,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, agentProv
30564
30740
  const workDescription = normalizedWorkDescription(
30565
30741
  control.workDescription
30566
30742
  );
30743
+ const delegated = await onThreadProcessStart?.(control, cwd.cwd);
30744
+ if (delegated !== void 0) {
30745
+ return delegated;
30746
+ }
30567
30747
  if (agentProvider === "claude-code") {
30568
30748
  const activeSession = activeClaudeCodeSessions.get(codexThreadId);
30569
30749
  if (activeSession !== void 0 && workDescription !== void 0) {
@@ -32371,10 +32551,182 @@ function startInstanceRuntimeSettings(options, control) {
32371
32551
  allowPortForwardingByDefault: control.allowPortForwardingByDefault ?? defaults.allowPortForwardingByDefault
32372
32552
  };
32373
32553
  }
32554
+ function threadRunnerOptions(args) {
32555
+ const runtimeSettings = startInstanceRuntimeSettings(
32556
+ args.options,
32557
+ args.control
32558
+ );
32559
+ return {
32560
+ ...args.options,
32561
+ clientId: `${args.options.clientId ?? args.options.machineId ?? args.options.runnerId}:thread:${args.kandanThreadId}`,
32562
+ machineId: void 0,
32563
+ runnerLockConfigPath: void 0,
32564
+ workspaceSlug: args.workspaceSlug,
32565
+ cwd: args.cwd,
32566
+ codexUrl: void 0,
32567
+ launchTui: false,
32568
+ allowedCwds: Array.from(/* @__PURE__ */ new Set([args.cwd, ...args.options.allowedCwds])),
32569
+ codeServerBin: args.options.editorRuntime?.mode === "server_managed" ? void 0 : args.options.codeServerBin,
32570
+ editorRuntime: void 0,
32571
+ threadProcess: {
32572
+ role: "thread",
32573
+ kandanThreadId: args.kandanThreadId,
32574
+ initialControl: args.control
32575
+ },
32576
+ spawnThreadRunner: void 0,
32577
+ runtimeDefaults: {
32578
+ model: runtimeSettings.model,
32579
+ reasoningEffort: runtimeSettings.reasoningEffort,
32580
+ sandbox: runtimeSettings.sandbox,
32581
+ approvalPolicy: runtimeSettings.approvalPolicy,
32582
+ allowPortForwardingByDefault: runtimeSettings.allowPortForwardingByDefault
32583
+ },
32584
+ channelSession: void 0
32585
+ };
32586
+ }
32587
+ async function closeThreadRunnerEntry(entry) {
32588
+ switch (entry.kind) {
32589
+ case "running":
32590
+ await entry.handle.close();
32591
+ return;
32592
+ case "starting":
32593
+ try {
32594
+ const handle = await entry.promise;
32595
+ await handle.close();
32596
+ } catch (_error) {
32597
+ return;
32598
+ }
32599
+ return;
32600
+ }
32601
+ }
32602
+ async function spawnLocalThreadRunnerProcess(options) {
32603
+ const scriptPath = process.argv[1];
32604
+ if (scriptPath === void 0 || scriptPath.trim() === "") {
32605
+ throw new Error(
32606
+ "cannot fork thread runner without current CLI script path"
32607
+ );
32608
+ }
32609
+ const args = ["run", ...threadRunnerCliArgs(options)];
32610
+ const env = {
32611
+ ...process.env,
32612
+ LINZUMI_THREAD_RUNNER_ROLE: "thread",
32613
+ LINZUMI_THREAD_RUNNER_TOKEN: options.token,
32614
+ LINZUMI_THREAD_RUNNER_CLIENT_ID: options.clientId ?? `${options.runnerId}:thread`,
32615
+ LINZUMI_THREAD_RUNNER_KANDAN_THREAD_ID: options.threadProcess?.kandanThreadId ?? "",
32616
+ ...options.threadProcess?.initialControl === void 0 ? {} : {
32617
+ LINZUMI_THREAD_RUNNER_INITIAL_CONTROL: JSON.stringify(
32618
+ options.threadProcess.initialControl
32619
+ )
32620
+ }
32621
+ };
32622
+ writeCliAuditEvent("process.spawn", {
32623
+ command: process.execPath,
32624
+ args: [scriptPath, ...redactedThreadRunnerCliArgs(args)],
32625
+ cwd: options.cwd,
32626
+ purpose: "linzumi.thread_runner"
32627
+ });
32628
+ const child = spawn6(process.execPath, [scriptPath, ...args], {
32629
+ cwd: options.cwd,
32630
+ env,
32631
+ stdio: "inherit"
32632
+ });
32633
+ writeCliAuditEvent("process.spawned", {
32634
+ command: process.execPath,
32635
+ args: [scriptPath, ...redactedThreadRunnerCliArgs(args)],
32636
+ cwd: options.cwd,
32637
+ pid: child.pid,
32638
+ purpose: "linzumi.thread_runner"
32639
+ });
32640
+ const exitListeners = /* @__PURE__ */ new Set();
32641
+ const exitState = { exited: false };
32642
+ child.once("exit", (code, signal) => {
32643
+ writeCliAuditEvent("process.exit", {
32644
+ command: process.execPath,
32645
+ args: [scriptPath, ...redactedThreadRunnerCliArgs(args)],
32646
+ cwd: options.cwd,
32647
+ pid: child.pid,
32648
+ code,
32649
+ signal,
32650
+ purpose: "linzumi.thread_runner"
32651
+ });
32652
+ exitState.exited = true;
32653
+ for (const listener of exitListeners) {
32654
+ listener();
32655
+ }
32656
+ exitListeners.clear();
32657
+ });
32658
+ return {
32659
+ kandanThreadId: options.threadProcess?.kandanThreadId ?? "",
32660
+ onExit: (listener) => {
32661
+ if (exitState.exited) {
32662
+ queueMicrotask(listener);
32663
+ return;
32664
+ }
32665
+ exitListeners.add(listener);
32666
+ },
32667
+ close: () => new Promise((resolveClose) => {
32668
+ if (child.exitCode !== null || child.signalCode !== null) {
32669
+ resolveClose();
32670
+ return;
32671
+ }
32672
+ child.once("exit", () => resolveClose());
32673
+ child.kill("SIGINT");
32674
+ })
32675
+ };
32676
+ }
32677
+ function threadRunnerCliArgs(options) {
32678
+ return [
32679
+ "--api-url",
32680
+ options.kandanUrl,
32681
+ "--cwd",
32682
+ options.cwd,
32683
+ "--codex-bin",
32684
+ options.codexBin,
32685
+ ...optionalCliValue("--workspace", options.workspaceSlug),
32686
+ ...optionalCliValue("--log-file", options.logFile),
32687
+ ...options.fast === true ? ["--fast"] : [],
32688
+ ...optionalCliValue("--allowed-cwd", options.allowedCwds.join(",")),
32689
+ ...optionalCliValue(
32690
+ "--forward-port",
32691
+ (options.allowedForwardPorts ?? []).join(",")
32692
+ ),
32693
+ ...optionalCliValue("--code-server-bin", options.codeServerBin),
32694
+ ...optionalCliValue("--model", options.runtimeDefaults?.model),
32695
+ ...optionalCliValue(
32696
+ "--reasoning-effort",
32697
+ options.runtimeDefaults?.reasoningEffort
32698
+ ),
32699
+ ...optionalCliValue("--sandbox", options.runtimeDefaults?.sandbox),
32700
+ ...optionalCliValue(
32701
+ "--approval-policy",
32702
+ options.runtimeDefaults?.approvalPolicy
32703
+ ),
32704
+ ...options.runtimeDefaults?.allowPortForwardingByDefault === true ? ["--allow-port-forwarding-by-default"] : []
32705
+ ];
32706
+ }
32707
+ function redactedThreadRunnerCliArgs(args) {
32708
+ const redacted = [];
32709
+ for (let index = 0; index < args.length; index += 1) {
32710
+ const arg = args[index];
32711
+ if (arg === "--token") {
32712
+ redacted.push(arg, "<redacted>");
32713
+ index += 1;
32714
+ continue;
32715
+ }
32716
+ if (arg !== void 0) {
32717
+ redacted.push(arg);
32718
+ }
32719
+ }
32720
+ return redacted;
32721
+ }
32722
+ function optionalCliValue(flag, value) {
32723
+ return value === void 0 || value === "" ? [] : [flag, value];
32724
+ }
32374
32725
  async function startOwnedCodexAppServer(options) {
32375
32726
  ensureCodexProjectTrusted(options.cwd);
32376
32727
  const defaults = runnerRuntimeDefaults(options);
32377
32728
  const mcpAuth = writeEphemeralMcpAuthFile(options);
32729
+ const mcpCommand = currentLinzumiCommand();
32378
32730
  try {
32379
32731
  const started = await startCodexAppServer(options.codexBin, options.cwd, {
32380
32732
  model: defaults.model,
@@ -32382,7 +32734,8 @@ async function startOwnedCodexAppServer(options) {
32382
32734
  fast: options.fast,
32383
32735
  mcpServers: [
32384
32736
  linzumiMcpServerConfig({
32385
- command: currentLinzumiCommand(),
32737
+ command: mcpCommand.command,
32738
+ argsPrefix: mcpCommand.argsPrefix,
32386
32739
  kandanUrl: options.kandanUrl,
32387
32740
  authFilePath: mcpAuth.path,
32388
32741
  ownerUsername: mcpOwnerUsername(options)
@@ -32435,8 +32788,7 @@ function once(action) {
32435
32788
  };
32436
32789
  }
32437
32790
  function currentLinzumiCommand() {
32438
- const scriptPath = process.argv[1];
32439
- return scriptPath === void 0 || scriptPath.trim() === "" ? "linzumi" : scriptPath;
32791
+ return linzumiMcpCommandForProcess(process.execPath, process.argv[1]);
32440
32792
  }
32441
32793
  function runnerWorkspaceSlug(options) {
32442
32794
  return options.channelSession?.workspaceSlug ?? options.workspaceSlug;
@@ -62781,18 +63133,24 @@ async function main(args) {
62781
63133
  return;
62782
63134
  case "agentRunner": {
62783
63135
  const options = await parseAgentRunnerArgs(parsed.args);
62784
- await runLocalCodexRunner(withLocalMachineId(options));
63136
+ await runLocalCodexRunner(
63137
+ withLocalMachineId(withThreadRunnerEnv(options))
63138
+ );
62785
63139
  return;
62786
63140
  }
62787
63141
  case "start": {
62788
63142
  const options = await parseStartRunnerArgs(parsed.args);
62789
63143
  addAllowedCwdForLinzumiUrl(options.cwd, options.kandanUrl);
62790
- await runLocalCodexRunner(withLocalMachineId(options));
63144
+ await runLocalCodexRunner(
63145
+ withLocalMachineId(withThreadRunnerEnv(options))
63146
+ );
62791
63147
  return;
62792
63148
  }
62793
63149
  case "run": {
62794
63150
  const options = await parseRunnerArgs(parsed.args);
62795
- await runLocalCodexRunner(withLocalMachineId(options));
63151
+ await runLocalCodexRunner(
63152
+ withLocalMachineId(withThreadRunnerEnv(options))
63153
+ );
62796
63154
  return;
62797
63155
  }
62798
63156
  }
@@ -63421,7 +63779,7 @@ async function parseRunnerArgs(args, deps = {
63421
63779
  ) : [...localConfiguredAllowedCwds.allowedCwds];
63422
63780
  const codexBin = stringValue5(values, "codex-bin") ?? "codex";
63423
63781
  const customCodeServerBin = stringValue5(values, "code-server-bin");
63424
- const explicitToken = stringValue5(values, "token");
63782
+ const explicitToken = stringValue5(values, "token") ?? threadRunnerTokenEnv();
63425
63783
  const token = await deps.resolveToken({
63426
63784
  kandanUrl,
63427
63785
  explicitToken,
@@ -63657,11 +64015,66 @@ function parseChannelPath(channel) {
63657
64015
  };
63658
64016
  }
63659
64017
  function withLocalMachineId(options) {
64018
+ if (options.threadProcess?.role === "thread") {
64019
+ return options;
64020
+ }
63660
64021
  return {
63661
64022
  ...options,
63662
64023
  machineId: localConfigScopeKey(options.kandanUrl) === localConfigScopeKey(defaultLinzumiWebSocketUrl) ? ensureLocalMachineId() : ensureLocalMachineIdForLinzumiUrl(options.kandanUrl)
63663
64024
  };
63664
64025
  }
64026
+ function withThreadRunnerEnv(options) {
64027
+ if (process.env.LINZUMI_THREAD_RUNNER_ROLE !== "thread") {
64028
+ return options;
64029
+ }
64030
+ const kandanThreadId = process.env.LINZUMI_THREAD_RUNNER_KANDAN_THREAD_ID;
64031
+ if (kandanThreadId === void 0 || kandanThreadId.trim() === "") {
64032
+ throw new Error(
64033
+ "thread runner is missing LINZUMI_THREAD_RUNNER_KANDAN_THREAD_ID"
64034
+ );
64035
+ }
64036
+ return {
64037
+ ...options,
64038
+ clientId: process.env.LINZUMI_THREAD_RUNNER_CLIENT_ID,
64039
+ machineId: void 0,
64040
+ runnerLockConfigPath: void 0,
64041
+ codexUrl: void 0,
64042
+ launchTui: false,
64043
+ threadProcess: {
64044
+ role: "thread",
64045
+ kandanThreadId,
64046
+ initialControl: initialThreadControlFromEnv()
64047
+ },
64048
+ channelSession: void 0
64049
+ };
64050
+ }
64051
+ function threadRunnerTokenEnv() {
64052
+ if (process.env.LINZUMI_THREAD_RUNNER_ROLE !== "thread") {
64053
+ return void 0;
64054
+ }
64055
+ const token = process.env.LINZUMI_THREAD_RUNNER_TOKEN;
64056
+ if (token === void 0 || token.trim() === "") {
64057
+ throw new Error("thread runner is missing LINZUMI_THREAD_RUNNER_TOKEN");
64058
+ }
64059
+ return token;
64060
+ }
64061
+ function initialThreadControlFromEnv() {
64062
+ const raw = process.env.LINZUMI_THREAD_RUNNER_INITIAL_CONTROL;
64063
+ if (raw === void 0 || raw.trim() === "") {
64064
+ return void 0;
64065
+ }
64066
+ const parsed = JSON.parse(raw);
64067
+ if (parsed === null || typeof parsed !== "object" || !("type" in parsed)) {
64068
+ throw new Error("thread runner initial control is invalid");
64069
+ }
64070
+ switch (parsed.type) {
64071
+ case "start_instance":
64072
+ case "reconnect_thread":
64073
+ return parsed;
64074
+ default:
64075
+ throw new Error("thread runner initial control type is invalid");
64076
+ }
64077
+ }
63665
64078
  function required3(values, key) {
63666
64079
  const value = stringValue5(values, key);
63667
64080
  if (value === void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.58-beta",
3
+ "version": "0.0.59-beta",
4
4
  "description": "Linzumi CLI — point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {