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.
- package/CHANGELOG.md +24 -0
- package/README.md +7 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +3 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings.schema.json +21 -0
- package/dist/core/subagents.d.ts.map +1 -1
- package/dist/core/subagents.js +4 -0
- package/dist/core/subagents.js.map +1 -1
- package/dist/core/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +48 -26
- package/dist/core/tools/task.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +77 -5
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +7 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +95 -10
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +2 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +49 -32
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/message-frame-border.d.ts +22 -0
- package/dist/modes/interactive/components/message-frame-border.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-frame-border.js +51 -0
- package/dist/modes/interactive/components/message-frame-border.js.map +1 -0
- package/dist/modes/interactive/components/message-window.d.ts +25 -0
- package/dist/modes/interactive/components/message-window.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-window.js +66 -0
- package/dist/modes/interactive/components/message-window.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +12 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/subagent-message.d.ts +12 -9
- package/dist/modes/interactive/components/subagent-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/subagent-message.js +138 -136
- package/dist/modes/interactive/components/subagent-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +33 -28
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +10 -3
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +11 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +509 -155
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- 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;
|
|
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
|
-
|
|
29
|
+
this.addChild(new Spacer(1));
|
|
30
|
+
// Container for message content
|
|
12
31
|
this.contentContainer = new Container();
|
|
13
|
-
this.
|
|
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
|
-
|
|
33
|
-
|
|
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;
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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":"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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\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;
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|