@linzumi/cli 0.0.78-beta → 0.0.79-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 +679 -434
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3831,6 +3831,15 @@ async function attachChannelSession(args) {
3831
3831
  if (turnTerminalNotificationIsStale(args, state, turnId)) {
3832
3832
  break;
3833
3833
  }
3834
+ if (rememberPendingStartingTerminalFailure(
3835
+ args,
3836
+ state,
3837
+ threadId,
3838
+ turnId,
3839
+ abortReason(params)
3840
+ )) {
3841
+ break;
3842
+ }
3834
3843
  void failActiveCodexTurn(
3835
3844
  args,
3836
3845
  state,
@@ -3856,10 +3865,28 @@ async function attachChannelSession(args) {
3856
3865
  if (turnId !== void 0 && turnTerminalNotificationIsStale(args, state, turnId)) {
3857
3866
  break;
3858
3867
  }
3868
+ if (turnId !== void 0 && rememberPendingStartingTerminalCompletion(
3869
+ args,
3870
+ state,
3871
+ threadId,
3872
+ turnId
3873
+ )) {
3874
+ break;
3875
+ }
3859
3876
  const completedTurnId = turnId ?? inferThreadOnlyCompletedTurnId(args, state, threadId);
3860
3877
  if (completedTurnId !== void 0) {
3861
- enqueueWebSearchProgressCompletion(args, state, completedTurnId);
3862
- enqueueFileChangeCompletion(args, state, completedTurnId);
3878
+ enqueueWebSearchProgressCompletion(
3879
+ args,
3880
+ state,
3881
+ completedTurnId,
3882
+ payloadContext
3883
+ );
3884
+ enqueueFileChangeCompletion(
3885
+ args,
3886
+ state,
3887
+ completedTurnId,
3888
+ payloadContext
3889
+ );
3863
3890
  void forwardCompletedCodexTurn(
3864
3891
  args,
3865
3892
  state,
@@ -4040,11 +4067,14 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
4040
4067
  closed: false,
4041
4068
  minSeq: cursor,
4042
4069
  queue: createPendingKandanMessageQueue(),
4070
+ queueDrainRunning: false,
4071
+ queueDrainRequested: false,
4043
4072
  forwardedTurnIds: /* @__PURE__ */ new Set(),
4044
4073
  forwardingTurnIds: /* @__PURE__ */ new Set(),
4045
4074
  completionForwardingTurnIds: /* @__PURE__ */ new Set(),
4046
4075
  recoveredCompletingTurnIds: /* @__PURE__ */ new Set(),
4047
4076
  pendingInterruptedThreadOnlyCompletions: [],
4077
+ pendingStartingTerminalNotifications: [],
4048
4078
  retryableTurnIds: /* @__PURE__ */ new Set(),
4049
4079
  completedAssistantItemKeys: /* @__PURE__ */ new Set(),
4050
4080
  completedAssistantItemTurns: createBoundedCache(maxForwardedTurnIds),
@@ -5515,6 +5545,21 @@ async function processKandanChatEvent(args, state, runnerIdentity, event, payloa
5515
5545
  }
5516
5546
  }
5517
5547
  async function drainKandanMessageQueue(args, state, payloadContext) {
5548
+ if (state.queueDrainRunning) {
5549
+ state.queueDrainRequested = true;
5550
+ return;
5551
+ }
5552
+ state.queueDrainRunning = true;
5553
+ try {
5554
+ do {
5555
+ state.queueDrainRequested = false;
5556
+ await drainKandanMessageQueueOnce(args, state, payloadContext);
5557
+ } while (state.queueDrainRequested);
5558
+ } finally {
5559
+ state.queueDrainRunning = false;
5560
+ }
5561
+ }
5562
+ async function drainKandanMessageQueueOnce(args, state, payloadContext) {
5518
5563
  if (state.closed) {
5519
5564
  logQueuedDrainBlocked(args, state, "session_closed");
5520
5565
  return;
@@ -5587,6 +5632,22 @@ async function drainKandanMessageQueue(args, state, payloadContext) {
5587
5632
  state.turn = { status: "active", turnId, queuedSeq: next.seq };
5588
5633
  rememberTurnReplyTarget(state, turnId, next.seq);
5589
5634
  args.log("codex.turn_started", { turn_id: turnId });
5635
+ const pendingStartingTerminal = consumePendingStartingTerminalNotification(
5636
+ state,
5637
+ codexThreadId,
5638
+ turnId,
5639
+ next.seq
5640
+ );
5641
+ if (pendingStartingTerminal !== void 0) {
5642
+ await handlePendingStartingTerminalNotification(
5643
+ args,
5644
+ state,
5645
+ pendingStartingTerminal,
5646
+ turnId,
5647
+ payloadContext
5648
+ );
5649
+ return;
5650
+ }
5590
5651
  if (interruptAfterStart) {
5591
5652
  await recoverInterruptedCodexTurn(
5592
5653
  args,
@@ -5932,7 +5993,12 @@ async function forwardCompletedCodexTurn(args, state, turnId, payloadContext) {
5932
5993
  ).length;
5933
5994
  const completedForwardedAssistantSnapshotCount = forwardedAssistantSnapshotOutputCount(state, messages);
5934
5995
  const completedSourceSeqAssistantItemCount = sourceMessageSeq === void 0 ? 0 : completedAssistantItemForwardedForSourceSeq(state, sourceMessageSeq);
5935
- const completedStreamingAssistantOutputCount = completedAssistantOutputCount === 0 ? await completeStreamingAssistantOutputsForTurn(args, state, turnId) : 0;
5996
+ const completedStreamingAssistantOutputCount = completedAssistantOutputCount === 0 ? await completeStreamingAssistantOutputsForTurn(
5997
+ args,
5998
+ state,
5999
+ turnId,
6000
+ payloadContext
6001
+ ) : 0;
5936
6002
  const completedAssistantResponseSeen = completedAssistantOutputCount > 0 || completedForwardedAssistantSnapshotCount > 0 || completedSourceSeqAssistantItemCount > 0 || completedStreamingAssistantOutputCount > 0 || completedAssistantItemForwardedForTurn(state, turnId);
5937
6003
  for (const output of completedOutputs) {
5938
6004
  if (completingTurnWasRecovered(state, turnId)) {
@@ -6277,7 +6343,9 @@ function completedMessageSemanticKey(message) {
6277
6343
  function completedSnapshotOutputProjection(state, turnId, message, snapshotIndex) {
6278
6344
  const streamedStructured = resolveStreamingStructuredOutputForCompletedMessage(
6279
6345
  state,
6346
+ turnId,
6280
6347
  message.itemKey,
6348
+ message.body,
6281
6349
  message.structured
6282
6350
  );
6283
6351
  if (streamedStructured !== void 0) {
@@ -6291,6 +6359,8 @@ function completedSnapshotOutputProjection(state, turnId, message, snapshotIndex
6291
6359
  match: {
6292
6360
  kind: "structured",
6293
6361
  itemKey: message.itemKey,
6362
+ streamKey: streamedStructured.streamKey,
6363
+ streamState: streamedStructured.streamState,
6294
6364
  structured: message.structured,
6295
6365
  matchKey: structuredOutputMatchKey(structuredKind, message.itemKey)
6296
6366
  }
@@ -6325,7 +6395,11 @@ function completedSnapshotOutputProjection(state, turnId, message, snapshotIndex
6325
6395
  sortOrder: streamed.output.seq,
6326
6396
  snapshotIndex,
6327
6397
  message,
6328
- match: { kind: "assistant", itemKey: streamed.output.itemKey }
6398
+ match: {
6399
+ kind: "assistant",
6400
+ itemKey: streamed.output.itemKey,
6401
+ streamKey: streamed.output.streamKey
6402
+ }
6329
6403
  }
6330
6404
  ];
6331
6405
  case "ambiguous":
@@ -6339,7 +6413,7 @@ async function postCompletedOutputProjection(args, state, payloadContext, params
6339
6413
  case "snapshot": {
6340
6414
  switch (params.output.match.kind) {
6341
6415
  case "none":
6342
- await streamCompletedCodexOutput(args, state, payloadContext, {
6416
+ await (stringValue(params.output.message.structured.kind) === "codex_assistant_message" ? postCompletedCodexOutput : streamCompletedCodexOutput)(args, state, payloadContext, {
6343
6417
  turnId: params.turnId,
6344
6418
  sourceMessageSeq: params.sourceMessageSeq,
6345
6419
  rootSeq: params.rootSeq,
@@ -6347,24 +6421,37 @@ async function postCompletedOutputProjection(args, state, payloadContext, params
6347
6421
  });
6348
6422
  break;
6349
6423
  case "assistant":
6350
- await editStreamedCodexOutput(
6351
- args,
6352
- state,
6353
- params.output.sortOrder,
6354
- params.output.message.itemKey,
6355
- params.output.message.body,
6356
- "completed"
6357
- );
6424
+ await postCompletedCodexOutput(args, state, payloadContext, {
6425
+ turnId: params.turnId,
6426
+ sourceMessageSeq: params.sourceMessageSeq,
6427
+ rootSeq: params.rootSeq,
6428
+ message: params.output.message
6429
+ });
6430
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6431
+ turnId: params.turnId,
6432
+ sourceMessageSeq: params.sourceMessageSeq,
6433
+ rootSeq: params.rootSeq,
6434
+ streamKey: params.output.match.streamKey,
6435
+ body: params.output.message.body,
6436
+ structured: codexAssistantStructuredMessage(
6437
+ params.output.match.itemKey,
6438
+ params.output.message.body,
6439
+ "completed"
6440
+ )
6441
+ });
6358
6442
  forgetStreamingAssistantOutput(state, params.output.match.itemKey);
6359
6443
  break;
6360
6444
  case "structured":
6361
- await editCodexStructuredOutput(
6362
- args,
6363
- state,
6364
- params.output.sortOrder,
6365
- params.output.message.body,
6366
- params.output.message.structured
6367
- );
6445
+ if (params.output.match.streamState === "streaming") {
6446
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6447
+ turnId: params.turnId,
6448
+ sourceMessageSeq: params.sourceMessageSeq,
6449
+ rootSeq: params.rootSeq,
6450
+ streamKey: params.output.match.streamKey,
6451
+ body: params.output.message.body,
6452
+ structured: params.output.message.structured
6453
+ });
6454
+ }
6368
6455
  forgetStreamingStructuredOutput(
6369
6456
  state,
6370
6457
  params.output.match.itemKey,
@@ -6391,13 +6478,14 @@ async function postCompletedOutputProjection(args, state, payloadContext, params
6391
6478
  "completed"
6392
6479
  )
6393
6480
  };
6394
- await editCodexStructuredOutput(
6395
- args,
6396
- state,
6397
- output.seq,
6398
- message.body,
6399
- message.structured
6400
- );
6481
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6482
+ turnId: params.turnId,
6483
+ sourceMessageSeq: params.sourceMessageSeq,
6484
+ rootSeq: params.rootSeq,
6485
+ streamKey: output.streamKey,
6486
+ body: message.body,
6487
+ structured: message.structured
6488
+ });
6401
6489
  forgetStreamingReasoningOutput(state, output.itemKey);
6402
6490
  logCompletedCodexOutput(args, state, params.turnId, message);
6403
6491
  break;
@@ -6418,13 +6506,14 @@ async function postCompletedOutputProjection(args, state, payloadContext, params
6418
6506
  "completed"
6419
6507
  )
6420
6508
  };
6421
- await editCodexStructuredOutput(
6422
- args,
6423
- state,
6424
- output.seq,
6425
- message.body,
6426
- message.structured
6427
- );
6509
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6510
+ turnId: params.turnId,
6511
+ sourceMessageSeq: params.sourceMessageSeq,
6512
+ rootSeq: params.rootSeq,
6513
+ streamKey: output.streamKey,
6514
+ body: message.body,
6515
+ structured: message.structured
6516
+ });
6428
6517
  forgetStreamingCommandOutput(state, output.itemKey);
6429
6518
  logCompletedCodexOutput(args, state, params.turnId, message);
6430
6519
  break;
@@ -6441,13 +6530,16 @@ async function postCompletedOutputProjection(args, state, payloadContext, params
6441
6530
  "completed"
6442
6531
  )
6443
6532
  };
6444
- await editCodexStructuredOutput(
6445
- args,
6446
- state,
6447
- output.seq,
6448
- message.body,
6449
- message.structured
6450
- );
6533
+ if (output.streamState === "streaming") {
6534
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6535
+ turnId: params.turnId,
6536
+ sourceMessageSeq: params.sourceMessageSeq,
6537
+ rootSeq: params.rootSeq,
6538
+ streamKey: output.streamKey,
6539
+ body: message.body,
6540
+ structured: message.structured
6541
+ });
6542
+ }
6451
6543
  forgetStreamingFileChangeOutput(state, output.itemKey);
6452
6544
  logCompletedCodexOutput(args, state, params.turnId, message);
6453
6545
  break;
@@ -6526,11 +6618,12 @@ async function forwardCompletedAssistantItem(args, state, params, payloadContext
6526
6618
  message.body,
6527
6619
  structured
6528
6620
  );
6621
+ const completedSourceMessageSeq = streamed.status === "matched" ? sourceMessageSeq ?? streamed.output.sourceMessageSeq : sourceMessageSeq;
6529
6622
  switch (streamed.status) {
6530
6623
  case "none":
6531
- await streamCompletedCodexOutput(args, state, payloadContext, {
6624
+ await postCompletedCodexOutput(args, state, payloadContext, {
6532
6625
  turnId: turnId ?? `item:${message.itemKey}`,
6533
- sourceMessageSeq,
6626
+ sourceMessageSeq: completedSourceMessageSeq,
6534
6627
  rootSeq: state.rootSeq,
6535
6628
  message: {
6536
6629
  ...message,
@@ -6539,14 +6632,23 @@ async function forwardCompletedAssistantItem(args, state, params, payloadContext
6539
6632
  });
6540
6633
  break;
6541
6634
  case "matched":
6542
- await editStreamedCodexOutput(
6543
- args,
6544
- state,
6545
- streamed.output.seq,
6546
- message.itemKey,
6547
- message.body,
6548
- "completed"
6549
- );
6635
+ await postCompletedCodexOutput(args, state, payloadContext, {
6636
+ turnId: turnId ?? `item:${message.itemKey}`,
6637
+ sourceMessageSeq: completedSourceMessageSeq,
6638
+ rootSeq: state.rootSeq,
6639
+ message: {
6640
+ ...message,
6641
+ structured
6642
+ }
6643
+ });
6644
+ await streamCodexStructuredOutput(args, state, payloadContext, {
6645
+ turnId: messageTurnId ?? `item:${message.itemKey}`,
6646
+ sourceMessageSeq: completedSourceMessageSeq,
6647
+ rootSeq: state.rootSeq,
6648
+ streamKey: streamed.output.streamKey,
6649
+ body: message.body,
6650
+ structured
6651
+ });
6550
6652
  forgetStreamingAssistantOutput(state, streamed.output.itemKey);
6551
6653
  break;
6552
6654
  case "ambiguous":
@@ -6558,7 +6660,7 @@ async function forwardCompletedAssistantItem(args, state, params, payloadContext
6558
6660
  state,
6559
6661
  message.itemKey,
6560
6662
  messageTurnId,
6561
- sourceMessageSeq
6663
+ completedSourceMessageSeq
6562
6664
  );
6563
6665
  logCompletedCodexOutput(
6564
6666
  args,
@@ -6652,58 +6754,26 @@ async function forwardAssistantDeltaPayload(args, state, delta, payloadContext)
6652
6754
  if (existing === void 0 && nextContent.trim() === "") {
6653
6755
  return;
6654
6756
  }
6655
- if (existing === void 0) {
6656
- const session = args.options.channelSession;
6657
- const reply = await pushOk2(
6658
- args.kandan,
6659
- args.topic,
6660
- "session:post_thread_message",
6661
- {
6662
- workspace: session.workspaceSlug,
6663
- channel: session.channelSlug,
6664
- thread_id: state.kandanThreadId,
6665
- body: nextContent,
6666
- payload: {
6667
- ...localRunnerPayload(
6668
- args.options,
6669
- args.instanceId,
6670
- "codex_output",
6671
- state.codexThreadId,
6672
- payloadContext,
6673
- sourceMessageSeq
6674
- ),
6675
- ...rootSeq === void 0 ? {} : { reply_to_seq: rootSeq },
6676
- structured: codexAssistantStructuredMessage(
6677
- delta.itemKey,
6678
- nextContent,
6679
- "streaming"
6680
- )
6681
- },
6682
- client_message_id: streamingClientMessageId(args.instanceId, delta)
6683
- }
6684
- );
6685
- const seq = integerValue(reply.seq);
6686
- if (seq !== void 0) {
6687
- rememberStreamingAssistantOutput(state, {
6688
- itemKey: delta.itemKey,
6689
- turnId: delta.turnId,
6690
- sourceMessageSeq,
6691
- seq,
6692
- content: nextContent
6693
- });
6694
- }
6695
- } else {
6696
- await editStreamedCodexOutput(
6697
- args,
6698
- state,
6699
- existing.seq,
6757
+ const streamKey = existing?.streamKey ?? streamingClientMessageId(args.instanceId, delta);
6758
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
6759
+ turnId: delta.turnId,
6760
+ sourceMessageSeq,
6761
+ rootSeq,
6762
+ streamKey,
6763
+ body: nextContent,
6764
+ structured: codexAssistantStructuredMessage(
6700
6765
  delta.itemKey,
6701
6766
  nextContent,
6702
6767
  "streaming"
6703
- );
6768
+ )
6769
+ }) ?? existing?.seq;
6770
+ if (seq !== void 0) {
6704
6771
  rememberStreamingAssistantOutput(state, {
6705
- ...existing,
6706
- sourceMessageSeq: existing.sourceMessageSeq ?? sourceMessageSeq,
6772
+ itemKey: delta.itemKey,
6773
+ turnId: existing?.turnId ?? delta.turnId,
6774
+ sourceMessageSeq: existing?.sourceMessageSeq ?? sourceMessageSeq,
6775
+ seq,
6776
+ streamKey,
6707
6777
  content: nextContent
6708
6778
  });
6709
6779
  }
@@ -6839,9 +6909,9 @@ function enqueueWebSearchProgress(args, state, params, payloadContext) {
6839
6909
  });
6840
6910
  });
6841
6911
  }
6842
- function enqueueWebSearchProgressCompletion(args, state, turnId) {
6912
+ function enqueueWebSearchProgressCompletion(args, state, turnId, payloadContext) {
6843
6913
  const previous = state.webSearchProgressForwardChain;
6844
- const next = previous.catch(() => void 0).then(() => completeWebSearchProgress(args, state, turnId));
6914
+ const next = previous.catch(() => void 0).then(() => completeWebSearchProgress(args, state, turnId, payloadContext));
6845
6915
  state.webSearchProgressForwardChain = next.catch((error) => {
6846
6916
  args.log("codex.web_search_progress_completion_failed", {
6847
6917
  turn_id: turnId,
@@ -6849,9 +6919,9 @@ function enqueueWebSearchProgressCompletion(args, state, turnId) {
6849
6919
  });
6850
6920
  });
6851
6921
  }
6852
- function enqueueFileChangeCompletion(args, state, turnId) {
6922
+ function enqueueFileChangeCompletion(args, state, turnId, payloadContext) {
6853
6923
  const previous = state.fileChangeQueue.chain;
6854
- const next = previous.catch(() => void 0).then(() => completeFileChangeOutputs(args, state, turnId));
6924
+ const next = previous.catch(() => void 0).then(() => completeFileChangeOutputs(args, state, turnId, payloadContext));
6855
6925
  state.fileChangeQueue.chain = next.catch((error) => {
6856
6926
  args.log("codex.file_change_completion_failed", {
6857
6927
  turn_id: turnId,
@@ -6901,58 +6971,28 @@ async function forwardReasoningDeltaPayload(args, state, delta, payloadContext)
6901
6971
  if (existing === void 0 && nextContent.trim() === "") {
6902
6972
  return;
6903
6973
  }
6904
- if (existing === void 0) {
6905
- const session = args.options.channelSession;
6906
- const reply = await pushOk2(
6907
- args.kandan,
6908
- args.topic,
6909
- "session:post_thread_message",
6910
- {
6911
- workspace: session.workspaceSlug,
6912
- channel: session.channelSlug,
6913
- thread_id: state.kandanThreadId,
6914
- body: nextContent,
6915
- payload: {
6916
- ...localRunnerPayload(
6917
- args.options,
6918
- args.instanceId,
6919
- "codex_output",
6920
- state.codexThreadId,
6921
- payloadContext,
6922
- sourceMessageSeq
6923
- ),
6924
- ...state.rootSeq === void 0 ? {} : { reply_to_seq: state.rootSeq },
6925
- structured: codexReasoningStructuredMessage(
6926
- delta.itemKey,
6927
- nextContent,
6928
- "streaming"
6929
- )
6930
- },
6931
- client_message_id: streamingClientMessageId(args.instanceId, {
6932
- itemKey: `reasoning:${delta.itemKey}`,
6933
- turnId
6934
- })
6935
- }
6936
- );
6937
- const seq = integerValue(reply.seq);
6938
- if (seq !== void 0) {
6939
- rememberStreamingReasoningOutput(state, {
6940
- itemKey: delta.itemKey,
6941
- turnId,
6942
- seq,
6943
- content: nextContent
6944
- });
6945
- }
6946
- } else {
6947
- await editCodexStructuredOutput(
6948
- args,
6949
- state,
6950
- existing.seq,
6974
+ const streamKey = existing?.streamKey ?? streamingClientMessageId(args.instanceId, {
6975
+ itemKey: `reasoning:${delta.itemKey}`,
6976
+ turnId
6977
+ });
6978
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
6979
+ turnId,
6980
+ sourceMessageSeq,
6981
+ rootSeq: state.rootSeq,
6982
+ streamKey,
6983
+ body: nextContent,
6984
+ structured: codexReasoningStructuredMessage(
6985
+ delta.itemKey,
6951
6986
  nextContent,
6952
- codexReasoningStructuredMessage(delta.itemKey, nextContent, "streaming")
6953
- );
6987
+ "streaming"
6988
+ )
6989
+ }) ?? existing?.seq;
6990
+ if (seq !== void 0) {
6954
6991
  rememberStreamingReasoningOutput(state, {
6955
- ...existing,
6992
+ itemKey: delta.itemKey,
6993
+ turnId: existing?.turnId ?? turnId,
6994
+ seq,
6995
+ streamKey,
6956
6996
  content: nextContent
6957
6997
  });
6958
6998
  }
@@ -7089,60 +7129,27 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
7089
7129
  persisted_length: persistedOutput.length
7090
7130
  });
7091
7131
  }
7092
- if (existing === void 0) {
7093
- const session = args.options.channelSession;
7094
- const reply = await pushOk2(
7095
- args.kandan,
7096
- args.topic,
7097
- "session:post_thread_message",
7098
- {
7099
- workspace: session.workspaceSlug,
7100
- channel: session.channelSlug,
7101
- thread_id: state.kandanThreadId,
7102
- body,
7103
- payload: {
7104
- ...localRunnerPayload(
7105
- args.options,
7106
- args.instanceId,
7107
- "codex_output",
7108
- state.codexThreadId,
7109
- payloadContext,
7110
- sourceMessageSeq
7111
- ),
7112
- ...state.rootSeq === void 0 ? {} : { reply_to_seq: state.rootSeq },
7113
- structured
7114
- },
7115
- client_message_id: streamingClientMessageId(args.instanceId, {
7116
- itemKey: `command:${delta.itemKey}`,
7117
- turnId
7118
- })
7119
- }
7120
- );
7121
- const seq = integerValue(reply.seq);
7122
- if (seq !== void 0) {
7123
- rememberStreamingCommandOutput(state, {
7124
- itemKey: delta.itemKey,
7125
- turnId,
7126
- seq,
7127
- command,
7128
- output: persistedOutput,
7129
- processId: delta.processId ?? pending?.processId,
7130
- stream: delta.stream
7131
- });
7132
- }
7133
- } else {
7134
- await editCodexStructuredOutput(
7135
- args,
7136
- state,
7137
- existing.seq,
7138
- body,
7139
- structured
7140
- );
7132
+ const streamKey = existing?.streamKey ?? streamingClientMessageId(args.instanceId, {
7133
+ itemKey: `command:${delta.itemKey}`,
7134
+ turnId
7135
+ });
7136
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
7137
+ turnId,
7138
+ sourceMessageSeq,
7139
+ rootSeq: state.rootSeq,
7140
+ streamKey,
7141
+ body,
7142
+ structured
7143
+ }) ?? existing?.seq;
7144
+ if (seq !== void 0) {
7141
7145
  rememberStreamingCommandOutput(state, {
7142
- ...existing,
7146
+ itemKey: delta.itemKey,
7147
+ turnId: existing?.turnId ?? turnId,
7148
+ seq,
7149
+ streamKey,
7143
7150
  command,
7144
7151
  output: persistedOutput,
7145
- processId: delta.processId ?? existing.processId,
7152
+ processId: delta.processId ?? existing?.processId ?? pending?.processId,
7146
7153
  stream: delta.stream
7147
7154
  });
7148
7155
  }
@@ -7180,55 +7187,26 @@ async function forwardFileChangeDeltaPayload(args, state, delta, payloadContext)
7180
7187
  "streaming",
7181
7188
  "started"
7182
7189
  );
7183
- if (existing === void 0) {
7184
- const session = args.options.channelSession;
7185
- const reply = await pushOk2(
7186
- args.kandan,
7187
- args.topic,
7188
- "session:post_thread_message",
7189
- {
7190
- workspace: session.workspaceSlug,
7191
- channel: session.channelSlug,
7192
- thread_id: state.kandanThreadId,
7193
- body: patchText,
7194
- payload: {
7195
- ...localRunnerPayload(
7196
- args.options,
7197
- args.instanceId,
7198
- "codex_output",
7199
- state.codexThreadId,
7200
- payloadContext,
7201
- sourceMessageSeq
7202
- ),
7203
- ...state.rootSeq === void 0 ? {} : { reply_to_seq: state.rootSeq },
7204
- structured
7205
- },
7206
- client_message_id: streamingClientMessageId(args.instanceId, {
7207
- itemKey: `file-change:${delta.itemKey}`,
7208
- turnId
7209
- })
7210
- }
7211
- );
7212
- const seq = integerValue(reply.seq);
7213
- if (seq !== void 0) {
7214
- rememberStreamingFileChangeOutput(state, {
7215
- itemKey: delta.itemKey,
7216
- turnId,
7217
- seq,
7218
- patchText
7219
- });
7220
- }
7221
- } else {
7222
- await editCodexStructuredOutput(
7223
- args,
7224
- state,
7225
- existing.seq,
7226
- patchText,
7227
- structured
7228
- );
7190
+ const streamKey = existing?.streamKey ?? streamingClientMessageId(args.instanceId, {
7191
+ itemKey: `file-change:${delta.itemKey}`,
7192
+ turnId
7193
+ });
7194
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
7195
+ turnId,
7196
+ sourceMessageSeq,
7197
+ rootSeq: state.rootSeq,
7198
+ streamKey,
7199
+ body: patchText,
7200
+ structured
7201
+ }) ?? existing?.seq;
7202
+ if (seq !== void 0) {
7229
7203
  rememberStreamingFileChangeOutput(state, {
7230
- ...existing,
7231
- patchText
7204
+ itemKey: delta.itemKey,
7205
+ turnId: existing?.turnId ?? turnId,
7206
+ seq,
7207
+ streamKey,
7208
+ patchText,
7209
+ streamState: "streaming"
7232
7210
  });
7233
7211
  }
7234
7212
  args.log("kandan.codex_file_change_forwarded", {
@@ -7262,56 +7240,34 @@ async function forwardCompletedFileChangePayload(args, state, delta, payloadCont
7262
7240
  "completed"
7263
7241
  );
7264
7242
  const existing = findStreamingFileChangeOutput(state, delta.itemKey);
7265
- if (existing === void 0) {
7266
- const session = args.options.channelSession;
7267
- const reply = await pushOk2(
7268
- args.kandan,
7269
- args.topic,
7270
- "session:post_thread_message",
7271
- {
7272
- workspace: session.workspaceSlug,
7273
- channel: session.channelSlug,
7274
- thread_id: state.kandanThreadId,
7275
- body: delta.patchText,
7276
- payload: {
7277
- ...localRunnerPayload(
7278
- args.options,
7279
- args.instanceId,
7280
- "codex_output",
7281
- state.codexThreadId,
7282
- payloadContext,
7283
- sourceMessageSeq
7284
- ),
7285
- ...state.rootSeq === void 0 ? {} : { reply_to_seq: state.rootSeq },
7286
- structured
7287
- },
7288
- client_message_id: streamingClientMessageId(args.instanceId, {
7289
- itemKey: `file-change:${delta.itemKey}`,
7290
- turnId
7291
- })
7292
- }
7293
- );
7294
- const seq = integerValue(reply.seq);
7295
- if (seq !== void 0) {
7296
- rememberStreamingFileChangeOutput(state, {
7297
- itemKey: delta.itemKey,
7298
- turnId,
7299
- seq,
7300
- patchText: delta.patchText
7301
- });
7243
+ if (existing?.streamState === "completed") {
7244
+ if (normalizedTranscriptText(existing.patchText) !== normalizedTranscriptText(delta.patchText)) {
7245
+ throw new LogicalProjectionError(
7246
+ `Cannot complete file-change item ${delta.itemKey} twice with different patch text`
7247
+ );
7302
7248
  }
7303
- } else {
7304
- await editCodexStructuredOutput(
7305
- args,
7306
- state,
7307
- existing.seq,
7308
- delta.patchText,
7309
- structured
7310
- );
7249
+ return;
7250
+ }
7251
+ const streamKey = existing?.streamKey ?? streamingClientMessageId(args.instanceId, {
7252
+ itemKey: `file-change:${delta.itemKey}`,
7253
+ turnId
7254
+ });
7255
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
7256
+ turnId,
7257
+ sourceMessageSeq,
7258
+ rootSeq: state.rootSeq,
7259
+ streamKey,
7260
+ body: delta.patchText,
7261
+ structured
7262
+ }) ?? existing?.seq;
7263
+ if (seq !== void 0) {
7311
7264
  rememberStreamingFileChangeOutput(state, {
7312
- ...existing,
7313
- turnId: existing.turnId ?? turnId,
7314
- patchText: delta.patchText
7265
+ itemKey: delta.itemKey,
7266
+ turnId: existing?.turnId ?? turnId,
7267
+ seq,
7268
+ streamKey,
7269
+ patchText: delta.patchText,
7270
+ streamState: "completed"
7315
7271
  });
7316
7272
  }
7317
7273
  args.log("kandan.codex_file_change_forwarded", {
@@ -7409,54 +7365,21 @@ async function forwardWebSearchProgress(args, state, params, payloadContext) {
7409
7365
  queries,
7410
7366
  "streaming"
7411
7367
  );
7412
- if (existing === void 0) {
7413
- const session = args.options.channelSession;
7414
- const reply = await pushOk2(
7415
- args.kandan,
7416
- args.topic,
7417
- "session:post_thread_message",
7418
- {
7419
- workspace: session.workspaceSlug,
7420
- channel: session.channelSlug,
7421
- thread_id: state.kandanThreadId,
7422
- body,
7423
- payload: {
7424
- ...localRunnerPayload(
7425
- args.options,
7426
- args.instanceId,
7427
- "codex_output",
7428
- state.codexThreadId,
7429
- payloadContext,
7430
- sourceMessageSeq
7431
- ),
7432
- ...state.rootSeq === void 0 ? {} : { reply_to_seq: state.rootSeq },
7433
- structured
7434
- },
7435
- client_message_id: webSearchProgressClientMessageId(
7436
- args.instanceId,
7437
- progress.turnId
7438
- )
7439
- }
7440
- );
7441
- const seq = integerValue(reply.seq);
7442
- if (seq !== void 0) {
7443
- rememberWebSearchProgressOutput(state, {
7444
- turnId: progress.turnId,
7445
- itemKey,
7446
- seq,
7447
- queries
7448
- });
7449
- }
7450
- } else if (queries.length !== existing.queries.length) {
7451
- await editCodexStructuredOutput(
7452
- args,
7453
- state,
7454
- existing.seq,
7455
- body,
7456
- structured
7457
- );
7368
+ const streamKey = existing?.streamKey ?? webSearchProgressClientMessageId(args.instanceId, progress.turnId);
7369
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
7370
+ turnId: progress.turnId,
7371
+ sourceMessageSeq,
7372
+ rootSeq: state.rootSeq,
7373
+ streamKey,
7374
+ body,
7375
+ structured
7376
+ }) ?? existing?.seq;
7377
+ if (seq !== void 0) {
7458
7378
  rememberWebSearchProgressOutput(state, {
7459
- ...existing,
7379
+ turnId: progress.turnId,
7380
+ itemKey,
7381
+ seq,
7382
+ streamKey,
7460
7383
  queries
7461
7384
  });
7462
7385
  }
@@ -7468,41 +7391,49 @@ async function forwardWebSearchProgress(args, state, params, payloadContext) {
7468
7391
  query_count: queries.length
7469
7392
  });
7470
7393
  }
7471
- async function completeWebSearchProgress(args, state, turnId) {
7394
+ async function completeWebSearchProgress(args, state, turnId, payloadContext) {
7472
7395
  const existing = findWebSearchProgressOutput(state, turnId);
7473
7396
  if (existing === void 0) {
7474
7397
  return;
7475
7398
  }
7476
- await editCodexStructuredOutput(
7477
- args,
7478
- state,
7479
- existing.seq,
7480
- webSearchProgressBody(existing.queries),
7481
- codexWebSearchStructuredMessage(
7399
+ await streamCodexStructuredOutput(args, state, payloadContext, {
7400
+ turnId,
7401
+ sourceMessageSeq: sourceMessageSeqForTurn(state, turnId),
7402
+ rootSeq: state.rootSeq,
7403
+ streamKey: existing.streamKey,
7404
+ body: webSearchProgressBody(existing.queries),
7405
+ structured: codexWebSearchStructuredMessage(
7482
7406
  existing.itemKey,
7483
7407
  existing.queries,
7484
7408
  "completed"
7485
7409
  )
7486
- );
7410
+ });
7487
7411
  forgetWebSearchProgressOutput(state, turnId);
7488
7412
  }
7489
- async function completeFileChangeOutputs(args, state, turnId) {
7413
+ async function completeFileChangeOutputs(args, state, turnId, payloadContext) {
7490
7414
  const outputs = boundedCacheValues(state.streamingFileChangeOutputs).filter(
7491
- (output) => output.turnId === turnId
7415
+ (output) => output.turnId === turnId && output.streamState === "streaming"
7492
7416
  );
7417
+ const sourceMessageSeq = sourceMessageSeqForTurn(state, turnId);
7493
7418
  for (const output of outputs) {
7494
- await editCodexStructuredOutput(
7495
- args,
7496
- state,
7497
- output.seq,
7498
- output.patchText,
7499
- codexFileChangeStructuredMessage(
7419
+ const seq = await streamCodexStructuredOutput(args, state, payloadContext, {
7420
+ turnId,
7421
+ sourceMessageSeq,
7422
+ rootSeq: state.rootSeq,
7423
+ streamKey: output.streamKey,
7424
+ body: output.patchText,
7425
+ structured: codexFileChangeStructuredMessage(
7500
7426
  output.itemKey,
7501
7427
  output.patchText,
7502
7428
  "completed",
7503
7429
  "completed"
7504
7430
  )
7505
- );
7431
+ }) ?? output.seq;
7432
+ rememberStreamingFileChangeOutput(state, {
7433
+ ...output,
7434
+ seq,
7435
+ streamState: "completed"
7436
+ });
7506
7437
  }
7507
7438
  }
7508
7439
  async function completeInterruptedAssistantOutputs(args, state, turnId, payloadContext) {
@@ -7511,19 +7442,23 @@ async function completeInterruptedAssistantOutputs(args, state, turnId, payloadC
7511
7442
  (output) => output.turnId === turnId && bodyWithoutKandanAttachmentFooter(output.content) !== output.content
7512
7443
  );
7513
7444
  for (const output of outputs) {
7514
- await editStreamedCodexOutput(
7515
- args,
7516
- state,
7517
- output.seq,
7518
- output.itemKey,
7519
- output.content,
7520
- "completed"
7521
- );
7445
+ await streamCodexStructuredOutput(args, state, payloadContext, {
7446
+ turnId,
7447
+ sourceMessageSeq: output.sourceMessageSeq ?? sourceMessageSeqForTurn(state, turnId),
7448
+ rootSeq: state.rootSeq,
7449
+ streamKey: output.streamKey,
7450
+ body: output.content,
7451
+ structured: codexAssistantStructuredMessage(
7452
+ output.itemKey,
7453
+ output.content,
7454
+ "completed"
7455
+ )
7456
+ });
7522
7457
  forgetStreamingAssistantOutput(state, output.itemKey);
7523
7458
  rememberCompletedAssistantItemKey(state, output.itemKey, turnId);
7524
7459
  }
7525
7460
  }
7526
- async function completeStreamingAssistantOutputsForTurn(args, state, turnId) {
7461
+ async function completeStreamingAssistantOutputsForTurn(args, state, turnId, payloadContext) {
7527
7462
  const sourceMessageSeq = sourceMessageSeqForTurn(state, turnId);
7528
7463
  const outputs = boundedCacheValues(state.streamingAssistantOutputs).filter(
7529
7464
  (output) => output.turnId === turnId || sourceMessageSeq !== void 0 && output.sourceMessageSeq === sourceMessageSeq
@@ -7538,14 +7473,24 @@ async function completeStreamingAssistantOutputsForTurn(args, state, turnId) {
7538
7473
  "completed"
7539
7474
  )
7540
7475
  };
7541
- await editStreamedCodexOutput(
7542
- args,
7543
- state,
7544
- output.seq,
7545
- output.itemKey,
7546
- output.content,
7547
- "completed"
7548
- );
7476
+ await postCompletedCodexOutput(args, state, payloadContext, {
7477
+ turnId,
7478
+ sourceMessageSeq: output.sourceMessageSeq ?? sourceMessageSeq,
7479
+ rootSeq: state.rootSeq,
7480
+ message
7481
+ });
7482
+ await streamCodexStructuredOutput(args, state, payloadContext, {
7483
+ turnId,
7484
+ sourceMessageSeq: output.sourceMessageSeq ?? sourceMessageSeq,
7485
+ rootSeq: state.rootSeq,
7486
+ streamKey: output.streamKey,
7487
+ body: output.content,
7488
+ structured: codexAssistantStructuredMessage(
7489
+ output.itemKey,
7490
+ output.content,
7491
+ "completed"
7492
+ )
7493
+ });
7549
7494
  forgetStreamingAssistantOutput(state, output.itemKey);
7550
7495
  rememberCompletedAssistantItemKey(
7551
7496
  state,
@@ -7568,7 +7513,7 @@ function webSearchProgressClientMessageId(instanceId, turnId) {
7568
7513
  function webSearchProgressItemKey(turnId) {
7569
7514
  return `web-search:${turnId}`;
7570
7515
  }
7571
- async function streamCompletedCodexOutput(args, state, payloadContext, params) {
7516
+ async function postCompletedCodexOutput(args, state, payloadContext, params) {
7572
7517
  if (state.kandanThreadId === void 0 || state.codexThreadId === void 0) {
7573
7518
  return;
7574
7519
  }
@@ -7607,11 +7552,10 @@ async function streamCompletedCodexOutput(args, state, payloadContext, params) {
7607
7552
  structured,
7608
7553
  displayBody
7609
7554
  );
7610
- await pushOk2(args.kandan, args.topic, "session:stream_thread_message", {
7555
+ await pushOk2(args.kandan, args.topic, "session:post_thread_message", {
7611
7556
  workspace: session.workspaceSlug,
7612
7557
  channel: session.channelSlug,
7613
7558
  thread_id: state.kandanThreadId,
7614
- stream_key: streamKey,
7615
7559
  body: displayBody,
7616
7560
  payload: {
7617
7561
  ...localRunnerPayload(
@@ -7632,24 +7576,24 @@ async function streamCompletedCodexOutput(args, state, payloadContext, params) {
7632
7576
  client_message_id: streamKey
7633
7577
  });
7634
7578
  }
7635
- function structuredWithStreamKey(structured, streamKey) {
7636
- return streamKey === void 0 ? structured : { ...structured, stream_key: streamKey };
7637
- }
7638
- async function editStreamedCodexOutput(args, state, targetSeq, itemKey, content, streamState) {
7639
- await editCodexStructuredOutput(
7640
- args,
7641
- state,
7642
- targetSeq,
7643
- content,
7644
- codexAssistantStructuredMessage(itemKey, content, streamState)
7645
- );
7646
- }
7647
- async function editCodexStructuredOutput(args, state, targetSeq, content, structured) {
7648
- if (state.kandanThreadId === void 0) {
7579
+ async function streamCompletedCodexOutput(args, state, payloadContext, params) {
7580
+ if (state.kandanThreadId === void 0 || state.codexThreadId === void 0) {
7649
7581
  return;
7650
7582
  }
7651
7583
  const session = args.options.channelSession;
7652
- const uploadedFileIds = stringValue(structured.stream_state) === "completed" ? await uploadedFileIdsForCommanderOutput(
7584
+ const streamKey = streamingClientMessageId(args.instanceId, {
7585
+ itemKey: params.message.itemKey,
7586
+ turnId: params.turnId
7587
+ });
7588
+ const streamMetadata = {
7589
+ turn_id: params.turnId,
7590
+ stream_key: streamKey
7591
+ };
7592
+ const structured = structuredWithStreamKey(
7593
+ params.message.structured,
7594
+ streamKey
7595
+ );
7596
+ const uploadedFileIds = await uploadedFileIdsForCommanderOutput(
7653
7597
  {
7654
7598
  kandan: args.kandan,
7655
7599
  topic: args.topic,
@@ -7660,27 +7604,111 @@ async function editCodexStructuredOutput(args, state, targetSeq, content, struct
7660
7604
  token: args.options.token,
7661
7605
  fetch: args.options.fetch
7662
7606
  },
7663
- content,
7607
+ params.message.body,
7664
7608
  structured
7665
- ) : [];
7666
- const displayContent = displayBodyForCommanderOutput(
7667
- content,
7609
+ );
7610
+ const displayBody = displayBodyForCommanderOutput(
7611
+ params.message.body,
7668
7612
  uploadedFileIds
7669
7613
  );
7670
7614
  const displayStructured = structuredWithAssistantContent(
7671
7615
  structured,
7672
- displayContent
7616
+ displayBody
7673
7617
  );
7674
- await pushOk2(args.kandan, args.topic, "session:edit_thread_message", {
7618
+ await pushOk2(args.kandan, args.topic, "session:stream_thread_message", {
7675
7619
  workspace: session.workspaceSlug,
7676
7620
  channel: session.channelSlug,
7677
7621
  thread_id: state.kandanThreadId,
7678
- target_seq: targetSeq,
7679
- body: displayContent,
7680
- structured: displayStructured,
7681
- ...uploadedFileIds.length === 0 ? {} : { uploaded_file_ids: uploadedFileIds }
7622
+ stream_key: streamKey,
7623
+ body: displayBody,
7624
+ payload: {
7625
+ ...localRunnerPayload(
7626
+ args.options,
7627
+ args.instanceId,
7628
+ "codex_output",
7629
+ state.codexThreadId,
7630
+ payloadContext,
7631
+ params.sourceMessageSeq,
7632
+ streamMetadata
7633
+ ),
7634
+ ...params.rootSeq === void 0 ? {} : { reply_to_seq: params.rootSeq },
7635
+ ...params.sourceMessageSeq === void 0 ? {} : { source_message_seq: params.sourceMessageSeq },
7636
+ ...streamMetadata,
7637
+ structured: displayStructured
7638
+ },
7639
+ ...uploadedFileIds.length === 0 ? {} : { uploaded_file_ids: uploadedFileIds },
7640
+ client_message_id: streamKey
7682
7641
  });
7683
7642
  }
7643
+ async function streamCodexStructuredOutput(args, state, payloadContext, params) {
7644
+ if (state.kandanThreadId === void 0 || state.codexThreadId === void 0) {
7645
+ return void 0;
7646
+ }
7647
+ const session = args.options.channelSession;
7648
+ const streamMetadata = params.turnId === void 0 ? { stream_key: params.streamKey } : {
7649
+ turn_id: params.turnId,
7650
+ stream_key: params.streamKey
7651
+ };
7652
+ const structured = structuredWithStreamKey(
7653
+ params.structured,
7654
+ params.streamKey
7655
+ );
7656
+ const uploadedFileIds = stringValue(structured.stream_state) === "completed" ? await uploadedFileIdsForCommanderOutput(
7657
+ {
7658
+ kandan: args.kandan,
7659
+ topic: args.topic,
7660
+ workspace: session.workspaceSlug,
7661
+ channel: session.channelSlug,
7662
+ cwd: args.options.cwd,
7663
+ kandanUrl: args.options.kandanUrl,
7664
+ token: args.options.token,
7665
+ fetch: args.options.fetch
7666
+ },
7667
+ params.body,
7668
+ structured
7669
+ ) : [];
7670
+ const displayBody = displayBodyForCommanderOutput(
7671
+ params.body,
7672
+ uploadedFileIds
7673
+ );
7674
+ const displayStructured = structuredWithAssistantContent(
7675
+ structured,
7676
+ displayBody
7677
+ );
7678
+ const reply = await pushOk2(
7679
+ args.kandan,
7680
+ args.topic,
7681
+ "session:stream_thread_message",
7682
+ {
7683
+ workspace: session.workspaceSlug,
7684
+ channel: session.channelSlug,
7685
+ thread_id: state.kandanThreadId,
7686
+ stream_key: params.streamKey,
7687
+ body: displayBody,
7688
+ payload: {
7689
+ ...localRunnerPayload(
7690
+ args.options,
7691
+ args.instanceId,
7692
+ "codex_output",
7693
+ state.codexThreadId,
7694
+ payloadContext,
7695
+ params.sourceMessageSeq,
7696
+ streamMetadata
7697
+ ),
7698
+ ...params.rootSeq === void 0 ? {} : { reply_to_seq: params.rootSeq },
7699
+ ...params.sourceMessageSeq === void 0 ? {} : { source_message_seq: params.sourceMessageSeq },
7700
+ ...streamMetadata,
7701
+ structured: displayStructured
7702
+ },
7703
+ ...uploadedFileIds.length === 0 ? {} : { uploaded_file_ids: uploadedFileIds },
7704
+ client_message_id: params.streamKey
7705
+ }
7706
+ );
7707
+ return integerValue(reply.seq);
7708
+ }
7709
+ function structuredWithStreamKey(structured, streamKey) {
7710
+ return streamKey === void 0 ? structured : { ...structured, stream_key: streamKey };
7711
+ }
7684
7712
  async function mirrorLocalTuiInputFromNotification(args, state, turnId, params, payloadContext) {
7685
7713
  if (!isLocalTuiTurn(state, turnId)) {
7686
7714
  return;
@@ -7796,24 +7824,60 @@ function findPendingCommandOutput(state, itemKey) {
7796
7824
  function findStreamingFileChangeOutput(state, itemKey) {
7797
7825
  return getBoundedCacheValue(state.streamingFileChangeOutputs, itemKey);
7798
7826
  }
7799
- function resolveStreamingStructuredOutputForCompletedMessage(state, itemKey, structured) {
7827
+ function resolveStreamingStructuredOutputForCompletedMessage(state, turnId, itemKey, body, structured) {
7800
7828
  switch (stringValue(structured.kind)) {
7801
7829
  case "codex_reasoning": {
7802
- const output = findStreamingReasoningOutput(state, itemKey);
7803
- return output === void 0 ? void 0 : { itemKey: output.itemKey, seq: output.seq };
7830
+ const output = findStreamingReasoningOutput(state, itemKey) ?? singleMatchingStreamedStructuredOutput(
7831
+ boundedCacheValues(state.streamingReasoningOutputs),
7832
+ turnId,
7833
+ body,
7834
+ (candidate) => candidate.content
7835
+ );
7836
+ return output === void 0 ? void 0 : {
7837
+ itemKey: output.itemKey,
7838
+ seq: output.seq,
7839
+ streamKey: output.streamKey,
7840
+ streamState: "streaming"
7841
+ };
7804
7842
  }
7805
7843
  case "codex_command_execution": {
7806
- const output = findStreamingCommandOutput(state, itemKey);
7807
- return output === void 0 ? void 0 : { itemKey: output.itemKey, seq: output.seq };
7844
+ const output = findStreamingCommandOutput(state, itemKey) ?? singleMatchingStreamedStructuredOutput(
7845
+ boundedCacheValues(state.streamingCommandOutputs),
7846
+ turnId,
7847
+ body,
7848
+ (candidate) => codexCommandOutputBody(candidate.command, candidate.output)
7849
+ );
7850
+ return output === void 0 ? void 0 : {
7851
+ itemKey: output.itemKey,
7852
+ seq: output.seq,
7853
+ streamKey: output.streamKey,
7854
+ streamState: "streaming"
7855
+ };
7808
7856
  }
7809
7857
  case "codex_file_change": {
7810
- const output = findStreamingFileChangeOutput(state, itemKey);
7811
- return output === void 0 ? void 0 : { itemKey: output.itemKey, seq: output.seq };
7858
+ const output = findStreamingFileChangeOutput(state, itemKey) ?? singleMatchingStreamedStructuredOutput(
7859
+ boundedCacheValues(state.streamingFileChangeOutputs),
7860
+ turnId,
7861
+ body,
7862
+ (candidate) => candidate.patchText
7863
+ );
7864
+ return output === void 0 ? void 0 : {
7865
+ itemKey: output.itemKey,
7866
+ seq: output.seq,
7867
+ streamKey: output.streamKey,
7868
+ streamState: output.streamState
7869
+ };
7812
7870
  }
7813
7871
  default:
7814
7872
  return void 0;
7815
7873
  }
7816
7874
  }
7875
+ function singleMatchingStreamedStructuredOutput(outputs, turnId, body, bodyForOutput) {
7876
+ const matches = outputs.filter(
7877
+ (output) => output.turnId === turnId && normalizedTranscriptText(bodyForOutput(output)) === normalizedTranscriptText(body)
7878
+ );
7879
+ return matches.length === 1 ? matches[0] : void 0;
7880
+ }
7817
7881
  function forgetStreamingStructuredOutput(state, itemKey, structured) {
7818
7882
  switch (stringValue(structured.kind)) {
7819
7883
  case "codex_reasoning":
@@ -8145,6 +8209,116 @@ function turnTerminalNotificationIsStale(args, state, turnId) {
8145
8209
  });
8146
8210
  return true;
8147
8211
  }
8212
+ function rememberPendingStartingTerminalCompletion(args, state, threadId, turnId) {
8213
+ if (state.turn.status !== "starting") {
8214
+ return false;
8215
+ }
8216
+ if (threadId === void 0 || state.codexThreadId !== threadId) {
8217
+ return false;
8218
+ }
8219
+ if (!terminalNotificationCanBelongToStartingTurn(
8220
+ state,
8221
+ turnId,
8222
+ state.turn.queuedSeq
8223
+ )) {
8224
+ return false;
8225
+ }
8226
+ const pending = {
8227
+ status: "completed",
8228
+ threadId,
8229
+ turnId,
8230
+ queuedSeq: state.turn.queuedSeq
8231
+ };
8232
+ rememberPendingStartingTerminalNotification(state, pending);
8233
+ args.log("codex.starting_turn_terminal_deferred", {
8234
+ status: pending.status,
8235
+ thread_id: threadId,
8236
+ turn_id: turnId,
8237
+ queued_seq: pending.queuedSeq
8238
+ });
8239
+ return true;
8240
+ }
8241
+ function rememberPendingStartingTerminalFailure(args, state, threadId, turnId, reason) {
8242
+ if (state.turn.status !== "starting") {
8243
+ return false;
8244
+ }
8245
+ if (threadId === void 0 || state.codexThreadId !== threadId) {
8246
+ return false;
8247
+ }
8248
+ if (!terminalNotificationCanBelongToStartingTurn(
8249
+ state,
8250
+ turnId,
8251
+ state.turn.queuedSeq
8252
+ )) {
8253
+ return false;
8254
+ }
8255
+ const pending = {
8256
+ status: "failed",
8257
+ threadId,
8258
+ turnId,
8259
+ queuedSeq: state.turn.queuedSeq,
8260
+ reason
8261
+ };
8262
+ rememberPendingStartingTerminalNotification(state, pending);
8263
+ args.log("codex.starting_turn_terminal_deferred", {
8264
+ status: pending.status,
8265
+ thread_id: threadId,
8266
+ turn_id: turnId,
8267
+ queued_seq: pending.queuedSeq,
8268
+ reason
8269
+ });
8270
+ return true;
8271
+ }
8272
+ function terminalNotificationCanBelongToStartingTurn(state, turnId, startingQueuedSeq) {
8273
+ const sourceMessageSeq = sourceMessageSeqForTurn(state, turnId);
8274
+ return sourceMessageSeq === void 0 || sourceMessageSeq === startingQueuedSeq;
8275
+ }
8276
+ function rememberPendingStartingTerminalNotification(state, pending) {
8277
+ const alreadyPending = state.pendingStartingTerminalNotifications.some(
8278
+ (entry) => entry.status === pending.status && entry.threadId === pending.threadId && entry.turnId === pending.turnId && entry.queuedSeq === pending.queuedSeq
8279
+ );
8280
+ if (alreadyPending) {
8281
+ return;
8282
+ }
8283
+ state.pendingStartingTerminalNotifications = [
8284
+ ...state.pendingStartingTerminalNotifications,
8285
+ pending
8286
+ ].slice(-maxForwardedTurnIds);
8287
+ }
8288
+ function consumePendingStartingTerminalNotification(state, threadId, turnId, queuedSeq) {
8289
+ const index = state.pendingStartingTerminalNotifications.findIndex(
8290
+ (pending2) => pending2.threadId === threadId && pending2.queuedSeq === queuedSeq && pending2.turnId === turnId
8291
+ );
8292
+ if (index < 0) {
8293
+ return void 0;
8294
+ }
8295
+ const [pending] = state.pendingStartingTerminalNotifications.splice(index, 1);
8296
+ return pending;
8297
+ }
8298
+ async function handlePendingStartingTerminalNotification(args, state, pending, turnId, payloadContext) {
8299
+ args.log("codex.starting_turn_terminal_replayed", {
8300
+ status: pending.status,
8301
+ thread_id: pending.threadId,
8302
+ turn_id: turnId,
8303
+ queued_seq: pending.queuedSeq
8304
+ });
8305
+ switch (pending.status) {
8306
+ case "completed":
8307
+ enqueueWebSearchProgressCompletion(args, state, turnId, payloadContext);
8308
+ enqueueFileChangeCompletion(args, state, turnId, payloadContext);
8309
+ await forwardCompletedCodexTurn(args, state, turnId, payloadContext);
8310
+ break;
8311
+ case "failed":
8312
+ await failActiveCodexTurn(
8313
+ args,
8314
+ state,
8315
+ turnId,
8316
+ pending.reason,
8317
+ payloadContext
8318
+ );
8319
+ break;
8320
+ }
8321
+ }
8148
8322
  function codexNotificationActiveTurnFallback(method, state, params, threadId) {
8149
8323
  if (threadId === void 0 || state.codexThreadId !== threadId) {
8150
8324
  return void 0;
@@ -14657,7 +14831,7 @@ var linzumiCliVersion, linzumiCliVersionText;
14657
14831
  var init_version = __esm({
14658
14832
  "src/version.ts"() {
14659
14833
  "use strict";
14660
- linzumiCliVersion = "0.0.78-beta";
14834
+ linzumiCliVersion = "0.0.79-beta";
14661
14835
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
14662
14836
  }
14663
14837
  });
@@ -18554,6 +18728,26 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
18554
18728
  const missingAllowedCwds = {
18555
18729
  value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
18556
18730
  };
18731
+ const applyRunnerConfigSync = (config, syncOptions) => {
18732
+ const persistedAllowedCwds = replaceAllowedCwdsForLinzumiUrl(
18733
+ config.allowedCwds,
18734
+ options.kandanUrl
18735
+ );
18736
+ const updatedAllowedCwds = configuredAllowedCwds(persistedAllowedCwds, {
18737
+ createMissing: true
18738
+ });
18739
+ allowedCwds.value = normalizeAllowedCwds([
18740
+ ...syncOptions.includeStartupAllowedCwds ? startupAllowedCwds : [],
18741
+ ...updatedAllowedCwds.allowedCwds
18742
+ ]);
18743
+ missingAllowedCwds.value = updatedAllowedCwds.missingAllowedCwds;
18744
+ log("runner.config_updated", {
18745
+ source: syncOptions.source,
18746
+ configVersion: config.configVersion,
18747
+ allowedCwdCount: allowedCwds.value.length,
18748
+ missingAllowedCwdCount: missingAllowedCwds.value.length
18749
+ });
18750
+ };
18557
18751
  const localEditorState = {
18558
18752
  value: { status: "disabled" }
18559
18753
  };
@@ -18660,6 +18854,28 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
18660
18854
  const joinResponse = await kandan.join(topic, joinPayload(), {
18661
18855
  rejoinPayload: joinPayload
18662
18856
  });
18857
+ const joinedRunnerConfig = parseRunnerConfigSyncPayload(
18858
+ objectValue(joinResponse)?.runner_config
18859
+ );
18860
+ switch (joinedRunnerConfig.type) {
18861
+ case "missing":
18862
+ break;
18863
+ case "invalid":
18864
+ log("runner.config_join_invalid", {
18865
+ runnerId: options.runnerId,
18866
+ kandanUrl: options.kandanUrl,
18867
+ reason: joinedRunnerConfig.reason
18868
+ });
18869
+ throw new Error(
18870
+ `invalid runner_config in Kandan join response: ${joinedRunnerConfig.reason}`
18871
+ );
18872
+ case "ok":
18873
+ applyRunnerConfigSync(joinedRunnerConfig.config, {
18874
+ includeStartupAllowedCwds: false,
18875
+ source: "join"
18876
+ });
18877
+ break;
18878
+ }
18663
18879
  const replacedRunners = replacementRunnerSummaries(
18664
18880
  objectValue(joinResponse)?.replaced_runners
18665
18881
  );
@@ -19971,23 +20187,16 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
19971
20187
  return;
19972
20188
  }
19973
20189
  if (isUpdateRunnerConfigControl(control)) {
19974
- const persistedAllowedCwds = replaceAllowedCwdsForLinzumiUrl(
19975
- control.allowedCwds,
19976
- options.kandanUrl
20190
+ applyRunnerConfigSync(
20191
+ {
20192
+ allowedCwds: control.allowedCwds,
20193
+ configVersion: control.configVersion ?? null
20194
+ },
20195
+ {
20196
+ includeStartupAllowedCwds: true,
20197
+ source: "control"
20198
+ }
19977
20199
  );
19978
- const updatedAllowedCwds = configuredAllowedCwds(persistedAllowedCwds, {
19979
- createMissing: true
19980
- });
19981
- allowedCwds.value = normalizeAllowedCwds([
19982
- ...startupAllowedCwds,
19983
- ...updatedAllowedCwds.allowedCwds
19984
- ]);
19985
- missingAllowedCwds.value = updatedAllowedCwds.missingAllowedCwds;
19986
- log("runner.config_updated", {
19987
- configVersion: control.configVersion ?? null,
19988
- allowedCwdCount: allowedCwds.value.length,
19989
- missingAllowedCwdCount: missingAllowedCwds.value.length
19990
- });
19991
20200
  void pushHeartbeat();
19992
20201
  return;
19993
20202
  }
@@ -20269,6 +20478,42 @@ function replacementRunnerSummaries(value) {
20269
20478
  ];
20270
20479
  });
20271
20480
  }
20481
+ function parseRunnerConfigSyncPayload(value) {
20482
+ if (value === void 0) {
20483
+ return { type: "missing" };
20484
+ }
20485
+ const payload = objectValue(value);
20486
+ if (payload === void 0) {
20487
+ return { type: "invalid", reason: "runner_config_not_object" };
20488
+ }
20489
+ const entries = arrayValue(payload.allowedCwds);
20490
+ if (entries === void 0) {
20491
+ return { type: "invalid", reason: "allowed_cwds_not_array" };
20492
+ }
20493
+ const allowedCwds = [];
20494
+ for (const [index, entry] of entries.entries()) {
20495
+ const allowedCwd = stringValue(entry);
20496
+ if (allowedCwd === void 0) {
20497
+ return {
20498
+ type: "invalid",
20499
+ reason: `allowed_cwd_${index}_not_string`
20500
+ };
20501
+ }
20502
+ allowedCwds.push(allowedCwd);
20503
+ }
20504
+ const rawConfigVersion = payload.configVersion;
20505
+ const configVersion = rawConfigVersion === void 0 || rawConfigVersion === null ? null : integerValue(rawConfigVersion);
20506
+ if (configVersion === void 0) {
20507
+ return { type: "invalid", reason: "config_version_not_integer" };
20508
+ }
20509
+ return {
20510
+ type: "ok",
20511
+ config: {
20512
+ allowedCwds,
20513
+ configVersion
20514
+ }
20515
+ };
20516
+ }
20272
20517
  function makeRunnerLogger(options) {
20273
20518
  const consoleReporter = options.launchTui && options.consoleReporter === void 0 ? void 0 : (event, payload) => {
20274
20519
  if (!options.launchTui) {