u-foo 2.3.17 → 2.3.19
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/package.json +1 -1
- package/src/chat/agentViewController.js +154 -16
- package/src/chat/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const { version: packageVersion } = require("../../package.json");
|
|
3
|
+
|
|
4
|
+
const ANSI_RESET = "\x1b[0m";
|
|
5
|
+
const CLAUDE_ORANGE = "\x1b[38;2;217;119;87m";
|
|
6
|
+
|
|
1
7
|
function createAgentViewController(options = {}) {
|
|
2
8
|
const {
|
|
3
9
|
screen,
|
|
@@ -17,6 +23,7 @@ function createAgentViewController(options = {}) {
|
|
|
17
23
|
setAgentListWindowStart = () => {},
|
|
18
24
|
getAgentLabel = (id) => id,
|
|
19
25
|
getAgentStates = () => ({}),
|
|
26
|
+
getProjectRoot = () => process.cwd(),
|
|
20
27
|
setDashboardView = () => {},
|
|
21
28
|
setScreenGrabKeys = (value) => {
|
|
22
29
|
if (screen) screen.grabKeys = Boolean(value);
|
|
@@ -51,6 +58,8 @@ function createAgentViewController(options = {}) {
|
|
|
51
58
|
let busInputValue = "";
|
|
52
59
|
let busInputCursor = 0;
|
|
53
60
|
let busLogLines = [];
|
|
61
|
+
let busStartupAgentId = "";
|
|
62
|
+
let busStartupLineCount = 0;
|
|
54
63
|
const originalRender = screen.render.bind(screen);
|
|
55
64
|
let renderFrozen = false;
|
|
56
65
|
|
|
@@ -72,6 +81,10 @@ function createAgentViewController(options = {}) {
|
|
|
72
81
|
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
73
82
|
}
|
|
74
83
|
|
|
84
|
+
function hasAnsi(text = "") {
|
|
85
|
+
return /\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|\[[0-9;?]*[ -/]*[@-~])/.test(String(text || ""));
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
function clamp(value, min, max) {
|
|
76
89
|
const normalized = Number.isFinite(value) ? Math.floor(value) : min;
|
|
77
90
|
return Math.max(min, Math.min(max, normalized));
|
|
@@ -137,6 +150,39 @@ function createAgentViewController(options = {}) {
|
|
|
137
150
|
return `${truncateToWidth(clean, normalizedWidth - 1).trimEnd()}…`;
|
|
138
151
|
}
|
|
139
152
|
|
|
153
|
+
function fitAnsiText(text = "", width = 1) {
|
|
154
|
+
const normalizedWidth = Math.max(1, width);
|
|
155
|
+
const raw = String(text || "").replace(/\r/g, "");
|
|
156
|
+
if (!hasAnsi(raw)) return fitText(raw, normalizedWidth);
|
|
157
|
+
if (displayWidth(raw) <= normalizedWidth) {
|
|
158
|
+
return padToWidth(raw, normalizedWidth);
|
|
159
|
+
}
|
|
160
|
+
if (normalizedWidth <= 1) return "…";
|
|
161
|
+
|
|
162
|
+
const ansiPattern = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
163
|
+
let out = "";
|
|
164
|
+
let cells = 0;
|
|
165
|
+
let index = 0;
|
|
166
|
+
while (index < raw.length && cells < normalizedWidth - 1) {
|
|
167
|
+
ansiPattern.lastIndex = index;
|
|
168
|
+
const match = ansiPattern.exec(raw);
|
|
169
|
+
if (match && match.index === index) {
|
|
170
|
+
out += match[0];
|
|
171
|
+
index += match[0].length;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const char = Array.from(raw.slice(index))[0] || "";
|
|
175
|
+
if (!char) break;
|
|
176
|
+
const charWidth = charDisplayWidth(char);
|
|
177
|
+
if (cells + charWidth > normalizedWidth - 1) break;
|
|
178
|
+
out += char;
|
|
179
|
+
cells += charWidth;
|
|
180
|
+
index += char.length;
|
|
181
|
+
}
|
|
182
|
+
const suffix = `${ANSI_RESET}…`;
|
|
183
|
+
return padToWidth(`${out}${suffix}`, normalizedWidth);
|
|
184
|
+
}
|
|
185
|
+
|
|
140
186
|
function horizontalLine(width = 80) {
|
|
141
187
|
return "─".repeat(Math.max(1, width));
|
|
142
188
|
}
|
|
@@ -145,6 +191,11 @@ function createAgentViewController(options = {}) {
|
|
|
145
191
|
return fitText(text, Math.max(1, width));
|
|
146
192
|
}
|
|
147
193
|
|
|
194
|
+
function logLine(text = "", width = 80) {
|
|
195
|
+
const normalizedWidth = Math.max(1, width);
|
|
196
|
+
return hasAnsi(text) ? fitAnsiText(text, normalizedWidth) : plainLine(text, normalizedWidth);
|
|
197
|
+
}
|
|
198
|
+
|
|
148
199
|
function sliceDisplayCells(text = "", startCell = 0, maxCells = 1) {
|
|
149
200
|
const targetStart = Math.max(0, startCell);
|
|
150
201
|
const targetWidth = Math.max(1, maxCells);
|
|
@@ -170,6 +221,7 @@ function createAgentViewController(options = {}) {
|
|
|
170
221
|
|
|
171
222
|
function wrapTextLine(text = "", width = 80) {
|
|
172
223
|
const inner = Math.max(1, width);
|
|
224
|
+
if (hasAnsi(text)) return [String(text || "")];
|
|
173
225
|
const clean = stripAnsi(String(text || ""));
|
|
174
226
|
if (!clean) return [""];
|
|
175
227
|
const lines = [];
|
|
@@ -202,31 +254,116 @@ function createAgentViewController(options = {}) {
|
|
|
202
254
|
processStdout.write(`\x1b[${row};1H\x1b[2K${content}`);
|
|
203
255
|
}
|
|
204
256
|
|
|
205
|
-
function
|
|
257
|
+
function forceScreenRepaint() {
|
|
258
|
+
if (typeof screen.realloc === "function") {
|
|
259
|
+
screen.realloc();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof screen.alloc === "function") {
|
|
263
|
+
screen.alloc(true);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function compactProjectPath(projectRoot = "") {
|
|
268
|
+
const raw = String(projectRoot || process.cwd() || "").trim();
|
|
269
|
+
const home = os.homedir();
|
|
270
|
+
if (home && (raw === home || raw.startsWith(`${home}/`))) {
|
|
271
|
+
return `~${raw.slice(home.length)}`;
|
|
272
|
+
}
|
|
273
|
+
return raw || ".";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function borderedLines(lines = [], innerWidth = 56) {
|
|
277
|
+
const contentWidth = Math.max(1, innerWidth);
|
|
278
|
+
const out = [`╭${"─".repeat(contentWidth + 2)}╮`];
|
|
279
|
+
for (const line of lines) {
|
|
280
|
+
out.push(`│ ${fitText(line, contentWidth)} │`);
|
|
281
|
+
}
|
|
282
|
+
out.push(`╰${"─".repeat(contentWidth + 2)}╯`);
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeAgentKind(agentId = "") {
|
|
287
|
+
const text = String(agentId || "").trim().toLowerCase();
|
|
288
|
+
if (text.startsWith("codex:") || text === "codex") return "codex";
|
|
289
|
+
if (text.startsWith("claude:") || text.startsWith("claude-code:") || text === "claude" || text === "claude-code") {
|
|
290
|
+
return "claude";
|
|
291
|
+
}
|
|
292
|
+
return "internal";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function buildClaudeStartupLines(agentLabel = "", width = 80) {
|
|
296
|
+
const label = String(agentLabel || "").trim();
|
|
297
|
+
const projectPath = compactProjectPath(getProjectRoot());
|
|
298
|
+
const product = "ClaudeCode";
|
|
299
|
+
const detail = label ? `${label} · managed headless` : "managed headless";
|
|
300
|
+
const iconTop = `${CLAUDE_ORANGE}▐▛███▜▌${ANSI_RESET}`;
|
|
301
|
+
const iconMiddle = `${CLAUDE_ORANGE}▝▜█████▛▘${ANSI_RESET}`;
|
|
302
|
+
const iconBottom = `${CLAUDE_ORANGE}▘▘▝▝${ANSI_RESET}`;
|
|
303
|
+
const lines = [
|
|
304
|
+
` ${iconTop} ${product}v${packageVersion}`,
|
|
305
|
+
`${iconMiddle}${detail}`,
|
|
306
|
+
` ${iconBottom} ${projectPath}`,
|
|
307
|
+
"",
|
|
308
|
+
];
|
|
309
|
+
if (width < 44) return lines;
|
|
310
|
+
return lines.map((line) => fitAnsiText(line, Math.min(58, Math.max(1, width))));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function buildCodexStartupLines(agentLabel = "", width = 80) {
|
|
206
314
|
const label = String(agentLabel || "").trim();
|
|
207
|
-
|
|
315
|
+
const projectPath = compactProjectPath(getProjectRoot());
|
|
316
|
+
if (width < 36) {
|
|
208
317
|
return [
|
|
209
|
-
|
|
210
|
-
label ? `
|
|
318
|
+
`>_ OpenAI Codex`,
|
|
319
|
+
label ? `model: ${label}` : "model: managed headless",
|
|
320
|
+
`directory: ${projectPath}`,
|
|
211
321
|
"",
|
|
212
322
|
];
|
|
213
323
|
}
|
|
324
|
+
const innerWidth = Math.min(56, Math.max(24, width - 4));
|
|
214
325
|
return [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
326
|
+
...borderedLines([
|
|
327
|
+
`>_ OpenAI Codex (ufoo v${packageVersion})`,
|
|
328
|
+
"",
|
|
329
|
+
`model: ${label ? `${label} · managed headless` : "managed headless"}`,
|
|
330
|
+
`directory: ${projectPath}`,
|
|
331
|
+
], innerWidth),
|
|
221
332
|
"",
|
|
222
333
|
];
|
|
223
334
|
}
|
|
224
335
|
|
|
336
|
+
function buildInternalStartupLines(agentId = "", agentLabel = "", width = 80) {
|
|
337
|
+
const kind = normalizeAgentKind(agentId);
|
|
338
|
+
if (kind === "codex") return buildCodexStartupLines(agentLabel || agentId, width);
|
|
339
|
+
return buildClaudeStartupLines(agentLabel || agentId, width);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function staticStartupLines(agentId = "", agentLabel = "", width = 80) {
|
|
343
|
+
const lines = buildInternalStartupLines(agentId, agentLabel, width);
|
|
344
|
+
if (lines.length > 0 && String(lines[lines.length - 1] || "") === "") {
|
|
345
|
+
return lines.slice(0, -1);
|
|
346
|
+
}
|
|
347
|
+
return lines;
|
|
348
|
+
}
|
|
349
|
+
|
|
225
350
|
function resetBusView(agentId) {
|
|
226
351
|
busInputValue = "";
|
|
227
352
|
busInputCursor = 0;
|
|
353
|
+
busStartupAgentId = agentId || "";
|
|
228
354
|
const label = getAgentLabel(agentId);
|
|
229
|
-
|
|
355
|
+
const startupLines = staticStartupLines(agentId, label, getCols());
|
|
356
|
+
busLogLines = startupLines.concat("");
|
|
357
|
+
busStartupLineCount = startupLines.length;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function refreshBusStartupLines(width = getCols()) {
|
|
361
|
+
if (!busStartupAgentId || busStartupLineCount <= 0) return;
|
|
362
|
+
const label = getAgentLabel(busStartupAgentId);
|
|
363
|
+
const startupLines = staticStartupLines(busStartupAgentId, label, width);
|
|
364
|
+
const tailLines = busLogLines.slice(busStartupLineCount);
|
|
365
|
+
busLogLines = startupLines.concat(tailLines.length > 0 ? tailLines : [""]);
|
|
366
|
+
busStartupLineCount = startupLines.length;
|
|
230
367
|
}
|
|
231
368
|
|
|
232
369
|
function appendBusLog(text = "") {
|
|
@@ -265,6 +402,7 @@ function createAgentViewController(options = {}) {
|
|
|
265
402
|
const rows = getRows();
|
|
266
403
|
const cols = getCols();
|
|
267
404
|
const width = Math.max(20, cols);
|
|
405
|
+
refreshBusStartupLines(width);
|
|
268
406
|
const inputTop = Math.max(4, rows - 3);
|
|
269
407
|
const logContentTop = 1;
|
|
270
408
|
const logContentBottom = Math.max(logContentTop, inputTop - 1);
|
|
@@ -273,7 +411,7 @@ function createAgentViewController(options = {}) {
|
|
|
273
411
|
processStdout.write("\x1b[?25l");
|
|
274
412
|
const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
|
|
275
413
|
for (let i = 0; i < logContentHeight; i += 1) {
|
|
276
|
-
writeAt(logContentTop + i,
|
|
414
|
+
writeAt(logContentTop + i, logLine(visibleLines[i] || "", width));
|
|
277
415
|
}
|
|
278
416
|
|
|
279
417
|
writeAt(inputTop, horizontalLine(width));
|
|
@@ -379,12 +517,14 @@ function createAgentViewController(options = {}) {
|
|
|
379
517
|
busInputValue = "";
|
|
380
518
|
busInputCursor = 0;
|
|
381
519
|
busLogLines = [];
|
|
520
|
+
busStartupAgentId = "";
|
|
521
|
+
busStartupLineCount = 0;
|
|
382
522
|
|
|
383
523
|
currentView = "main";
|
|
384
524
|
viewingAgent = null;
|
|
385
525
|
|
|
386
526
|
processStdout.write(`\x1b[1;${rows}r`);
|
|
387
|
-
processStdout.write("\x1b[
|
|
527
|
+
processStdout.write("\x1b[?25h");
|
|
388
528
|
|
|
389
529
|
if (detachedChildren) {
|
|
390
530
|
for (const child of detachedChildren) screen.append(child);
|
|
@@ -396,9 +536,7 @@ function createAgentViewController(options = {}) {
|
|
|
396
536
|
setDashboardView("agents");
|
|
397
537
|
setSelectedAgentIndex(-1);
|
|
398
538
|
setScreenGrabKeys(false);
|
|
399
|
-
|
|
400
|
-
screen.alloc();
|
|
401
|
-
}
|
|
539
|
+
forceScreenRepaint();
|
|
402
540
|
clearTargetAgent();
|
|
403
541
|
renderDashboard();
|
|
404
542
|
focusInput();
|