@oh-my-pi/pi-coding-agent 15.12.1 → 15.12.3

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.
@@ -178,7 +178,12 @@ export class InputController {
178
178
  } else if (this.ctx.session.isStreaming) {
179
179
  this.ctx.notifyInterrupting();
180
180
  void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
181
- } else if (!this.ctx.editor.getText().trim()) {
181
+ } else if (this.ctx.editor.getText().trim()) {
182
+ // Esc with typed text clears the draft instead of (or before) any double-Esc action
183
+ this.ctx.editor.setText("");
184
+ this.ctx.ui.requestRender();
185
+ this.ctx.lastEscapeTime = 0;
186
+ } else {
182
187
  // Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
183
188
  const action = settings.get("doubleEscapeAction");
184
189
  if (action !== "none") {
@@ -189,6 +194,7 @@ export class InputController {
189
194
  } else {
190
195
  this.ctx.showUserMessageSelector();
191
196
  }
197
+ this.ctx.ui.resetDisplay();
192
198
  this.ctx.lastEscapeTime = 0;
193
199
  } else {
194
200
  this.ctx.lastEscapeTime = now;
@@ -1139,9 +1139,24 @@ export class AgentSession {
1139
1139
  if (this.#promptInFlightCount === 0) {
1140
1140
  this.#releasePowerAssertion();
1141
1141
  this.#flushPendingAgentEnd();
1142
+ this.#drainStrandedQueuedMessages();
1142
1143
  }
1143
1144
  }
1144
1145
 
1146
+ /** A steer/follow-up can land after the agent loop's final queue poll but
1147
+ * before the prompt unwinds: #promptInFlightCount keeps isStreaming true
1148
+ * through post-prompt recovery, so senders (collab guests, skills) still
1149
+ * queue via agent.steer()/followUp() instead of starting a fresh prompt.
1150
+ * Without a drain those messages strand invisibly until the next manual
1151
+ * prompt. Runs when the session settles; the guard makes it a no-op when
1152
+ * the queue was consumed normally or a new turn already started. */
1153
+ #drainStrandedQueuedMessages(): void {
1154
+ if (!this.agent.hasQueuedMessages()) return;
1155
+ this.#scheduleAgentContinue({
1156
+ shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
1157
+ });
1158
+ }
1159
+
1145
1160
  #resetInFlight(): void {
1146
1161
  this.#promptInFlightCount = 0;
1147
1162
  this.#releasePowerAssertion();
@@ -5255,6 +5270,14 @@ export class AgentSession {
5255
5270
  } else {
5256
5271
  this.agent.steer(normalizedAppMessage);
5257
5272
  }
5273
+ // The isStreaming check above can be stale: image normalization is
5274
+ // awaited, so the turn may have ended in between, leaving the message
5275
+ // queued on an idle agent. Mirror #queueSteer's idle-path delivery.
5276
+ if (this.#canAutoContinueForFollowUp()) {
5277
+ this.#scheduleAgentContinue({
5278
+ shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
5279
+ });
5280
+ }
5258
5281
  return;
5259
5282
  }
5260
5283
 
@@ -512,6 +512,14 @@ function buildChangeBody(groups: string[][], expanded: boolean, budget: number,
512
512
  return lines;
513
513
  }
514
514
 
515
+ /** One-line header preview of an AST pattern. `renderStatusLine` only flattens
516
+ * CR/LF, so a multi-line tab-indented pattern would otherwise punch raw tabs
517
+ * into the status line; collapse all whitespace runs to single spaces. */
518
+ function patternPreview(pat: string | undefined): string | undefined {
519
+ const collapsed = pat?.replace(/\s+/g, " ").trim();
520
+ return collapsed || undefined;
521
+ }
522
+
515
523
  export const astEditToolRenderer = {
516
524
  inline: true,
517
525
  renderCall(args: AstEditRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
@@ -520,7 +528,8 @@ export const astEditToolRenderer = {
520
528
  const rewriteCount = args.ops?.length ?? 0;
521
529
  if (rewriteCount > 1) meta.push(`${rewriteCount} rewrites`);
522
530
 
523
- const description = rewriteCount === 1 ? args.ops?.[0]?.pat : rewriteCount ? `${rewriteCount} rewrites` : "?";
531
+ const description =
532
+ rewriteCount === 1 ? patternPreview(args.ops?.[0]?.pat) : rewriteCount ? `${rewriteCount} rewrites` : "?";
524
533
  const header = renderStatusLine({ icon: "pending", title: "AST Edit", description, meta }, uiTheme);
525
534
  // Pending call has no body yet — a lone status line is sleeker than an empty frame.
526
535
  return new Text(header, 0, 0);
@@ -553,7 +562,7 @@ export const astEditToolRenderer = {
553
562
 
554
563
  if (totalReplacements === 0) {
555
564
  const rewriteCount = args?.ops?.length ?? 0;
556
- const description = rewriteCount === 1 ? args?.ops?.[0]?.pat : undefined;
565
+ const description = rewriteCount === 1 ? patternPreview(args?.ops?.[0]?.pat) : undefined;
557
566
  const meta = ["0 replacements"];
558
567
  if (details?.scopePath) meta.push(`in ${details.scopePath}`);
559
568
  if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
@@ -578,7 +587,7 @@ export const astEditToolRenderer = {
578
587
  meta.push(`searched ${filesSearched}`);
579
588
  if (limitReached) meta.push(uiTheme.fg("warning", "limit reached"));
580
589
  const rewriteCount = args?.ops?.length ?? 0;
581
- const description = rewriteCount === 1 ? args?.ops?.[0]?.pat : undefined;
590
+ const description = rewriteCount === 1 ? patternPreview(args?.ops?.[0]?.pat) : undefined;
582
591
 
583
592
  const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
584
593
  const allLines = textContent.split("\n");