@leanandmean/coding-agent 0.74.1-scramjet.73 → 0.74.1-scramjet.75

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.
@@ -11,7 +11,7 @@ import { getProviders, } from "@leanandmean/ai";
11
11
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, isKeyRelease, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, TUI_KEYBINDINGS, visibleWidth, } from "@leanandmean/tui";
12
12
  import { spawn, spawnSync } from "child_process";
13
13
  import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, VERSION } from "../../config.js";
14
- import { parseSkillBlock } from "../../core/agent-session.js";
14
+ import { parseSkillBlock, } from "../../core/agent-session.js";
15
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
16
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
17
17
  import { KeybindingsManager } from "../../core/keybindings.js";
@@ -130,6 +130,7 @@ export class InteractiveMode {
130
130
  version;
131
131
  isInitialized = false;
132
132
  onInputCallback;
133
+ promptSubmission;
133
134
  loadingAnimation = undefined;
134
135
  workingMessage = undefined;
135
136
  workingVisible = true;
@@ -877,7 +878,7 @@ export class InteractiveMode {
877
878
  // Process initial messages
878
879
  if (initialMessage) {
879
880
  try {
880
- await this.session.prompt(initialMessage, { images: initialImages });
881
+ await this.promptInteractive(initialMessage, initialImages);
881
882
  }
882
883
  catch (error) {
883
884
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -887,7 +888,7 @@ export class InteractiveMode {
887
888
  if (initialMessages) {
888
889
  for (const message of initialMessages) {
889
890
  try {
890
- await this.session.prompt(message);
891
+ await this.promptInteractive(message);
891
892
  }
892
893
  catch (error) {
893
894
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -899,7 +900,7 @@ export class InteractiveMode {
899
900
  while (true) {
900
901
  const userInput = await this.getUserInput();
901
902
  try {
902
- await this.session.prompt(userInput);
903
+ await this.promptInteractive(userInput);
903
904
  }
904
905
  catch (error) {
905
906
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -907,6 +908,71 @@ export class InteractiveMode {
907
908
  }
908
909
  }
909
910
  }
911
+ // SCRAMJET-DIVERGENCE: native consumers retain invocation identity through recovery.
912
+ async promptInteractive(text, images) {
913
+ const submission = { session: this.session, retried: false };
914
+ this.promptSubmission = submission;
915
+ const draftGeneration = this.clipboardPasteGeneration;
916
+ try {
917
+ await submission.session.prompt(text, {
918
+ images,
919
+ onRunSettlement: (settlement) => {
920
+ submission.settlement = settlement;
921
+ void settlement.catch(() => { });
922
+ },
923
+ });
924
+ const outcome = submission.settlement ? await submission.settlement : undefined;
925
+ if (this.promptSubmission !== submission || this.session !== submission.session)
926
+ return;
927
+ if (outcome?.status === "cancelled") {
928
+ this.showStatus("Execution cancelled. Review interrupted work before submitting a new prompt.");
929
+ }
930
+ else if (outcome?.status === "failed") {
931
+ const retryRecord = submission.session.sessionManager
932
+ .getBranch()
933
+ .reverse()
934
+ .find((entry) => entry.type === "custom" && entry.customType === "coding-agent:auto-retry");
935
+ const exhausted = submission.retryFailure &&
936
+ retryRecord?.type === "custom" &&
937
+ retryRecord.data?.outcome === "exhausted";
938
+ const prefix = exhausted ? "Recovery exhausted" : "Execution failed";
939
+ this.showError(`${prefix}: ${outcome.errorMessage}. Review the failure before trying again.`);
940
+ }
941
+ else if (outcome?.status === "completed" && submission.retried) {
942
+ this.showStatus("Execution recovered and continued.");
943
+ }
944
+ }
945
+ catch (error) {
946
+ if (this.promptSubmission !== submission || this.session !== submission.session)
947
+ return;
948
+ if (!this.editor.getText() && this.clipboardPasteGeneration === draftGeneration)
949
+ this.editor.setText(text);
950
+ this.showError(`${error instanceof Error ? error.message : String(error)}. Review the failure before trying again.`);
951
+ }
952
+ finally {
953
+ if (this.promptSubmission === submission && this.session === submission.session) {
954
+ this.promptSubmission = undefined;
955
+ this.clearRetryControls();
956
+ this.stopWorkingLoader();
957
+ if (this.settingsManager.getShowTerminalProgress())
958
+ this.ui.terminal.setProgress(false);
959
+ this.ui.requestRender();
960
+ }
961
+ }
962
+ }
963
+ clearRetryControls() {
964
+ if (this.retryEscapeHandler) {
965
+ this.defaultEditor.onEscape = this.retryEscapeHandler;
966
+ this.retryEscapeHandler = undefined;
967
+ }
968
+ this.retryCountdown?.dispose();
969
+ this.retryCountdown = undefined;
970
+ if (this.retryLoader) {
971
+ this.statusContainer.removeChild(this.retryLoader);
972
+ this.retryLoader.stop();
973
+ this.retryLoader = undefined;
974
+ }
975
+ }
910
976
  async checkForPackageUpdates() {
911
977
  // SCRAMJET-DIVERGENCE: prefer SCRAMJET_OFFLINE, fall back to PI_OFFLINE.
912
978
  if (process.env.SCRAMJET_OFFLINE || process.env.PI_OFFLINE) {
@@ -1513,6 +1579,13 @@ export class InteractiveMode {
1513
1579
  }
1514
1580
  }
1515
1581
  async rebindCurrentSession() {
1582
+ if (this.promptSubmission && this.promptSubmission.session !== this.session) {
1583
+ this.promptSubmission = undefined;
1584
+ this.clearRetryControls();
1585
+ this.stopWorkingLoader();
1586
+ if (this.settingsManager.getShowTerminalProgress())
1587
+ this.ui.terminal.setProgress(false);
1588
+ }
1516
1589
  this.unsubscribe?.();
1517
1590
  this.unsubscribe = undefined;
1518
1591
  this.applyRuntimeSettings();
@@ -2366,7 +2439,7 @@ export class InteractiveMode {
2366
2439
  this.pendingSelectorOpenGeneration = undefined;
2367
2440
  return;
2368
2441
  }
2369
- if (this.session.isStreaming) {
2442
+ if (this.promptSubmission?.session === this.session || this.session.isStreaming || this.session.isRetrying) {
2370
2443
  this.restoreQueuedMessagesToEditor({ abort: true });
2371
2444
  }
2372
2445
  else if (this.session.isBashRunning) {
@@ -2608,11 +2681,16 @@ export class InteractiveMode {
2608
2681
  if (this.session.isStreaming) {
2609
2682
  this.editor.addToHistory?.(text);
2610
2683
  this.editor.setText("");
2611
- await this.session.prompt(text, { streamingBehavior: "steer" });
2684
+ await this.submitQueuedPrompt(text, "steer");
2612
2685
  this.updatePendingMessagesDisplay();
2613
2686
  this.ui.requestRender();
2614
2687
  return;
2615
2688
  }
2689
+ if (!this.onInputCallback) {
2690
+ this.editor.setText(text);
2691
+ this.showWarning("Execution is still settling or recovering. Wait, or press Esc to cancel before submitting again.");
2692
+ return;
2693
+ }
2616
2694
  // Normal message submission
2617
2695
  // First, move any pending bash components to chat
2618
2696
  this.flushPendingBashComponents();
@@ -2823,10 +2901,10 @@ export class InteractiveMode {
2823
2901
  await Promise.all(pendingToolFinalizations);
2824
2902
  if (runGeneration !== this.agentRunGeneration || pendingToolFinalizations !== this.pendingToolFinalizations)
2825
2903
  break;
2826
- if (this.settingsManager.getShowTerminalProgress()) {
2904
+ if (this.promptSubmission?.session !== this.session && this.settingsManager.getShowTerminalProgress()) {
2827
2905
  this.ui.terminal.setProgress(false);
2828
2906
  }
2829
- if (this.loadingAnimation) {
2907
+ if (this.loadingAnimation && this.promptSubmission?.session !== this.session) {
2830
2908
  this.loadingAnimation.stop();
2831
2909
  this.loadingAnimation = undefined;
2832
2910
  this.statusContainer.clear();
@@ -2853,8 +2931,14 @@ export class InteractiveMode {
2853
2931
  }
2854
2932
  // Keep editor active; submissions are queued during compaction.
2855
2933
  this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
2934
+ const compactingSession = this.session;
2856
2935
  this.defaultEditor.onEscape = () => {
2857
- this.session.abortCompaction();
2936
+ if (this.session !== compactingSession)
2937
+ return;
2938
+ if (this.promptSubmission?.session === compactingSession)
2939
+ compactingSession.abortRetry();
2940
+ else
2941
+ compactingSession.abortCompaction();
2858
2942
  };
2859
2943
  this.statusContainer.clear();
2860
2944
  const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
@@ -2867,7 +2951,7 @@ export class InteractiveMode {
2867
2951
  break;
2868
2952
  }
2869
2953
  case "compaction_end": {
2870
- if (this.settingsManager.getShowTerminalProgress()) {
2954
+ if (this.promptSubmission?.session !== this.session && this.settingsManager.getShowTerminalProgress()) {
2871
2955
  this.ui.terminal.setProgress(false);
2872
2956
  }
2873
2957
  if (this.autoCompactionEscapeHandler) {
@@ -2908,9 +2992,14 @@ export class InteractiveMode {
2908
2992
  }
2909
2993
  case "auto_retry_start": {
2910
2994
  // Set up escape to abort retry
2995
+ if (this.promptSubmission?.session === this.session)
2996
+ this.promptSubmission.retried = true;
2997
+ this.clearRetryControls();
2911
2998
  this.retryEscapeHandler = this.defaultEditor.onEscape;
2999
+ const retrySession = this.session;
2912
3000
  this.defaultEditor.onEscape = () => {
2913
- this.session.abortRetry();
3001
+ if (this.session === retrySession)
3002
+ this.restoreQueuedMessagesToEditor({ abort: true });
2914
3003
  };
2915
3004
  // Show retry indicator
2916
3005
  this.statusContainer.clear();
@@ -2949,8 +3038,11 @@ export class InteractiveMode {
2949
3038
  this.statusContainer.clear();
2950
3039
  }
2951
3040
  // Show error only on final failure (success shows normal response)
2952
- if (!event.success) {
2953
- this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
3041
+ if (!event.success && this.promptSubmission?.session === this.session) {
3042
+ this.promptSubmission.retryFailure = event.finalError;
3043
+ }
3044
+ else if (!event.success) {
3045
+ this.showStatus("Recovery stopped. Review interrupted work before submitting a new prompt.");
2954
3046
  }
2955
3047
  this.ui.requestRender();
2956
3048
  break;
@@ -3411,6 +3503,20 @@ export class InteractiveMode {
3411
3503
  throw error;
3412
3504
  }
3413
3505
  }
3506
+ async submitQueuedPrompt(text, streamingBehavior) {
3507
+ const session = this.session;
3508
+ const generation = this.clipboardPasteGeneration;
3509
+ try {
3510
+ await session.prompt(text, { streamingBehavior });
3511
+ }
3512
+ catch (error) {
3513
+ if (this.session !== session)
3514
+ return;
3515
+ if (!this.editor.getText() && generation === this.clipboardPasteGeneration)
3516
+ this.editor.setText(text);
3517
+ this.showError(`${error instanceof Error ? error.message : String(error)}. Wait or cancel recovery before submitting again.`);
3518
+ }
3519
+ }
3414
3520
  async handleFollowUp() {
3415
3521
  const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
3416
3522
  if (!text)
@@ -3433,14 +3539,14 @@ export class InteractiveMode {
3433
3539
  if (this.session.isStreaming) {
3434
3540
  this.editor.addToHistory?.(text);
3435
3541
  this.editor.setText("");
3436
- await this.session.prompt(text, { streamingBehavior: "followUp" });
3542
+ await this.submitQueuedPrompt(text, "followUp");
3437
3543
  this.updatePendingMessagesDisplay();
3438
3544
  this.ui.requestRender();
3439
3545
  }
3440
3546
  // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
3441
3547
  else if (this.editor.onSubmit) {
3442
3548
  this.editor.setText("");
3443
- this.editor.onSubmit(text);
3549
+ await this.editor.onSubmit(text);
3444
3550
  }
3445
3551
  }
3446
3552
  handleDequeue() {
@@ -3659,7 +3765,7 @@ export class InteractiveMode {
3659
3765
  if (allQueued.length === 0) {
3660
3766
  this.updatePendingMessagesDisplay();
3661
3767
  if (options?.abort) {
3662
- this.agent.abort();
3768
+ this.session.abortRetry();
3663
3769
  }
3664
3770
  return 0;
3665
3771
  }
@@ -3669,7 +3775,7 @@ export class InteractiveMode {
3669
3775
  this.editor.setText(combinedText);
3670
3776
  this.updatePendingMessagesDisplay();
3671
3777
  if (options?.abort) {
3672
- this.agent.abort();
3778
+ this.session.abortRetry();
3673
3779
  }
3674
3780
  return allQueued.length;
3675
3781
  }