mastracode 0.44.2-alpha.0 → 0.45.0-alpha.2

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.
@@ -2535,10 +2535,18 @@ function getCommands(modes) {
2535
2535
  key: "/new",
2536
2536
  description: "Start a new thread"
2537
2537
  },
2538
+ {
2539
+ key: "/clone",
2540
+ description: "Clone the current thread"
2541
+ },
2538
2542
  {
2539
2543
  key: "/threads",
2540
2544
  description: "Switch between threads"
2541
2545
  },
2546
+ {
2547
+ key: "/resume",
2548
+ description: "Alias for /threads"
2549
+ },
2542
2550
  {
2543
2551
  key: "/thread",
2544
2552
  description: "Show current thread info"
@@ -2551,6 +2559,10 @@ function getCommands(modes) {
2551
2559
  key: "/name",
2552
2560
  description: "Rename current thread"
2553
2561
  },
2562
+ {
2563
+ key: "/rename",
2564
+ description: "Alias for /name"
2565
+ },
2554
2566
  {
2555
2567
  key: "/resource",
2556
2568
  description: "Show/switch resource ID"
@@ -20967,6 +20979,8 @@ const TRACKED_COMMANDS = /* @__PURE__ */ new Set([
20967
20979
  "memory-gateway",
20968
20980
  "custom-providers",
20969
20981
  "threads",
20982
+ "resume",
20983
+ "clone",
20970
20984
  "new"
20971
20985
  ]);
20972
20986
  /**
@@ -21038,6 +21052,7 @@ async function dispatchSlashCommand(input, state, buildCtx) {
21038
21052
  await handleCloneCommand(ctx);
21039
21053
  return true;
21040
21054
  case "threads":
21055
+ case "resume":
21041
21056
  await handleThreadsCommand(ctx);
21042
21057
  return true;
21043
21058
  case "thread":
@@ -21120,6 +21135,7 @@ async function dispatchSlashCommand(input, state, buildCtx) {
21120
21135
  await handleDiffCommand(ctx, args[0]);
21121
21136
  return true;
21122
21137
  case "name":
21138
+ case "rename":
21123
21139
  await handleNameCommand(ctx, args);
21124
21140
  return true;
21125
21141
  case "resource":
@@ -25902,120 +25918,6 @@ function formatIdleStatusTimingSegments(state, now = Date.now()) {
25902
25918
  } : null;
25903
25919
  }
25904
25920
  //#endregion
25905
- //#region src/tui/components/simple-progress.ts
25906
- /**
25907
- * Simple progress indicator component for single operations.
25908
- * Shows a compact progress display with spinner and status text.
25909
- */
25910
- /**
25911
- * Lightweight progress indicator for simple operations.
25912
- * Features:
25913
- * - Animated spinner
25914
- * - Custom status text
25915
- * - Optional elapsed time
25916
- * - Optional percentage display
25917
- */
25918
- var SimpleProgressComponent = class extends _earendil_works_pi_tui.Container {
25919
- status = "";
25920
- progress;
25921
- startTime;
25922
- spinnerFrame = 0;
25923
- lastRenderTime = 0;
25924
- isActive = false;
25925
- options;
25926
- constructor(options = {}) {
25927
- super();
25928
- this.options = {
25929
- prefix: "",
25930
- showElapsed: true,
25931
- showPercentage: true,
25932
- ...options
25933
- };
25934
- this.startTime = Date.now();
25935
- }
25936
- /**
25937
- * Start showing progress with initial status.
25938
- */
25939
- start(status) {
25940
- this.status = status;
25941
- this.isActive = true;
25942
- this.startTime = Date.now();
25943
- this.rebuild();
25944
- }
25945
- /**
25946
- * Update the status text.
25947
- */
25948
- updateStatus(status) {
25949
- this.status = status;
25950
- this.rebuild();
25951
- }
25952
- /**
25953
- * Update the progress percentage (0-100).
25954
- */
25955
- updateProgress(progress) {
25956
- this.progress = Math.min(100, Math.max(0, progress));
25957
- this.rebuild();
25958
- }
25959
- /**
25960
- * Mark the operation as complete.
25961
- */
25962
- complete(message) {
25963
- this.isActive = false;
25964
- this.status = message || "Complete";
25965
- this.progress = 100;
25966
- this.rebuild();
25967
- }
25968
- /**
25969
- * Mark the operation as failed.
25970
- */
25971
- fail(error) {
25972
- this.isActive = false;
25973
- this.status = error;
25974
- this.rebuild();
25975
- }
25976
- rebuild() {
25977
- this.clear();
25978
- if (!this.status) return;
25979
- const now = Date.now();
25980
- if (this.isActive && now - this.lastRenderTime > 100) {
25981
- this.spinnerFrame = (this.spinnerFrame + 1) % 10;
25982
- this.lastRenderTime = now;
25983
- }
25984
- let text = "";
25985
- if (this.options.prefix) text += this.options.prefix + " ";
25986
- if (this.isActive) {
25987
- const spinner = this.getSpinner();
25988
- text += theme.fg("accent", spinner) + " ";
25989
- } else if (this.progress === 100) text += theme.fg("success", "✓") + " ";
25990
- else text += theme.fg("error", "✗") + " ";
25991
- text += this.status;
25992
- if (this.options.showPercentage && this.progress !== void 0) text += theme.fg("dim", ` (${this.progress}%)`);
25993
- if (this.options.showElapsed && this.isActive) {
25994
- const elapsed = Math.floor((now - this.startTime) / 1e3);
25995
- text += theme.fg("dim", ` ${elapsed}s`);
25996
- }
25997
- this.addChild(new _earendil_works_pi_tui.Text(text, 0, 0));
25998
- }
25999
- getSpinner() {
26000
- return [
26001
- "⠋",
26002
- "⠙",
26003
- "⠹",
26004
- "⠸",
26005
- "⠼",
26006
- "⠴",
26007
- "⠦",
26008
- "⠧",
26009
- "⠇",
26010
- "⠏"
26011
- ][this.spinnerFrame];
26012
- }
26013
- render(maxWidth) {
26014
- this.rebuild();
26015
- return super.render(maxWidth);
26016
- }
26017
- };
26018
- //#endregion
26019
25921
  //#region src/tui/setup.ts
26020
25922
  /**
26021
25923
  * TUI setup: keyboard shortcuts, layout building, autocomplete, key handlers.
@@ -26279,6 +26181,10 @@ function setupAutocomplete(state) {
26279
26181
  name: "threads",
26280
26182
  description: "Switch between threads"
26281
26183
  },
26184
+ {
26185
+ name: "resume",
26186
+ description: "Alias for /threads"
26187
+ },
26282
26188
  {
26283
26189
  name: "models",
26284
26190
  description: "Switch model pack"
@@ -26348,6 +26254,10 @@ function setupAutocomplete(state) {
26348
26254
  name: "name",
26349
26255
  description: "Rename current thread"
26350
26256
  },
26257
+ {
26258
+ name: "rename",
26259
+ description: "Alias for /name"
26260
+ },
26351
26261
  {
26352
26262
  name: "resource",
26353
26263
  description: "Show/switch resource ID (tag for sharing)"
@@ -26716,94 +26626,6 @@ function subscribeToAgentController(state, handleEvent) {
26716
26626
  state.waitForAgentControllerEvents = state.options.backgroundToolsEnabled ? () => eventQueue : void 0;
26717
26627
  state.unsubscribe = state.session.subscribe(listener);
26718
26628
  }
26719
- async function promptForThreadSelection(state) {
26720
- const currentPath = state.projectInfo.rootPath;
26721
- const currentResourceId = state.session.identity.getResourceId();
26722
- const allThreads = await state.session.thread.list();
26723
- const activeThreadId = state.session.thread.getId();
26724
- const taggedThreads = allThreads.filter((t) => {
26725
- const threadPath = t.metadata?.projectPath;
26726
- return !!threadPath && threadPath === currentPath;
26727
- });
26728
- const threads = [];
26729
- for (const thread of taggedThreads) {
26730
- if (thread.id === activeThreadId && !thread.title) {
26731
- if ((await state.session.thread.listMessages({
26732
- threadId: thread.id,
26733
- limit: 1
26734
- })).length === 0) continue;
26735
- }
26736
- threads.push(thread);
26737
- }
26738
- if (threads.length === 0) {
26739
- const [thread] = [...(await state.session.thread.list({
26740
- allResources: true,
26741
- metadata: { projectPath: currentPath }
26742
- })).filter((t) => t.resourceId !== currentResourceId)].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
26743
- if (thread) {
26744
- if (await askModalQuestion(state.ui, {
26745
- question: [
26746
- "This directory is tagged on a different resource.",
26747
- "",
26748
- `Project: ${currentPath}`,
26749
- `Thread: ${thread.title || thread.id}`,
26750
- `Old resource: ${thread.resourceId}`,
26751
- `Current resource: ${currentResourceId}`,
26752
- "",
26753
- "Clone this thread into the current resource and resume the clone?"
26754
- ].join("\n"),
26755
- options: [{ label: "Clone and resume" }, { label: "Start fresh" }],
26756
- selectedOptionLabel: "Clone and resume",
26757
- allowCustomResponse: false,
26758
- overlay: {
26759
- widthPercent: 80,
26760
- maxHeight: "70%"
26761
- }
26762
- }) === "Clone and resume") {
26763
- const progress = new SimpleProgressComponent({
26764
- showElapsed: false,
26765
- showPercentage: false
26766
- });
26767
- progress.start("Cloning thread into the current resource...");
26768
- showModalOverlay(state.ui, progress, {
26769
- widthPercent: 70,
26770
- maxHeight: "40%",
26771
- minHeightPercent: .35
26772
- });
26773
- state.ui.requestRender();
26774
- try {
26775
- await new Promise((resolve) => setTimeout(resolve, 50));
26776
- progress.updateStatus("Loading cloned thread...");
26777
- state.ui.requestRender();
26778
- await state.session.thread.cloneToCurrentResource({
26779
- threadId: thread.id,
26780
- expectedResourceId: thread.resourceId,
26781
- expectedProjectPath: currentPath
26782
- });
26783
- } finally {
26784
- state.ui.hideOverlay();
26785
- state.ui.requestRender();
26786
- }
26787
- return;
26788
- }
26789
- }
26790
- state.pendingNewThread = true;
26791
- return;
26792
- }
26793
- const sortedThreads = [...threads].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
26794
- for (const thread of sortedThreads) try {
26795
- await state.session.thread.switch({ threadId: thread.id });
26796
- if (!thread.metadata?.projectPath) await state.session.thread.setSetting({
26797
- key: "projectPath",
26798
- value: currentPath
26799
- });
26800
- return;
26801
- } catch (error) {
26802
- if (error instanceof _mastra_code_sdk_utils_thread_lock.ThreadLockError) continue;
26803
- throw error;
26804
- }
26805
- state.pendingNewThread = true;
26806
- }
26807
26629
  async function renderExistingTasks(state) {
26808
26630
  try {
26809
26631
  const tasks = state.session.displayState.get().tasks;
@@ -26984,6 +26806,239 @@ async function handleShellPassthrough(state, command) {
26984
26806
  }
26985
26807
  }
26986
26808
  //#endregion
26809
+ //#region src/tui/components/simple-progress.ts
26810
+ /**
26811
+ * Simple progress indicator component for single operations.
26812
+ * Shows a compact progress display with spinner and status text.
26813
+ */
26814
+ /**
26815
+ * Lightweight progress indicator for simple operations.
26816
+ * Features:
26817
+ * - Animated spinner
26818
+ * - Custom status text
26819
+ * - Optional elapsed time
26820
+ * - Optional percentage display
26821
+ */
26822
+ var SimpleProgressComponent = class extends _earendil_works_pi_tui.Container {
26823
+ status = "";
26824
+ progress;
26825
+ startTime;
26826
+ spinnerFrame = 0;
26827
+ lastRenderTime = 0;
26828
+ isActive = false;
26829
+ options;
26830
+ constructor(options = {}) {
26831
+ super();
26832
+ this.options = {
26833
+ prefix: "",
26834
+ showElapsed: true,
26835
+ showPercentage: true,
26836
+ ...options
26837
+ };
26838
+ this.startTime = Date.now();
26839
+ }
26840
+ /**
26841
+ * Start showing progress with initial status.
26842
+ */
26843
+ start(status) {
26844
+ this.status = status;
26845
+ this.isActive = true;
26846
+ this.startTime = Date.now();
26847
+ this.rebuild();
26848
+ }
26849
+ /**
26850
+ * Update the status text.
26851
+ */
26852
+ updateStatus(status) {
26853
+ this.status = status;
26854
+ this.rebuild();
26855
+ }
26856
+ /**
26857
+ * Update the progress percentage (0-100).
26858
+ */
26859
+ updateProgress(progress) {
26860
+ this.progress = Math.min(100, Math.max(0, progress));
26861
+ this.rebuild();
26862
+ }
26863
+ /**
26864
+ * Mark the operation as complete.
26865
+ */
26866
+ complete(message) {
26867
+ this.isActive = false;
26868
+ this.status = message || "Complete";
26869
+ this.progress = 100;
26870
+ this.rebuild();
26871
+ }
26872
+ /**
26873
+ * Mark the operation as failed.
26874
+ */
26875
+ fail(error) {
26876
+ this.isActive = false;
26877
+ this.status = error;
26878
+ this.rebuild();
26879
+ }
26880
+ rebuild() {
26881
+ this.clear();
26882
+ if (!this.status) return;
26883
+ const now = Date.now();
26884
+ if (this.isActive && now - this.lastRenderTime > 100) {
26885
+ this.spinnerFrame = (this.spinnerFrame + 1) % 10;
26886
+ this.lastRenderTime = now;
26887
+ }
26888
+ let text = "";
26889
+ if (this.options.prefix) text += this.options.prefix + " ";
26890
+ if (this.isActive) {
26891
+ const spinner = this.getSpinner();
26892
+ text += theme.fg("accent", spinner) + " ";
26893
+ } else if (this.progress === 100) text += theme.fg("success", "✓") + " ";
26894
+ else text += theme.fg("error", "✗") + " ";
26895
+ text += this.status;
26896
+ if (this.options.showPercentage && this.progress !== void 0) text += theme.fg("dim", ` (${this.progress}%)`);
26897
+ if (this.options.showElapsed && this.isActive) {
26898
+ const elapsed = Math.floor((now - this.startTime) / 1e3);
26899
+ text += theme.fg("dim", ` ${elapsed}s`);
26900
+ }
26901
+ this.addChild(new _earendil_works_pi_tui.Text(text, 0, 0));
26902
+ }
26903
+ getSpinner() {
26904
+ return [
26905
+ "⠋",
26906
+ "⠙",
26907
+ "⠹",
26908
+ "⠸",
26909
+ "⠼",
26910
+ "⠴",
26911
+ "⠦",
26912
+ "⠧",
26913
+ "⠇",
26914
+ "⠏"
26915
+ ][this.spinnerFrame];
26916
+ }
26917
+ render(maxWidth) {
26918
+ this.rebuild();
26919
+ return super.render(maxWidth);
26920
+ }
26921
+ };
26922
+ //#endregion
26923
+ //#region src/tui/thread-startup.ts
26924
+ /**
26925
+ * Selects the thread to open at startup. A requested thread that is missing or
26926
+ * locked does not throw; the session is left pending a new thread and the issue
26927
+ * is returned so the caller can tell the user once the UI is ready.
26928
+ */
26929
+ async function resumeThreadOnStartup(state, requestedThreadId) {
26930
+ const currentPath = state.projectInfo.rootPath;
26931
+ const allThreads = await state.session.thread.list(requestedThreadId ? { allResources: true } : void 0);
26932
+ const activeThreadId = state.session.thread.getId();
26933
+ if (requestedThreadId) {
26934
+ const thread = allThreads.find((candidate) => candidate.id === requestedThreadId);
26935
+ if (!thread) {
26936
+ await startPendingNewThread(state);
26937
+ return {
26938
+ kind: "missing",
26939
+ threadId: requestedThreadId
26940
+ };
26941
+ }
26942
+ const originalResourceId = state.session.identity.getResourceId();
26943
+ if (thread.resourceId !== originalResourceId) await state.controller.setResourceId(state.session, { resourceId: thread.resourceId });
26944
+ if (requestedThreadId !== activeThreadId) try {
26945
+ await state.session.thread.switch({ threadId: requestedThreadId });
26946
+ } catch (error) {
26947
+ if (!(error instanceof _mastra_code_sdk_utils_thread_lock.ThreadLockError)) throw error;
26948
+ if (state.session.identity.getResourceId() !== originalResourceId) await state.controller.setResourceId(state.session, { resourceId: originalResourceId });
26949
+ await startPendingNewThread(state);
26950
+ return {
26951
+ kind: "locked",
26952
+ threadId: thread.id,
26953
+ title: thread.title || thread.id,
26954
+ ownerPid: error.ownerPid
26955
+ };
26956
+ }
26957
+ state.pendingNewThread = false;
26958
+ return;
26959
+ }
26960
+ const threads = allThreads.filter((thread) => thread.metadata?.projectPath === currentPath);
26961
+ if (threads.length === 0) {
26962
+ if (await cloneDriftedThread(state)) return;
26963
+ state.pendingNewThread = true;
26964
+ return;
26965
+ }
26966
+ for (const thread of [...threads].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())) {
26967
+ if (thread.id === state.session.thread.getId()) return;
26968
+ try {
26969
+ await state.session.thread.switch({ threadId: thread.id });
26970
+ return;
26971
+ } catch (error) {
26972
+ if (error instanceof _mastra_code_sdk_utils_thread_lock.ThreadLockError) continue;
26973
+ throw error;
26974
+ }
26975
+ }
26976
+ state.pendingNewThread = true;
26977
+ }
26978
+ /**
26979
+ * Leave the session waiting for a new thread. Session creation may already have
26980
+ * bound the latest project thread; unbind it (and release its lock) so the UI
26981
+ * and exit hint don't show a thread the user didn't ask for.
26982
+ */
26983
+ async function startPendingNewThread(state) {
26984
+ state.session.thread.cleanupSubscription();
26985
+ await state.session.thread.clearAndReleaseLock();
26986
+ state.pendingNewThread = true;
26987
+ }
26988
+ async function cloneDriftedThread(state) {
26989
+ const currentPath = state.projectInfo.rootPath;
26990
+ const currentResourceId = state.session.identity.getResourceId();
26991
+ const thread = [...(await state.session.thread.list({
26992
+ allResources: true,
26993
+ metadata: { projectPath: currentPath }
26994
+ })).filter((thread) => thread.resourceId !== currentResourceId)].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())[0];
26995
+ if (!thread) return false;
26996
+ if (await askModalQuestion(state.ui, {
26997
+ question: [
26998
+ "This directory is tagged on a different resource.",
26999
+ "",
27000
+ `Project: ${currentPath}`,
27001
+ `Thread: ${thread.title || thread.id}`,
27002
+ `Old resource: ${thread.resourceId}`,
27003
+ `Current resource: ${currentResourceId}`,
27004
+ "",
27005
+ "Clone this thread into the current resource and resume the clone?"
27006
+ ].join("\n"),
27007
+ options: [{ label: "Clone and resume" }, { label: "Start fresh" }],
27008
+ selectedOptionLabel: "Clone and resume",
27009
+ allowCustomResponse: false,
27010
+ overlay: {
27011
+ widthPercent: 80,
27012
+ maxHeight: "70%"
27013
+ }
27014
+ }) !== "Clone and resume") return false;
27015
+ const progress = new SimpleProgressComponent({
27016
+ showElapsed: false,
27017
+ showPercentage: false
27018
+ });
27019
+ progress.start("Cloning thread into the current resource...");
27020
+ showModalOverlay(state.ui, progress, {
27021
+ widthPercent: 70,
27022
+ maxHeight: "40%",
27023
+ minHeightPercent: .35
27024
+ });
27025
+ state.ui.requestRender();
27026
+ try {
27027
+ await new Promise((resolve) => setTimeout(resolve, 50));
27028
+ progress.updateStatus("Loading cloned thread...");
27029
+ state.ui.requestRender();
27030
+ await state.session.thread.cloneToCurrentResource({
27031
+ threadId: thread.id,
27032
+ expectedResourceId: thread.resourceId,
27033
+ expectedProjectPath: currentPath
27034
+ });
27035
+ } finally {
27036
+ state.ui.hideOverlay();
27037
+ state.ui.requestRender();
27038
+ }
27039
+ return true;
27040
+ }
27041
+ //#endregion
26987
27042
  //#region src/tui/mastra-tui.ts
26988
27043
  /**
26989
27044
  * Main TUI class for Mastra Code.
@@ -27131,6 +27186,7 @@ var MastraTUI = class MastraTUI {
27131
27186
  clearFinishedBackgroundActivities: () => this.clearFinishedBackgroundActivity()
27132
27187
  } : {}
27133
27188
  });
27189
+ this.installUserInputHandler();
27134
27190
  }
27135
27191
  getCurrentThreadBackgroundActivities() {
27136
27192
  return getBackgroundActivitiesForTarget(this.state.backgroundActivities, this.state.session.identity.getResourceId(), this.state.pendingNewThread ? null : this.state.session.thread.getId());
@@ -27380,6 +27436,10 @@ var MastraTUI = class MastraTUI {
27380
27436
  updateStatusLine(this.state);
27381
27437
  flushRender(this.state);
27382
27438
  }
27439
+ /** The thread to suggest resuming on exit, or null while waiting for a new thread. */
27440
+ getResumeThreadId() {
27441
+ return this.state.pendingNewThread ? null : this.state.session.thread.getId();
27442
+ }
27383
27443
  /**
27384
27444
  * Stop the TUI and clean up.
27385
27445
  */
@@ -27453,7 +27513,8 @@ var MastraTUI = class MastraTUI {
27453
27513
  doubleCtrlCMs: MastraTUI.DOUBLE_CTRL_C_MS
27454
27514
  });
27455
27515
  this.state.ui.start();
27456
- await promptForThreadSelection(this.state);
27516
+ const startupResumeIssue = await resumeThreadOnStartup(this.state, this.state.options.resumeThreadId);
27517
+ await this.syncThreadActivePackMetadata();
27457
27518
  subscribeToAgentController(this.state, (event) => this.handleEvent(event));
27458
27519
  if (this.state.options.backgroundToolsEnabled) this.cleanupBackgroundCompletionListener = this.state.options.backgroundCompletionEvents?.subscribe((event) => this.handleBackgroundCompletion(event));
27459
27520
  if (this.state.session.state.get()?.escapeAsCancel === false) this.state.editor.escapeEnabled = false;
@@ -27471,6 +27532,8 @@ var MastraTUI = class MastraTUI {
27471
27532
  await renderExistingTasks(this.state);
27472
27533
  if (this.shouldShowOnboarding()) await this.showOnboarding();
27473
27534
  await this.showQuietModePreferencePromptIfNeeded();
27535
+ if (startupResumeIssue?.kind === "missing") showError(this.state, `Thread not found: ${startupResumeIssue.threadId}. Started a new thread; use /threads to pick an existing one.`);
27536
+ else if (startupResumeIssue?.kind === "locked") showThreadLockPrompt(this.buildCommandContext(), startupResumeIssue.title, startupResumeIssue.ownerPid, startupResumeIssue.threadId);
27474
27537
  this.checkForUpdate().catch(() => {});
27475
27538
  this.updateCheckTimer = setInterval(() => {
27476
27539
  this.checkForUpdate(true);
@@ -27780,49 +27843,52 @@ var MastraTUI = class MastraTUI {
27780
27843
  }
27781
27844
  insertChatComponentWithBoundarySpacing(this.state.chatContainer, child);
27782
27845
  }
27783
- getUserInput() {
27784
- const queued = this.queuedUserInput.shift();
27785
- if (queued !== void 0) return Promise.resolve(queued);
27786
- return new Promise((resolve) => {
27787
- this.pendingUserInputResolve = resolve;
27788
- this.state.editor.onSubmit = (text) => {
27789
- if (isGoalJudgeInputLocked(this.state)) {
27790
- this.state.editor.setText(text);
27791
- showGoalJudgeInputLockInfo(this.state);
27792
- flushRender(this.state);
27846
+ installUserInputHandler() {
27847
+ this.state.editor.onSubmit = (text) => {
27848
+ if (isGoalJudgeInputLocked(this.state)) {
27849
+ this.state.editor.setText(text);
27850
+ showGoalJudgeInputLockInfo(this.state);
27851
+ flushRender(this.state);
27852
+ return;
27853
+ }
27854
+ if (text.trim()) this.state.editor.addToHistory(text);
27855
+ this.state.editor.setText("");
27856
+ if (this.state.session.run.isRunning()) {
27857
+ if (text.startsWith("/")) {
27858
+ this.handleSlashCommand(text).catch((error) => {
27859
+ showError(this.state, error instanceof Error ? error.message : "Slash command failed");
27860
+ });
27793
27861
  return;
27794
27862
  }
27795
- if (text.trim()) this.state.editor.addToHistory(text);
27796
- this.state.editor.setText("");
27797
- if (this.state.session.run.isRunning()) {
27798
- if (text.startsWith("/")) {
27799
- this.handleSlashCommand(text).catch((error) => {
27800
- showError(this.state, error instanceof Error ? error.message : "Slash command failed");
27801
- });
27802
- return;
27803
- }
27804
- if (text.startsWith("!")) {
27805
- handleShellPassthrough(this.state, text.slice(1).trim());
27806
- return;
27807
- }
27808
- const { content, images } = consumePendingImages(text, this.state.pendingImages);
27809
- this.state.pendingImages = [];
27810
- if (images?.length) {
27811
- this.state.pendingImages = images;
27812
- this.queueFollowUpMessage(text);
27813
- return;
27814
- }
27815
- this.signalMessage(content);
27863
+ if (text.startsWith("!")) {
27864
+ handleShellPassthrough(this.state, text.slice(1).trim());
27816
27865
  return;
27817
27866
  }
27818
- const pending = this.pendingUserInputResolve;
27819
- if (!pending) {
27820
- this.queuedUserInput.push(text);
27867
+ const { content, images } = consumePendingImages(text, this.state.pendingImages);
27868
+ this.state.pendingImages = [];
27869
+ if (images?.length) {
27870
+ this.state.pendingImages = images;
27871
+ this.queueFollowUpMessage(text);
27821
27872
  return;
27822
27873
  }
27823
- this.pendingUserInputResolve = void 0;
27824
- pending(text);
27825
- };
27874
+ this.signalMessage(content);
27875
+ return;
27876
+ }
27877
+ const pending = this.pendingUserInputResolve;
27878
+ if (!pending) {
27879
+ this.queuedUserInput.push(text);
27880
+ return;
27881
+ }
27882
+ this.pendingUserInputResolve = void 0;
27883
+ pending(text);
27884
+ };
27885
+ }
27886
+ getUserInput() {
27887
+ const queued = this.queuedUserInput.shift();
27888
+ if (queued !== void 0) return Promise.resolve(queued);
27889
+ return new Promise((resolve) => {
27890
+ this.pendingUserInputResolve = resolve;
27891
+ if (!this.state.editor.onSubmit) this.installUserInputHandler();
27826
27892
  });
27827
27893
  }
27828
27894
  /**
@@ -28379,4 +28445,4 @@ Object.defineProperty(exports, "theme", {
28379
28445
  }
28380
28446
  });
28381
28447
 
28382
- //# sourceMappingURL=tui-Be7-xm5L.cjs.map
28448
+ //# sourceMappingURL=tui-CPJv6T_R.cjs.map