@hydra-acp/cli 0.1.37 → 0.1.38

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.
package/dist/cli.js CHANGED
@@ -308,7 +308,12 @@ var init_config = __esm({
308
308
  // "enqueue" — flips the two: Enter enqueues the prompt (sends
309
309
  // immediately when idle, queues behind an in-flight turn);
310
310
  // Shift+Enter amends the in-flight turn.
311
- defaultEnterAction: z.enum(["enqueue", "amend"]).default("amend")
311
+ defaultEnterAction: z.enum(["enqueue", "amend"]).default("amend"),
312
+ // When true (default), agent_thought events render as dim italic
313
+ // streaming lines beneath the live thinking block. Set false to
314
+ // suppress them — the TUI hotkey ^T toggles this at runtime without
315
+ // persisting back to config.
316
+ showThoughts: z.boolean().default(true)
312
317
  });
313
318
  ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
314
319
  ExtensionBody = z.object({
@@ -353,7 +358,8 @@ var init_config = __esm({
353
358
  logMaxBytes: 5 * 1024 * 1024,
354
359
  cwdColumnMaxWidth: 24,
355
360
  progressIndicator: true,
356
- defaultEnterAction: "amend"
361
+ defaultEnterAction: "amend",
362
+ showThoughts: true
357
363
  })
358
364
  });
359
365
  }
@@ -2462,15 +2468,15 @@ var init_session = __esm({
2462
2468
  return count;
2463
2469
  }
2464
2470
  broadcastQueueAdded(entry, options) {
2465
- const depth = this.visibleQueueDepth();
2466
- const position = options?.position ?? Math.max(0, depth - 1);
2471
+ const depth2 = this.visibleQueueDepth();
2472
+ const position = options?.position ?? Math.max(0, depth2 - 1);
2467
2473
  const params = {
2468
2474
  sessionId: this.sessionId,
2469
2475
  messageId: entry.messageId,
2470
2476
  originator: entry.originator,
2471
2477
  prompt: entry.prompt,
2472
2478
  position,
2473
- queueDepth: depth,
2479
+ queueDepth: depth2,
2474
2480
  enqueuedAt: entry.enqueuedAt
2475
2481
  };
2476
2482
  if (options?.amending !== void 0) {
@@ -5224,6 +5230,9 @@ var init_input = __esm({
5224
5230
  case "ctrl-p":
5225
5231
  return [{ type: "switch-session" }];
5226
5232
  case "ctrl-t":
5233
+ return [{ type: "toggle-thoughts" }];
5234
+ case "alt-n":
5235
+ case "alt-tab":
5227
5236
  return [{ type: "next-live-session" }];
5228
5237
  case "ctrl-r":
5229
5238
  return this.startHistorySearch();
@@ -5928,6 +5937,38 @@ var init_attachments = __esm({
5928
5937
  }
5929
5938
  });
5930
5939
 
5940
+ // src/tui/sync.ts
5941
+ function beginSync() {
5942
+ if (depth === 0) {
5943
+ process.stdout.write("\x1B[?2026h");
5944
+ }
5945
+ depth++;
5946
+ }
5947
+ function endSync() {
5948
+ if (depth === 0) {
5949
+ return;
5950
+ }
5951
+ depth--;
5952
+ if (depth === 0) {
5953
+ process.stdout.write("\x1B[?2026l");
5954
+ }
5955
+ }
5956
+ function withSync(fn) {
5957
+ beginSync();
5958
+ try {
5959
+ fn();
5960
+ } finally {
5961
+ endSync();
5962
+ }
5963
+ }
5964
+ var depth;
5965
+ var init_sync = __esm({
5966
+ "src/tui/sync.ts"() {
5967
+ "use strict";
5968
+ depth = 0;
5969
+ }
5970
+ });
5971
+
5931
5972
  // src/tui/screen.ts
5932
5973
  import stringWidth from "string-width";
5933
5974
  import wrapAnsi from "wrap-ansi";
@@ -6410,6 +6451,12 @@ function mapKeyName(name) {
6410
6451
  case "ALT_F":
6411
6452
  case "META_F":
6412
6453
  return "alt-f";
6454
+ case "ALT_N":
6455
+ case "META_N":
6456
+ return "alt-n";
6457
+ case "ALT_TAB":
6458
+ case "META_TAB":
6459
+ return "alt-tab";
6413
6460
  case "CTRL_T":
6414
6461
  return "ctrl-t";
6415
6462
  case "SHIFT_TAB":
@@ -6551,6 +6598,9 @@ function mapCsiUToKeyName(code, mod) {
6551
6598
  if (mod === 2) {
6552
6599
  return "shift-tab";
6553
6600
  }
6601
+ if (mod === 3) {
6602
+ return "alt-tab";
6603
+ }
6554
6604
  if (mod === 1) {
6555
6605
  return "tab";
6556
6606
  }
@@ -6581,6 +6631,9 @@ function mapCsiUToKeyName(code, mod) {
6581
6631
  if (code === 102 || code === 70) {
6582
6632
  return "alt-f";
6583
6633
  }
6634
+ if (code === 110 || code === 78) {
6635
+ return "alt-n";
6636
+ }
6584
6637
  return null;
6585
6638
  }
6586
6639
  return null;
@@ -6593,6 +6646,7 @@ var init_screen = __esm({
6593
6646
  init_paths();
6594
6647
  init_session();
6595
6648
  init_attachments();
6649
+ init_sync();
6596
6650
  SESSIONBAR_ROWS = 1;
6597
6651
  BANNER_ROWS = 1;
6598
6652
  SEPARATOR_ROWS = 1;
@@ -6619,6 +6673,11 @@ var init_screen = __esm({
6619
6673
  // the starts of any later keyed blocks if the size changes.
6620
6674
  keyedBlocks = /* @__PURE__ */ new Map();
6621
6675
  streamingActive = false;
6676
+ // When true, lines with bodyStyle="thought" are skipped at draw time
6677
+ // (they remain in `this.lines` so toggling back on reveals them again).
6678
+ // Set via setHideThoughts; the app drives this from the ^T hotkey and
6679
+ // the tui.showThoughts config.
6680
+ hideThoughts = false;
6622
6681
  lastPromptRows = 0;
6623
6682
  queuedTexts = [];
6624
6683
  lastQueueEditingIndex = -1;
@@ -6746,12 +6805,23 @@ var init_screen = __esm({
6746
6805
  this.mouseHandler = (name) => this.handleMouse(name);
6747
6806
  this.rawStdinHandler = (chunk) => this.handleRawStdin(chunk);
6748
6807
  }
6749
- start() {
6808
+ // Starts (or resumes) the screen's painting + input pipeline. When
6809
+ // called fresh from the process entrypoint (no opts), enters the
6810
+ // alternate screen buffer and saves the host shell's cursor for
6811
+ // later restore. When resuming from a picker that ran with
6812
+ // `keepFullscreen: true` (`skipFullscreen: true`), we don't toggle
6813
+ // fullscreen — re-emitting CSI ? 1049 h while already in alt would
6814
+ // (a) clear the alt buffer, briefly showing black before our repaint
6815
+ // lands, and (b) overwrite the cursor save with the picker's last
6816
+ // cursor position, which then becomes wrong on final exit.
6817
+ start(opts = {}) {
6750
6818
  if (this.started) {
6751
6819
  return;
6752
6820
  }
6753
6821
  this.started = true;
6754
- this.term.fullscreen(true);
6822
+ if (!opts.skipFullscreen) {
6823
+ this.term.fullscreen(true);
6824
+ }
6755
6825
  this.lastFrameRows.clear();
6756
6826
  this.lastFrameW = 0;
6757
6827
  this.lastFrameH = 0;
@@ -6774,7 +6844,16 @@ var init_screen = __esm({
6774
6844
  this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
6775
6845
  this.repaint();
6776
6846
  }
6777
- stop() {
6847
+ // Stops the screen's painting + input pipeline. When called from the
6848
+ // process-exit path (no opts), also leaves the alternate screen buffer
6849
+ // and re-enables auto-wrap so the host shell behaves normally. When
6850
+ // entering the session picker (`keepFullscreen: true`), we skip the
6851
+ // alt-screen toggle so the user doesn't see a frame of the host
6852
+ // shell's main-buffer content flash between the live session
6853
+ // tearing down and the picker painting from row 1 — the picker's
6854
+ // moveTo(1,1) + eraseDisplayBelow simply repaints over the same alt
6855
+ // screen buffer the live session was using.
6856
+ stop(opts = {}) {
6778
6857
  if (!this.started) {
6779
6858
  return;
6780
6859
  }
@@ -6795,11 +6874,15 @@ var init_screen = __esm({
6795
6874
  this.term.off("resize", this.resizeHandler);
6796
6875
  this.term.grabInput(false);
6797
6876
  this.term.hideCursor(false);
6798
- process.stdout.write("\x1B[?7h");
6877
+ if (!opts.keepFullscreen) {
6878
+ process.stdout.write("\x1B[?7h");
6879
+ }
6799
6880
  this.writeProgressIndicator(0);
6800
6881
  this.started = false;
6801
- this.term.fullscreen(false);
6802
- this.term("\n");
6882
+ if (!opts.keepFullscreen) {
6883
+ this.term.fullscreen(false);
6884
+ this.term("\n");
6885
+ }
6803
6886
  }
6804
6887
  // Enables bracketed paste mode + modifyOtherKeys on the terminal and
6805
6888
  // rewires stdin so we see the \x1b[200~/\x1b[201~ paste markers and
@@ -7233,8 +7316,27 @@ uncaught: ${err.stack ?? err.message}
7233
7316
  setBanner(banner) {
7234
7317
  this.banner = { ...this.banner, ...banner };
7235
7318
  this.writeProgressIndicator(this.banner.status === "busy" ? 3 : 0);
7236
- this.drawBanner();
7237
- this.placeCursor();
7319
+ this.syncedPartialRepaint(() => this.drawBanner());
7320
+ }
7321
+ // Wrap a partial repaint (banner-only, indicator-only, etc.) in a
7322
+ // synchronized-output bracket so the row swap is atomic on terminals
7323
+ // that support DEC 2026, and hide the cursor across the paint so it
7324
+ // doesn't visibly jump to the row being repainted before placeCursor
7325
+ // snaps it back. placeCursor re-asserts visibility for normal /
7326
+ // scrollback-search / readonly; modal modes only moveTo, so we
7327
+ // re-show explicitly when one of them is active.
7328
+ syncedPartialRepaint(paint) {
7329
+ if (!this.started) {
7330
+ return;
7331
+ }
7332
+ withSync(() => {
7333
+ this.term.hideCursor();
7334
+ paint();
7335
+ this.placeCursor();
7336
+ if (this.permissionPrompt || this.confirmPrompt || this.helpPrompt) {
7337
+ this.term.hideCursor(false);
7338
+ }
7339
+ });
7238
7340
  }
7239
7341
  currentModeId() {
7240
7342
  return this.banner.currentMode;
@@ -7273,11 +7375,9 @@ uncaught: ${err.stack ?? err.message}
7273
7375
  this.bannerNotificationTimer = setTimeout(() => {
7274
7376
  this.bannerNotification = null;
7275
7377
  this.bannerNotificationTimer = null;
7276
- this.drawBanner();
7277
- this.placeCursor();
7378
+ this.syncedPartialRepaint(() => this.drawBanner());
7278
7379
  }, durationMs);
7279
- this.drawBanner();
7280
- this.placeCursor();
7380
+ this.syncedPartialRepaint(() => this.drawBanner());
7281
7381
  }
7282
7382
  // Runtime toggle for terminal mouse capture. With capture on, the
7283
7383
  // wheel drives scrollback but text selection requires shift+drag
@@ -7338,8 +7438,7 @@ uncaught: ${err.stack ?? err.message}
7338
7438
  return;
7339
7439
  }
7340
7440
  this.bannerSearchIndicator = text;
7341
- this.drawBanner();
7342
- this.placeCursor();
7441
+ this.syncedPartialRepaint(() => this.drawBanner());
7343
7442
  }
7344
7443
  // Computes what (if anything) the right-side banner slot should show
7345
7444
  // this paint. Priority: scrollback search term > prompt-history
@@ -7370,6 +7469,17 @@ uncaught: ${err.stack ?? err.message}
7370
7469
  this.scrollOffset = 0;
7371
7470
  this.repaint();
7372
7471
  }
7472
+ // Toggle visibility of agent-thought lines without removing them from
7473
+ // storage. Idempotent — repeated calls with the same value are no-ops.
7474
+ // Reveals are immediate (a repaint runs) so the user sees thoughts
7475
+ // appear / disappear the moment they press ^T.
7476
+ setHideThoughts(hide) {
7477
+ if (this.hideThoughts === hide) {
7478
+ return;
7479
+ }
7480
+ this.hideThoughts = hide;
7481
+ this.repaint();
7482
+ }
7373
7483
  // Forget an upsert key without touching scrollback. The next upsertLines
7374
7484
  // for this key will append at the bottom instead of splicing in place —
7375
7485
  // used to scope a logical block (e.g. an agent's plan) to one turn so
@@ -7509,19 +7619,23 @@ uncaught: ${err.stack ?? err.message}
7509
7619
  // otherwise an in-place prompt redraw is enough. (Queued-zone changes
7510
7620
  // already trigger a full repaint via setQueuedPrompts.) Queue-edit
7511
7621
  // navigation may also change which queued row is marked, so check
7512
- // for that and redraw just that zone in-place.
7622
+ // for that and redraw just that zone in-place. Wrap the per-keystroke
7623
+ // paint in withSync + hide the cursor so the user doesn't see it walk
7624
+ // across the prompt row each frame before snapping back to the typing
7625
+ // position; placeCursor + hideCursor(false) restore it at the end.
7513
7626
  refreshPrompt() {
7514
7627
  if (this.promptRows() !== this.lastPromptRows) {
7515
7628
  this.repaint();
7516
7629
  return;
7517
7630
  }
7518
- const editingIndex = this.dispatcher.state().queueIndex;
7519
- if (editingIndex !== this.lastQueueEditingIndex) {
7520
- this.lastQueueEditingIndex = editingIndex;
7521
- this.drawQueuedZone();
7522
- }
7523
- this.drawPrompt();
7524
- this.placeCursor();
7631
+ this.syncedPartialRepaint(() => {
7632
+ const editingIndex = this.dispatcher.state().queueIndex;
7633
+ if (editingIndex !== this.lastQueueEditingIndex) {
7634
+ this.lastQueueEditingIndex = editingIndex;
7635
+ this.drawQueuedZone();
7636
+ }
7637
+ this.drawPrompt();
7638
+ });
7525
7639
  }
7526
7640
  handleKey(name, data) {
7527
7641
  if (data.isCharacter) {
@@ -7854,10 +7968,20 @@ uncaught: ${err.stack ?? err.message}
7854
7968
  }, this.contentRepaintThrottleMs - elapsed);
7855
7969
  }
7856
7970
  // Funnel for every row that any drawX method renders. Skips emitting
7857
- // moveTo+eraseLineAfter+paint when the row's signature matches the
7858
- // previous frame's. The signature must capture everything that affects
7859
- // visible output for that row (width, FormattedLine fields, banner
7860
- // state, etc.) so identical sigs guarantee identical bytes.
7971
+ // moveTo + paint when the row's signature matches the previous frame's.
7972
+ // The signature must capture everything that affects visible output for
7973
+ // that row (width, FormattedLine fields, banner state, etc.) so
7974
+ // identical sigs guarantee identical bytes.
7975
+ //
7976
+ // Order matters: we move, draw the new content over the old, reset SGR,
7977
+ // then erase from the cursor to end of line. Erasing BEFORE paint
7978
+ // blanks the whole row first — visible as a per-row flash on banner
7979
+ // ticks and single-char prompt edits, since some terminals still
7980
+ // render incrementally inside DEC 2026 brackets. Overwriting first
7981
+ // and erasing only the trailing leftovers means the row is never
7982
+ // blank mid-frame. The styleReset stops the trailing erase from
7983
+ // inheriting the paint's last SGR (a bgBlue selection slice, etc.)
7984
+ // and painting the rest of the line in that colour.
7861
7985
  paintRow(row, signature, paint) {
7862
7986
  if (!this.started) {
7863
7987
  return;
@@ -7869,8 +7993,10 @@ uncaught: ${err.stack ?? err.message}
7869
7993
  return;
7870
7994
  }
7871
7995
  this.lastFrameRows.set(row, signature);
7872
- this.term.moveTo(1, row).eraseLineAfter();
7996
+ this.term.moveTo(1, row);
7873
7997
  paint();
7998
+ this.term.styleReset();
7999
+ this.term.eraseLineAfter();
7874
8000
  }
7875
8001
  repaint() {
7876
8002
  if (!this.started) {
@@ -7895,19 +8021,21 @@ uncaught: ${err.stack ?? err.message}
7895
8021
  this.lastFrameW = w;
7896
8022
  this.lastFrameH = h;
7897
8023
  }
7898
- this.drawScrollback();
7899
- this.drawCompletionZone();
7900
- this.drawQueuedZone();
7901
- this.drawAttachmentChipZone();
7902
- const promptRows = this.promptRows();
7903
- const separatorAbovePromptRow = h - promptRows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
7904
- this.drawSeparator(separatorAbovePromptRow);
7905
- this.drawPrompt();
7906
- this.drawBanner();
7907
- this.drawSeparator(h - SESSIONBAR_ROWS);
7908
- this.drawSessionbar();
7909
- this.placeCursor();
7910
- this.lastPromptRows = promptRows;
8024
+ withSync(() => {
8025
+ this.drawScrollback();
8026
+ this.drawCompletionZone();
8027
+ this.drawQueuedZone();
8028
+ this.drawAttachmentChipZone();
8029
+ const promptRows = this.promptRows();
8030
+ const separatorAbovePromptRow = h - promptRows - BANNER_ROWS - SEPARATOR_ROWS - SESSIONBAR_ROWS;
8031
+ this.drawSeparator(separatorAbovePromptRow);
8032
+ this.drawPrompt();
8033
+ this.drawBanner();
8034
+ this.drawSeparator(h - SESSIONBAR_ROWS);
8035
+ this.drawSessionbar();
8036
+ this.placeCursor();
8037
+ this.lastPromptRows = promptRows;
8038
+ });
7911
8039
  }
7912
8040
  drawSessionbar() {
7913
8041
  const w = this.term.width;
@@ -8342,9 +8470,10 @@ uncaught: ${err.stack ?? err.message}
8342
8470
  `\u21E7\u21E5 mode: ${this.banner.currentMode}`
8343
8471
  ) : this.banner.hint;
8344
8472
  this.term(" \xB7 ").dim(hint);
8473
+ this.term.eraseLineAfter();
8345
8474
  if (right) {
8346
8475
  const visibleWidth = stringWidth(right.text);
8347
- const col = Math.max(1, w - visibleWidth + 1);
8476
+ const col = Math.max(1, w - visibleWidth);
8348
8477
  this.term.moveTo(col, row).eraseLineAfter();
8349
8478
  if (right.kind === "search") {
8350
8479
  this.term.brightCyan.noFormat(right.text);
@@ -8437,11 +8566,19 @@ uncaught: ${err.stack ?? err.message}
8437
8566
  // instead of 10k. With `needed = Infinity` this walks everything and
8438
8567
  // doubles as a total-row counter for maxScrollOffset.
8439
8568
  wrapTail(width, needed) {
8569
+ const isThought = (line) => this.hideThoughts && line.bodyStyle === "thought";
8440
8570
  if (width <= 4) {
8441
- const take = Math.min(needed, this.lines.length);
8571
+ const visible = [];
8572
+ for (const line of this.lines) {
8573
+ if (isThought(line)) {
8574
+ continue;
8575
+ }
8576
+ visible.push(line);
8577
+ }
8578
+ const take = Math.min(needed, visible.length);
8442
8579
  return {
8443
- rows: this.lines.slice(this.lines.length - take),
8444
- exhausted: needed >= this.lines.length
8580
+ rows: visible.slice(visible.length - take),
8581
+ exhausted: needed >= visible.length
8445
8582
  };
8446
8583
  }
8447
8584
  if (this.wrapCacheWidth !== width) {
@@ -8454,8 +8591,16 @@ uncaught: ${err.stack ?? err.message}
8454
8591
  const batches = [];
8455
8592
  let total = 0;
8456
8593
  let stoppedAt = 0;
8594
+ let sawOldest = false;
8457
8595
  for (let i = this.lines.length - 1; i >= 0; i--) {
8458
- const wrapped = this.wrapOne(this.lines[i], width);
8596
+ const line = this.lines[i];
8597
+ if (isThought(line)) {
8598
+ if (i === 0) {
8599
+ sawOldest = true;
8600
+ }
8601
+ continue;
8602
+ }
8603
+ const wrapped = this.wrapOne(line, width);
8459
8604
  batches.push(wrapped);
8460
8605
  total += wrapped.length;
8461
8606
  stoppedAt = i;
@@ -8467,7 +8612,7 @@ uncaught: ${err.stack ?? err.message}
8467
8612
  for (let i = batches.length - 1; i >= 0; i--) {
8468
8613
  rows.push(...batches[i]);
8469
8614
  }
8470
- return { rows, exhausted: stoppedAt === 0 };
8615
+ return { rows, exhausted: stoppedAt === 0 || sawOldest };
8471
8616
  }
8472
8617
  wrapOne(line, width) {
8473
8618
  const id = this.lineIds.get(line);
@@ -8655,8 +8800,12 @@ async function pickSession(term, opts) {
8655
8800
  const reserved = 6 + composerRows;
8656
8801
  const maxViewportRows = Math.max(3, termHeight - reserved);
8657
8802
  viewportSize = Math.min(visible.length, maxViewportRows);
8658
- headerLine = formatRow(HEADER, widths, rowMaxWidth, cwdMaxWidth);
8659
- sessionLines = rows.map((r) => formatRow(r, widths, rowMaxWidth, cwdMaxWidth));
8803
+ headerLine = formatRow(HEADER, widths, rowMaxWidth, cwdMaxWidth).padEnd(
8804
+ rowMaxWidth
8805
+ );
8806
+ sessionLines = rows.map(
8807
+ (r) => formatRow(r, widths, rowMaxWidth, cwdMaxWidth).padEnd(rowMaxWidth)
8808
+ );
8660
8809
  };
8661
8810
  const rebuildRows = () => {
8662
8811
  rows = visible.map((s) => toRow(s, Date.now()));
@@ -8777,37 +8926,31 @@ async function pickSession(term, opts) {
8777
8926
  };
8778
8927
  const shortId2 = (sessionId) => stripHydraSessionPrefix(sessionId);
8779
8928
  const paintIndicator = () => {
8780
- term.moveTo(1, indicatorRow()).eraseLineAfter();
8781
- if (mode === "confirm-kill" && pendingAction) {
8782
- term.brightYellow.noFormat(` kill ${shortId2(pendingAction.sessionId)}? [y/N]`);
8783
- return;
8784
- }
8785
- if (mode === "confirm-delete" && pendingAction) {
8786
- term.brightRed.noFormat(` delete ${shortId2(pendingAction.sessionId)}? [y/N]`);
8787
- return;
8788
- }
8789
- if (mode === "busy" && pendingAction) {
8790
- term.dim.noFormat(` working on ${shortId2(pendingAction.sessionId)}\u2026`);
8791
- return;
8792
- }
8793
- if (mode === "rename" && pendingAction) {
8794
- term.brightYellow.noFormat(` title: ${renameBuffer}`);
8795
- term.bgBrightYellow(" ");
8796
- term.dim.noFormat(" Enter saves \xB7 Esc cancels");
8797
- return;
8798
- }
8799
- if (transientStatus !== null) {
8800
- term.dim.noFormat(` ${transientStatus}`);
8801
- return;
8802
- }
8803
- if (searchActive) {
8804
- term.brightYellow.noFormat(` /${searchTerm}`);
8805
- term.bgBrightYellow(" ");
8806
- const hint = visible.length === 0 ? " no matches" : ` ${visible.length} match${visible.length === 1 ? "" : "es"}`;
8807
- term.dim.noFormat(`${hint} \xB7 ^c clears`);
8808
- return;
8809
- }
8810
- term.dim.noFormat(formatIndicator());
8929
+ withSync(() => {
8930
+ term.moveTo(1, indicatorRow());
8931
+ if (mode === "confirm-kill" && pendingAction) {
8932
+ term.brightYellow.noFormat(` kill ${shortId2(pendingAction.sessionId)}? [y/N]`);
8933
+ } else if (mode === "confirm-delete" && pendingAction) {
8934
+ term.brightRed.noFormat(` delete ${shortId2(pendingAction.sessionId)}? [y/N]`);
8935
+ } else if (mode === "busy" && pendingAction) {
8936
+ term.dim.noFormat(` working on ${shortId2(pendingAction.sessionId)}\u2026`);
8937
+ } else if (mode === "rename" && pendingAction) {
8938
+ term.brightYellow.noFormat(` title: ${renameBuffer}`);
8939
+ term.bgBrightYellow(" ");
8940
+ term.dim.noFormat(" Enter saves \xB7 Esc cancels");
8941
+ } else if (transientStatus !== null) {
8942
+ term.dim.noFormat(` ${transientStatus}`);
8943
+ } else if (searchActive) {
8944
+ term.brightYellow.noFormat(` /${searchTerm}`);
8945
+ term.bgBrightYellow(" ");
8946
+ const hint = visible.length === 0 ? " no matches" : ` ${visible.length} match${visible.length === 1 ? "" : "es"}`;
8947
+ term.dim.noFormat(`${hint} \xB7 ^c clears`);
8948
+ } else {
8949
+ term.dim.noFormat(formatIndicator());
8950
+ }
8951
+ term.styleReset();
8952
+ term.eraseLineAfter();
8953
+ });
8811
8954
  };
8812
8955
  const composerBodyRow = (visualOffset) => startRow + 1 + visualOffset;
8813
8956
  const composerBottomRow = () => startRow + composerRows + 1;
@@ -8827,93 +8970,120 @@ async function pickSession(term, opts) {
8827
8970
  renderHelp();
8828
8971
  return;
8829
8972
  }
8830
- computeLayout();
8831
- adjustScroll();
8832
- startRow = 1;
8833
- term.moveTo(1, 1).eraseDisplayBelow();
8834
- paintComposerTopBorder();
8835
- term("\n");
8836
- for (let v = 0; v < composerRows; v++) {
8837
- paintComposerBodyRow(composerWindowStart + v);
8973
+ withSync(() => {
8974
+ term.hideCursor();
8975
+ computeLayout();
8976
+ adjustScroll();
8977
+ startRow = 1;
8978
+ term.moveTo(1, 1).eraseDisplayBelow();
8979
+ paintComposerTopBorder();
8838
8980
  term("\n");
8839
- }
8840
- paintComposerBottomBorder();
8841
- term("\n\n");
8842
- term.dim.noFormat(` ${headerLine}`)("\n");
8843
- for (let v = 0; v < viewportSize; v++) {
8844
- paintSessionRow(scrollOffset + v);
8981
+ for (let v = 0; v < composerRows; v++) {
8982
+ paintComposerBodyRow(composerWindowStart + v);
8983
+ term("\n");
8984
+ }
8985
+ paintComposerBottomBorder();
8986
+ term("\n\n");
8987
+ term.dim.noFormat(` ${headerLine}`)("\n");
8988
+ for (let v = 0; v < viewportSize; v++) {
8989
+ paintSessionRow(scrollOffset + v);
8990
+ term("\n");
8991
+ }
8992
+ paintIndicator();
8845
8993
  term("\n");
8846
- }
8847
- paintIndicator();
8848
- term("\n");
8849
- if (selectedIdx === 0) {
8850
- placeComposerCursor();
8851
- term.hideCursor(false);
8852
- } else {
8853
- term.hideCursor();
8854
- }
8994
+ if (selectedIdx === 0) {
8995
+ placeComposerCursor();
8996
+ term.hideCursor(false);
8997
+ }
8998
+ });
8855
8999
  };
8856
9000
  const renderHelp = () => {
8857
- term.moveTo(1, 1).eraseDisplayBelow();
8858
- term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
8859
- for (const entry of HELP_ENTRIES) {
8860
- if (entry === null) {
8861
- term("\n");
8862
- continue;
9001
+ withSync(() => {
9002
+ term.hideCursor();
9003
+ term.moveTo(1, 1).eraseDisplayBelow();
9004
+ term.brightWhite.bold.noFormat(" Picker hotkeys")("\n\n");
9005
+ for (const entry of HELP_ENTRIES) {
9006
+ if (entry === null) {
9007
+ term("\n");
9008
+ continue;
9009
+ }
9010
+ const [keys, desc] = entry;
9011
+ term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
9012
+ term.noFormat(desc)("\n");
8863
9013
  }
8864
- const [keys, desc] = entry;
8865
- term.brightCyan.noFormat(` ${keys.padEnd(HELP_KEYS_WIDTH)}`);
8866
- term.noFormat(desc)("\n");
8867
- }
8868
- term("\n");
8869
- term.dim.noFormat(" press any key to dismiss")("\n");
9014
+ term("\n");
9015
+ term.dim.noFormat(" press any key to dismiss")("\n");
9016
+ });
8870
9017
  };
8871
9018
  const repaintComposerChrome = () => {
8872
- term.moveTo(1, startRow).eraseLineAfter();
8873
- paintComposerTopBorder();
8874
- term.moveTo(1, composerBottomRow()).eraseLineAfter();
8875
- paintComposerBottomBorder();
8876
- for (let v = 0; v < composerRows; v++) {
8877
- term.moveTo(1, composerBodyRow(v)).eraseLineAfter();
8878
- paintComposerBodyRow(composerWindowStart + v);
8879
- }
9019
+ withSync(() => {
9020
+ const showCursor = selectedIdx === 0;
9021
+ if (showCursor) {
9022
+ term.hideCursor();
9023
+ }
9024
+ term.moveTo(1, startRow);
9025
+ paintComposerTopBorder();
9026
+ term.moveTo(1, composerBottomRow());
9027
+ paintComposerBottomBorder();
9028
+ for (let v = 0; v < composerRows; v++) {
9029
+ term.moveTo(1, composerBodyRow(v));
9030
+ paintComposerBodyRow(composerWindowStart + v);
9031
+ }
9032
+ if (showCursor) {
9033
+ placeComposerCursor();
9034
+ term.hideCursor(false);
9035
+ }
9036
+ });
8880
9037
  };
8881
9038
  const repaintComposerBody = () => {
8882
- const state = composer.state();
8883
- composerVisualRows = computePromptVisualRows(state.buffer, composerRoom);
8884
- const layout = computePromptLayout(
8885
- composerVisualRows,
8886
- state,
8887
- PICKER_COMPOSER_MAX_ROWS
8888
- );
8889
- composerWindowStart = layout.windowStart;
8890
- composerCursorRow = layout.cursorVisualRow;
8891
- composerCursorCol = layout.cursorVisualCol;
8892
- for (let v = 0; v < composerRows; v++) {
8893
- term.moveTo(1, composerBodyRow(v)).eraseLineAfter();
8894
- paintComposerBodyRow(composerWindowStart + v);
8895
- }
8896
- if (selectedIdx === 0) {
8897
- placeComposerCursor();
8898
- }
9039
+ withSync(() => {
9040
+ const state = composer.state();
9041
+ composerVisualRows = computePromptVisualRows(state.buffer, composerRoom);
9042
+ const layout = computePromptLayout(
9043
+ composerVisualRows,
9044
+ state,
9045
+ PICKER_COMPOSER_MAX_ROWS
9046
+ );
9047
+ composerWindowStart = layout.windowStart;
9048
+ composerCursorRow = layout.cursorVisualRow;
9049
+ composerCursorCol = layout.cursorVisualCol;
9050
+ const showCursor = selectedIdx === 0;
9051
+ if (showCursor) {
9052
+ term.hideCursor();
9053
+ }
9054
+ for (let v = 0; v < composerRows; v++) {
9055
+ term.moveTo(1, composerBodyRow(v));
9056
+ paintComposerBodyRow(composerWindowStart + v);
9057
+ }
9058
+ if (showCursor) {
9059
+ placeComposerCursor();
9060
+ term.hideCursor(false);
9061
+ }
9062
+ });
8899
9063
  };
8900
9064
  const repaintSessionRow = (sessionIdx) => {
8901
9065
  if (sessionIdx < scrollOffset || sessionIdx >= scrollOffset + viewportSize) {
8902
9066
  return;
8903
9067
  }
8904
- term.moveTo(1, sessionRow(sessionIdx)).eraseLineAfter();
8905
- paintSessionRow(sessionIdx);
9068
+ withSync(() => {
9069
+ term.moveTo(1, sessionRow(sessionIdx));
9070
+ paintSessionRow(sessionIdx);
9071
+ });
8906
9072
  };
8907
9073
  const repaintViewport = () => {
8908
- for (let v = 0; v < viewportSize; v++) {
8909
- const row = headerRow() + 1 + v;
8910
- term.moveTo(1, row).eraseLineAfter();
8911
- const sessionIdx = scrollOffset + v;
8912
- if (sessionIdx < visible.length) {
8913
- paintSessionRow(sessionIdx);
9074
+ withSync(() => {
9075
+ for (let v = 0; v < viewportSize; v++) {
9076
+ const row = headerRow() + 1 + v;
9077
+ const sessionIdx = scrollOffset + v;
9078
+ if (sessionIdx < visible.length) {
9079
+ term.moveTo(1, row);
9080
+ paintSessionRow(sessionIdx);
9081
+ } else {
9082
+ term.moveTo(1, row).eraseLineAfter();
9083
+ }
8914
9084
  }
8915
- }
8916
- paintIndicator();
9085
+ paintIndicator();
9086
+ });
8917
9087
  };
8918
9088
  renderFromScratch();
8919
9089
  return await new Promise((resolve6) => {
@@ -9035,18 +9205,20 @@ async function pickSession(term, opts) {
9035
9205
  const oldScroll = scrollOffset;
9036
9206
  selectedIdx = next;
9037
9207
  adjustScroll();
9038
- if (scrollOffset !== oldScroll) {
9039
- repaintViewport();
9208
+ withSync(() => {
9209
+ if (scrollOffset !== oldScroll) {
9210
+ repaintViewport();
9211
+ onFocusChange(old, selectedIdx);
9212
+ return;
9213
+ }
9214
+ if (old !== 0) {
9215
+ repaintSessionRow(old - 1);
9216
+ }
9217
+ if (selectedIdx !== 0) {
9218
+ repaintSessionRow(selectedIdx - 1);
9219
+ }
9040
9220
  onFocusChange(old, selectedIdx);
9041
- return;
9042
- }
9043
- if (old !== 0) {
9044
- repaintSessionRow(old - 1);
9045
- }
9046
- if (selectedIdx !== 0) {
9047
- repaintSessionRow(selectedIdx - 1);
9048
- }
9049
- onFocusChange(old, selectedIdx);
9221
+ });
9050
9222
  };
9051
9223
  const clearTransient = () => {
9052
9224
  if (transientStatus === null) {
@@ -9370,10 +9542,12 @@ async function pickSession(term, opts) {
9370
9542
  switch (name) {
9371
9543
  case "UP":
9372
9544
  case "SHIFT_TAB":
9545
+ case "CTRL_P":
9373
9546
  move(-1);
9374
9547
  return;
9375
9548
  case "DOWN":
9376
9549
  case "TAB":
9550
+ case "CTRL_N":
9377
9551
  move(1);
9378
9552
  return;
9379
9553
  case "PAGE_UP":
@@ -9491,6 +9665,7 @@ var init_picker = __esm({
9491
9665
  init_discovery();
9492
9666
  init_input();
9493
9667
  init_screen();
9668
+ init_sync();
9494
9669
  ROW_PREFIX_WIDTH = 2;
9495
9670
  PICKER_COMPOSER_MAX_ROWS = 4;
9496
9671
  BOX_HORIZONTAL_PAD = 4;
@@ -9499,7 +9674,7 @@ var init_picker = __esm({
9499
9674
  ["Composer", "type prompt for new session; Enter creates + submits"],
9500
9675
  ["\u2193 from composer", "drop focus into session list"],
9501
9676
  null,
9502
- ["\u2191 / \u2193 or n / p", "navigate sessions"],
9677
+ ["\u2191 / \u2193, n / p, ^p / ^n", "navigate sessions"],
9503
9678
  ["PgUp / PgDn", "page up / page down"],
9504
9679
  ["Home / End", "first / last"],
9505
9680
  ["Enter", "open selected session"],
@@ -11380,6 +11555,7 @@ async function runSession(term, config, target, opts, exitHint) {
11380
11555
  }
11381
11556
  let turnInFlight = null;
11382
11557
  let pendingPrefill = null;
11558
+ let showThoughts = config.tui.showThoughts;
11383
11559
  const screen = new Screen({
11384
11560
  term,
11385
11561
  dispatcher,
@@ -11584,6 +11760,7 @@ async function runSession(term, config, target, opts, exitHint) {
11584
11760
  const usage = { ...initialUsage ?? {} };
11585
11761
  installStatus.finalize();
11586
11762
  screen.start();
11763
+ screen.setHideThoughts(!showThoughts);
11587
11764
  screen.setSessionbar({
11588
11765
  agent: sessionbarAgent,
11589
11766
  cwd: resolvedCwd,
@@ -11754,7 +11931,7 @@ async function runSession(term, config, target, opts, exitHint) {
11754
11931
  dispatcher.setHistory(history);
11755
11932
  }
11756
11933
  screen.pauseRepaint();
11757
- screen.stop();
11934
+ screen.stop({ keepFullscreen: true });
11758
11935
  saveHistory(historyFile, history).catch(() => void 0);
11759
11936
  let resolvedChoice = null;
11760
11937
  let attachOverrides = null;
@@ -11768,7 +11945,7 @@ async function runSession(term, config, target, opts, exitHint) {
11768
11945
  currentSessionId: resolvedSessionId
11769
11946
  });
11770
11947
  if (choice2.kind === "abort") {
11771
- screen.start();
11948
+ screen.start({ skipFullscreen: true });
11772
11949
  screen.resumeRepaint();
11773
11950
  return;
11774
11951
  }
@@ -11785,7 +11962,7 @@ async function runSession(term, config, target, opts, exitHint) {
11785
11962
  const opsShim = { ...opts, readonly: false };
11786
11963
  const decided = await runImportedFirstLaunchFlow(term, chosen, choice2, opsShim);
11787
11964
  if (decided.kind === "cancel") {
11788
- screen.start();
11965
+ screen.start({ skipFullscreen: true });
11789
11966
  screen.resumeRepaint();
11790
11967
  return;
11791
11968
  }
@@ -11969,6 +12146,11 @@ async function runSession(term, config, target, opts, exitHint) {
11969
12146
  toolsExpanded = !toolsExpanded;
11970
12147
  renderToolsBlock();
11971
12148
  return;
12149
+ case "toggle-thoughts":
12150
+ showThoughts = !showThoughts;
12151
+ screen.setHideThoughts(!showThoughts);
12152
+ screen.notify(showThoughts ? "thoughts shown" : "thoughts hidden");
12153
+ return;
11972
12154
  case "toggle-mouse": {
11973
12155
  const next = !screen.isMouseEnabled();
11974
12156
  screen.setMouseEnabled(next);
@@ -13163,7 +13345,8 @@ var init_app = __esm({
13163
13345
  ["^Y", "yank last kill"],
13164
13346
  null,
13165
13347
  ["^P", "switch session (picker)"],
13166
- ["^T", "next live session"],
13348
+ ["Alt+N / Alt+Tab", "next live session"],
13349
+ ["^T", "show / hide thoughts"],
13167
13350
  ["^V", "paste image from clipboard"],
13168
13351
  ["^O", "expand / collapse tools block"],
13169
13352
  null,
package/dist/index.d.ts CHANGED
@@ -106,6 +106,7 @@ declare const HydraConfig: z.ZodObject<{
106
106
  cwdColumnMaxWidth: z.ZodDefault<z.ZodNumber>;
107
107
  progressIndicator: z.ZodDefault<z.ZodBoolean>;
108
108
  defaultEnterAction: z.ZodDefault<z.ZodEnum<["enqueue", "amend"]>>;
109
+ showThoughts: z.ZodDefault<z.ZodBoolean>;
109
110
  }, "strip", z.ZodTypeAny, {
110
111
  repaintThrottleMs: number;
111
112
  maxScrollbackLines: number;
@@ -114,6 +115,7 @@ declare const HydraConfig: z.ZodObject<{
114
115
  cwdColumnMaxWidth: number;
115
116
  progressIndicator: boolean;
116
117
  defaultEnterAction: "enqueue" | "amend";
118
+ showThoughts: boolean;
117
119
  }, {
118
120
  repaintThrottleMs?: number | undefined;
119
121
  maxScrollbackLines?: number | undefined;
@@ -122,6 +124,7 @@ declare const HydraConfig: z.ZodObject<{
122
124
  cwdColumnMaxWidth?: number | undefined;
123
125
  progressIndicator?: boolean | undefined;
124
126
  defaultEnterAction?: "enqueue" | "amend" | undefined;
127
+ showThoughts?: boolean | undefined;
125
128
  }>>;
126
129
  }, "strip", z.ZodTypeAny, {
127
130
  tui: {
@@ -132,6 +135,7 @@ declare const HydraConfig: z.ZodObject<{
132
135
  cwdColumnMaxWidth: number;
133
136
  progressIndicator: boolean;
134
137
  defaultEnterAction: "enqueue" | "amend";
138
+ showThoughts: boolean;
135
139
  };
136
140
  daemon: {
137
141
  host: string;
@@ -170,6 +174,7 @@ declare const HydraConfig: z.ZodObject<{
170
174
  cwdColumnMaxWidth?: number | undefined;
171
175
  progressIndicator?: boolean | undefined;
172
176
  defaultEnterAction?: "enqueue" | "amend" | undefined;
177
+ showThoughts?: boolean | undefined;
173
178
  } | undefined;
174
179
  daemon?: {
175
180
  host?: string | undefined;
package/dist/index.js CHANGED
@@ -216,7 +216,12 @@ var TuiConfig = z.object({
216
216
  // "enqueue" — flips the two: Enter enqueues the prompt (sends
217
217
  // immediately when idle, queues behind an in-flight turn);
218
218
  // Shift+Enter amends the in-flight turn.
219
- defaultEnterAction: z.enum(["enqueue", "amend"]).default("amend")
219
+ defaultEnterAction: z.enum(["enqueue", "amend"]).default("amend"),
220
+ // When true (default), agent_thought events render as dim italic
221
+ // streaming lines beneath the live thinking block. Set false to
222
+ // suppress them — the TUI hotkey ^T toggles this at runtime without
223
+ // persisting back to config.
224
+ showThoughts: z.boolean().default(true)
220
225
  });
221
226
  var ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
222
227
  var ExtensionBody = z.object({
@@ -261,7 +266,8 @@ var HydraConfig = z.object({
261
266
  logMaxBytes: 5 * 1024 * 1024,
262
267
  cwdColumnMaxWidth: 24,
263
268
  progressIndicator: true,
264
- defaultEnterAction: "amend"
269
+ defaultEnterAction: "amend",
270
+ showThoughts: true
265
271
  })
266
272
  });
267
273
  function extensionList(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",