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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.14",
3
+ "version": "2.3.15",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -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
- const label = getAgentLabel(agentId);
131
- processStdout.write(`ufoo internal · ${label}\r\n\r\n> `);
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
- renderAgentDashboard();
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();