@linzumi/cli 0.0.45-beta → 0.0.47-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 +67 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ Install the CLI or run it with `npx`:
63
63
 
64
64
  ```bash
65
65
  npm install -g @linzumi/cli@latest
66
- npx -y @linzumi/cli@0.0.45-beta --version
66
+ npx -y @linzumi/cli@0.0.47-beta --version
67
67
  linzumi --version
68
68
  ```
69
69
 
package/dist/index.js CHANGED
@@ -937,6 +937,44 @@ function fuseQueuedMessages(selected) {
937
937
  };
938
938
  }
939
939
 
940
+ // src/linzumiContext.ts
941
+ function parseLinzumiConversationContext(value, warn = console.warn) {
942
+ if (value === undefined) {
943
+ return;
944
+ }
945
+ const context = objectValue(value);
946
+ if (context === undefined) {
947
+ return invalidLinzumiContext("linzumiContext must be an object", warn);
948
+ }
949
+ const threadId = stringValue(context.threadId);
950
+ const creatorUserId = stringValue(context.creatorUserId) ?? integerValue(context.creatorUserId)?.toString();
951
+ const creatorUsername = stringValue(context.creatorUsername);
952
+ const conversationUrl = stringValue(context.conversationUrl);
953
+ if (threadId === undefined || creatorUserId === undefined || creatorUsername === undefined || conversationUrl === undefined) {
954
+ return invalidLinzumiContext("linzumiContext must include threadId, creatorUserId, creatorUsername, and conversationUrl", warn);
955
+ }
956
+ return { threadId, creatorUserId, creatorUsername, conversationUrl };
957
+ }
958
+ function formatLinzumiConversationContextForPrompt(context) {
959
+ return `<linzumi_context>
960
+ <linzumi_thread_id>${escapeXmlText(context.threadId)}</linzumi_thread_id>
961
+ <linzumi_creator_user_id>${escapeXmlText(context.creatorUserId)}</linzumi_creator_user_id>
962
+ <linzumi_creator_username>${escapeXmlText(context.creatorUsername)}</linzumi_creator_username>
963
+ <linzumi_conversation_url>${escapeXmlText(context.conversationUrl)}</linzumi_conversation_url>
964
+ </linzumi_context>
965
+
966
+ <linzumi_pr_footer_instruction>
967
+ For any GitHub pull request you open from this session, include this Markdown footer link in the PR description: [Continue this on Linzumi](${context.conversationUrl})
968
+ </linzumi_pr_footer_instruction>`;
969
+ }
970
+ function invalidLinzumiContext(reason, warn) {
971
+ warn(`Ignoring invalid linzumiContext: ${reason}`);
972
+ return;
973
+ }
974
+ function escapeXmlText(value) {
975
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
976
+ }
977
+
940
978
  // src/reconnectContext.ts
941
979
  var recentVerbatimMessageCount = 5;
942
980
  var reconnectContextModel = "openai/gpt-oss-120b";
@@ -975,7 +1013,7 @@ function filterReconnectContextMessages(messages) {
975
1013
  return true;
976
1014
  });
977
1015
  }
978
- async function buildReconnectContextInjection(messages, summarizer = createConfiguredReconnectContextSummarizer()) {
1016
+ async function buildReconnectContextInjection(messages, summarizer = createConfiguredReconnectContextSummarizer(), linzumiContext) {
979
1017
  const filtered = filterReconnectContextMessages(messages);
980
1018
  if (filtered.length === 0) {
981
1019
  throw new Error("durable Linzumi thread history did not include user-visible context");
@@ -988,6 +1026,7 @@ async function buildReconnectContextInjection(messages, summarizer = createConfi
988
1026
  "",
989
1027
  "The local Codex app-server thread was restarted, so this durable Linzumi thread history is being injected before retrying the latest user message.",
990
1028
  "",
1029
+ ...linzumiContext === undefined ? [] : [formatLinzumiConversationContextForPrompt(linzumiContext), ""],
991
1030
  ...summary === undefined ? [] : ["Summary of earlier messages:", summary, ""],
992
1031
  "Last five user-visible messages verbatim:",
993
1032
  recent.map(formatReconnectContextMessage).join(`
@@ -1488,7 +1527,6 @@ var defaultIntervalMs = 2000;
1488
1527
  var defaultDebounceMs = 750;
1489
1528
  function startPortForwardWatcher(options) {
1490
1529
  const rootPid = options.rootPid ?? process.pid;
1491
- const rootCwd = normalizeCwd(options.rootCwd);
1492
1530
  const intervalMs = options.intervalMs ?? defaultIntervalMs;
1493
1531
  const debounceMs = options.debounceMs ?? defaultDebounceMs;
1494
1532
  const lostDebounceMs = options.lostDebounceMs ?? intervalMs * 2 + debounceMs;
@@ -1508,8 +1546,8 @@ function startPortForwardWatcher(options) {
1508
1546
  Promise.resolve().then(async () => {
1509
1547
  const descendants = descendantPidSet(scanProcesses(), rootPid);
1510
1548
  const sockets = scanListenSockets();
1511
- const candidatePids = rootCwd === undefined ? sockets.filter((socket) => descendants.has(socket.pid)).map((socket) => socket.pid) : sockets.map((socket) => socket.pid);
1512
- const candidates = detectedForwardCandidates(sockets, descendants, scanProcessCwds(candidatePids), { rootCwd });
1549
+ const candidatePids = sockets.filter((socket) => descendants.has(socket.pid)).map((socket) => socket.pid);
1550
+ const candidates = detectedForwardCandidates(sockets, descendants, scanProcessCwds(candidatePids));
1513
1551
  const scanTimeMs = nowMs();
1514
1552
  const stable = stableForwardCandidates(candidateStabilityByPort, candidates, scanTimeMs, debounceMs);
1515
1553
  const changes = debouncedForwardCandidateChanges(emittedByPort, missingByPort, stable.stableCandidates, scanTimeMs, lostDebounceMs);
@@ -1613,17 +1651,8 @@ function descendantPidSet(rows, rootPid) {
1613
1651
  }
1614
1652
  return descendants;
1615
1653
  }
1616
- function detectedForwardCandidates(sockets, descendantPids, processCwds = new Map, options = {}) {
1617
- const rootCwd = normalizeCwd(options.rootCwd);
1618
- return sockets.filter((socket) => {
1619
- if (descendantPids.has(socket.pid)) {
1620
- return true;
1621
- }
1622
- if (rootCwd === undefined) {
1623
- return false;
1624
- }
1625
- return cwdMatchesRoot(processCwds.get(socket.pid), rootCwd);
1626
- }).filter((socket) => socket.port > 0 && socket.port < 65536).sort((left, right) => left.port - right.port).map((socket) => {
1654
+ function detectedForwardCandidates(sockets, descendantPids, processCwds = new Map) {
1655
+ return sockets.filter((socket) => descendantPids.has(socket.pid)).filter((socket) => socket.port > 0 && socket.port < 65536).sort((left, right) => left.port - right.port).map((socket) => {
1627
1656
  const cwd = processCwds.get(socket.pid);
1628
1657
  return {
1629
1658
  port: socket.port,
@@ -1633,20 +1662,6 @@ function detectedForwardCandidates(sockets, descendantPids, processCwds = new Ma
1633
1662
  };
1634
1663
  });
1635
1664
  }
1636
- function normalizeCwd(cwd) {
1637
- if (cwd === undefined) {
1638
- return;
1639
- }
1640
- const normalized = cwd.trim().replace(/\/+$/, "");
1641
- return normalized === "" ? undefined : normalized;
1642
- }
1643
- function cwdMatchesRoot(candidateCwd, rootCwd) {
1644
- const normalizedCandidate = normalizeCwd(candidateCwd);
1645
- if (normalizedCandidate === undefined) {
1646
- return false;
1647
- }
1648
- return normalizedCandidate === rootCwd || normalizedCandidate.startsWith(`${rootCwd}/`);
1649
- }
1650
1665
  function parseProcessRows(output) {
1651
1666
  return output.split(`
1652
1667
  `).map((line) => line.trim()).filter((line) => line !== "").map((line) => {
@@ -2679,6 +2694,7 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
2679
2694
  portForwardWatcher: undefined,
2680
2695
  activeProcessingState: undefined,
2681
2696
  pendingReconnectContextInjection: undefined,
2697
+ linzumiContext: parseLinzumiConversationContext(options.channelSession.linzumiContext),
2682
2698
  terminalInputForwardChain: Promise.resolve(),
2683
2699
  webSearchProgressForwardChain: Promise.resolve(),
2684
2700
  typingHeartbeat: undefined,
@@ -2716,7 +2732,6 @@ function startPortForwardWatchIfEnabled(args, state, payloadContext) {
2716
2732
  state.approvedForwardPorts.add(port);
2717
2733
  }
2718
2734
  state.portForwardWatcher = start({
2719
- rootCwd: watchOptions.rootCwd ?? args.options.cwd,
2720
2735
  ...watchOptions,
2721
2736
  onCandidate: (candidate) => publishPortForwardPrompt(args, state, payloadContext, candidate),
2722
2737
  onCandidateLost: (candidate) => handleLostPortForwardCandidate(args, state, payloadContext, candidate),
@@ -3711,7 +3726,7 @@ async function fetchReconnectContextInjection(args, state) {
3711
3726
  });
3712
3727
  const messages = parseReconnectContextMessages(reply.messages);
3713
3728
  const summarizer = args.options.reconnectContextSummarizer ?? createConfiguredReconnectContextSummarizer();
3714
- const injection = await buildReconnectContextInjection(messages, summarizer);
3729
+ const injection = await buildReconnectContextInjection(messages, summarizer, state.linzumiContext);
3715
3730
  args.log("codex.thread_reconnect_context_prepared", {
3716
3731
  codex_thread_id: state.codexThreadId ?? null,
3717
3732
  kandan_thread_id: state.kandanThreadId,
@@ -5085,7 +5100,7 @@ async function downloadQueuedKandanAttachments(args, message) {
5085
5100
  }
5086
5101
  const fileName = safeAttachmentFileName(attachment.fileName ?? attachment.id ?? `attachment-${index + 1}`, index);
5087
5102
  const localPath = join(directory, fileName);
5088
- const response = await fetch(resolveKandanAttachmentUrl(args.options.kandanUrl, url), {
5103
+ const response = await (args.options.fetch ?? globalThis.fetch)(resolveKandanAttachmentUrl(args.options.kandanUrl, url), {
5089
5104
  headers: {
5090
5105
  authorization: `Bearer ${args.options.token}`
5091
5106
  }
@@ -5203,7 +5218,7 @@ async function uploadedFileIdsForCodexOutput(args, body, structured) {
5203
5218
  }
5204
5219
  const bytes = await readFile(file.path);
5205
5220
  const uploadBody = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
5206
- const response = await fetch(resolveKandanAttachmentUrl(args.options.kandanUrl, uploadUrl), {
5221
+ const response = await (args.options.fetch ?? globalThis.fetch)(resolveKandanAttachmentUrl(args.options.kandanUrl, uploadUrl), {
5207
5222
  method: uploadMethod,
5208
5223
  headers: { "content-type": file.contentType },
5209
5224
  body: uploadBody
@@ -6890,7 +6905,7 @@ function installDirectory(sourceDir, destinationDir) {
6890
6905
  mkdirSync3(dirname2(destinationDir), { recursive: true });
6891
6906
  cpSync(sourceDir, destinationDir, { recursive: true });
6892
6907
  }
6893
- function codeServerEnv(env, cwd, _userDataDir, collaboration) {
6908
+ function codeServerEnv(env, cwd, userDataDir, collaboration) {
6894
6909
  const { PORT: _port, ...hostEnv } = env;
6895
6910
  const base = {
6896
6911
  ...hostEnv,
@@ -6904,7 +6919,8 @@ function codeServerEnv(env, cwd, _userDataDir, collaboration) {
6904
6919
  KANDAN_EDITOR_COLLABORATION_DEPLOYMENT_SHAPE: "local_runner_sidecar",
6905
6920
  KANDAN_EDITOR_COLLABORATION_ENTRY_MODE: "kandan_auto_host_or_join",
6906
6921
  KANDAN_EDITOR_COLLABORATION_ROOM_ID: collaboration.roomId,
6907
- KANDAN_EDITOR_COLLABORATION_SERVER_URL: collaboration.bootstrapServerUrl
6922
+ KANDAN_EDITOR_COLLABORATION_SERVER_URL: collaboration.bootstrapServerUrl,
6923
+ KANDAN_EDITOR_COLLABORATION_AUTO_HOST_CLAIM_PATH: join4(userDataDir, "kandan-oct-auto-host.lock")
6908
6924
  };
6909
6925
  }
6910
6926
  function collaborationEvent(collaboration) {
@@ -8484,7 +8500,7 @@ function realpathOrResolved(pathValue) {
8484
8500
  }
8485
8501
 
8486
8502
  // src/version.ts
8487
- var linzumiCliVersion = "0.0.45-beta";
8503
+ var linzumiCliVersion = "0.0.47-beta";
8488
8504
  var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
8489
8505
 
8490
8506
  // src/runnerLock.ts
@@ -8975,11 +8991,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
8975
8991
  cleanup.actions.push(() => kandan.close());
8976
8992
  const topic = `local_runner:${options.runnerId}`;
8977
8993
  const clientId = options.machineId ?? options.runnerId;
8994
+ const runnerHost = hostname2();
8978
8995
  const joinPayload = () => ({
8979
8996
  clientName: "kandan-local-codex-runner",
8980
8997
  clientId,
8981
8998
  runnerPid: process.pid,
8982
8999
  version: linzumiCliVersion,
9000
+ hostname: runnerHost,
9001
+ cwd: options.cwd,
8983
9002
  workspace: runnerWorkspaceSlug(options) ?? null,
8984
9003
  channel: options.channelSession?.channelSlug ?? null,
8985
9004
  capabilities: capabilitiesPayload()
@@ -9248,7 +9267,6 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
9248
9267
  const seq = { value: 0 };
9249
9268
  const codexThreads = options.channelSession === undefined ? await discoverCodexThreads(codex, options.cwd) : [];
9250
9269
  const discoveredCodexThreads = { value: codexThreads };
9251
- const runnerHost = hostname2();
9252
9270
  const runtimeDefaults = runnerRuntimeDefaults(options);
9253
9271
  const instancePayload = {
9254
9272
  instanceId,
@@ -9280,10 +9298,12 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
9280
9298
  topic,
9281
9299
  instanceId,
9282
9300
  options: {
9301
+ kandanUrl: options.kandanUrl,
9283
9302
  token: options.token,
9284
9303
  runnerId: options.runnerId,
9285
9304
  cwd: options.cwd,
9286
9305
  codexBin: options.codexBin,
9306
+ fetch: options.fetch,
9287
9307
  fast: options.fast,
9288
9308
  launchTui: options.launchTui,
9289
9309
  enablePortForwardWatch: true,
@@ -9335,10 +9355,12 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
9335
9355
  topic,
9336
9356
  instanceId,
9337
9357
  options: {
9358
+ kandanUrl: options.kandanUrl,
9338
9359
  token: options.token,
9339
9360
  runnerId: options.runnerId,
9340
9361
  cwd,
9341
9362
  codexBin: options.codexBin,
9363
+ fetch: options.fetch,
9342
9364
  fast: control.fast ?? options.fast,
9343
9365
  launchTui: false,
9344
9366
  enablePortForwardWatch: true,
@@ -9360,6 +9382,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
9360
9382
  kandanThreadId,
9361
9383
  rootSeq: integerValue(control.rootSeq),
9362
9384
  codexThreadId,
9385
+ linzumiContext: control.linzumiContext,
9363
9386
  listenUser,
9364
9387
  model: runtimeSettings.model,
9365
9388
  reasoningEffort: runtimeSettings.reasoningEffort,
@@ -9868,6 +9891,7 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
9868
9891
  }
9869
9892
  startupStage = "starting_codex_session";
9870
9893
  const developerPrompt = normalizedWorkDescription(control.developerPrompt);
9894
+ const linzumiContext = parseLinzumiConversationContext(control.linzumiContext);
9871
9895
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
9872
9896
  const response = await codex.request("thread/start", {
9873
9897
  cwd: cwd.cwd,
@@ -9875,7 +9899,8 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
9875
9899
  personality: "pragmatic",
9876
9900
  developerInstructions: commanderDeveloperInstructions({
9877
9901
  cwd: cwd.cwd,
9878
- developerPrompt
9902
+ developerPrompt,
9903
+ linzumiContext
9879
9904
  }),
9880
9905
  ...runtimeSettings.model === undefined ? {} : { model: runtimeSettings.model },
9881
9906
  ...runtimeSettings.reasoningEffort === undefined ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
@@ -10067,6 +10092,9 @@ function commanderDeveloperInstructions(args) {
10067
10092
  <invoker_developer_prompt>
10068
10093
  ${args.developerPrompt}
10069
10094
  </invoker_developer_prompt>
10095
+ `;
10096
+ const linzumiContext = args.linzumiContext === undefined ? "" : `
10097
+ ${formatLinzumiConversationContextForPrompt(args.linzumiContext)}
10070
10098
  `;
10071
10099
  return `<context>
10072
10100
  You are a Linzumi Codex session launched by the Linzumi Commander.
@@ -10110,6 +10138,7 @@ fails.
10110
10138
  GOOD preview command: npm run dev -- --host 0.0.0.0 --port 8787
10111
10139
  BAD preview command: npm run dev -- --host 127.0.0.1
10112
10140
  </examples>
10141
+ ${linzumiContext}
10113
10142
  ${customPrompt}
10114
10143
  <task_reminder>
10115
10144
  You are the Commander-launched Linzumi Codex session. Do the implementation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.45-beta",
3
+ "version": "0.0.47-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": {