u-foo 2.3.14 → 2.3.15
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 +267 -3
- package/src/chat/index.js +13 -0
package/package.json
CHANGED
|
@@ -32,6 +32,7 @@ function createAgentViewController(options = {}) {
|
|
|
32
32
|
connectAgentInput = () => {},
|
|
33
33
|
disconnectAgentInput = () => {},
|
|
34
34
|
sendRaw = () => {},
|
|
35
|
+
sendBusMessage = () => {},
|
|
35
36
|
sendResize = () => {},
|
|
36
37
|
requestScreenSnapshot = () => {},
|
|
37
38
|
} = options;
|
|
@@ -47,6 +48,9 @@ function createAgentViewController(options = {}) {
|
|
|
47
48
|
let agentBarVisible = false;
|
|
48
49
|
let detachedChildren = null;
|
|
49
50
|
let agentInputSuppressUntil = 0;
|
|
51
|
+
let busInputValue = "";
|
|
52
|
+
let busInputCursor = 0;
|
|
53
|
+
let busLogLines = [];
|
|
50
54
|
const originalRender = screen.render.bind(screen);
|
|
51
55
|
let renderFrozen = false;
|
|
52
56
|
|
|
@@ -63,6 +67,139 @@ function createAgentViewController(options = {}) {
|
|
|
63
67
|
return processStdout.columns || 80;
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
function stripAnsi(text = "") {
|
|
71
|
+
return String(text || "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
72
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clamp(value, min, max) {
|
|
76
|
+
const normalized = Number.isFinite(value) ? Math.floor(value) : min;
|
|
77
|
+
return Math.max(min, Math.min(max, normalized));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function fitText(text = "", width = 1) {
|
|
81
|
+
const normalizedWidth = Math.max(1, width);
|
|
82
|
+
const clean = stripAnsi(String(text || "")).replace(/\r/g, "");
|
|
83
|
+
if (clean.length <= normalizedWidth) {
|
|
84
|
+
return clean + " ".repeat(normalizedWidth - clean.length);
|
|
85
|
+
}
|
|
86
|
+
if (normalizedWidth <= 1) return clean.slice(0, normalizedWidth);
|
|
87
|
+
return clean.slice(0, normalizedWidth - 1) + "…";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function boxTop(title = "", width = 80) {
|
|
91
|
+
const inner = Math.max(1, width - 2);
|
|
92
|
+
const label = title ? ` ${title} ` : "";
|
|
93
|
+
const safe = stripAnsi(label).slice(0, inner);
|
|
94
|
+
return `┌${safe}${"─".repeat(Math.max(0, inner - safe.length))}┐`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function boxBottom(width = 80) {
|
|
98
|
+
return `└${"─".repeat(Math.max(1, width - 2))}┘`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function boxMiddle(text = "", width = 80) {
|
|
102
|
+
const inner = Math.max(1, width - 2);
|
|
103
|
+
return `│${fitText(text, inner)}│`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function wrapTextLine(text = "", width = 80) {
|
|
107
|
+
const inner = Math.max(1, width);
|
|
108
|
+
const clean = stripAnsi(String(text || ""));
|
|
109
|
+
if (!clean) return [""];
|
|
110
|
+
const lines = [];
|
|
111
|
+
let rest = clean;
|
|
112
|
+
while (rest.length > inner) {
|
|
113
|
+
lines.push(rest.slice(0, inner));
|
|
114
|
+
rest = rest.slice(inner);
|
|
115
|
+
}
|
|
116
|
+
lines.push(rest);
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getWrappedBusLogLines(width = 80) {
|
|
121
|
+
const inner = Math.max(1, width - 2);
|
|
122
|
+
const wrapped = [];
|
|
123
|
+
for (const line of busLogLines) {
|
|
124
|
+
wrapped.push(...wrapTextLine(line, inner));
|
|
125
|
+
}
|
|
126
|
+
return wrapped;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function writeAt(row, content = "") {
|
|
130
|
+
processStdout.write(`\x1b[${row};1H\x1b[2K${content}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resetBusView(agentId) {
|
|
134
|
+
busInputValue = "";
|
|
135
|
+
busInputCursor = 0;
|
|
136
|
+
const label = getAgentLabel(agentId);
|
|
137
|
+
busLogLines = [
|
|
138
|
+
`ufoo internal · ${label}`,
|
|
139
|
+
"Enter 发送 · Esc 返回 · ↓ agent bar",
|
|
140
|
+
"",
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function appendBusLog(text = "") {
|
|
145
|
+
const clean = stripAnsi(String(text || "")).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
146
|
+
if (busLogLines.length === 0) busLogLines.push("");
|
|
147
|
+
for (const char of clean) {
|
|
148
|
+
if (char === "\n") {
|
|
149
|
+
busLogLines.push("");
|
|
150
|
+
} else {
|
|
151
|
+
busLogLines[busLogLines.length - 1] += char;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (busLogLines.length > 1000) {
|
|
155
|
+
busLogLines = busLogLines.slice(-1000);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getBusInputViewport(width) {
|
|
160
|
+
const inner = Math.max(1, width - 4);
|
|
161
|
+
const value = String(busInputValue || "").replace(/\n/g, "⏎");
|
|
162
|
+
let start = 0;
|
|
163
|
+
if (busInputCursor >= inner) {
|
|
164
|
+
start = busInputCursor - inner + 1;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
text: value.slice(start, start + inner),
|
|
168
|
+
cursorCol: Math.max(0, busInputCursor - start),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderBusView() {
|
|
173
|
+
if (currentView !== "agent" || !agentViewUsesBus) return;
|
|
174
|
+
const rows = getRows();
|
|
175
|
+
const cols = getCols();
|
|
176
|
+
const width = Math.max(20, cols);
|
|
177
|
+
const inputTop = Math.max(4, rows - 3);
|
|
178
|
+
const logTop = 1;
|
|
179
|
+
const logBottom = Math.max(logTop + 1, inputTop - 1);
|
|
180
|
+
const logContentTop = logTop + 1;
|
|
181
|
+
const logContentBottom = logBottom - 1;
|
|
182
|
+
const logContentHeight = Math.max(1, logContentBottom - logContentTop + 1);
|
|
183
|
+
const label = getAgentLabel(viewingAgent);
|
|
184
|
+
|
|
185
|
+
processStdout.write("\x1b[?25l");
|
|
186
|
+
writeAt(logTop, boxTop(`ufoo internal · ${label}`, width));
|
|
187
|
+
const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
|
|
188
|
+
for (let i = 0; i < logContentHeight; i += 1) {
|
|
189
|
+
writeAt(logContentTop + i, boxMiddle(visibleLines[i] || "", width));
|
|
190
|
+
}
|
|
191
|
+
writeAt(logBottom, boxBottom(width));
|
|
192
|
+
|
|
193
|
+
writeAt(inputTop, boxTop("message", width));
|
|
194
|
+
const viewport = getBusInputViewport(width);
|
|
195
|
+
writeAt(inputTop + 1, boxMiddle(`> ${viewport.text}`, width));
|
|
196
|
+
writeAt(inputTop + 2, boxBottom(width));
|
|
197
|
+
|
|
198
|
+
renderAgentDashboard();
|
|
199
|
+
const cursorCol = clamp(4 + viewport.cursorCol, 1, width);
|
|
200
|
+
processStdout.write(`\x1b[${inputTop + 1};${cursorCol}H\x1b[?25h`);
|
|
201
|
+
}
|
|
202
|
+
|
|
66
203
|
function renderAgentDashboard() {
|
|
67
204
|
if (!agentBarVisible && getFocusMode() !== "dashboard") return;
|
|
68
205
|
const rows = getRows();
|
|
@@ -127,8 +264,8 @@ function createAgentViewController(options = {}) {
|
|
|
127
264
|
agentInputSuppressUntil = now() + 300;
|
|
128
265
|
agentViewUsesBus = Boolean(options.useBus);
|
|
129
266
|
if (agentViewUsesBus) {
|
|
130
|
-
|
|
131
|
-
|
|
267
|
+
resetBusView(agentId);
|
|
268
|
+
renderBusView();
|
|
132
269
|
} else {
|
|
133
270
|
const sockPath = getInjectSockPath(agentId);
|
|
134
271
|
connectAgentOutput(sockPath);
|
|
@@ -153,6 +290,9 @@ function createAgentViewController(options = {}) {
|
|
|
153
290
|
agentViewUsesBus = false;
|
|
154
291
|
agentOutputSuppressed = false;
|
|
155
292
|
agentBarVisible = false;
|
|
293
|
+
busInputValue = "";
|
|
294
|
+
busInputCursor = 0;
|
|
295
|
+
busLogLines = [];
|
|
156
296
|
|
|
157
297
|
currentView = "main";
|
|
158
298
|
viewingAgent = null;
|
|
@@ -199,6 +339,117 @@ function createAgentViewController(options = {}) {
|
|
|
199
339
|
agentOutputSuppressed = true;
|
|
200
340
|
}
|
|
201
341
|
|
|
342
|
+
function insertBusInput(text = "") {
|
|
343
|
+
const value = String(text || "");
|
|
344
|
+
if (!value) return;
|
|
345
|
+
busInputValue = busInputValue.slice(0, busInputCursor) + value + busInputValue.slice(busInputCursor);
|
|
346
|
+
busInputCursor += value.length;
|
|
347
|
+
renderBusView();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function deleteBusInputBeforeCursor() {
|
|
351
|
+
if (busInputCursor <= 0) return;
|
|
352
|
+
busInputValue = busInputValue.slice(0, busInputCursor - 1) + busInputValue.slice(busInputCursor);
|
|
353
|
+
busInputCursor -= 1;
|
|
354
|
+
renderBusView();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function clearBusInput() {
|
|
358
|
+
busInputValue = "";
|
|
359
|
+
busInputCursor = 0;
|
|
360
|
+
renderBusView();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function submitBusInput() {
|
|
364
|
+
const text = String(busInputValue || "").trim();
|
|
365
|
+
if (!text) {
|
|
366
|
+
renderBusView();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
appendBusLog(`> ${text}\n`);
|
|
370
|
+
busInputValue = "";
|
|
371
|
+
busInputCursor = 0;
|
|
372
|
+
sendBusMessage(viewingAgent, text);
|
|
373
|
+
renderBusView();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function handleBusAgentKey(ch, key = {}) {
|
|
377
|
+
if (currentView !== "agent" || !agentViewUsesBus) return false;
|
|
378
|
+
const keyName = key && key.name;
|
|
379
|
+
|
|
380
|
+
if (keyName === "down") return false;
|
|
381
|
+
|
|
382
|
+
if (keyName === "escape") {
|
|
383
|
+
exitAgentView();
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
if (keyName === "return" || keyName === "enter") {
|
|
387
|
+
if (key && (key.shift || key.meta)) {
|
|
388
|
+
insertBusInput("\n");
|
|
389
|
+
} else {
|
|
390
|
+
submitBusInput();
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
if (key && key.ctrl && keyName === "u") {
|
|
395
|
+
clearBusInput();
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
if (key && key.ctrl && keyName === "a") {
|
|
399
|
+
busInputCursor = 0;
|
|
400
|
+
renderBusView();
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
if (key && key.ctrl && keyName === "e") {
|
|
404
|
+
busInputCursor = busInputValue.length;
|
|
405
|
+
renderBusView();
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
if (keyName === "left") {
|
|
409
|
+
busInputCursor = Math.max(0, busInputCursor - 1);
|
|
410
|
+
renderBusView();
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
if (keyName === "right") {
|
|
414
|
+
busInputCursor = Math.min(busInputValue.length, busInputCursor + 1);
|
|
415
|
+
renderBusView();
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
if (keyName === "home") {
|
|
419
|
+
busInputCursor = 0;
|
|
420
|
+
renderBusView();
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
if (keyName === "end") {
|
|
424
|
+
busInputCursor = busInputValue.length;
|
|
425
|
+
renderBusView();
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
if (keyName === "backspace") {
|
|
429
|
+
deleteBusInputBeforeCursor();
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
if (keyName === "delete") {
|
|
433
|
+
if (busInputCursor < busInputValue.length) {
|
|
434
|
+
busInputValue = busInputValue.slice(0, busInputCursor) + busInputValue.slice(busInputCursor + 1);
|
|
435
|
+
renderBusView();
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
|
|
440
|
+
insertBusInput(ch.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
const insertChar = (ch && ch.length === 1)
|
|
444
|
+
? ch
|
|
445
|
+
: (keyName && keyName.length === 1 ? keyName : "");
|
|
446
|
+
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
|
|
447
|
+
insertBusInput(insertChar);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
202
453
|
function sendRawToAgent(data) {
|
|
203
454
|
sendRaw(data);
|
|
204
455
|
}
|
|
@@ -219,6 +470,11 @@ function createAgentViewController(options = {}) {
|
|
|
219
470
|
const cleaned = text
|
|
220
471
|
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
221
472
|
.replace(/\x1b\[(?:[?>=]?[0-9]*c|[?]?6n|5n)/g, "");
|
|
473
|
+
if (agentViewUsesBus) {
|
|
474
|
+
appendBusLog(cleaned);
|
|
475
|
+
renderBusView();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
222
478
|
if (cleaned) processStdout.write(cleaned);
|
|
223
479
|
if (agentBarVisible) {
|
|
224
480
|
const rows = getRows();
|
|
@@ -244,7 +500,11 @@ function createAgentViewController(options = {}) {
|
|
|
244
500
|
const cols = getCols();
|
|
245
501
|
processStdout.write(`\x1b[1;${rows - 1}r`);
|
|
246
502
|
sendResize(cols, Math.max(1, rows - 1));
|
|
247
|
-
|
|
503
|
+
if (agentViewUsesBus) {
|
|
504
|
+
renderBusView();
|
|
505
|
+
} else {
|
|
506
|
+
renderAgentDashboard();
|
|
507
|
+
}
|
|
248
508
|
return true;
|
|
249
509
|
}
|
|
250
510
|
|
|
@@ -270,6 +530,9 @@ function createAgentViewController(options = {}) {
|
|
|
270
530
|
|
|
271
531
|
function setAgentOutputSuppressed(value) {
|
|
272
532
|
agentOutputSuppressed = Boolean(value);
|
|
533
|
+
if (!agentOutputSuppressed && agentViewUsesBus) {
|
|
534
|
+
renderBusView();
|
|
535
|
+
}
|
|
273
536
|
}
|
|
274
537
|
|
|
275
538
|
function isAgentBarVisible() {
|
|
@@ -295,6 +558,7 @@ function createAgentViewController(options = {}) {
|
|
|
295
558
|
writeToAgentTerm,
|
|
296
559
|
placeAgentCursor,
|
|
297
560
|
handleResizeInAgentView,
|
|
561
|
+
handleBusAgentKey,
|
|
298
562
|
};
|
|
299
563
|
}
|
|
300
564
|
|
package/src/chat/index.js
CHANGED
|
@@ -1565,6 +1565,16 @@ async function runChat(projectRoot, options = {}) {
|
|
|
1565
1565
|
sendRaw: (data) => {
|
|
1566
1566
|
sendRawWithCapabilities(data);
|
|
1567
1567
|
},
|
|
1568
|
+
sendBusMessage: (target, message) => {
|
|
1569
|
+
if (!target || !message) return;
|
|
1570
|
+
send({
|
|
1571
|
+
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
1572
|
+
target,
|
|
1573
|
+
message,
|
|
1574
|
+
injection_mode: "immediate",
|
|
1575
|
+
source: "chat-internal-agent-view",
|
|
1576
|
+
});
|
|
1577
|
+
},
|
|
1568
1578
|
sendResize: (cols, rows) => {
|
|
1569
1579
|
sendResizeWithCapabilities(cols, rows);
|
|
1570
1580
|
},
|
|
@@ -2026,6 +2036,9 @@ async function runChat(projectRoot, options = {}) {
|
|
|
2026
2036
|
if (key && key.ctrl && key.name === "c") {
|
|
2027
2037
|
return; // handled by screen.key(["C-c"])
|
|
2028
2038
|
}
|
|
2039
|
+
if (agentViewController && agentViewController.handleBusAgentKey(ch, key)) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2029
2042
|
// Down arrow: enter agents bar (same pattern as normal chat dashboard)
|
|
2030
2043
|
if (key && key.name === "down") {
|
|
2031
2044
|
enterAgentDashboardMode();
|