@linzumi/cli 0.0.28-beta → 0.0.30-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/README.md CHANGED
@@ -26,7 +26,7 @@ codex \
26
26
  -c sandbox_workspace_write.network_access=true \
27
27
  --ask-for-approval never \
28
28
  --sandbox danger-full-access \
29
- -- 'Follow https://linzumi.com/agents.md to get me "wow"'
29
+ -- 'Follow https://linzumi.com/agents.md to get me to "wow"'
30
30
  ```
31
31
 
32
32
  That's the launch path: the first Codex is only the bootstrapper. It
@@ -293,7 +293,7 @@ intentionally. Every action is auditable from the thread.
293
293
  ## Pinning a version
294
294
 
295
295
  ```bash
296
- npm install -g @linzumi/cli@0.0.28-beta
296
+ npm install -g @linzumi/cli@0.0.30-beta
297
297
  linzumi --version
298
298
  ```
299
299
 
package/dist/index.js CHANGED
@@ -6622,6 +6622,60 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6622
6622
  cleanup.actions.push(() => channelSession.close());
6623
6623
  kandan.onReconnect(() => channelSession.handleKandanReconnect());
6624
6624
  }
6625
+ const dynamicChannelSessions = new Map;
6626
+ cleanup.actions.push(async () => {
6627
+ await Promise.all(Array.from(dynamicChannelSessions.values(), (session) => session.close()));
6628
+ dynamicChannelSessions.clear();
6629
+ });
6630
+ const attachStartedThreadSession = async (control, cwd) => {
6631
+ const workspaceSlug = normalizedWorkDescription(control.workspace);
6632
+ const channelSlug = normalizedWorkDescription(control.channel);
6633
+ const kandanThreadId = normalizedWorkDescription(control.threadId);
6634
+ if (workspaceSlug === undefined || channelSlug === undefined || kandanThreadId === undefined || dynamicChannelSessions.has(kandanThreadId)) {
6635
+ return;
6636
+ }
6637
+ const listenUser = options.channelSession?.listenUser ?? identityFromAccessToken(options.token).actorUsername;
6638
+ if (listenUser === undefined) {
6639
+ throw new Error("missing listen user for Commander-started Codex session");
6640
+ }
6641
+ const session = await attachChannelSession({
6642
+ kandan,
6643
+ codex,
6644
+ topic,
6645
+ instanceId,
6646
+ options: {
6647
+ token: options.token,
6648
+ runnerId: options.runnerId,
6649
+ cwd,
6650
+ codexBin: options.codexBin,
6651
+ fast: control.fast ?? options.fast,
6652
+ launchTui: false,
6653
+ enablePortForwardWatch: true,
6654
+ initialForwardPorts: allowedForwardPorts,
6655
+ suppressedForwardPorts: () => Array.from(managedForwardPorts),
6656
+ onForwardPortApproved: (port) => {
6657
+ liveForwardPorts.add(port);
6658
+ return capabilitiesPayload();
6659
+ },
6660
+ onForwardPortRevoked: (port) => {
6661
+ liveForwardPorts.delete(port);
6662
+ return capabilitiesPayload();
6663
+ },
6664
+ channelSession: {
6665
+ workspaceSlug,
6666
+ channelSlug,
6667
+ kandanThreadId,
6668
+ listenUser,
6669
+ model: control.model,
6670
+ reasoningEffort: control.reasoningEffort,
6671
+ sandbox: control.sandbox,
6672
+ approvalPolicy: control.approvalPolicy
6673
+ }
6674
+ },
6675
+ log
6676
+ });
6677
+ dynamicChannelSessions.set(kandanThreadId, session);
6678
+ };
6625
6679
  const heartbeatPayload = () => ({
6626
6680
  instanceId,
6627
6681
  codexUrl,
@@ -6684,6 +6738,9 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6684
6738
  metadata
6685
6739
  });
6686
6740
  channelSession?.handleCodexNotification(notification.method, params);
6741
+ for (const session of dynamicChannelSessions.values()) {
6742
+ session.handleCodexNotification(notification.method, params);
6743
+ }
6687
6744
  });
6688
6745
  const handleControl = (control) => {
6689
6746
  log("kandan.control", { control });
@@ -6777,11 +6834,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6777
6834
  pushHeartbeat();
6778
6835
  return;
6779
6836
  }
6780
- (channelSession?.handleControl(control) ?? Promise.resolve(undefined)).then((handled) => {
6837
+ resolveSessionControl(channelSession, dynamicChannelSessions, control).then((handled) => {
6781
6838
  if (handled !== undefined) {
6782
6839
  return handled;
6783
6840
  }
6784
- return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control);
6841
+ return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control, attachStartedThreadSession);
6785
6842
  }).then((response) => {
6786
6843
  return kandan.push(topic, "codex_response", response);
6787
6844
  }).catch((error) => {
@@ -6802,6 +6859,19 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
6802
6859
  function controlTargetsInstance(control, instanceId) {
6803
6860
  return control.instanceId === undefined || control.instanceId === instanceId;
6804
6861
  }
6862
+ async function resolveSessionControl(channelSession, dynamicChannelSessions, control) {
6863
+ const primaryHandled = await (channelSession?.handleControl(control) ?? Promise.resolve(undefined));
6864
+ if (primaryHandled !== undefined) {
6865
+ return primaryHandled;
6866
+ }
6867
+ for (const session of dynamicChannelSessions.values()) {
6868
+ const handled = await session.handleControl(control);
6869
+ if (handled !== undefined) {
6870
+ return handled;
6871
+ }
6872
+ }
6873
+ return;
6874
+ }
6805
6875
  async function closeCleanupStack(cleanup) {
6806
6876
  if (cleanup.closePromise !== undefined) {
6807
6877
  return cleanup.closePromise;
@@ -6916,7 +6986,7 @@ async function prepareCodexThreadForTuiResume(codex, codexThreadId) {
6916
6986
  throw new Error(`failed to verify Codex TUI resume: ${verified.error.message}`);
6917
6987
  }
6918
6988
  }
6919
- async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control) {
6989
+ async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control, onStartedThread) {
6920
6990
  switch (control.type) {
6921
6991
  case "start_instance": {
6922
6992
  const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
@@ -6949,7 +7019,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
6949
7019
  const codexThreadId = extractStartedThreadId(response);
6950
7020
  const workDescription = normalizedWorkDescription(control.workDescription);
6951
7021
  if (codexThreadId !== undefined && developerPrompt !== undefined) {
6952
- await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt);
7022
+ await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId);
7023
+ }
7024
+ if (codexThreadId !== undefined && onStartedThread !== undefined) {
7025
+ await onStartedThread(control, cwd.cwd);
6953
7026
  }
6954
7027
  if (codexThreadId !== undefined && workDescription !== undefined) {
6955
7028
  await codex.request("turn/start", {
@@ -7078,7 +7151,7 @@ work in the approved project folder, keep preview servers reachable through the
7078
7151
  secure tunnel, and keep the Linzumi thread truthful.
7079
7152
  </task_reminder>`;
7080
7153
  }
7081
- async function postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt) {
7154
+ async function postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId) {
7082
7155
  const workspace = normalizedWorkDescription(control.workspace);
7083
7156
  const channel = normalizedWorkDescription(control.channel);
7084
7157
  const threadId = normalizedWorkDescription(control.threadId);
@@ -7093,8 +7166,11 @@ async function postVisibleDeveloperPrompt(kandan, topic, control, developerPromp
7093
7166
 
7094
7167
  ${developerPrompt}`,
7095
7168
  payload: {
7096
- local_codex_runner: {
7097
- event_type: "codex_start_instructions"
7169
+ metadata: {
7170
+ local_codex_runner: {
7171
+ event_type: "codex_start_instructions",
7172
+ codex_thread_id: codexThreadId
7173
+ }
7098
7174
  }
7099
7175
  },
7100
7176
  client_message_id: `codex-start-instructions-${threadId}`
@@ -8836,7 +8912,7 @@ async function main(args) {
8836
8912
  process.stdout.write(connectGuideText());
8837
8913
  return;
8838
8914
  case "version":
8839
- process.stdout.write(`linzumi 0.0.28-beta
8915
+ process.stdout.write(`linzumi 0.0.30-beta
8840
8916
  `);
8841
8917
  return;
8842
8918
  case "auth":
@@ -9343,7 +9419,7 @@ async function parseRunnerArgs(args, deps = {
9343
9419
  process.exit(0);
9344
9420
  }
9345
9421
  if (values.get("version") === true) {
9346
- process.stdout.write(`linzumi 0.0.28-beta
9422
+ process.stdout.write(`linzumi 0.0.30-beta
9347
9423
  `);
9348
9424
  process.exit(0);
9349
9425
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.28-beta",
3
+ "version": "0.0.30-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": {