@mariozechner/pi-coding-agent 0.47.0 → 0.49.0

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +43 -3
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +10 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +7 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +63 -9
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  11. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  12. package/dist/core/compaction/branch-summarization.js +11 -4
  13. package/dist/core/compaction/branch-summarization.js.map +1 -1
  14. package/dist/core/compaction/compaction.d.ts +11 -0
  15. package/dist/core/compaction/compaction.d.ts.map +1 -1
  16. package/dist/core/compaction/compaction.js +50 -3
  17. package/dist/core/compaction/compaction.js.map +1 -1
  18. package/dist/core/extensions/index.d.ts +1 -1
  19. package/dist/core/extensions/index.d.ts.map +1 -1
  20. package/dist/core/extensions/index.js.map +1 -1
  21. package/dist/core/extensions/loader.d.ts.map +1 -1
  22. package/dist/core/extensions/loader.js +4 -0
  23. package/dist/core/extensions/loader.js.map +1 -1
  24. package/dist/core/extensions/runner.d.ts +5 -0
  25. package/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/dist/core/extensions/runner.js +7 -0
  27. package/dist/core/extensions/runner.js.map +1 -1
  28. package/dist/core/extensions/types.d.ts +44 -5
  29. package/dist/core/extensions/types.d.ts.map +1 -1
  30. package/dist/core/extensions/types.js.map +1 -1
  31. package/dist/core/prompt-templates.d.ts +5 -1
  32. package/dist/core/prompt-templates.d.ts.map +1 -1
  33. package/dist/core/prompt-templates.js +18 -1
  34. package/dist/core/prompt-templates.js.map +1 -1
  35. package/dist/core/sdk.d.ts.map +1 -1
  36. package/dist/core/sdk.js +5 -1
  37. package/dist/core/sdk.js.map +1 -1
  38. package/dist/core/session-manager.d.ts +8 -0
  39. package/dist/core/session-manager.d.ts.map +1 -1
  40. package/dist/core/session-manager.js +43 -0
  41. package/dist/core/session-manager.js.map +1 -1
  42. package/dist/core/settings-manager.d.ts +12 -0
  43. package/dist/core/settings-manager.d.ts.map +1 -1
  44. package/dist/core/settings-manager.js +28 -0
  45. package/dist/core/settings-manager.js.map +1 -1
  46. package/dist/core/system-prompt.d.ts.map +1 -1
  47. package/dist/core/system-prompt.js +16 -21
  48. package/dist/core/system-prompt.js.map +1 -1
  49. package/dist/core/tools/bash.d.ts +2 -0
  50. package/dist/core/tools/bash.d.ts.map +1 -1
  51. package/dist/core/tools/bash.js +4 -1
  52. package/dist/core/tools/bash.js.map +1 -1
  53. package/dist/core/tools/index.d.ts +4 -1
  54. package/dist/core/tools/index.d.ts.map +1 -1
  55. package/dist/core/tools/index.js +8 -3
  56. package/dist/core/tools/index.js.map +1 -1
  57. package/dist/core/tools/path-utils.d.ts +0 -1
  58. package/dist/core/tools/path-utils.d.ts.map +1 -1
  59. package/dist/core/tools/path-utils.js +0 -7
  60. package/dist/core/tools/path-utils.js.map +1 -1
  61. package/dist/core/tools/read.d.ts.map +1 -1
  62. package/dist/core/tools/read.js +2 -12
  63. package/dist/core/tools/read.js.map +1 -1
  64. package/dist/index.d.ts +4 -3
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +4 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/main.d.ts.map +1 -1
  69. package/dist/main.js +52 -14
  70. package/dist/main.js.map +1 -1
  71. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  72. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  73. package/dist/modes/interactive/components/custom-editor.js +2 -2
  74. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  75. package/dist/modes/interactive/components/extension-editor.d.ts +2 -2
  76. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/extension-editor.js +3 -3
  78. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  79. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  80. package/dist/modes/interactive/components/session-selector.js +5 -3
  81. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  82. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  83. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  84. package/dist/modes/interactive/components/settings-selector.js +24 -0
  85. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  86. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/tool-execution.js +3 -1
  88. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  90. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  91. package/dist/modes/interactive/interactive-mode.js +168 -80
  92. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  93. package/dist/modes/interactive/theme/theme-schema.json +23 -3
  94. package/dist/modes/print-mode.d.ts.map +1 -1
  95. package/dist/modes/print-mode.js +22 -1
  96. package/dist/modes/print-mode.js.map +1 -1
  97. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  98. package/dist/modes/rpc/rpc-mode.js +22 -1
  99. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  100. package/dist/utils/image-convert.d.ts.map +1 -1
  101. package/dist/utils/image-convert.js +8 -2
  102. package/dist/utils/image-convert.js.map +1 -1
  103. package/dist/utils/image-resize.d.ts.map +1 -1
  104. package/dist/utils/image-resize.js +14 -1
  105. package/dist/utils/image-resize.js.map +1 -1
  106. package/dist/utils/photon.d.ts +20 -0
  107. package/dist/utils/photon.d.ts.map +1 -0
  108. package/dist/utils/photon.js +39 -0
  109. package/dist/utils/photon.js.map +1 -0
  110. package/docs/extensions.md +99 -5
  111. package/docs/rpc.md +2 -0
  112. package/docs/sdk.md +1 -1
  113. package/docs/tree.md +20 -2
  114. package/examples/extensions/README.md +1 -0
  115. package/examples/extensions/custom-header.ts +2 -1
  116. package/examples/extensions/trigger-compact.ts +40 -0
  117. package/examples/extensions/with-deps/package-lock.json +2 -2
  118. package/examples/extensions/with-deps/package.json +1 -1
  119. package/package.json +4 -4
@@ -65,6 +65,7 @@ export class InteractiveMode {
65
65
  keybindings;
66
66
  version;
67
67
  isInitialized = false;
68
+ hasRenderedInitialMessages = false;
68
69
  onInputCallback;
69
70
  loadingAnimation = undefined;
70
71
  defaultWorkingMessage = "Working...";
@@ -130,13 +131,14 @@ export class InteractiveMode {
130
131
  this.options = options;
131
132
  this.session = session;
132
133
  this.version = VERSION;
133
- this.ui = new TUI(new ProcessTerminal());
134
+ this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
134
135
  this.chatContainer = new Container();
135
136
  this.pendingMessagesContainer = new Container();
136
137
  this.statusContainer = new Container();
137
138
  this.widgetContainer = new Container();
138
139
  this.keybindings = KeybindingsManager.create();
139
- this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings);
140
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
141
+ this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, { paddingX: editorPaddingX });
140
142
  this.editor = this.defaultEditor;
141
143
  this.editorContainer = new Container();
142
144
  this.editorContainer.addChild(this.editor);
@@ -204,6 +206,7 @@ export class InteractiveMode {
204
206
  const extensionCommands = (this.session.extensionRunner?.getRegisteredCommands() ?? []).map((cmd) => ({
205
207
  name: cmd.name,
206
208
  description: cmd.description ?? "(extension command)",
209
+ getArgumentCompletions: cmd.getArgumentCompletions,
207
210
  }));
208
211
  // Build skill commands from session.skills (if enabled)
209
212
  this.skillCommands.clear();
@@ -230,53 +233,67 @@ export class InteractiveMode {
230
233
  // Setup autocomplete with fd tool for file path completion
231
234
  this.fdPath = await ensureTool("fd");
232
235
  this.setupAutocomplete(this.fdPath);
233
- // Add header with keybindings from config
234
- const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
235
- // Build startup instructions using keybinding hint helpers
236
- const kb = this.keybindings;
237
- const hint = (action, desc) => appKeyHint(kb, action, desc);
238
- const instructions = [
239
- hint("interrupt", "to interrupt"),
240
- hint("clear", "to clear"),
241
- rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
242
- hint("exit", "to exit (empty)"),
243
- hint("suspend", "to suspend"),
244
- keyHint("deleteToLineEnd", "to delete to end"),
245
- hint("cycleThinkingLevel", "to cycle thinking"),
246
- rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
247
- hint("selectModel", "to select model"),
248
- hint("expandTools", "to expand tools"),
249
- hint("toggleThinking", "to toggle thinking"),
250
- hint("externalEditor", "for external editor"),
251
- rawKeyHint("/", "for commands"),
252
- rawKeyHint("!", "to run bash"),
253
- rawKeyHint("!!", "to run bash (no context)"),
254
- hint("followUp", "to queue follow-up"),
255
- hint("dequeue", "to edit all queued messages"),
256
- hint("pasteImage", "to paste image"),
257
- rawKeyHint("drop files", "to attach"),
258
- ].join("\n");
259
- this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
260
- // Setup UI layout
261
- this.ui.addChild(new Spacer(1));
262
- this.ui.addChild(this.builtInHeader);
263
- this.ui.addChild(new Spacer(1));
264
- // Add changelog if provided
265
- if (this.changelogMarkdown) {
266
- this.ui.addChild(new DynamicBorder());
267
- if (this.settingsManager.getCollapseChangelog()) {
236
+ // Add header with keybindings from config (unless silenced)
237
+ if (!this.settingsManager.getQuietStartup()) {
238
+ const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
239
+ // Build startup instructions using keybinding hint helpers
240
+ const kb = this.keybindings;
241
+ const hint = (action, desc) => appKeyHint(kb, action, desc);
242
+ const instructions = [
243
+ hint("interrupt", "to interrupt"),
244
+ hint("clear", "to clear"),
245
+ rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
246
+ hint("exit", "to exit (empty)"),
247
+ hint("suspend", "to suspend"),
248
+ keyHint("deleteToLineEnd", "to delete to end"),
249
+ hint("cycleThinkingLevel", "to cycle thinking"),
250
+ rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
251
+ hint("selectModel", "to select model"),
252
+ hint("expandTools", "to expand tools"),
253
+ hint("toggleThinking", "to toggle thinking"),
254
+ hint("externalEditor", "for external editor"),
255
+ rawKeyHint("/", "for commands"),
256
+ rawKeyHint("!", "to run bash"),
257
+ rawKeyHint("!!", "to run bash (no context)"),
258
+ hint("followUp", "to queue follow-up"),
259
+ hint("dequeue", "to edit all queued messages"),
260
+ hint("pasteImage", "to paste image"),
261
+ rawKeyHint("drop files", "to attach"),
262
+ ].join("\n");
263
+ this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
264
+ // Setup UI layout
265
+ this.ui.addChild(new Spacer(1));
266
+ this.ui.addChild(this.builtInHeader);
267
+ this.ui.addChild(new Spacer(1));
268
+ // Add changelog if provided
269
+ if (this.changelogMarkdown) {
270
+ this.ui.addChild(new DynamicBorder());
271
+ if (this.settingsManager.getCollapseChangelog()) {
272
+ const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
273
+ const latestVersion = versionMatch ? versionMatch[1] : this.version;
274
+ const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
275
+ this.ui.addChild(new Text(condensedText, 1, 0));
276
+ }
277
+ else {
278
+ this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
279
+ this.ui.addChild(new Spacer(1));
280
+ this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
281
+ this.ui.addChild(new Spacer(1));
282
+ }
283
+ this.ui.addChild(new DynamicBorder());
284
+ }
285
+ }
286
+ else {
287
+ // Minimal header when silenced
288
+ this.builtInHeader = new Text("", 0, 0);
289
+ if (this.changelogMarkdown) {
290
+ // Still show changelog notification even in silent mode
291
+ this.ui.addChild(new Spacer(1));
268
292
  const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
269
293
  const latestVersion = versionMatch ? versionMatch[1] : this.version;
270
294
  const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
271
295
  this.ui.addChild(new Text(condensedText, 1, 0));
272
296
  }
273
- else {
274
- this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
275
- this.ui.addChild(new Spacer(1));
276
- this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
277
- this.ui.addChild(new Spacer(1));
278
- }
279
- this.ui.addChild(new DynamicBorder());
280
297
  }
281
298
  this.ui.addChild(this.chatContainer);
282
299
  this.ui.addChild(this.pendingMessagesContainer);
@@ -421,33 +438,38 @@ export class InteractiveMode {
421
438
  * Initialize the extension system with TUI-based UI context.
422
439
  */
423
440
  async initExtensions() {
424
- // Show loaded project context files
425
- const contextFiles = loadProjectContextFiles();
426
- if (contextFiles.length > 0) {
427
- const contextList = contextFiles.map((f) => theme.fg("dim", ` ${f.path}`)).join("\n");
428
- this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded context:\n") + contextList, 0, 0));
429
- this.chatContainer.addChild(new Spacer(1));
430
- }
431
- // Show loaded skills (already discovered by SDK)
432
- const skills = this.session.skills;
433
- if (skills.length > 0) {
434
- const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
435
- this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
436
- this.chatContainer.addChild(new Spacer(1));
437
- }
438
- // Show skill warnings if any
439
- const skillWarnings = this.session.skillWarnings;
440
- if (skillWarnings.length > 0) {
441
- const warningList = skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n");
442
- this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
443
- this.chatContainer.addChild(new Spacer(1));
444
- }
445
- // Show loaded prompt templates
446
- const templates = this.session.promptTemplates;
447
- if (templates.length > 0) {
448
- const templateList = templates.map((t) => theme.fg("dim", ` /${t.name} ${t.source}`)).join("\n");
449
- this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded prompt templates:\n") + templateList, 0, 0));
450
- this.chatContainer.addChild(new Spacer(1));
441
+ // Show discovery info unless silenced
442
+ if (!this.settingsManager.getQuietStartup()) {
443
+ // Show loaded project context files
444
+ const contextFiles = loadProjectContextFiles();
445
+ if (contextFiles.length > 0) {
446
+ const contextList = contextFiles.map((f) => theme.fg("dim", ` ${f.path}`)).join("\n");
447
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded context:\n") + contextList, 0, 0));
448
+ this.chatContainer.addChild(new Spacer(1));
449
+ }
450
+ // Show loaded skills (already discovered by SDK)
451
+ const skills = this.session.skills;
452
+ if (skills.length > 0) {
453
+ const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
454
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
455
+ this.chatContainer.addChild(new Spacer(1));
456
+ }
457
+ // Show skill warnings if any
458
+ const skillWarnings = this.session.skillWarnings;
459
+ if (skillWarnings.length > 0) {
460
+ const warningList = skillWarnings
461
+ .map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`))
462
+ .join("\n");
463
+ this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
464
+ this.chatContainer.addChild(new Spacer(1));
465
+ }
466
+ // Show loaded prompt templates
467
+ const templates = this.session.promptTemplates;
468
+ if (templates.length > 0) {
469
+ const templateList = templates.map((t) => theme.fg("dim", ` /${t.name} ${t.source}`)).join("\n");
470
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded prompt templates:\n") + templateList, 0, 0));
471
+ this.chatContainer.addChild(new Spacer(1));
472
+ }
451
473
  }
452
474
  const extensionRunner = this.session.extensionRunner;
453
475
  if (!extensionRunner) {
@@ -463,7 +485,9 @@ export class InteractiveMode {
463
485
  this.session
464
486
  .sendCustomMessage(message, options)
465
487
  .then(() => {
466
- if (!wasStreaming && message.display) {
488
+ // Don't rebuild if initial render hasn't happened yet
489
+ // (renderInitialMessages will handle it)
490
+ if (!wasStreaming && message.display && this.hasRenderedInitialMessages) {
467
491
  this.rebuildChatFromMessages();
468
492
  }
469
493
  })
@@ -485,6 +509,9 @@ export class InteractiveMode {
485
509
  getSessionName: () => {
486
510
  return this.sessionManager.getSessionName();
487
511
  },
512
+ setLabel: (entryId, label) => {
513
+ this.sessionManager.appendLabelChange(entryId, label);
514
+ },
488
515
  getActiveTools: () => this.session.getActiveToolNames(),
489
516
  getAllTools: () => this.session.getAllTools(),
490
517
  setActiveTools: (toolNames) => this.session.setActiveToolsByName(toolNames),
@@ -507,6 +534,21 @@ export class InteractiveMode {
507
534
  shutdown: () => {
508
535
  this.shutdownRequested = true;
509
536
  },
537
+ getContextUsage: () => this.session.getContextUsage(),
538
+ compact: (options) => {
539
+ void (async () => {
540
+ try {
541
+ const result = await this.executeCompaction(options?.customInstructions, false);
542
+ if (result) {
543
+ options?.onComplete?.(result);
544
+ }
545
+ }
546
+ catch (error) {
547
+ const err = error instanceof Error ? error : new Error(String(error));
548
+ options?.onError?.(err);
549
+ }
550
+ })();
551
+ },
510
552
  },
511
553
  // ExtensionCommandContextActions - for ctx.* in command handlers
512
554
  {
@@ -547,7 +589,12 @@ export class InteractiveMode {
547
589
  return { cancelled: false };
548
590
  },
549
591
  navigateTree: async (targetId, options) => {
550
- const result = await this.session.navigateTree(targetId, { summarize: options?.summarize });
592
+ const result = await this.session.navigateTree(targetId, {
593
+ summarize: options?.summarize,
594
+ customInstructions: options?.customInstructions,
595
+ replaceInstructions: options?.replaceInstructions,
596
+ label: options?.label,
597
+ });
551
598
  if (result.cancelled) {
552
599
  return { cancelled: true };
553
600
  }
@@ -566,12 +613,14 @@ export class InteractiveMode {
566
613
  });
567
614
  // Set up extension-registered shortcuts
568
615
  this.setupExtensionShortcuts(extensionRunner);
569
- // Show loaded extensions
570
- const extensionPaths = extensionRunner.getExtensionPaths();
571
- if (extensionPaths.length > 0) {
572
- const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
573
- this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
574
- this.chatContainer.addChild(new Spacer(1));
616
+ // Show loaded extensions (unless silenced)
617
+ if (!this.settingsManager.getQuietStartup()) {
618
+ const extensionPaths = extensionRunner.getExtensionPaths();
619
+ if (extensionPaths.length > 0) {
620
+ const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
621
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
622
+ this.chatContainer.addChild(new Spacer(1));
623
+ }
575
624
  }
576
625
  // Emit session_start event
577
626
  await extensionRunner.emit({
@@ -607,6 +656,21 @@ export class InteractiveMode {
607
656
  shutdown: () => {
608
657
  this.shutdownRequested = true;
609
658
  },
659
+ getContextUsage: () => this.session.getContextUsage(),
660
+ compact: (options) => {
661
+ void (async () => {
662
+ try {
663
+ const result = await this.executeCompaction(options?.customInstructions, false);
664
+ if (result) {
665
+ options?.onComplete?.(result);
666
+ }
667
+ }
668
+ catch (error) {
669
+ const err = error instanceof Error ? error : new Error(String(error));
670
+ options?.onError?.(err);
671
+ }
672
+ })();
673
+ },
610
674
  });
611
675
  // Set up the extension shortcut handler on the default editor
612
676
  this.defaultEditor.onExtensionShortcut = (data) => {
@@ -1490,6 +1554,11 @@ export class InteractiveMode {
1490
1554
  });
1491
1555
  this.footer.invalidate();
1492
1556
  }
1557
+ else if (event.errorMessage) {
1558
+ // Compaction failed (e.g., quota exceeded, API error)
1559
+ this.chatContainer.addChild(new Spacer(1));
1560
+ this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
1561
+ }
1493
1562
  void this.flushCompactionQueue({ willRetry: event.willRetry });
1494
1563
  this.ui.requestRender();
1495
1564
  break;
@@ -1677,6 +1746,7 @@ export class InteractiveMode {
1677
1746
  this.ui.requestRender();
1678
1747
  }
1679
1748
  renderInitialMessages() {
1749
+ this.hasRenderedInitialMessages = true;
1680
1750
  // Get aligned messages and entries from session context
1681
1751
  const context = this.sessionManager.buildSessionContext();
1682
1752
  this.renderSessionContext(context, {
@@ -2107,6 +2177,8 @@ export class InteractiveMode {
2107
2177
  hideThinkingBlock: this.hideThinkingBlock,
2108
2178
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
2109
2179
  doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
2180
+ showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
2181
+ editorPaddingX: this.settingsManager.getEditorPaddingX(),
2110
2182
  }, {
2111
2183
  onAutoCompactChange: (enabled) => {
2112
2184
  this.session.setAutoCompactionEnabled(enabled);
@@ -2173,6 +2245,14 @@ export class InteractiveMode {
2173
2245
  onDoubleEscapeActionChange: (action) => {
2174
2246
  this.settingsManager.setDoubleEscapeAction(action);
2175
2247
  },
2248
+ onShowHardwareCursorChange: (enabled) => {
2249
+ this.settingsManager.setShowHardwareCursor(enabled);
2250
+ this.ui.setShowHardwareCursor(enabled);
2251
+ },
2252
+ onEditorPaddingXChange: (padding) => {
2253
+ this.settingsManager.setEditorPaddingX(padding);
2254
+ this.defaultEditor.setPaddingX(padding);
2255
+ },
2176
2256
  onCancel: () => {
2177
2257
  done();
2178
2258
  this.ui.requestRender();
@@ -2864,8 +2944,11 @@ export class InteractiveMode {
2864
2944
  const submit = this.getEditorKeyDisplay("submit");
2865
2945
  const newLine = this.getEditorKeyDisplay("newLine");
2866
2946
  const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
2947
+ const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
2867
2948
  const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
2868
2949
  const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
2950
+ const yank = this.getEditorKeyDisplay("yank");
2951
+ const yankPop = this.getEditorKeyDisplay("yankPop");
2869
2952
  const tab = this.getEditorKeyDisplay("tab");
2870
2953
  // App keybindings
2871
2954
  const interrupt = this.getAppKeyDisplay("interrupt");
@@ -2894,8 +2977,11 @@ export class InteractiveMode {
2894
2977
  | \`${submit}\` | Send message |
2895
2978
  | \`${newLine}\` | New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |
2896
2979
  | \`${deleteWordBackward}\` | Delete word backwards |
2980
+ | \`${deleteWordForward}\` | Delete word forwards |
2897
2981
  | \`${deleteToLineStart}\` | Delete to start of line |
2898
2982
  | \`${deleteToLineEnd}\` | Delete to end of line |
2983
+ | \`${yank}\` | Paste the most-recently-deleted text |
2984
+ | \`${yankPop}\` | Cycle through the deleted text after pasting |
2899
2985
 
2900
2986
  **Other**
2901
2987
  | Key | Action |
@@ -3087,8 +3173,9 @@ export class InteractiveMode {
3087
3173
  const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3088
3174
  this.statusContainer.addChild(compactingLoader);
3089
3175
  this.ui.requestRender();
3176
+ let result;
3090
3177
  try {
3091
- const result = await this.session.compact(customInstructions);
3178
+ result = await this.session.compact(customInstructions);
3092
3179
  // Rebuild UI
3093
3180
  this.rebuildChatFromMessages();
3094
3181
  // Add compaction component at bottom so user sees it without scrolling
@@ -3111,6 +3198,7 @@ export class InteractiveMode {
3111
3198
  this.defaultEditor.onEscape = originalOnEscape;
3112
3199
  }
3113
3200
  void this.flushCompactionQueue({ willRetry: false });
3201
+ return result;
3114
3202
  }
3115
3203
  stop() {
3116
3204
  if (this.loadingAnimation) {