@phi-code-admin/phi-code 0.96.1 → 0.97.1

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.
@@ -20,7 +20,7 @@ import { DefaultPackageManager } from "../../core/package-manager.js";
20
20
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
21
21
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
22
22
  import { SessionManager } from "../../core/session-manager.js";
23
- import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
23
+ import { BUILTIN_SLASH_COMMANDS, matchBareBuiltinWithArgs } from "../../core/slash-commands.js";
24
24
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
25
25
  import { copyToClipboard } from "../../utils/clipboard.js";
26
26
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
@@ -123,6 +123,12 @@ export class InteractiveMode {
123
123
  version;
124
124
  isInitialized = false;
125
125
  onInputCallback;
126
+ // Submissions typed while the main loop was busy (prompt preflight) or a
127
+ // session rebuild was in flight (/new). Queued and replayed instead of
128
+ // silently dropped — a prompt typed right after /new used to disappear
129
+ // with no feedback.
130
+ pendingSubmissions = [];
131
+ sessionTransitioning = false;
126
132
  // Input mode: "act" sends messages to the agent directly; "plan" routes the next
127
133
  // message through the /plan orchestrator (5 sequential phases, one model per phase).
128
134
  // Toggled with Tab (when the editor is empty); shown by the footer Act/Plan indicator.
@@ -2098,9 +2104,17 @@ export class InteractiveMode {
2098
2104
  return;
2099
2105
  }
2100
2106
  if (text === "/debug") {
2101
- this.handleDebugCommand();
2102
- this.editor.setText("");
2103
- return;
2107
+ // An extension may own /debug (the debug orchestrator does). The TUI
2108
+ // debug overlay only claims the bare form when nothing else does —
2109
+ // otherwise it would silently shadow the extension command.
2110
+ const extensionOwnsDebug = this.session.extensionRunner
2111
+ .getRegisteredCommands()
2112
+ .some((command) => command.name === "debug" || command.invocationName === "debug");
2113
+ if (!extensionOwnsDebug) {
2114
+ this.handleDebugCommand();
2115
+ this.editor.setText("");
2116
+ return;
2117
+ }
2104
2118
  }
2105
2119
  if (text === "/arminsayshi") {
2106
2120
  this.handleArminSaysHi();
@@ -2122,6 +2136,18 @@ export class InteractiveMode {
2122
2136
  await this.shutdown();
2123
2137
  return;
2124
2138
  }
2139
+ // A bare builtin invoked with arguments used to leak to the model as
2140
+ // prose ("/new please"). Surface it instead — unless an extension
2141
+ // registered a command with that name (it then owns the arguments).
2142
+ const bareWithArgs = matchBareBuiltinWithArgs(text);
2143
+ if (bareWithArgs &&
2144
+ !this.session.extensionRunner
2145
+ .getRegisteredCommands()
2146
+ .some((command) => command.name === bareWithArgs || command.invocationName === bareWithArgs)) {
2147
+ this.showWarning(`/${bareWithArgs} takes no arguments. Run \`/${bareWithArgs}\` alone.`);
2148
+ this.editor.setText(text);
2149
+ return;
2150
+ }
2125
2151
  // Handle bash command (! for normal, !! for excluded from context)
2126
2152
  if (text.startsWith("!")) {
2127
2153
  const isExcluded = text.startsWith("!!");
@@ -2164,7 +2190,14 @@ export class InteractiveMode {
2164
2190
  // Normal message submission
2165
2191
  // First, move any pending bash components to chat
2166
2192
  this.flushPendingBashComponents();
2167
- if (this.onInputCallback) {
2193
+ if (this.sessionTransitioning || !this.onInputCallback) {
2194
+ // The main loop is busy (prompt preflight) or the session is being
2195
+ // rebuilt (/new in flight): queue and replay once ready instead of
2196
+ // dropping the message on the floor.
2197
+ this.pendingSubmissions.push(text);
2198
+ this.showStatus("Message queued — sending when the session is ready");
2199
+ }
2200
+ else {
2168
2201
  this.onInputCallback(text);
2169
2202
  }
2170
2203
  this.editor.addToHistory?.(text);
@@ -2650,6 +2683,14 @@ export class InteractiveMode {
2650
2683
  }
2651
2684
  }
2652
2685
  async getUserInput() {
2686
+ // Replay submissions queued while the loop was busy — but never during a
2687
+ // session rebuild (drainPendingSubmissions fires when it completes).
2688
+ if (!this.sessionTransitioning) {
2689
+ const queued = this.pendingSubmissions.shift();
2690
+ if (queued !== undefined) {
2691
+ return queued;
2692
+ }
2693
+ }
2653
2694
  return new Promise((resolve) => {
2654
2695
  this.onInputCallback = (text) => {
2655
2696
  this.onInputCallback = undefined;
@@ -2657,6 +2698,20 @@ export class InteractiveMode {
2657
2698
  };
2658
2699
  });
2659
2700
  }
2701
+ /**
2702
+ * Hand one queued submission to the waiting main loop, if any. Called when
2703
+ * a session transition completes; the loop drains the rest through
2704
+ * getUserInput() as it comes back around.
2705
+ */
2706
+ drainPendingSubmissions() {
2707
+ if (this.sessionTransitioning)
2708
+ return;
2709
+ const callback = this.onInputCallback;
2710
+ if (!callback || this.pendingSubmissions.length === 0)
2711
+ return;
2712
+ const next = this.pendingSubmissions.shift();
2713
+ callback(next);
2714
+ }
2660
2715
  rebuildChatFromMessages() {
2661
2716
  this.chatContainer.clear();
2662
2717
  const context = this.sessionManager.buildSessionContext();
@@ -4530,6 +4585,9 @@ export class InteractiveMode {
4530
4585
  this.loadingAnimation = undefined;
4531
4586
  }
4532
4587
  this.statusContainer.clear();
4588
+ // While the runtime rebuilds, submissions are queued (not sent into the
4589
+ // half-torn-down session) and replayed by the finally below.
4590
+ this.sessionTransitioning = true;
4533
4591
  try {
4534
4592
  const result = await this.runtimeHost.newSession();
4535
4593
  if (result.cancelled) {
@@ -4543,6 +4601,10 @@ export class InteractiveMode {
4543
4601
  catch (error) {
4544
4602
  await this.handleFatalRuntimeError("Failed to create session", error);
4545
4603
  }
4604
+ finally {
4605
+ this.sessionTransitioning = false;
4606
+ this.drainPendingSubmissions();
4607
+ }
4546
4608
  }
4547
4609
  handleDebugCommand() {
4548
4610
  const width = this.ui.terminal.columns;