@mariozechner/pi-coding-agent 0.46.0 → 0.48.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 (144) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +34 -2
  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 +16 -3
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +125 -22
  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/export-html/ansi-to-html.d.ts +22 -0
  15. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  16. package/dist/core/export-html/ansi-to-html.js +249 -0
  17. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  18. package/dist/core/export-html/index.d.ts +17 -0
  19. package/dist/core/export-html/index.d.ts.map +1 -1
  20. package/dist/core/export-html/index.js +52 -23
  21. package/dist/core/export-html/index.js.map +1 -1
  22. package/dist/core/export-html/template.css +0 -33
  23. package/dist/core/export-html/template.js +171 -18
  24. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  25. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  26. package/dist/core/export-html/tool-renderer.js +57 -0
  27. package/dist/core/export-html/tool-renderer.js.map +1 -0
  28. package/dist/core/extensions/index.d.ts +1 -1
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/loader.d.ts.map +1 -1
  32. package/dist/core/extensions/loader.js.map +1 -1
  33. package/dist/core/extensions/runner.d.ts +8 -1
  34. package/dist/core/extensions/runner.d.ts.map +1 -1
  35. package/dist/core/extensions/runner.js +41 -0
  36. package/dist/core/extensions/runner.js.map +1 -1
  37. package/dist/core/extensions/types.d.ts +45 -6
  38. package/dist/core/extensions/types.d.ts.map +1 -1
  39. package/dist/core/extensions/types.js.map +1 -1
  40. package/dist/core/prompt-templates.d.ts +5 -1
  41. package/dist/core/prompt-templates.d.ts.map +1 -1
  42. package/dist/core/prompt-templates.js +22 -28
  43. package/dist/core/prompt-templates.js.map +1 -1
  44. package/dist/core/sdk.d.ts.map +1 -1
  45. package/dist/core/sdk.js +5 -1
  46. package/dist/core/sdk.js.map +1 -1
  47. package/dist/core/session-manager.d.ts +8 -0
  48. package/dist/core/session-manager.d.ts.map +1 -1
  49. package/dist/core/session-manager.js +43 -0
  50. package/dist/core/session-manager.js.map +1 -1
  51. package/dist/core/settings-manager.d.ts +9 -0
  52. package/dist/core/settings-manager.d.ts.map +1 -1
  53. package/dist/core/settings-manager.js +21 -0
  54. package/dist/core/settings-manager.js.map +1 -1
  55. package/dist/core/skills.d.ts.map +1 -1
  56. package/dist/core/skills.js +6 -37
  57. package/dist/core/skills.js.map +1 -1
  58. package/dist/core/system-prompt.d.ts.map +1 -1
  59. package/dist/core/system-prompt.js +19 -14
  60. package/dist/core/system-prompt.js.map +1 -1
  61. package/dist/core/tools/bash.d.ts +2 -0
  62. package/dist/core/tools/bash.d.ts.map +1 -1
  63. package/dist/core/tools/bash.js +4 -1
  64. package/dist/core/tools/bash.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +4 -1
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +8 -3
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/path-utils.d.ts +1 -0
  70. package/dist/core/tools/path-utils.d.ts.map +1 -1
  71. package/dist/core/tools/path-utils.js +7 -0
  72. package/dist/core/tools/path-utils.js.map +1 -1
  73. package/dist/core/tools/read.d.ts.map +1 -1
  74. package/dist/core/tools/read.js +13 -2
  75. package/dist/core/tools/read.js.map +1 -1
  76. package/dist/index.d.ts +3 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/main.d.ts.map +1 -1
  81. package/dist/main.js +84 -14
  82. package/dist/main.js.map +1 -1
  83. package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
  84. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/custom-editor.js +2 -2
  86. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  87. package/dist/modes/interactive/components/extension-editor.d.ts +2 -2
  88. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/extension-editor.js +3 -3
  90. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  91. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  92. package/dist/modes/interactive/components/session-selector.js +5 -3
  93. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  94. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  95. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/settings-selector.js +12 -0
  97. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  98. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/tool-execution.js +3 -1
  100. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  101. package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
  102. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/tree-selector.js +140 -4
  104. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  105. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  106. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-mode.js +123 -106
  108. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/dist/modes/interactive/theme/theme-schema.json +23 -3
  110. package/dist/modes/print-mode.d.ts.map +1 -1
  111. package/dist/modes/print-mode.js +7 -2
  112. package/dist/modes/print-mode.js.map +1 -1
  113. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  114. package/dist/modes/rpc/rpc-mode.js +7 -1
  115. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  116. package/dist/utils/frontmatter.d.ts +8 -0
  117. package/dist/utils/frontmatter.d.ts.map +1 -0
  118. package/dist/utils/frontmatter.js +26 -0
  119. package/dist/utils/frontmatter.js.map +1 -0
  120. package/dist/utils/image-convert.d.ts.map +1 -1
  121. package/dist/utils/image-convert.js +6 -1
  122. package/dist/utils/image-convert.js.map +1 -1
  123. package/dist/utils/image-resize.d.ts.map +1 -1
  124. package/dist/utils/image-resize.js +14 -1
  125. package/dist/utils/image-resize.js.map +1 -1
  126. package/dist/utils/photon.d.ts +28 -0
  127. package/dist/utils/photon.d.ts.map +1 -0
  128. package/dist/utils/photon.js +51 -0
  129. package/dist/utils/photon.js.map +1 -0
  130. package/docs/extensions.md +83 -4
  131. package/docs/rpc.md +17 -15
  132. package/docs/sdk.md +1 -1
  133. package/docs/tree.md +20 -2
  134. package/docs/tui.md +26 -0
  135. package/examples/extensions/input-transform.ts +43 -0
  136. package/examples/extensions/modal-editor.ts +1 -1
  137. package/examples/extensions/overlay-test.ts +8 -3
  138. package/examples/extensions/question.ts +1 -1
  139. package/examples/extensions/questionnaire.ts +1 -1
  140. package/examples/extensions/rainbow-editor.ts +1 -8
  141. package/examples/extensions/subagent/agents.ts +3 -32
  142. package/examples/extensions/with-deps/package-lock.json +2 -2
  143. package/examples/extensions/with-deps/package.json +1 -1
  144. package/package.json +6 -5
@@ -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...";
@@ -136,7 +137,8 @@ export class InteractiveMode {
136
137
  this.statusContainer = new Container();
137
138
  this.widgetContainer = new Container();
138
139
  this.keybindings = KeybindingsManager.create();
139
- this.defaultEditor = new CustomEditor(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
  })
@@ -547,7 +571,12 @@ export class InteractiveMode {
547
571
  return { cancelled: false };
548
572
  },
549
573
  navigateTree: async (targetId, options) => {
550
- const result = await this.session.navigateTree(targetId, { summarize: options?.summarize });
574
+ const result = await this.session.navigateTree(targetId, {
575
+ summarize: options?.summarize,
576
+ customInstructions: options?.customInstructions,
577
+ replaceInstructions: options?.replaceInstructions,
578
+ label: options?.label,
579
+ });
551
580
  if (result.cancelled) {
552
581
  return { cancelled: true };
553
582
  }
@@ -566,12 +595,14 @@ export class InteractiveMode {
566
595
  });
567
596
  // Set up extension-registered shortcuts
568
597
  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));
598
+ // Show loaded extensions (unless silenced)
599
+ if (!this.settingsManager.getQuietStartup()) {
600
+ const extensionPaths = extensionRunner.getExtensionPaths();
601
+ if (extensionPaths.length > 0) {
602
+ const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
603
+ this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
604
+ this.chatContainer.addChild(new Spacer(1));
605
+ }
575
606
  }
576
607
  // Emit session_start event
577
608
  await extensionRunner.emit({
@@ -1241,19 +1272,6 @@ export class InteractiveMode {
1241
1272
  await this.shutdown();
1242
1273
  return;
1243
1274
  }
1244
- // Handle skill commands (/skill:name [args])
1245
- if (text.startsWith("/skill:")) {
1246
- const spaceIndex = text.indexOf(" ");
1247
- const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
1248
- const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
1249
- const skillPath = this.skillCommands.get(commandName);
1250
- if (skillPath) {
1251
- this.editor.addToHistory?.(text);
1252
- this.editor.setText("");
1253
- await this.handleSkillCommand(skillPath, args);
1254
- return;
1255
- }
1256
- }
1257
1275
  // Handle bash command (! for normal, !! for excluded from context)
1258
1276
  if (text.startsWith("!")) {
1259
1277
  const isExcluded = text.startsWith("!!");
@@ -1503,6 +1521,11 @@ export class InteractiveMode {
1503
1521
  });
1504
1522
  this.footer.invalidate();
1505
1523
  }
1524
+ else if (event.errorMessage) {
1525
+ // Compaction failed (e.g., quota exceeded, API error)
1526
+ this.chatContainer.addChild(new Spacer(1));
1527
+ this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
1528
+ }
1506
1529
  void this.flushCompactionQueue({ willRetry: event.willRetry });
1507
1530
  this.ui.requestRender();
1508
1531
  break;
@@ -1690,6 +1713,7 @@ export class InteractiveMode {
1690
1713
  this.ui.requestRender();
1691
1714
  }
1692
1715
  renderInitialMessages() {
1716
+ this.hasRenderedInitialMessages = true;
1693
1717
  // Get aligned messages and entries from session context
1694
1718
  const context = this.sessionManager.buildSessionContext();
1695
1719
  this.renderSessionContext(context, {
@@ -1750,6 +1774,9 @@ export class InteractiveMode {
1750
1774
  type: "session_shutdown",
1751
1775
  });
1752
1776
  }
1777
+ // Wait for any pending renders to complete
1778
+ // requestRender() uses process.nextTick(), so we wait one tick
1779
+ await new Promise((resolve) => process.nextTick(resolve));
1753
1780
  this.stop();
1754
1781
  process.exit(0);
1755
1782
  }
@@ -2117,6 +2144,7 @@ export class InteractiveMode {
2117
2144
  hideThinkingBlock: this.hideThinkingBlock,
2118
2145
  collapseChangelog: this.settingsManager.getCollapseChangelog(),
2119
2146
  doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
2147
+ editorPaddingX: this.settingsManager.getEditorPaddingX(),
2120
2148
  }, {
2121
2149
  onAutoCompactChange: (enabled) => {
2122
2150
  this.session.setAutoCompactionEnabled(enabled);
@@ -2183,6 +2211,10 @@ export class InteractiveMode {
2183
2211
  onDoubleEscapeActionChange: (action) => {
2184
2212
  this.settingsManager.setDoubleEscapeAction(action);
2185
2213
  },
2214
+ onEditorPaddingXChange: (padding) => {
2215
+ this.settingsManager.setEditorPaddingX(padding);
2216
+ this.defaultEditor.setPaddingX(padding);
2217
+ },
2186
2218
  onCancel: () => {
2187
2219
  done();
2188
2220
  this.ui.requestRender();
@@ -2823,21 +2855,6 @@ export class InteractiveMode {
2823
2855
  this.chatContainer.addChild(new Text(info, 1, 0));
2824
2856
  this.ui.requestRender();
2825
2857
  }
2826
- async handleSkillCommand(skillPath, args) {
2827
- try {
2828
- const content = fs.readFileSync(skillPath, "utf-8");
2829
- // Strip YAML frontmatter if present
2830
- const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
2831
- const skillDir = path.dirname(skillPath);
2832
- const header = `Skill location: ${skillPath}\nReferences are relative to ${skillDir}.`;
2833
- const skillMessage = `${header}\n\n${body}`;
2834
- const message = args ? `${skillMessage}\n\n---\n\nUser: ${args}` : skillMessage;
2835
- await this.session.prompt(message);
2836
- }
2837
- catch (err) {
2838
- this.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
2839
- }
2840
- }
2841
2858
  handleChangelogCommand() {
2842
2859
  const changelogPath = getChangelogPath();
2843
2860
  const allEntries = parseChangelog(changelogPath);