pi-open-tui 0.3.14 → 0.4.1
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/README.md +72 -5
- package/README.zh-CN.md +72 -5
- package/assets/preview_dashboard_1.png +0 -0
- package/extensions/open-tui/config.ts +26 -2
- package/extensions/open-tui/copy-buttons.ts +363 -0
- package/extensions/open-tui/editor.ts +322 -122
- package/extensions/open-tui/footer.ts +13 -11
- package/extensions/open-tui/git.ts +10 -32
- package/extensions/open-tui/header.ts +133 -244
- package/extensions/open-tui/icons.ts +9 -0
- package/extensions/open-tui/index.ts +45 -20
- package/extensions/open-tui/runtime.ts +10 -11
- package/extensions/open-tui/session-lifecycle.ts +23 -43
- package/extensions/open-tui/settings-command.ts +66 -45
- package/extensions/open-tui/state.ts +11 -14
- package/extensions/open-tui/tool-preview.ts +167 -0
- package/extensions/open-tui/tool-view.ts +418 -0
- package/extensions/open-tui/utils.ts +3 -3
- package/package.json +11 -15
|
@@ -58,7 +58,7 @@ export async function readGitStatus(
|
|
|
58
58
|
options: { readCommit?: boolean; readTag?: boolean; readCounts?: boolean } = {},
|
|
59
59
|
): Promise<GitStatus> {
|
|
60
60
|
const stdout = await gitExec(
|
|
61
|
-
["status", "--porcelain=v1", "--branch"
|
|
61
|
+
["status", "--porcelain=v1", "--branch"],
|
|
62
62
|
cwd,
|
|
63
63
|
);
|
|
64
64
|
if (stdout === null) {
|
|
@@ -67,7 +67,6 @@ export async function readGitStatus(
|
|
|
67
67
|
|
|
68
68
|
const status = emptyGitStatus();
|
|
69
69
|
const lines = stdout.split("\n");
|
|
70
|
-
let stashSupported = true;
|
|
71
70
|
|
|
72
71
|
for (const line of lines) {
|
|
73
72
|
if (line.startsWith("## ")) {
|
|
@@ -89,17 +88,7 @@ export async function readGitStatus(
|
|
|
89
88
|
continue;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
const stashCount = parseInt(line.slice(8).trim(), 10);
|
|
94
|
-
if (!Number.isNaN(stashCount)) {
|
|
95
|
-
status.stashed = stashCount;
|
|
96
|
-
stashSupported = true;
|
|
97
|
-
}
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ponytail: skip file-entry counting when only branch/commit display is on;
|
|
102
|
-
// the first `git status` call itself can't be split, but the stash fallback below can.
|
|
91
|
+
// Skip file-entry counting and the separate stash query for branch-only display.
|
|
103
92
|
if (options.readCounts === false) continue;
|
|
104
93
|
if (line.length < 3) continue;
|
|
105
94
|
const x = line[0]!;
|
|
@@ -107,19 +96,21 @@ export async function readGitStatus(
|
|
|
107
96
|
|
|
108
97
|
if (x === "U" || y === "U" || (x === "A" && y === "A") || (x === "D" && y === "D")) status.conflicted++;
|
|
109
98
|
else if (x === "?" && y === "?") status.untracked++;
|
|
110
|
-
else if (x === "R")
|
|
111
|
-
|
|
99
|
+
else if (x === "R") {
|
|
100
|
+
status.renamed++;
|
|
101
|
+
if (y === "M") status.modified++;
|
|
102
|
+
else if (y === "D") status.deleted++;
|
|
103
|
+
} else if (x === "D" || y === "D") status.deleted++;
|
|
112
104
|
else {
|
|
113
105
|
if (x !== " " && x !== "?") status.staged++;
|
|
114
106
|
if (y === "M" || y === "D") status.modified++;
|
|
115
107
|
}
|
|
116
108
|
}
|
|
117
109
|
|
|
118
|
-
if (options.readCounts !== false
|
|
119
|
-
const stashOut = await gitExec(["stash", "list", "--
|
|
110
|
+
if (options.readCounts !== false) {
|
|
111
|
+
const stashOut = await gitExec(["stash", "list", "--format=%H"], cwd);
|
|
120
112
|
if (stashOut !== null) {
|
|
121
|
-
|
|
122
|
-
if (!Number.isNaN(count)) status.stashed = count;
|
|
113
|
+
status.stashed = stashOut.split("\n").filter((line) => line.trim() !== "").length;
|
|
123
114
|
}
|
|
124
115
|
}
|
|
125
116
|
|
|
@@ -138,16 +129,3 @@ export async function readGitStatus(
|
|
|
138
129
|
|
|
139
130
|
return status;
|
|
140
131
|
}
|
|
141
|
-
|
|
142
|
-
export function hasGitChanges(s: GitStatus): boolean {
|
|
143
|
-
return (
|
|
144
|
-
s.modified > 0 ||
|
|
145
|
-
s.untracked > 0 ||
|
|
146
|
-
s.staged > 0 ||
|
|
147
|
-
s.conflicted > 0 ||
|
|
148
|
-
s.renamed > 0 ||
|
|
149
|
-
s.deleted > 0 ||
|
|
150
|
-
s.ahead > 0 ||
|
|
151
|
-
s.behind > 0
|
|
152
|
-
);
|
|
153
|
-
}
|
|
@@ -1,244 +1,133 @@
|
|
|
1
|
-
import { VERSION, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
4
|
-
center,
|
|
5
|
-
collectPiCommandNames,
|
|
6
|
-
formatCwd,
|
|
7
|
-
formatModelLabel,
|
|
8
|
-
formatThinkingLabel,
|
|
9
|
-
headerColumnWidths,
|
|
10
|
-
padRight,
|
|
11
|
-
pickSlashCommandTips,
|
|
12
|
-
truncateToWidth,
|
|
13
|
-
visibleWidth,
|
|
14
|
-
} from "./utils.ts";
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
paint: (text: string) => string,
|
|
135
|
-
): string {
|
|
136
|
-
if (width <= 1) return "";
|
|
137
|
-
if (width < 8 || label.length === 0) {
|
|
138
|
-
return paint(truncateToWidth(left + "─".repeat(Math.max(0, width - 2)) + right, width, ""));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const before = "─── ";
|
|
142
|
-
const after = " ─────";
|
|
143
|
-
const fixedWidth = visibleWidth(before) + visibleWidth(label) + visibleWidth(after);
|
|
144
|
-
const fill = Math.max(0, width - 2 - fixedWidth);
|
|
145
|
-
return `${paint(left)}${paint(before)}${label}${paint(after)}${paint("─".repeat(fill))}${paint(right)}`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function boxedLine(content: string, width: number, paint: (text: string) => string): string {
|
|
149
|
-
if (width <= 2) return truncateToWidth(content, width, "");
|
|
150
|
-
return `${paint("│")}${padRight(content, width - 2)}${paint("│")}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function twoColumn(
|
|
154
|
-
left: string,
|
|
155
|
-
right: string,
|
|
156
|
-
leftWidth: number,
|
|
157
|
-
rightWidth: number,
|
|
158
|
-
paint: (text: string) => string,
|
|
159
|
-
): string {
|
|
160
|
-
return `${padRight(left, leftWidth)} ${paint("│")} ${padRight(right, rightWidth, "…")}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export class OpenTuiHeader implements Component {
|
|
164
|
-
private readonly pi: ExtensionAPI;
|
|
165
|
-
private readonly ctx: ExtensionContext;
|
|
166
|
-
private readonly frame = LOGO_FRAMES.length - 1;
|
|
167
|
-
private readonly tipCommands: string[];
|
|
168
|
-
|
|
169
|
-
constructor(pi: ExtensionAPI, ctx: ExtensionContext, _tui: TUI) {
|
|
170
|
-
this.pi = pi;
|
|
171
|
-
this.ctx = ctx;
|
|
172
|
-
const pool = collectPiCommandNames(pi.getCommands());
|
|
173
|
-
this.tipCommands = pickSlashCommandTips(pool, {
|
|
174
|
-
fixed: ["open-tui"],
|
|
175
|
-
count: 3,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
render(width: number): string[] {
|
|
180
|
-
const theme = this.ctx.ui.theme;
|
|
181
|
-
const paint = (s: string) => theme.fg("accent", s);
|
|
182
|
-
const muted = (s: string) => theme.fg("muted", s);
|
|
183
|
-
const dim = (s: string) => theme.fg("dim", s);
|
|
184
|
-
const bold = (s: string) => theme.bold(s);
|
|
185
|
-
|
|
186
|
-
if (width < 24) return [paint(`Pi v${VERSION}`)];
|
|
187
|
-
|
|
188
|
-
const innerWidth = width - 2;
|
|
189
|
-
const { leftWidth, rightWidth, useTips } = headerColumnWidths(innerWidth);
|
|
190
|
-
const model = formatModelLabel(this.ctx.model);
|
|
191
|
-
const effort = formatThinkingLabel(this.pi.getThinkingLevel());
|
|
192
|
-
const cwd = formatCwd(this.ctx.cwd);
|
|
193
|
-
|
|
194
|
-
const leftLines = [
|
|
195
|
-
...renderLogo(this.frame, paint).map((line) => center(line, leftWidth)),
|
|
196
|
-
center(bold("Let's build something great"), leftWidth),
|
|
197
|
-
center(muted(`${model} · ${effort}`), leftWidth),
|
|
198
|
-
center(dim(cwd), leftWidth),
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
const tipDivider = paint("─".repeat(Math.max(8, Math.min(rightWidth, 22))));
|
|
202
|
-
const [cmd0 = "", cmd1 = "", cmd2 = "", cmd3 = ""] = this.tipCommands;
|
|
203
|
-
const tipLines = [
|
|
204
|
-
"",
|
|
205
|
-
paint(bold("Welcome")),
|
|
206
|
-
muted("Ask Pi anything"),
|
|
207
|
-
tipDivider,
|
|
208
|
-
paint(bold("Commands")),
|
|
209
|
-
muted(cmd0),
|
|
210
|
-
muted(cmd1),
|
|
211
|
-
muted(cmd2),
|
|
212
|
-
muted(cmd3),
|
|
213
|
-
"",
|
|
214
|
-
];
|
|
215
|
-
|
|
216
|
-
const lines = [borderLine("╭", `${paint("Pi")} v${VERSION}`, "╮", width, paint)];
|
|
217
|
-
for (let i = 0; i < leftLines.length; i++) {
|
|
218
|
-
const content = useTips
|
|
219
|
-
? twoColumn(leftLines[i] ?? "", tipLines[i] ?? "", leftWidth, rightWidth, paint)
|
|
220
|
-
: padRight(leftLines[i] ?? "", leftWidth);
|
|
221
|
-
lines.push(boxedLine(content, width, paint));
|
|
222
|
-
}
|
|
223
|
-
lines.push(borderLine("╰", "", "╯", width, paint));
|
|
224
|
-
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
invalidate(): void {}
|
|
228
|
-
|
|
229
|
-
dispose(): void {}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function installHeader(pi: ExtensionAPI, ctx: ExtensionContext): () => void {
|
|
233
|
-
let header: OpenTuiHeader | undefined;
|
|
234
|
-
ctx.ui.setHeader((tui) => {
|
|
235
|
-
header?.dispose();
|
|
236
|
-
header = new OpenTuiHeader(pi, ctx, tui);
|
|
237
|
-
return header;
|
|
238
|
-
});
|
|
239
|
-
return () => {
|
|
240
|
-
header?.dispose();
|
|
241
|
-
header = undefined;
|
|
242
|
-
ctx.ui.setHeader(undefined);
|
|
243
|
-
};
|
|
244
|
-
}
|
|
1
|
+
import { VERSION, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import {
|
|
4
|
+
center,
|
|
5
|
+
collectPiCommandNames,
|
|
6
|
+
formatCwd,
|
|
7
|
+
formatModelLabel,
|
|
8
|
+
formatThinkingLabel,
|
|
9
|
+
headerColumnWidths,
|
|
10
|
+
padRight,
|
|
11
|
+
pickSlashCommandTips,
|
|
12
|
+
truncateToWidth,
|
|
13
|
+
visibleWidth,
|
|
14
|
+
} from "./utils.ts";
|
|
15
|
+
|
|
16
|
+
const LOGO = [
|
|
17
|
+
" ",
|
|
18
|
+
" ",
|
|
19
|
+
"█████████ ",
|
|
20
|
+
"███ ███ ",
|
|
21
|
+
"██████ ███",
|
|
22
|
+
"███ ███",
|
|
23
|
+
" ",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function renderLogo(paintBrand: (text: string) => string): string[] {
|
|
27
|
+
return LOGO.map((line) => line.replace(/███/g, paintBrand));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function borderLine(
|
|
31
|
+
left: string,
|
|
32
|
+
label: string,
|
|
33
|
+
right: string,
|
|
34
|
+
width: number,
|
|
35
|
+
paint: (text: string) => string,
|
|
36
|
+
): string {
|
|
37
|
+
if (width <= 1) return "";
|
|
38
|
+
if (width < 8 || label.length === 0) {
|
|
39
|
+
return paint(truncateToWidth(left + "─".repeat(Math.max(0, width - 2)) + right, width, ""));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const before = "─── ";
|
|
43
|
+
const after = " ─────";
|
|
44
|
+
const fixedWidth = visibleWidth(before) + visibleWidth(label) + visibleWidth(after);
|
|
45
|
+
const fill = Math.max(0, width - 2 - fixedWidth);
|
|
46
|
+
return `${paint(left)}${paint(before)}${label}${paint(after)}${paint("─".repeat(fill))}${paint(right)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function boxedLine(content: string, width: number, paint: (text: string) => string): string {
|
|
50
|
+
if (width <= 2) return truncateToWidth(content, width, "");
|
|
51
|
+
return `${paint("│")}${padRight(content, width - 2)}${paint("│")}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function twoColumn(
|
|
55
|
+
left: string,
|
|
56
|
+
right: string,
|
|
57
|
+
leftWidth: number,
|
|
58
|
+
rightWidth: number,
|
|
59
|
+
paint: (text: string) => string,
|
|
60
|
+
): string {
|
|
61
|
+
return `${padRight(left, leftWidth)} ${paint("│")} ${padRight(right, rightWidth, "…")}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class OpenTuiHeader implements Component {
|
|
65
|
+
private readonly pi: ExtensionAPI;
|
|
66
|
+
private readonly ctx: ExtensionContext;
|
|
67
|
+
private readonly tipCommands: string[];
|
|
68
|
+
|
|
69
|
+
constructor(pi: ExtensionAPI, ctx: ExtensionContext, _tui: TUI) {
|
|
70
|
+
this.pi = pi;
|
|
71
|
+
this.ctx = ctx;
|
|
72
|
+
const pool = collectPiCommandNames(pi.getCommands());
|
|
73
|
+
this.tipCommands = pickSlashCommandTips(pool, {
|
|
74
|
+
fixed: ["open-tui"],
|
|
75
|
+
count: 3,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
render(width: number): string[] {
|
|
80
|
+
const theme = this.ctx.ui.theme;
|
|
81
|
+
const paint = (s: string) => theme.fg("accent", s);
|
|
82
|
+
const muted = (s: string) => theme.fg("muted", s);
|
|
83
|
+
const dim = (s: string) => theme.fg("dim", s);
|
|
84
|
+
const bold = (s: string) => theme.bold(s);
|
|
85
|
+
|
|
86
|
+
if (width < 24) return [truncateToWidth(paint(`Pi v${VERSION}`), width, "")];
|
|
87
|
+
|
|
88
|
+
const innerWidth = width - 2;
|
|
89
|
+
const { leftWidth, rightWidth, useTips } = headerColumnWidths(innerWidth);
|
|
90
|
+
const model = formatModelLabel(this.ctx.model);
|
|
91
|
+
const effort = formatThinkingLabel(this.pi.getThinkingLevel());
|
|
92
|
+
const cwd = formatCwd(this.ctx.cwd);
|
|
93
|
+
|
|
94
|
+
const leftLines = [
|
|
95
|
+
...renderLogo(paint).map((line) => center(line, leftWidth)),
|
|
96
|
+
center(bold("Let's build something great"), leftWidth),
|
|
97
|
+
center(muted(`${model} · ${effort}`), leftWidth),
|
|
98
|
+
center(dim(cwd), leftWidth),
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const tipDivider = paint("─".repeat(Math.max(8, Math.min(rightWidth, 22))));
|
|
102
|
+
const [cmd0 = "", cmd1 = "", cmd2 = "", cmd3 = ""] = this.tipCommands;
|
|
103
|
+
const tipLines = [
|
|
104
|
+
"",
|
|
105
|
+
paint(bold("Welcome")),
|
|
106
|
+
muted("Ask Pi anything"),
|
|
107
|
+
tipDivider,
|
|
108
|
+
paint(bold("Commands")),
|
|
109
|
+
muted(cmd0),
|
|
110
|
+
muted(cmd1),
|
|
111
|
+
muted(cmd2),
|
|
112
|
+
muted(cmd3),
|
|
113
|
+
"",
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const lines = [borderLine("╭", `${paint("Pi")} v${VERSION}`, "╮", width, paint)];
|
|
117
|
+
for (let i = 0; i < leftLines.length; i++) {
|
|
118
|
+
const content = useTips
|
|
119
|
+
? twoColumn(leftLines[i] ?? "", tipLines[i] ?? "", leftWidth, rightWidth, paint)
|
|
120
|
+
: padRight(leftLines[i] ?? "", leftWidth);
|
|
121
|
+
lines.push(boxedLine(content, width, paint));
|
|
122
|
+
}
|
|
123
|
+
lines.push(borderLine("╰", "", "╯", width, paint));
|
|
124
|
+
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
invalidate(): void {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function installHeader(pi: ExtensionAPI, ctx: ExtensionContext): () => void {
|
|
131
|
+
ctx.ui.setHeader((tui) => new OpenTuiHeader(pi, ctx, tui));
|
|
132
|
+
return () => ctx.ui.setHeader(undefined);
|
|
133
|
+
}
|
|
@@ -28,6 +28,9 @@ export interface IconGlyphs {
|
|
|
28
28
|
untracked: string;
|
|
29
29
|
renamed: string;
|
|
30
30
|
deleted: string;
|
|
31
|
+
/** Compact tool view: call marker and result gutter. */
|
|
32
|
+
toolCall: string;
|
|
33
|
+
toolResult: string;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
const NERD_GLYPHS: IconGlyphs = {
|
|
@@ -59,6 +62,8 @@ const NERD_GLYPHS: IconGlyphs = {
|
|
|
59
62
|
untracked: "?",
|
|
60
63
|
renamed: "»",
|
|
61
64
|
deleted: "✘",
|
|
65
|
+
toolCall: "●",
|
|
66
|
+
toolResult: "⎿",
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
// ponytail: ASCII fallback uses compact symbols (not English words) to keep
|
|
@@ -92,6 +97,8 @@ const ASCII_GLYPHS: IconGlyphs = {
|
|
|
92
97
|
untracked: "?",
|
|
93
98
|
renamed: "r",
|
|
94
99
|
deleted: "x",
|
|
100
|
+
toolCall: "*",
|
|
101
|
+
toolResult: "`-",
|
|
95
102
|
};
|
|
96
103
|
|
|
97
104
|
// Portable Unicode icon set for terminals without a Nerd Font. Keeps the
|
|
@@ -129,6 +136,8 @@ const UNICODE_GLYPHS: IconGlyphs = {
|
|
|
129
136
|
untracked: "?",
|
|
130
137
|
renamed: "→",
|
|
131
138
|
deleted: "✗",
|
|
139
|
+
toolCall: "●",
|
|
140
|
+
toolResult: "└",
|
|
132
141
|
};
|
|
133
142
|
|
|
134
143
|
function isSshSession(): boolean {
|