@linzumi/cli 0.0.40-beta → 0.0.41-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 +230 -135
  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.40-beta --version
66
+ npx -y @linzumi/cli@0.0.41-beta --version
67
67
  linzumi --version
68
68
  ```
69
69
 
package/dist/index.js CHANGED
@@ -290,6 +290,9 @@ function codexSandboxPolicy(sandbox, cwd) {
290
290
  // src/codexOutput.ts
291
291
  import { Buffer as Buffer2 } from "node:buffer";
292
292
  var maxVisibleWebSearchQueries = 6;
293
+ var maxPersistedCommandOutputChars = 14000;
294
+ var maxPersistedCommandLabelChars = 512;
295
+ var commandOutputTruncationReason = "phoenix_insert_body_limit";
293
296
  function codexOutputMessagesForTurn(response, turnId) {
294
297
  if ("error" in response) {
295
298
  return [];
@@ -355,21 +358,20 @@ function codexOutputMessagesForItem(item, index) {
355
358
  case "commandExecution": {
356
359
  const command = stringValue(item.command) ?? "command";
357
360
  const output = nonBlankStringValue(item.aggregatedOutput) ?? "";
358
- const body = [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
359
-
360
- `);
361
+ const projection = codexCommandOutputProjection(command, output);
361
362
  return [
362
363
  {
363
364
  itemKey,
364
- body,
365
+ body: projection.body,
365
366
  structured: baseStructured("codex_command_execution", {
366
- command,
367
+ command: projection.command,
367
368
  cwd: stringValue(item.cwd) ?? "",
368
369
  status: stringValue(item.status) ?? "",
369
370
  process_id: stringValue(item.processId) ?? "",
370
371
  duration_ms: integerValue(item.durationMs) ?? null,
371
372
  exit_code: integerValue(item.exitCode) ?? null,
372
- output
373
+ output: projection.output,
374
+ ...commandOutputTruncationMetadata(projection)
373
375
  })
374
376
  }
375
377
  ];
@@ -501,21 +503,29 @@ function codexCommandOutputDeltaFromNotification(params) {
501
503
  };
502
504
  }
503
505
  function codexCommandExecutionStructuredMessage(itemKey, fields, streamState) {
506
+ const projection = codexCommandOutputProjection(fields.command, fields.output);
507
+ return codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection);
508
+ }
509
+ function codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection) {
504
510
  return {
505
511
  kind: "codex_command_execution",
506
512
  item_id: itemKey,
507
513
  transcript_unit_id: `codex_command_execution:${itemKey}`,
508
514
  stream_state: streamState,
509
- command: fields.command,
515
+ command: projection.command,
510
516
  cwd: fields.cwd ?? "",
511
517
  status: fields.status ?? streamState,
512
518
  process_id: fields.processId ?? "",
513
519
  duration_ms: fields.durationMs ?? null,
514
520
  exit_code: fields.exitCode ?? null,
515
521
  stream: fields.stream ?? "",
516
- output: fields.output
522
+ output: projection.output,
523
+ ...commandOutputTruncationMetadata(projection)
517
524
  };
518
525
  }
526
+ function codexCommandOutputBody(command, output) {
527
+ return codexCommandOutputProjection(command, output).body;
528
+ }
519
529
  function codexFileChangeDeltaFromNotification(params) {
520
530
  const patchText = textDeltaFromParams(params);
521
531
  if (patchText === undefined || patchText === "") {
@@ -618,6 +628,65 @@ function assistantVisibleText(item) {
618
628
  function assistantDeltaText(params) {
619
629
  return textDeltaFromParams(params);
620
630
  }
631
+ function codexCommandOutputProjection(command, output) {
632
+ const normalizedCommand = truncatePersistedText(sanitizePersistedText(command), maxPersistedCommandLabelChars, "command label");
633
+ const normalizedOutput = truncateCommandOutput(sanitizePersistedText(output));
634
+ const body = commandOutputBodyForProjection(normalizedCommand.text, normalizedOutput.text);
635
+ return {
636
+ command: normalizedCommand.text,
637
+ output: normalizedOutput.text,
638
+ body,
639
+ truncated: normalizedCommand.truncated || normalizedOutput.truncated,
640
+ originalLength: output.length,
641
+ maxOutputChars: maxPersistedCommandOutputChars,
642
+ truncationReason: commandOutputTruncationReason
643
+ };
644
+ }
645
+ function commandOutputTruncationMetadata(projection) {
646
+ return projection.truncated ? {
647
+ output_truncated: true,
648
+ output_original_length: projection.originalLength,
649
+ output_max_chars: projection.maxOutputChars,
650
+ output_truncation_reason: projection.truncationReason
651
+ } : {};
652
+ }
653
+ function commandOutputBodyForProjection(command, output) {
654
+ return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
655
+
656
+ `);
657
+ }
658
+ function truncateCommandOutput(output) {
659
+ if (output.length <= maxPersistedCommandOutputChars) {
660
+ return { text: output, truncated: false };
661
+ }
662
+ const marker = `
663
+
664
+ [Linzumi truncated command output: output was too large to persist fully. Owners should search runner logs for codex.command_output_truncated failure_class=phoenix_insert_body_limit.]
665
+
666
+ `;
667
+ const availableChars = Math.max(0, maxPersistedCommandOutputChars - marker.length);
668
+ const headChars = Math.min(2000, Math.floor(availableChars / 3));
669
+ const tailChars = availableChars - headChars;
670
+ const text = `${output.slice(0, headChars)}${marker}${output.slice(-tailChars)}`;
671
+ return {
672
+ text: text.slice(0, maxPersistedCommandOutputChars),
673
+ truncated: true
674
+ };
675
+ }
676
+ function truncatePersistedText(text, maxChars, label) {
677
+ if (text.length <= maxChars) {
678
+ return { text, truncated: false };
679
+ }
680
+ const marker = `[Linzumi truncated ${label}]`;
681
+ const availableChars = Math.max(0, maxChars - marker.length - 1);
682
+ return {
683
+ text: `${text.slice(0, availableChars)} ${marker}`.slice(0, maxChars),
684
+ truncated: true
685
+ };
686
+ }
687
+ function sanitizePersistedText(text) {
688
+ return text.replaceAll("\x00", "�");
689
+ }
621
690
  function textDeltaFromParams(params) {
622
691
  const delta = params.delta;
623
692
  if (typeof delta === "string") {
@@ -641,57 +710,67 @@ function commandMessageForFunctionCall(item, output, index) {
641
710
  const command = name === "exec_command" ? nonBlankStringValue(args?.cmd) ?? name : `${name}${toolArgumentsSummary(item.arguments)}`;
642
711
  const cwd = nonBlankStringValue(args?.cwd) ?? "";
643
712
  const outputText = toolOutputText(output);
644
- const body = [`$ ${command}`, outputText].filter((part) => part.trim() !== "").join(`
645
-
646
- `);
647
- return [{
648
- itemKey,
649
- body,
650
- structured: codexCommandExecutionStructuredMessage(itemKey, {
651
- command,
652
- cwd,
653
- output: outputText,
654
- status: output === undefined ? "started" : "completed"
655
- }, "completed")
656
- }];
713
+ const projection = codexCommandOutputProjection(command, outputText);
714
+ return [
715
+ {
716
+ itemKey,
717
+ body: projection.body,
718
+ structured: codexCommandExecutionStructuredMessage(itemKey, {
719
+ command,
720
+ cwd,
721
+ output: outputText,
722
+ status: output === undefined ? "started" : "completed"
723
+ }, "completed")
724
+ }
725
+ ];
657
726
  }
658
727
  function messageForCustomToolCall(item, output, index) {
659
728
  const name = stringValue(item.name) ?? "custom_tool_call";
660
729
  const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
661
730
  const input = nonBlankStringValue(item.input) ?? nonBlankStringValue(item.arguments) ?? "";
662
731
  if (name === "apply_patch") {
663
- return [{
664
- itemKey,
665
- body: input,
666
- structured: {
667
- kind: "codex_file_change",
668
- item_id: itemKey,
669
- transcript_unit_id: `codex_file_change:${itemKey}`,
670
- stream_state: "completed",
671
- status: output === undefined ? "started" : "completed",
672
- patch_text: input,
673
- changes: fileChangesFromPatch(input)
732
+ return [
733
+ {
734
+ itemKey,
735
+ body: input,
736
+ structured: {
737
+ kind: "codex_file_change",
738
+ item_id: itemKey,
739
+ transcript_unit_id: `codex_file_change:${itemKey}`,
740
+ stream_state: "completed",
741
+ status: output === undefined ? "started" : "completed",
742
+ patch_text: input,
743
+ changes: fileChangesFromPatch(input)
744
+ }
674
745
  }
675
- }];
746
+ ];
676
747
  }
677
748
  const outputText = toolOutputText(output);
678
749
  const command = `${name}${toolArgumentsSummary(item.input)}`;
679
- return [{
680
- itemKey,
681
- body: [`$ ${command}`, outputText].filter((part) => part.trim() !== "").join(`
682
-
683
- `),
684
- structured: codexCommandExecutionStructuredMessage(itemKey, { command, output: outputText, status: output === undefined ? "started" : "completed" }, "completed")
685
- }];
750
+ const projection = codexCommandOutputProjection(command, outputText);
751
+ return [
752
+ {
753
+ itemKey,
754
+ body: projection.body,
755
+ structured: codexCommandExecutionStructuredMessage(itemKey, {
756
+ command,
757
+ output: outputText,
758
+ status: output === undefined ? "started" : "completed"
759
+ }, "completed")
760
+ }
761
+ ];
686
762
  }
687
763
  function commandMessageForUnpairedToolOutput(item, index) {
688
764
  const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
689
765
  const output = toolOutputText(item);
690
- return [{
691
- itemKey,
692
- body: output,
693
- structured: codexCommandExecutionStructuredMessage(itemKey, { command: "tool output", output, status: "completed" }, "completed")
694
- }];
766
+ const projection = codexCommandOutputProjection("tool output", output);
767
+ return [
768
+ {
769
+ itemKey,
770
+ body: projection.output,
771
+ structured: codexCommandExecutionStructuredMessageFromProjection(itemKey, { status: "completed" }, "completed", projection)
772
+ }
773
+ ];
695
774
  }
696
775
  function toolArgumentsSummary(value) {
697
776
  const text = nonBlankStringValue(value);
@@ -710,13 +789,34 @@ function fileChangesFromPatch(patchText) {
710
789
  const addPrefix = "*** Add File: ";
711
790
  const deletePrefix = "*** Delete File: ";
712
791
  if (line.startsWith(updatePrefix)) {
713
- return [{ path: line.slice(updatePrefix.length).trim(), diff: "", kind: "update", move_path: "" }];
792
+ return [
793
+ {
794
+ path: line.slice(updatePrefix.length).trim(),
795
+ diff: "",
796
+ kind: "update",
797
+ move_path: ""
798
+ }
799
+ ];
714
800
  }
715
801
  if (line.startsWith(addPrefix)) {
716
- return [{ path: line.slice(addPrefix.length).trim(), diff: "", kind: "add", move_path: "" }];
802
+ return [
803
+ {
804
+ path: line.slice(addPrefix.length).trim(),
805
+ diff: "",
806
+ kind: "add",
807
+ move_path: ""
808
+ }
809
+ ];
717
810
  }
718
811
  if (line.startsWith(deletePrefix)) {
719
- return [{ path: line.slice(deletePrefix.length).trim(), diff: "", kind: "delete", move_path: "" }];
812
+ return [
813
+ {
814
+ path: line.slice(deletePrefix.length).trim(),
815
+ diff: "",
816
+ kind: "delete",
817
+ move_path: ""
818
+ }
819
+ ];
720
820
  }
721
821
  return [];
722
822
  });
@@ -2345,8 +2445,8 @@ async function attachChannelSession(args) {
2345
2445
  const session = args.options.channelSession;
2346
2446
  const chatTopic = `chat:${session.workspaceSlug}:${session.channelSlug}`;
2347
2447
  const state = initialChannelSessionState(0, session.rootSeq, session.kandanThreadId, session.codexThreadId, args.options);
2348
- const joined = await args.kandan.join(chatTopic, { last_seq: 0 }, {
2349
- rejoinPayload: () => ({ last_seq: state.minSeq })
2448
+ const joined = await args.kandan.join(chatTopic, chatJoinPayload(args.options.runnerId, state, 0), {
2449
+ rejoinPayload: () => chatJoinPayload(args.options.runnerId, state, state.minSeq)
2350
2450
  });
2351
2451
  const cursor = integerValue(joined.cursor) ?? 0;
2352
2452
  const runnerIdentity = identityFromAccessToken(args.options.token);
@@ -2492,6 +2592,19 @@ async function attachChannelSession(args) {
2492
2592
  }
2493
2593
  };
2494
2594
  }
2595
+ function chatJoinPayload(runnerId, state, lastSeq) {
2596
+ const base = { last_seq: lastSeq };
2597
+ switch (state.kandanThreadId) {
2598
+ case undefined:
2599
+ return base;
2600
+ default:
2601
+ return {
2602
+ ...base,
2603
+ runner_id: runnerId,
2604
+ thread_id: state.kandanThreadId
2605
+ };
2606
+ }
2607
+ }
2495
2608
  async function bindCurrentCodexThread(args, state) {
2496
2609
  if (state.codexThreadId === undefined) {
2497
2610
  return;
@@ -3972,13 +4085,25 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
3972
4085
  }
3973
4086
  const existing = findStreamingCommandOutput(state, delta.itemKey);
3974
4087
  const output = `${existing?.output ?? ""}${delta.delta}`;
3975
- const body = commandOutputBody("command output", output);
3976
4088
  const structured = codexCommandExecutionStructuredMessage(delta.itemKey, {
3977
4089
  command: "command output",
3978
4090
  output,
3979
4091
  processId: delta.processId,
3980
4092
  stream: delta.stream
3981
4093
  }, "streaming");
4094
+ const persistedOutput = typeof structured.output === "string" ? structured.output : output;
4095
+ const body = codexCommandOutputBody("command output", output);
4096
+ if (structured.output_truncated === true) {
4097
+ args.log("codex.command_output_truncated", {
4098
+ item_key: delta.itemKey,
4099
+ turn_id: turnId,
4100
+ process_id: delta.processId ?? null,
4101
+ stream: delta.stream,
4102
+ failure_class: stringValue(structured.output_truncation_reason) ?? null,
4103
+ original_length: integerValue(structured.output_original_length) ?? null,
4104
+ persisted_length: persistedOutput.length
4105
+ });
4106
+ }
3982
4107
  if (existing === undefined) {
3983
4108
  const session = args.options.channelSession;
3984
4109
  const reply = await pushOk(args.kandan, args.topic, "session:post_thread_message", {
@@ -4002,7 +4127,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
4002
4127
  itemKey: delta.itemKey,
4003
4128
  turnId,
4004
4129
  seq,
4005
- output,
4130
+ output: persistedOutput,
4006
4131
  processId: delta.processId,
4007
4132
  stream: delta.stream
4008
4133
  });
@@ -4011,7 +4136,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
4011
4136
  await editCodexStructuredOutput(args, state, existing.seq, body, structured);
4012
4137
  rememberStreamingCommandOutput(state, {
4013
4138
  ...existing,
4014
- output,
4139
+ output: persistedOutput,
4015
4140
  processId: delta.processId ?? existing.processId,
4016
4141
  stream: delta.stream
4017
4142
  });
@@ -4021,7 +4146,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
4021
4146
  turn_id: turnId,
4022
4147
  process_id: delta.processId ?? null,
4023
4148
  stream: delta.stream,
4024
- output_length: output.length
4149
+ output_length: persistedOutput.length
4025
4150
  });
4026
4151
  }
4027
4152
  async function forwardFileChangeDeltaPayload(args, state, delta, payloadContext) {
@@ -4216,11 +4341,6 @@ function webSearchProgressClientMessageId(instanceId, turnId) {
4216
4341
  function webSearchProgressItemKey(turnId) {
4217
4342
  return `web-search:${turnId}`;
4218
4343
  }
4219
- function commandOutputBody(command, output) {
4220
- return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
4221
-
4222
- `);
4223
- }
4224
4344
  async function streamCompletedCodexOutput(args, state, payloadContext, params) {
4225
4345
  if (state.kandanThreadId === undefined || state.codexThreadId === undefined) {
4226
4346
  return;
@@ -6249,30 +6369,14 @@ function prepareCodeServerLaunch(options) {
6249
6369
  if (platform !== "darwin") {
6250
6370
  return filesystemSandboxUnavailable();
6251
6371
  }
6252
- const sandboxExecBin = options.sandboxExecBin ?? "/usr/bin/sandbox-exec";
6253
- if (!existsSync2(sandboxExecBin)) {
6254
- return filesystemSandboxUnavailable();
6255
- }
6256
6372
  const codeServerExecutable = resolveCodeServerExecutable(options.codeServerBin, options.envPath ?? process.env.PATH ?? "");
6257
6373
  if (!codeServerExecutable.ok) {
6258
6374
  return filesystemSandboxUnavailable();
6259
6375
  }
6260
6376
  return {
6261
6377
  ok: true,
6262
- command: sandboxExecBin,
6263
- args: [
6264
- "-p",
6265
- codeServerSandboxProfile(options, codeServerExecutable.directory),
6266
- "--",
6267
- "/bin/sh",
6268
- "-c",
6269
- 'export HOME="$1"; export PWD="$1"; export TMPDIR="$2"; export TMP="$2"; export TEMP="$2"; shift 2; exec "$@"',
6270
- "kandan-code-server-env",
6271
- options.cwd,
6272
- join4(options.userDataDir, "tmp"),
6273
- codeServerExecutable.command,
6274
- ...codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
6275
- ]
6378
+ command: codeServerExecutable.command,
6379
+ args: codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
6276
6380
  };
6277
6381
  }
6278
6382
  function prepareLinuxCodeServerLaunch(options) {
@@ -6343,43 +6447,6 @@ function filesystemSandboxUnavailable() {
6343
6447
  reason: "local_editor_filesystem_sandbox_unavailable"
6344
6448
  };
6345
6449
  }
6346
- function codeServerSandboxProfile(options, codeServerBinDir) {
6347
- const readOnlyRoots = uniquePaths([
6348
- "/System",
6349
- "/Library",
6350
- "/usr",
6351
- "/bin",
6352
- "/sbin",
6353
- "/etc",
6354
- "/private/etc",
6355
- "/opt/homebrew",
6356
- ...options.codeServerRuntimeRoot === undefined ? [] : sandboxPathAliases(options.codeServerRuntimeRoot),
6357
- codeServerBinDir
6358
- ]);
6359
- const readWriteRoots = uniquePaths([
6360
- ...sandboxPathAliases(options.cwd),
6361
- ...sandboxPathAliases(options.userDataDir),
6362
- ...options.extensionsDir === undefined ? [] : sandboxPathAliases(options.extensionsDir)
6363
- ]);
6364
- return [
6365
- "(version 1)",
6366
- "(deny default)",
6367
- "(allow process*)",
6368
- "(allow signal (target self))",
6369
- "(allow signal (target same-sandbox))",
6370
- "(allow sysctl*)",
6371
- "(allow mach*)",
6372
- "(allow ipc*)",
6373
- "(allow network*)",
6374
- "(allow file-read-metadata)",
6375
- "(allow file-map-executable)",
6376
- '(allow file-read* (literal "/") (literal "/private") (literal "/private/var"))',
6377
- `(allow file-read* ${readOnlyRoots.map(sandboxSubpath).join(" ")})`,
6378
- '(allow file-read* file-write* (subpath "/dev"))',
6379
- `(allow file-read* file-write* ${readWriteRoots.map(sandboxSubpath).join(" ")})`
6380
- ].join(`
6381
- `);
6382
- }
6383
6450
  function resolveCodeServerExecutable(command, envPath) {
6384
6451
  if (hasPathSeparator(command)) {
6385
6452
  const directory = safeRealpathDir(command);
@@ -6420,9 +6487,6 @@ function sandboxPathAliases(path) {
6420
6487
  function uniquePaths(paths) {
6421
6488
  return Array.from(new Set(paths.filter((path) => path.length > 0)));
6422
6489
  }
6423
- function sandboxSubpath(path) {
6424
- return `(subpath "${path.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}")`;
6425
- }
6426
6490
  function prepareLocalEditorCollaboration(collaboration, runnerId, serverPort, browserBaseUrl) {
6427
6491
  if (collaboration === undefined || serverPort === undefined) {
6428
6492
  return;
@@ -6548,19 +6612,11 @@ function installDirectory(sourceDir, destinationDir) {
6548
6612
  mkdirSync3(dirname2(destinationDir), { recursive: true });
6549
6613
  cpSync(sourceDir, destinationDir, { recursive: true });
6550
6614
  }
6551
- function codeServerEnv(env, cwd, userDataDir, collaboration) {
6615
+ function codeServerEnv(env, cwd, _userDataDir, collaboration) {
6552
6616
  const { PORT: _port, ...hostEnv } = env;
6553
- const tempDir = join4(userDataDir, "tmp");
6554
6617
  const base = {
6555
6618
  ...hostEnv,
6556
- HOME: cwd,
6557
- PWD: cwd,
6558
- TMPDIR: tempDir,
6559
- TMP: tempDir,
6560
- TEMP: tempDir,
6561
- XDG_CACHE_HOME: join4(userDataDir, "xdg-cache"),
6562
- XDG_CONFIG_HOME: join4(userDataDir, "xdg-config"),
6563
- XDG_DATA_HOME: join4(userDataDir, "xdg-data")
6619
+ PWD: cwd
6564
6620
  };
6565
6621
  if (collaboration === undefined) {
6566
6622
  return base;
@@ -8654,6 +8710,8 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
8654
8710
  switch (control.type) {
8655
8711
  case "start_instance": {
8656
8712
  const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
8713
+ let startupStage = "starting_codex_session";
8714
+ let startedCodexThreadId;
8657
8715
  if (!cwd.ok) {
8658
8716
  return {
8659
8717
  instanceId,
@@ -8672,8 +8730,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
8672
8730
  }
8673
8731
  try {
8674
8732
  if (options.codexUrl === undefined) {
8733
+ startupStage = "checking_project_trust";
8675
8734
  ensureCodexProjectTrusted(cwd.cwd);
8676
8735
  }
8736
+ startupStage = "starting_codex_session";
8677
8737
  const developerPrompt = normalizedWorkDescription(control.developerPrompt);
8678
8738
  const runtimeSettings = startInstanceRuntimeSettings(options, control);
8679
8739
  const response = await codex.request("thread/start", {
@@ -8691,12 +8751,18 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
8691
8751
  ...runtimeSettings.fast === true ? { serviceTier: "fast" } : {}
8692
8752
  });
8693
8753
  const codexThreadId = extractStartedThreadId(response);
8754
+ startedCodexThreadId = codexThreadId;
8694
8755
  const workDescription = normalizedWorkDescription(control.workDescription);
8695
8756
  if (codexThreadId !== undefined && developerPrompt !== undefined) {
8696
8757
  await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId);
8697
8758
  }
8698
- const startedThreadSession = codexThreadId !== undefined && onStartedThread !== undefined ? await onStartedThread(control, cwd.cwd, codexThreadId) : undefined;
8759
+ let startedThreadSession;
8760
+ if (codexThreadId !== undefined && onStartedThread !== undefined) {
8761
+ startupStage = "binding_kandan_thread";
8762
+ startedThreadSession = await onStartedThread(control, cwd.cwd, codexThreadId);
8763
+ }
8699
8764
  if (codexThreadId !== undefined && workDescription !== undefined) {
8765
+ startupStage = "starting_first_turn";
8700
8766
  const rootSeq = integerValue(control.rootSeq);
8701
8767
  const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
8702
8768
  if (startedThreadSession !== undefined && sourceSeq !== undefined) {
@@ -8722,8 +8788,16 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
8722
8788
  response
8723
8789
  };
8724
8790
  } catch (error) {
8791
+ const failureReason = startInstanceFailureReason(startupStage, error);
8792
+ log("kandan.start_instance_failed", {
8793
+ stage: startupStage,
8794
+ message: error instanceof Error ? error.message : String(error),
8795
+ thread_id: optionalThreadControlField(control, "threadId") ?? null,
8796
+ source_seq: integerValue(control.sourceSeq) ?? integerValue(control.rootSeq) ?? null,
8797
+ codex_thread_id: startedCodexThreadId ?? null
8798
+ });
8725
8799
  try {
8726
- await publishStartInstanceMessageState(kandan, topic, control, "failed", "failed to start Codex session");
8800
+ await publishStartInstanceMessageState(kandan, topic, control, "failed", failureReason, { codexThreadId: startedCodexThreadId });
8727
8801
  } catch (_publishError) {}
8728
8802
  throw error;
8729
8803
  }
@@ -8835,6 +8909,26 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
8835
8909
  return { instanceId, controlType: control.type, skipped: true };
8836
8910
  }
8837
8911
  }
8912
+ function startInstanceFailureReason(stage, error) {
8913
+ const detail = truncateFailureDetail(error instanceof Error ? error.message : String(error));
8914
+ switch (stage) {
8915
+ case "checking_project_trust":
8916
+ return `Codex could not start because this folder is not trusted locally. Trust the folder with linzumi paths add, or restart the runner from a trusted folder. Detail: ${detail}`;
8917
+ case "starting_codex_session":
8918
+ return `Codex did not create a session. Restart the local runner and try again. Detail: ${detail}`;
8919
+ case "binding_kandan_thread":
8920
+ return `Codex started, but Linzumi could not attach it to this thread. Try starting the agent again; if it repeats, share this failed message with the runner owner. Detail: ${detail}`;
8921
+ case "starting_first_turn":
8922
+ return `Codex started, but the first prompt could not be delivered. Try sending the message again; if it repeats, reconnect the local runner. Detail: ${detail}`;
8923
+ }
8924
+ }
8925
+ function truncateFailureDetail(message) {
8926
+ const trimmed = message.trim();
8927
+ if (trimmed.length <= 240) {
8928
+ return trimmed;
8929
+ }
8930
+ return `${trimmed.slice(0, 237)}...`;
8931
+ }
8838
8932
  function commanderDeveloperInstructions(args) {
8839
8933
  const customPrompt = args.developerPrompt === undefined ? "" : `
8840
8934
  <invoker_developer_prompt>
@@ -8915,14 +9009,14 @@ ${developerPrompt}`,
8915
9009
  client_message_id: `codex-start-instructions-${threadId}`
8916
9010
  });
8917
9011
  }
8918
- async function publishStartInstanceMessageState(kandan, topic, control, status, reason) {
8919
- const payload = startInstanceMessageStatePayload(control, status, reason);
9012
+ async function publishStartInstanceMessageState(kandan, topic, control, status, reason, diagnostics = {}) {
9013
+ const payload = startInstanceMessageStatePayload(control, status, reason, diagnostics);
8920
9014
  if (payload === undefined) {
8921
9015
  return;
8922
9016
  }
8923
9017
  await kandan.push(topic, "message_state", payload);
8924
9018
  }
8925
- function startInstanceMessageStatePayload(control, status, reason) {
9019
+ function startInstanceMessageStatePayload(control, status, reason, diagnostics = {}) {
8926
9020
  const rootSeq = integerValue(control.rootSeq);
8927
9021
  const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
8928
9022
  if (sourceSeq === undefined) {
@@ -8937,7 +9031,8 @@ function startInstanceMessageStatePayload(control, status, reason) {
8937
9031
  thread_id: threadId,
8938
9032
  seq: sourceSeq,
8939
9033
  status,
8940
- reason
9034
+ reason,
9035
+ ...diagnostics.codexThreadId === undefined ? {} : { codex_thread_id: diagnostics.codexThreadId }
8941
9036
  };
8942
9037
  }
8943
9038
  function requiredStartInstanceControlField(control, field) {
@@ -10881,7 +10976,7 @@ async function main(args) {
10881
10976
  process.stdout.write(connectGuideText());
10882
10977
  return;
10883
10978
  case "version":
10884
- process.stdout.write(`linzumi 0.0.40-beta
10979
+ process.stdout.write(`linzumi 0.0.41-beta
10885
10980
  `);
10886
10981
  return;
10887
10982
  case "auth":
@@ -11407,7 +11502,7 @@ async function parseRunnerArgs(args, deps = {
11407
11502
  process.exit(0);
11408
11503
  }
11409
11504
  if (values.get("version") === true) {
11410
- process.stdout.write(`linzumi 0.0.40-beta
11505
+ process.stdout.write(`linzumi 0.0.41-beta
11411
11506
  `);
11412
11507
  process.exit(0);
11413
11508
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.40-beta",
3
+ "version": "0.0.41-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": {