iosm-cli 0.2.17 → 0.3.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 (56) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -4
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +3 -0
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/settings-manager.d.ts +3 -0
  7. package/dist/core/settings-manager.d.ts.map +1 -1
  8. package/dist/core/settings-manager.js +11 -0
  9. package/dist/core/settings-manager.js.map +1 -1
  10. package/dist/core/settings.schema.json +21 -0
  11. package/dist/core/subagents.d.ts.map +1 -1
  12. package/dist/core/subagents.js +4 -0
  13. package/dist/core/subagents.js.map +1 -1
  14. package/dist/core/tools/task.d.ts.map +1 -1
  15. package/dist/core/tools/task.js +48 -26
  16. package/dist/core/tools/task.js.map +1 -1
  17. package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
  18. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  19. package/dist/modes/interactive/components/assistant-message.js +77 -5
  20. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  21. package/dist/modes/interactive/components/custom-editor.d.ts +7 -0
  22. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  23. package/dist/modes/interactive/components/custom-editor.js +95 -10
  24. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  25. package/dist/modes/interactive/components/footer.d.ts +2 -0
  26. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  27. package/dist/modes/interactive/components/footer.js +49 -32
  28. package/dist/modes/interactive/components/footer.js.map +1 -1
  29. package/dist/modes/interactive/components/message-frame-border.d.ts +22 -0
  30. package/dist/modes/interactive/components/message-frame-border.d.ts.map +1 -0
  31. package/dist/modes/interactive/components/message-frame-border.js +51 -0
  32. package/dist/modes/interactive/components/message-frame-border.js.map +1 -0
  33. package/dist/modes/interactive/components/message-window.d.ts +25 -0
  34. package/dist/modes/interactive/components/message-window.d.ts.map +1 -0
  35. package/dist/modes/interactive/components/message-window.js +66 -0
  36. package/dist/modes/interactive/components/message-window.js.map +1 -0
  37. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  38. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  39. package/dist/modes/interactive/components/settings-selector.js +12 -0
  40. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  41. package/dist/modes/interactive/components/subagent-message.d.ts +12 -9
  42. package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/subagent-message.js +138 -136
  44. package/dist/modes/interactive/components/subagent-message.js.map +1 -1
  45. package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  46. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  47. package/dist/modes/interactive/components/tool-execution.js +33 -28
  48. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  49. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  50. package/dist/modes/interactive/components/user-message.js +10 -3
  51. package/dist/modes/interactive/components/user-message.js.map +1 -1
  52. package/dist/modes/interactive/interactive-mode.d.ts +11 -1
  53. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  54. package/dist/modes/interactive/interactive-mode.js +509 -155
  55. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  56. package/package.json +1 -1
@@ -5,12 +5,19 @@ import { Container, type MarkdownTheme } from "@mariozechner/pi-tui";
5
5
  */
6
6
  export declare class AssistantMessageComponent extends Container {
7
7
  private contentContainer;
8
+ private messageWindow;
8
9
  private hideThinkingBlock;
10
+ private expanded;
11
+ private isStreaming;
12
+ private renderEnabled;
9
13
  private markdownTheme;
10
14
  private lastMessage?;
11
15
  constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, markdownTheme?: MarkdownTheme);
12
16
  invalidate(): void;
17
+ render(width: number): string[];
13
18
  setHideThinkingBlock(hide: boolean): void;
19
+ setExpanded(expanded: boolean): void;
20
+ setStreaming(streaming: boolean): void;
14
21
  updateContent(message: AssistantMessage): void;
15
22
  }
16
23
  //# sourceMappingURL=assistant-message.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AAG7F;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAC,CAAmB;gBAGtC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,iBAAiB,UAAQ,EACzB,aAAa,GAAE,aAAkC;IAgBzC,UAAU,IAAI,IAAI;IAO3B,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIzC,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;CAuE9C"}
1
+ {"version":3,"file":"assistant-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AAmB7F;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAC,CAAmB;gBAGtC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,iBAAiB,UAAQ,EACzB,aAAa,GAAE,aAAkC;IAyBzC,UAAU,IAAI,IAAI;IAOlB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAKxC,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAOzC,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAOpC,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAOtC,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;CAqG9C"}
@@ -1,16 +1,42 @@
1
1
  import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
2
+ import { editorKey } from "./keybinding-hints.js";
3
+ import { MessageWindow } from "./message-window.js";
2
4
  import { getMarkdownTheme, theme } from "../theme/theme.js";
5
+ const THINKING_SPINNER_FRAMES = ["-", "\\", "|", "/"];
6
+ function toSingleLinePreview(text, maxChars = 100) {
7
+ const normalized = text.replace(/\s+/g, " ").trim();
8
+ if (!normalized)
9
+ return "";
10
+ if (normalized.length <= maxChars)
11
+ return normalized;
12
+ return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
13
+ }
14
+ function getSpinnerFrame(signal) {
15
+ const index = Math.max(0, Math.floor(signal)) % THINKING_SPINNER_FRAMES.length;
16
+ return THINKING_SPINNER_FRAMES[index] ?? "-";
17
+ }
3
18
  /**
4
19
  * Component that renders a complete assistant message
5
20
  */
6
21
  export class AssistantMessageComponent extends Container {
7
22
  constructor(message, hideThinkingBlock = false, markdownTheme = getMarkdownTheme()) {
8
23
  super();
24
+ this.expanded = false;
25
+ this.isStreaming = false;
26
+ this.renderEnabled = true;
9
27
  this.hideThinkingBlock = hideThinkingBlock;
10
28
  this.markdownTheme = markdownTheme;
11
- // Container for text/thinking content
29
+ this.addChild(new Spacer(1));
30
+ // Container for message content
12
31
  this.contentContainer = new Container();
13
- this.addChild(this.contentContainer);
32
+ this.messageWindow = new MessageWindow(this.contentContainer, {
33
+ label: "IOSM Agent",
34
+ lineColor: "borderMuted",
35
+ labelColor: "muted",
36
+ paddingY: 1,
37
+ });
38
+ this.messageWindow.setVisible(false);
39
+ this.addChild(this.messageWindow);
14
40
  if (message) {
15
41
  this.updateContent(message);
16
42
  }
@@ -21,17 +47,43 @@ export class AssistantMessageComponent extends Container {
21
47
  this.updateContent(this.lastMessage);
22
48
  }
23
49
  }
50
+ render(width) {
51
+ if (!this.renderEnabled)
52
+ return [];
53
+ return super.render(width);
54
+ }
24
55
  setHideThinkingBlock(hide) {
25
56
  this.hideThinkingBlock = hide;
57
+ if (this.lastMessage) {
58
+ this.updateContent(this.lastMessage);
59
+ }
60
+ }
61
+ setExpanded(expanded) {
62
+ this.expanded = expanded;
63
+ if (this.lastMessage) {
64
+ this.updateContent(this.lastMessage);
65
+ }
66
+ }
67
+ setStreaming(streaming) {
68
+ this.isStreaming = streaming;
69
+ if (this.lastMessage) {
70
+ this.updateContent(this.lastMessage);
71
+ }
26
72
  }
27
73
  updateContent(message) {
28
74
  this.lastMessage = message;
29
75
  // Clear content container
30
76
  this.contentContainer.clear();
31
77
  const hasVisibleContent = message.content.some((c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
32
- if (hasVisibleContent) {
33
- this.contentContainer.addChild(new Spacer(1));
78
+ const hasToolCalls = message.content.some((c) => c.type === "toolCall");
79
+ const shouldShowError = !hasToolCalls && (message.stopReason === "aborted" || message.stopReason === "error");
80
+ const showFrame = hasVisibleContent || shouldShowError || this.isStreaming;
81
+ this.renderEnabled = showFrame;
82
+ this.messageWindow.setVisible(showFrame);
83
+ if (!showFrame) {
84
+ return;
34
85
  }
86
+ this.contentContainer.addChild(new Spacer(1));
35
87
  // Render content in order
36
88
  for (let i = 0; i < message.content.length; i++) {
37
89
  const content = message.content[i];
@@ -53,6 +105,18 @@ export class AssistantMessageComponent extends Container {
53
105
  this.contentContainer.addChild(new Spacer(1));
54
106
  }
55
107
  }
108
+ else if (!this.expanded) {
109
+ // Collapsed reasoning preview line with short excerpt.
110
+ const preview = toSingleLinePreview(content.thinking, 96);
111
+ const prefix = this.isStreaming ? `Thinking ${getSpinnerFrame(content.thinking.length / 12)}` : "Reasoning";
112
+ const summary = preview.length > 0 ? `${prefix}: ${preview}` : this.isStreaming ? `${prefix}...` : "Reasoning hidden";
113
+ const collapsedLabel = theme.italic(theme.fg("thinkingText", summary)) +
114
+ theme.fg("dim", ` (${editorKey("expandTools")} to expand)`);
115
+ this.contentContainer.addChild(new Text(collapsedLabel, 1, 0));
116
+ if (hasVisibleContentAfter) {
117
+ this.contentContainer.addChild(new Spacer(1));
118
+ }
119
+ }
56
120
  else {
57
121
  // Thinking traces in thinkingText color, italic
58
122
  this.contentContainer.addChild(new Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {
@@ -65,9 +129,17 @@ export class AssistantMessageComponent extends Container {
65
129
  }
66
130
  }
67
131
  }
132
+ // Show a live placeholder while streaming before text/thinking arrives.
133
+ if (this.isStreaming && !hasVisibleContent) {
134
+ const spinner = getSpinnerFrame(Date.now() / 160);
135
+ const collapsedLabel = theme.italic(theme.fg("thinkingText", `Thinking ${spinner}...`)) +
136
+ (this.hideThinkingBlock || this.expanded
137
+ ? ""
138
+ : theme.fg("dim", ` (${editorKey("expandTools")} to expand)`));
139
+ this.contentContainer.addChild(new Text(collapsedLabel, 1, 0));
140
+ }
68
141
  // Check if aborted - show after partial content
69
142
  // But only if there are no tool calls (tool execution components will show the error)
70
- const hasToolCalls = message.content.some((c) => c.type === "toolCall");
71
143
  if (!hasToolCalls) {
72
144
  if (message.stopReason === "aborted") {
73
145
  const abortMessage = message.errorMessage && message.errorMessage !== "Request was aborted"
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate lastMessage?: AssistantMessage;\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t}\n\n\tupdateContent(message: AssistantMessage): void {\n\t\tthis.lastMessage = message;\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show static \"Thinking...\" label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(new Text(theme.italic(theme.fg(\"thinkingText\", \"Thinking...\")), 1, 0));\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {\n\t\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\t\titalic: true,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tif (hasVisibleContent) {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t} else {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t}\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@mariozechner/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport { editorKey } from \"./keybinding-hints.js\";\nimport { MessageWindow } from \"./message-window.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\nconst THINKING_SPINNER_FRAMES = [\"-\", \"\\\\\", \"|\", \"/\"] as const;\n\nfunction toSingleLinePreview(text: string, maxChars = 100): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\tif (!normalized) return \"\";\n\tif (normalized.length <= maxChars) return normalized;\n\treturn `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;\n}\n\nfunction getSpinnerFrame(signal: number): string {\n\tconst index = Math.max(0, Math.floor(signal)) % THINKING_SPINNER_FRAMES.length;\n\treturn THINKING_SPINNER_FRAMES[index] ?? \"-\";\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate messageWindow: MessageWindow;\n\tprivate hideThinkingBlock: boolean;\n\tprivate expanded = false;\n\tprivate isStreaming = false;\n\tprivate renderEnabled = true;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate lastMessage?: AssistantMessage;\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Container for message content\n\t\tthis.contentContainer = new Container();\n\t\tthis.messageWindow = new MessageWindow(this.contentContainer, {\n\t\t\tlabel: \"IOSM Agent\",\n\t\t\tlineColor: \"borderMuted\",\n\t\t\tlabelColor: \"muted\",\n\t\t\tpaddingY: 1,\n\t\t});\n\t\tthis.messageWindow.setVisible(false);\n\t\tthis.addChild(this.messageWindow);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tif (!this.renderEnabled) return [];\n\t\treturn super.render(width);\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetStreaming(streaming: boolean): void {\n\t\tthis.isStreaming = streaming;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tupdateContent(message: AssistantMessage): void {\n\t\tthis.lastMessage = message;\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tconst shouldShowError = !hasToolCalls && (message.stopReason === \"aborted\" || message.stopReason === \"error\");\n\t\tconst showFrame = hasVisibleContent || shouldShowError || this.isStreaming;\n\t\tthis.renderEnabled = showFrame;\n\t\tthis.messageWindow.setVisible(showFrame);\n\n\t\tif (!showFrame) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show static \"Thinking...\" label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(new Text(theme.italic(theme.fg(\"thinkingText\", \"Thinking...\")), 1, 0));\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else if (!this.expanded) {\n\t\t\t\t\t// Collapsed reasoning preview line with short excerpt.\n\t\t\t\t\tconst preview = toSingleLinePreview(content.thinking, 96);\n\t\t\t\t\tconst prefix = this.isStreaming ? `Thinking ${getSpinnerFrame(content.thinking.length / 12)}` : \"Reasoning\";\n\t\t\t\t\tconst summary =\n\t\t\t\t\t\tpreview.length > 0 ? `${prefix}: ${preview}` : this.isStreaming ? `${prefix}...` : \"Reasoning hidden\";\n\t\t\t\t\tconst collapsedLabel =\n\t\t\t\t\t\ttheme.italic(theme.fg(\"thinkingText\", summary)) +\n\t\t\t\t\t\ttheme.fg(\"dim\", ` (${editorKey(\"expandTools\")} to expand)`);\n\t\t\t\t\tthis.contentContainer.addChild(new Text(collapsedLabel, 1, 0));\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {\n\t\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\t\titalic: true,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Show a live placeholder while streaming before text/thinking arrives.\n\t\tif (this.isStreaming && !hasVisibleContent) {\n\t\t\tconst spinner = getSpinnerFrame(Date.now() / 160);\n\t\t\tconst collapsedLabel =\n\t\t\t\ttheme.italic(theme.fg(\"thinkingText\", `Thinking ${spinner}...`)) +\n\t\t\t\t(this.hideThinkingBlock || this.expanded\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: theme.fg(\"dim\", ` (${editorKey(\"expandTools\")} to expand)`));\n\t\t\tthis.contentContainer.addChild(new Text(collapsedLabel, 1, 0));\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tif (hasVisibleContent) {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t} else {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t}\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -23,6 +23,13 @@ export declare class CustomEditor extends Editor {
23
23
  private schedulePlainPasteFlush;
24
24
  private flushPlainPasteBuffer;
25
25
  private rewritePasteMarker;
26
+ private rewriteRenderedLine;
27
+ private stripAnsiSgr;
28
+ private isInternalEditorBorder;
29
+ private splitInternalEditorLayout;
30
+ private getInputFrameLabel;
31
+ private renderTopFrame;
32
+ private wrapFrameLine;
26
33
  render(width: number): string[];
27
34
  handleInput(data: string): void;
28
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiC,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC7H,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElF;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACjC,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAa;IAC9D,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAM;IAGtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;gBAE3C,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,aAAa;IAKlG;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAItD,OAAO,CAAC,6BAA6B;IAkBrC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,kBAAkB;IAMjB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAWxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CA4D/B"}
1
+ {"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiC,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC7H,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAIlF;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACjC,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAa;IAC9D,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAM;IAGtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;gBAE3C,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,aAAa;IAKlG;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAItD,OAAO,CAAC,6BAA6B;IAkBrC,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,sBAAsB;IAO9B,OAAO,CAAC,yBAAyB;IA4BjC,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,aAAa;IAMZ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IA6BxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CA4D/B"}
@@ -1,4 +1,5 @@
1
1
  import { Editor, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
2
+ const ANSI_SGR_PATTERN = /\x1b\[[0-9;]*m/g;
2
3
  /**
3
4
  * Custom editor that handles app-level keybindings for coding-agent.
4
5
  */
@@ -62,17 +63,101 @@ export class CustomEditor extends Editor {
62
63
  return `[Pasted text #${id}${suffix ?? ""}]`;
63
64
  });
64
65
  }
66
+ rewriteRenderedLine(line, width) {
67
+ const rewritten = this.rewritePasteMarker(line);
68
+ if (rewritten === line)
69
+ return line;
70
+ if (visibleWidth(rewritten) <= width)
71
+ return rewritten;
72
+ return truncateToWidth(rewritten, width, "", true);
73
+ }
74
+ stripAnsiSgr(value) {
75
+ return value.replace(ANSI_SGR_PATTERN, "");
76
+ }
77
+ isInternalEditorBorder(line) {
78
+ const plain = this.stripAnsiSgr(line).trimEnd();
79
+ if (!plain)
80
+ return false;
81
+ if (/^─+$/.test(plain))
82
+ return true;
83
+ return /^─── [↑↓] \d+ more ─*$/.test(plain);
84
+ }
85
+ splitInternalEditorLayout(lines) {
86
+ if (lines.length === 0) {
87
+ return { content: [""], completion: [] };
88
+ }
89
+ if (lines.length < 3) {
90
+ return { content: lines, completion: [] };
91
+ }
92
+ let bottomBorderIndex = -1;
93
+ for (let i = lines.length - 1; i >= 1; i -= 1) {
94
+ if (this.isInternalEditorBorder(lines[i] ?? "")) {
95
+ bottomBorderIndex = i;
96
+ break;
97
+ }
98
+ }
99
+ if (bottomBorderIndex <= 0) {
100
+ return { content: lines, completion: [] };
101
+ }
102
+ const content = lines.slice(1, bottomBorderIndex);
103
+ const completion = lines.slice(bottomBorderIndex + 1);
104
+ return {
105
+ content: content.length > 0 ? content : [""],
106
+ completion,
107
+ };
108
+ }
109
+ getInputFrameLabel() {
110
+ const text = this.getText().trimStart();
111
+ if (text.startsWith("!"))
112
+ return "bash !";
113
+ if (text.startsWith("/"))
114
+ return "command /";
115
+ if (text.startsWith("&"))
116
+ return "mode &";
117
+ return "input >";
118
+ }
119
+ renderTopFrame(width, label, borderFn) {
120
+ const innerWidth = Math.max(0, width - 2);
121
+ const trimmedLabel = label.trim();
122
+ if (!trimmedLabel) {
123
+ return borderFn(`╭${"─".repeat(innerWidth)}╮`);
124
+ }
125
+ let decoratedLabel = ` ${trimmedLabel} `;
126
+ if (visibleWidth(decoratedLabel) >= innerWidth) {
127
+ decoratedLabel = truncateToWidth(decoratedLabel, innerWidth, "");
128
+ return borderFn(`╭${decoratedLabel}╮`);
129
+ }
130
+ const left = Math.max(0, innerWidth - visibleWidth(decoratedLabel));
131
+ return borderFn(`╭${"─".repeat(left)}${decoratedLabel}╮`);
132
+ }
133
+ wrapFrameLine(line, innerWidth, borderFn) {
134
+ const truncated = truncateToWidth(line, innerWidth, "");
135
+ const pad = Math.max(0, innerWidth - visibleWidth(truncated));
136
+ return borderFn("│") + " " + truncated + " ".repeat(pad) + " " + borderFn("│");
137
+ }
65
138
  render(width) {
66
- const lines = super.render(width);
67
- return lines.map((line) => {
68
- const rewritten = this.rewritePasteMarker(line);
69
- if (rewritten === line)
70
- return line;
71
- // Keep hard width guarantees after rewrite by trimming trailing visual width if needed.
72
- if (visibleWidth(rewritten) <= width)
73
- return rewritten;
74
- return truncateToWidth(rewritten, width, "", true);
75
- });
139
+ // Fallback to default renderer for extremely narrow layouts.
140
+ if (width < 8) {
141
+ return super.render(width).map((line) => this.rewriteRenderedLine(line, width));
142
+ }
143
+ const safeWidth = Math.max(1, width);
144
+ const frameInnerWidth = Math.max(1, safeWidth - 4); // "│ " + content + " │"
145
+ const rawLines = super.render(frameInnerWidth).map((line) => this.rewriteRenderedLine(line, frameInnerWidth));
146
+ const { content, completion } = this.splitInternalEditorLayout(rawLines);
147
+ const borderFn = this.borderColor;
148
+ const output = [];
149
+ output.push(this.renderTopFrame(safeWidth, this.getInputFrameLabel(), borderFn));
150
+ for (const line of content) {
151
+ output.push(this.wrapFrameLine(line, frameInnerWidth, borderFn));
152
+ }
153
+ if (completion.length > 0) {
154
+ output.push(borderFn(`├${"─".repeat(Math.max(0, safeWidth - 2))}┤`));
155
+ for (const line of completion) {
156
+ output.push(this.wrapFrameLine(line, frameInnerWidth, borderFn));
157
+ }
158
+ }
159
+ output.push(borderFn(`╰${"─".repeat(Math.max(0, safeWidth - 2))}╯`));
160
+ return output;
76
161
  }
77
162
  handleInput(data) {
78
163
  if (this.isLikelyUnbracketedPasteChunk(data)) {
@@ -1 +1 @@
1
- {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { Editor, truncateToWidth, visibleWidth, type EditorOptions, type EditorTheme, type TUI } from \"@mariozechner/pi-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\tprivate plainPasteBuffer = \"\";\n\tprivate plainPasteFlushTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate readonly plainPasteFlushDelayMs = 80;\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\tprivate isLikelyUnbracketedPasteChunk(data: string): boolean {\n\t\tif (!data) return false;\n\t\t// Ignore known escape/control sequences (arrows, alt-combos, etc.)\n\t\tif (data.includes(\"\\x1b\")) return false;\n\t\t// Single Enter key should still submit immediately.\n\t\tif (data.length <= 1) return false;\n\t\tif (!data.includes(\"\\n\") && !data.includes(\"\\r\")) return false;\n\t\tlet hasPrintable = false;\n\t\tfor (const char of data) {\n\t\t\tif (char === \"\\n\" || char === \"\\r\" || char === \"\\t\") continue;\n\t\t\tif (char.charCodeAt(0) >= 32) {\n\t\t\t\thasPrintable = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn hasPrintable;\n\t}\n\n\tprivate schedulePlainPasteFlush(): void {\n\t\tif (this.plainPasteFlushTimer) {\n\t\t\tclearTimeout(this.plainPasteFlushTimer);\n\t\t}\n\t\tthis.plainPasteFlushTimer = setTimeout(() => {\n\t\t\tthis.flushPlainPasteBuffer();\n\t\t}, this.plainPasteFlushDelayMs);\n\t}\n\n\tprivate flushPlainPasteBuffer(): void {\n\t\tif (this.plainPasteFlushTimer) {\n\t\t\tclearTimeout(this.plainPasteFlushTimer);\n\t\t\tthis.plainPasteFlushTimer = undefined;\n\t\t}\n\t\tif (!this.plainPasteBuffer) return;\n\t\tconst payload = this.plainPasteBuffer;\n\t\tthis.plainPasteBuffer = \"\";\n\t\tsuper.handleInput(`\\x1b[200~${payload}\\x1b[201~`);\n\t}\n\n\tprivate rewritePasteMarker(line: string): string {\n\t\treturn line.replace(/\\[paste #(\\d+)( (\\+\\d+ lines|\\d+ chars))?\\]/gi, (_match, id: string, suffix?: string) => {\n\t\t\treturn `[Pasted text #${id}${suffix ?? \"\"}]`;\n\t\t});\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\treturn lines.map((line) => {\n\t\t\tconst rewritten = this.rewritePasteMarker(line);\n\t\t\tif (rewritten === line) return line;\n\t\t\t// Keep hard width guarantees after rewrite by trimming trailing visual width if needed.\n\t\t\tif (visibleWidth(rewritten) <= width) return rewritten;\n\t\t\treturn truncateToWidth(rewritten, width, \"\", true);\n\t\t});\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.isLikelyUnbracketedPasteChunk(data)) {\n\t\t\tthis.plainPasteBuffer += data.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\t\t\tthis.schedulePlainPasteFlush();\n\t\t\treturn;\n\t\t}\n\t\t// If we buffered plain paste chunks, flush them before processing the next key.\n\t\tif (this.plainPasteBuffer) {\n\t\t\tthis.flushPlainPasteBuffer();\n\t\t}\n\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,YAAY,EAAkD,MAAM,sBAAsB,CAAC;AAG7H,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAE3C;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAcvC,YAAY,GAAQ,EAAE,KAAkB,EAAE,WAA+B,EAAE,OAAuB;QACjG,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAbrB,mBAAc,GAA+B,IAAI,GAAG,EAAE,CAAC;QACtD,qBAAgB,GAAG,EAAE,CAAC;QAEb,2BAAsB,GAAG,EAAE,CAAC;QAW5C,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,MAAiB,EAAE,OAAmB;QAC9C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAEO,6BAA6B,CAAC,IAAY;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,mEAAmE;QACnE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,oDAAoD;QACpD,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/D,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;YACzB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;gBAAE,SAAS;YAC9D,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC9B,YAAY,GAAG,IAAI,CAAC;gBACpB,MAAM;YACP,CAAC;QACF,CAAC;QACD,OAAO,YAAY,CAAC;IACrB,CAAC;IAEO,uBAAuB;QAC9B,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC/B,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3C,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC9B,CAAC,EAAE,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACjC,CAAC;IAEO,qBAAqB;QAC5B,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC/B,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACxC,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC;QACvC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB;YAAE,OAAO;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC;QACtC,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC3B,KAAK,CAAC,WAAW,CAAC,YAAY,OAAO,WAAW,CAAC,CAAC;IACnD,CAAC;IAEO,kBAAkB,CAAC,IAAY;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC,+CAA+C,EAAE,CAAC,MAAM,EAAE,EAAU,EAAE,MAAe,EAAE,EAAE;YAC5G,OAAO,iBAAiB,EAAE,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC;QAC9C,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,mBAAmB,CAAC,IAAY,EAAE,KAAa;QACtD,MAAM,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,KAAK;YAAE,OAAO,SAAS,CAAC;QACvD,OAAO,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAEO,YAAY,CAAC,KAAa;QACjC,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAEO,sBAAsB,CAAC,IAAY;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAChD,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAEO,yBAAyB,CAAC,KAAe;QAChD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC3C,CAAC;QAED,IAAI,iBAAiB,GAAG,CAAC,CAAC,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,IAAI,IAAI,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBACjD,iBAAiB,GAAG,CAAC,CAAC;gBACtB,MAAM;YACP,CAAC;QACF,CAAC;QAED,IAAI,iBAAiB,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC3C,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC;QACtD,OAAO;YACN,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,UAAU;SACV,CAAC;IACH,CAAC;IAEO,kBAAkB;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,WAAW,CAAC;QAC7C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC1C,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,cAAc,CAAC,KAAa,EAAE,KAAa,EAAE,QAAkC;QACtF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,cAAc,GAAG,IAAI,YAAY,GAAG,CAAC;QACzC,IAAI,YAAY,CAAC,cAAc,CAAC,IAAI,UAAU,EAAE,CAAC;YAChD,cAAc,GAAG,eAAe,CAAC,cAAc,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YACjE,OAAO,QAAQ,CAAC,IAAI,cAAc,GAAG,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC,CAAC;QACpE,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,GAAG,CAAC,CAAC;IAC3D,CAAC;IAEO,aAAa,CAAC,IAAY,EAAE,UAAkB,EAAE,QAAkC;QACzF,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9D,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChF,CAAC;IAEQ,MAAM,CAAC,KAAa;QAC5B,6DAA6D;QAC7D,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACf,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,wBAAwB;QAC5E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC;QAC9G,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC;QAElC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;QACjF,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;QAClE,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACrE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;YAClE,CAAC;QACF,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrE,OAAO,MAAM,CAAC;IACf,CAAC;IAED,WAAW,CAAC,IAAY;QACvB,IAAI,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC1E,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC/B,OAAO;QACR,CAAC;QACD,gFAAgF;QAChF,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC9B,CAAC;QAED,6CAA6C;QAC7C,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO;QACR,CAAC;QAED,mCAAmC;QACnC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,OAAO;QACR,CAAC;QAED,8BAA8B;QAE9B,wDAAwD;QACxD,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;gBACnC,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACtE,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO,EAAE,CAAC;oBACV,OAAO;gBACR,CAAC;YACF,CAAC;YACD,yDAAyD;YACzD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,4CAA4C;QAC5C,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAChE,IAAI,OAAO;oBAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YACR,CAAC;YACD,yEAAyE;QAC1E,CAAC;QAED,8BAA8B;QAC9B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC3F,OAAO,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;QACF,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;CACD","sourcesContent":["import { Editor, truncateToWidth, visibleWidth, type EditorOptions, type EditorTheme, type TUI } from \"@mariozechner/pi-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\nconst ANSI_SGR_PATTERN = /\\x1b\\[[0-9;]*m/g;\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\tprivate plainPasteBuffer = \"\";\n\tprivate plainPasteFlushTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate readonly plainPasteFlushDelayMs = 80;\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\tprivate isLikelyUnbracketedPasteChunk(data: string): boolean {\n\t\tif (!data) return false;\n\t\t// Ignore known escape/control sequences (arrows, alt-combos, etc.)\n\t\tif (data.includes(\"\\x1b\")) return false;\n\t\t// Single Enter key should still submit immediately.\n\t\tif (data.length <= 1) return false;\n\t\tif (!data.includes(\"\\n\") && !data.includes(\"\\r\")) return false;\n\t\tlet hasPrintable = false;\n\t\tfor (const char of data) {\n\t\t\tif (char === \"\\n\" || char === \"\\r\" || char === \"\\t\") continue;\n\t\t\tif (char.charCodeAt(0) >= 32) {\n\t\t\t\thasPrintable = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn hasPrintable;\n\t}\n\n\tprivate schedulePlainPasteFlush(): void {\n\t\tif (this.plainPasteFlushTimer) {\n\t\t\tclearTimeout(this.plainPasteFlushTimer);\n\t\t}\n\t\tthis.plainPasteFlushTimer = setTimeout(() => {\n\t\t\tthis.flushPlainPasteBuffer();\n\t\t}, this.plainPasteFlushDelayMs);\n\t}\n\n\tprivate flushPlainPasteBuffer(): void {\n\t\tif (this.plainPasteFlushTimer) {\n\t\t\tclearTimeout(this.plainPasteFlushTimer);\n\t\t\tthis.plainPasteFlushTimer = undefined;\n\t\t}\n\t\tif (!this.plainPasteBuffer) return;\n\t\tconst payload = this.plainPasteBuffer;\n\t\tthis.plainPasteBuffer = \"\";\n\t\tsuper.handleInput(`\\x1b[200~${payload}\\x1b[201~`);\n\t}\n\n\tprivate rewritePasteMarker(line: string): string {\n\t\treturn line.replace(/\\[paste #(\\d+)( (\\+\\d+ lines|\\d+ chars))?\\]/gi, (_match, id: string, suffix?: string) => {\n\t\t\treturn `[Pasted text #${id}${suffix ?? \"\"}]`;\n\t\t});\n\t}\n\n\tprivate rewriteRenderedLine(line: string, width: number): string {\n\t\tconst rewritten = this.rewritePasteMarker(line);\n\t\tif (rewritten === line) return line;\n\t\tif (visibleWidth(rewritten) <= width) return rewritten;\n\t\treturn truncateToWidth(rewritten, width, \"\", true);\n\t}\n\n\tprivate stripAnsiSgr(value: string): string {\n\t\treturn value.replace(ANSI_SGR_PATTERN, \"\");\n\t}\n\n\tprivate isInternalEditorBorder(line: string): boolean {\n\t\tconst plain = this.stripAnsiSgr(line).trimEnd();\n\t\tif (!plain) return false;\n\t\tif (/^─+$/.test(plain)) return true;\n\t\treturn /^─── [↑↓] \\d+ more ─*$/.test(plain);\n\t}\n\n\tprivate splitInternalEditorLayout(lines: string[]): { content: string[]; completion: string[] } {\n\t\tif (lines.length === 0) {\n\t\t\treturn { content: [\"\"], completion: [] };\n\t\t}\n\t\tif (lines.length < 3) {\n\t\t\treturn { content: lines, completion: [] };\n\t\t}\n\n\t\tlet bottomBorderIndex = -1;\n\t\tfor (let i = lines.length - 1; i >= 1; i -= 1) {\n\t\t\tif (this.isInternalEditorBorder(lines[i] ?? \"\")) {\n\t\t\t\tbottomBorderIndex = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (bottomBorderIndex <= 0) {\n\t\t\treturn { content: lines, completion: [] };\n\t\t}\n\n\t\tconst content = lines.slice(1, bottomBorderIndex);\n\t\tconst completion = lines.slice(bottomBorderIndex + 1);\n\t\treturn {\n\t\t\tcontent: content.length > 0 ? content : [\"\"],\n\t\t\tcompletion,\n\t\t};\n\t}\n\n\tprivate getInputFrameLabel(): string {\n\t\tconst text = this.getText().trimStart();\n\t\tif (text.startsWith(\"!\")) return \"bash !\";\n\t\tif (text.startsWith(\"/\")) return \"command /\";\n\t\tif (text.startsWith(\"&\")) return \"mode &\";\n\t\treturn \"input >\";\n\t}\n\n\tprivate renderTopFrame(width: number, label: string, borderFn: (text: string) => string): string {\n\t\tconst innerWidth = Math.max(0, width - 2);\n\t\tconst trimmedLabel = label.trim();\n\t\tif (!trimmedLabel) {\n\t\t\treturn borderFn(`╭${\"─\".repeat(innerWidth)}╮`);\n\t\t}\n\n\t\tlet decoratedLabel = ` ${trimmedLabel} `;\n\t\tif (visibleWidth(decoratedLabel) >= innerWidth) {\n\t\t\tdecoratedLabel = truncateToWidth(decoratedLabel, innerWidth, \"\");\n\t\t\treturn borderFn(`╭${decoratedLabel}╮`);\n\t\t}\n\n\t\tconst left = Math.max(0, innerWidth - visibleWidth(decoratedLabel));\n\t\treturn borderFn(`╭${\"─\".repeat(left)}${decoratedLabel}╮`);\n\t}\n\n\tprivate wrapFrameLine(line: string, innerWidth: number, borderFn: (text: string) => string): string {\n\t\tconst truncated = truncateToWidth(line, innerWidth, \"\");\n\t\tconst pad = Math.max(0, innerWidth - visibleWidth(truncated));\n\t\treturn borderFn(\"│\") + \" \" + truncated + \" \".repeat(pad) + \" \" + borderFn(\"│\");\n\t}\n\n\toverride render(width: number): string[] {\n\t\t// Fallback to default renderer for extremely narrow layouts.\n\t\tif (width < 8) {\n\t\t\treturn super.render(width).map((line) => this.rewriteRenderedLine(line, width));\n\t\t}\n\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst frameInnerWidth = Math.max(1, safeWidth - 4); // \"│ \" + content + \" │\"\n\t\tconst rawLines = super.render(frameInnerWidth).map((line) => this.rewriteRenderedLine(line, frameInnerWidth));\n\t\tconst { content, completion } = this.splitInternalEditorLayout(rawLines);\n\t\tconst borderFn = this.borderColor;\n\n\t\tconst output: string[] = [];\n\t\toutput.push(this.renderTopFrame(safeWidth, this.getInputFrameLabel(), borderFn));\n\t\tfor (const line of content) {\n\t\t\toutput.push(this.wrapFrameLine(line, frameInnerWidth, borderFn));\n\t\t}\n\n\t\tif (completion.length > 0) {\n\t\t\toutput.push(borderFn(`├${\"─\".repeat(Math.max(0, safeWidth - 2))}┤`));\n\t\t\tfor (const line of completion) {\n\t\t\t\toutput.push(this.wrapFrameLine(line, frameInnerWidth, borderFn));\n\t\t\t}\n\t\t}\n\n\t\toutput.push(borderFn(`╰${\"─\".repeat(Math.max(0, safeWidth - 2))}╯`));\n\t\treturn output;\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.isLikelyUnbracketedPasteChunk(data)) {\n\t\t\tthis.plainPasteBuffer += data.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\t\t\tthis.schedulePlainPasteFlush();\n\t\t\treturn;\n\t\t}\n\t\t// If we buffered plain paste chunks, flush them before processing the next key.\n\t\tif (this.plainPasteBuffer) {\n\t\t\tthis.flushPlainPasteBuffer();\n\t\t}\n\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
@@ -9,10 +9,12 @@ export declare class FooterComponent implements Component {
9
9
  private session;
10
10
  private footerData;
11
11
  private autoCompactEnabled;
12
+ private compactModeEnabled;
12
13
  private planMode;
13
14
  private activeProfile;
14
15
  constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider);
15
16
  setAutoCompactEnabled(enabled: boolean): void;
17
+ setCompactModeEnabled(enabled: boolean): void;
16
18
  /**
17
19
  * Toggle plan-mode badge in the status line.
18
20
  * When enabled, a [plan] badge is prepended before the session state badge.
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,sBAAsB,CAAC;AAErF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0DxF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAM/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IANnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAM;gBAGlB,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B;IAG/C,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI7C;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAInC;;;;OAIG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIvC;;;OAGG;IACH,UAAU,IAAI,IAAI;IAIlB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAoM/B"}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,sBAAsB,CAAC;AAErF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA0FxF;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAO/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAPnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAM;gBAGlB,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B;IAG/C,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI7C,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI7C;;;OAGG;IACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAInC;;;;OAIG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIvC;;;OAGG;IACH,UAAU,IAAI,IAAI;IAIlB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAkL/B"}
@@ -31,6 +31,32 @@ function formatTokens(count) {
31
31
  function badge(text, color) {
32
32
  return theme.fg("dim", "[") + theme.fg(color, text) + theme.fg("dim", "]");
33
33
  }
34
+ function composeAlignedLine(left, right, width, minPadding = 2) {
35
+ const safeWidth = Math.max(0, width);
36
+ let leftPart = left;
37
+ let leftWidth = visibleWidth(leftPart);
38
+ if (leftWidth > safeWidth) {
39
+ leftPart = truncateToWidth(leftPart, safeWidth, "...");
40
+ leftWidth = visibleWidth(leftPart);
41
+ }
42
+ if (!right) {
43
+ return leftPart;
44
+ }
45
+ const rightWidth = visibleWidth(right);
46
+ const totalNeeded = leftWidth + minPadding + rightWidth;
47
+ if (totalNeeded <= safeWidth) {
48
+ const padding = " ".repeat(safeWidth - leftWidth - rightWidth);
49
+ return leftPart + padding + right;
50
+ }
51
+ const availableForRight = safeWidth - leftWidth - minPadding;
52
+ if (availableForRight <= 0) {
53
+ return leftPart;
54
+ }
55
+ const truncatedRight = truncateToWidth(right, availableForRight, "");
56
+ const truncatedRightWidth = visibleWidth(truncatedRight);
57
+ const padding = " ".repeat(Math.max(0, safeWidth - leftWidth - truncatedRightWidth));
58
+ return leftPart + padding + truncatedRight;
59
+ }
34
60
  /**
35
61
  * Detect IOSM workspace and return a compact status segment.
36
62
  * Returns empty string when no IOSM workspace is found.
@@ -66,12 +92,16 @@ export class FooterComponent {
66
92
  this.session = session;
67
93
  this.footerData = footerData;
68
94
  this.autoCompactEnabled = true;
95
+ this.compactModeEnabled = false;
69
96
  this.planMode = false;
70
97
  this.activeProfile = "";
71
98
  }
72
99
  setAutoCompactEnabled(enabled) {
73
100
  this.autoCompactEnabled = enabled;
74
101
  }
102
+ setCompactModeEnabled(enabled) {
103
+ this.compactModeEnabled = enabled;
104
+ }
75
105
  /**
76
106
  * Toggle plan-mode badge in the status line.
77
107
  * When enabled, a [plan] badge is prepended before the session state badge.
@@ -205,17 +235,9 @@ export class FooterComponent {
205
235
  contextPercentStr = theme.fg("muted", contextPercentDisplay);
206
236
  }
207
237
  usageParts.push(contextPercentStr);
208
- let statsLeft = [...statusParts, ...usageParts].join(separator);
238
+ const statsLeft = [...statusParts, ...usageParts].join(separator);
209
239
  // Add provider/model on the right side, plus thinking level if model supports it
210
240
  const modelName = state.model ? `${state.model.provider}/${state.model.id}` : "no-model";
211
- let statsLeftWidth = visibleWidth(statsLeft);
212
- // If statsLeft is too wide, truncate it
213
- if (statsLeftWidth > width) {
214
- statsLeft = truncateToWidth(statsLeft, width, "...");
215
- statsLeftWidth = visibleWidth(statsLeft);
216
- }
217
- // Calculate available space for padding (minimum 2 spaces between stats and model)
218
- const minPadding = 2;
219
241
  // Add thinking level indicator if model supports reasoning
220
242
  let rightSideWithoutProvider = state.model ? theme.fg("accent", modelName) : theme.fg("warning", modelName);
221
243
  if (state.model?.reasoning) {
@@ -226,28 +248,7 @@ export class FooterComponent {
226
248
  : `${theme.fg("accent", modelName)}${theme.fg("muted", ` • ${thinkingLevel}`)}`;
227
249
  }
228
250
  const rightSide = rightSideWithoutProvider;
229
- const rightSideWidth = visibleWidth(rightSide);
230
- const totalNeeded = statsLeftWidth + minPadding + rightSideWidth;
231
- let statsLine;
232
- if (totalNeeded <= width) {
233
- // Both fit - add padding to right-align model
234
- const padding = " ".repeat(width - statsLeftWidth - rightSideWidth);
235
- statsLine = statsLeft + padding + rightSide;
236
- }
237
- else {
238
- // Need to truncate right side
239
- const availableForRight = width - statsLeftWidth - minPadding;
240
- if (availableForRight > 0) {
241
- const truncatedRight = truncateToWidth(rightSide, availableForRight, "");
242
- const truncatedRightWidth = visibleWidth(truncatedRight);
243
- const padding = " ".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));
244
- statsLine = statsLeft + padding + truncatedRight;
245
- }
246
- else {
247
- // Not enough space for right side at all
248
- statsLine = statsLeft;
249
- }
250
- }
251
+ const statsLine = composeAlignedLine(statsLeft, rightSide, width);
251
252
  // Build pwd line, optionally with IOSM status segment appended
252
253
  const iosmStatus = getIosmStatus(sessionCwd);
253
254
  let pwdLine;
@@ -263,7 +264,23 @@ export class FooterComponent {
263
264
  else {
264
265
  pwdLine = truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "..."));
265
266
  }
266
- const lines = [pwdLine, statsLine];
267
+ let lines;
268
+ if (this.compactModeEnabled) {
269
+ const compactUsageParts = [];
270
+ if (totalInput)
271
+ compactUsageParts.push(theme.fg("muted", `↑${formatTokens(totalInput)}`));
272
+ if (totalOutput)
273
+ compactUsageParts.push(theme.fg("muted", `↓${formatTokens(totalOutput)}`));
274
+ if (totalCost || usingSubscription) {
275
+ compactUsageParts.push(theme.fg("muted", `$${totalCost.toFixed(3)}${usingSubscription ? " (sub)" : ""}`));
276
+ }
277
+ compactUsageParts.push(contextPercentStr);
278
+ const compactLeft = [theme.fg("dim", pwd), ...statusParts, ...compactUsageParts].join(separator);
279
+ lines = [composeAlignedLine(compactLeft, rightSide, width)];
280
+ }
281
+ else {
282
+ lines = [pwdLine, statsLine];
283
+ }
267
284
  // Add extension statuses on a single line, sorted by key alphabetically
268
285
  const extensionStatuses = this.footerData.getExtensionStatuses();
269
286
  if (extensionStatuses.size > 0) {