u-foo 2.3.14 → 2.3.16

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.16",
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,125 @@ 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 horizontalLine(width = 80) {
91
+ return "─".repeat(Math.max(1, width));
92
+ }
93
+
94
+ function plainLine(text = "", width = 80) {
95
+ return fitText(text, Math.max(1, width));
96
+ }
97
+
98
+ function wrapTextLine(text = "", width = 80) {
99
+ const inner = Math.max(1, width);
100
+ const clean = stripAnsi(String(text || ""));
101
+ if (!clean) return [""];
102
+ const lines = [];
103
+ let rest = clean;
104
+ while (rest.length > inner) {
105
+ lines.push(rest.slice(0, inner));
106
+ rest = rest.slice(inner);
107
+ }
108
+ lines.push(rest);
109
+ return lines;
110
+ }
111
+
112
+ function getWrappedBusLogLines(width = 80) {
113
+ const inner = Math.max(1, width);
114
+ const wrapped = [];
115
+ for (const line of busLogLines) {
116
+ wrapped.push(...wrapTextLine(line, inner));
117
+ }
118
+ return wrapped;
119
+ }
120
+
121
+ function writeAt(row, content = "") {
122
+ processStdout.write(`\x1b[${row};1H\x1b[2K${content}`);
123
+ }
124
+
125
+ function resetBusView(agentId) {
126
+ busInputValue = "";
127
+ busInputCursor = 0;
128
+ const label = getAgentLabel(agentId);
129
+ busLogLines = [
130
+ `ufoo internal · ${label}`,
131
+ "",
132
+ ];
133
+ }
134
+
135
+ function appendBusLog(text = "") {
136
+ const clean = stripAnsi(String(text || "")).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
137
+ if (busLogLines.length === 0) busLogLines.push("");
138
+ for (const char of clean) {
139
+ if (char === "\n") {
140
+ busLogLines.push("");
141
+ } else {
142
+ busLogLines[busLogLines.length - 1] += char;
143
+ }
144
+ }
145
+ if (busLogLines.length > 1000) {
146
+ busLogLines = busLogLines.slice(-1000);
147
+ }
148
+ }
149
+
150
+ function getBusInputViewport(width) {
151
+ const inner = Math.max(1, width - 4);
152
+ const value = String(busInputValue || "").replace(/\n/g, "⏎");
153
+ let start = 0;
154
+ if (busInputCursor >= inner) {
155
+ start = busInputCursor - inner + 1;
156
+ }
157
+ return {
158
+ text: value.slice(start, start + inner),
159
+ cursorCol: Math.max(0, busInputCursor - start),
160
+ };
161
+ }
162
+
163
+ function renderBusView() {
164
+ if (currentView !== "agent" || !agentViewUsesBus) return;
165
+ const rows = getRows();
166
+ const cols = getCols();
167
+ const width = Math.max(20, cols);
168
+ const inputTop = Math.max(4, rows - 3);
169
+ const logContentTop = 1;
170
+ const logContentBottom = Math.max(logContentTop, inputTop - 1);
171
+ const logContentHeight = Math.max(1, logContentBottom - logContentTop + 1);
172
+
173
+ processStdout.write("\x1b[?25l");
174
+ const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
175
+ for (let i = 0; i < logContentHeight; i += 1) {
176
+ writeAt(logContentTop + i, plainLine(visibleLines[i] || "", width));
177
+ }
178
+
179
+ writeAt(inputTop, horizontalLine(width));
180
+ const viewport = getBusInputViewport(width);
181
+ writeAt(inputTop + 1, plainLine(`> ${viewport.text}`, width));
182
+ writeAt(inputTop + 2, horizontalLine(width));
183
+
184
+ renderAgentDashboard();
185
+ const cursorCol = clamp(3 + viewport.cursorCol, 1, width);
186
+ processStdout.write(`\x1b[${inputTop + 1};${cursorCol}H\x1b[?25h`);
187
+ }
188
+
66
189
  function renderAgentDashboard() {
67
190
  if (!agentBarVisible && getFocusMode() !== "dashboard") return;
68
191
  const rows = getRows();
@@ -127,8 +250,8 @@ function createAgentViewController(options = {}) {
127
250
  agentInputSuppressUntil = now() + 300;
128
251
  agentViewUsesBus = Boolean(options.useBus);
129
252
  if (agentViewUsesBus) {
130
- const label = getAgentLabel(agentId);
131
- processStdout.write(`ufoo internal · ${label}\r\n\r\n> `);
253
+ resetBusView(agentId);
254
+ renderBusView();
132
255
  } else {
133
256
  const sockPath = getInjectSockPath(agentId);
134
257
  connectAgentOutput(sockPath);
@@ -153,6 +276,9 @@ function createAgentViewController(options = {}) {
153
276
  agentViewUsesBus = false;
154
277
  agentOutputSuppressed = false;
155
278
  agentBarVisible = false;
279
+ busInputValue = "";
280
+ busInputCursor = 0;
281
+ busLogLines = [];
156
282
 
157
283
  currentView = "main";
158
284
  viewingAgent = null;
@@ -199,6 +325,117 @@ function createAgentViewController(options = {}) {
199
325
  agentOutputSuppressed = true;
200
326
  }
201
327
 
328
+ function insertBusInput(text = "") {
329
+ const value = String(text || "");
330
+ if (!value) return;
331
+ busInputValue = busInputValue.slice(0, busInputCursor) + value + busInputValue.slice(busInputCursor);
332
+ busInputCursor += value.length;
333
+ renderBusView();
334
+ }
335
+
336
+ function deleteBusInputBeforeCursor() {
337
+ if (busInputCursor <= 0) return;
338
+ busInputValue = busInputValue.slice(0, busInputCursor - 1) + busInputValue.slice(busInputCursor);
339
+ busInputCursor -= 1;
340
+ renderBusView();
341
+ }
342
+
343
+ function clearBusInput() {
344
+ busInputValue = "";
345
+ busInputCursor = 0;
346
+ renderBusView();
347
+ }
348
+
349
+ function submitBusInput() {
350
+ const text = String(busInputValue || "").trim();
351
+ if (!text) {
352
+ renderBusView();
353
+ return;
354
+ }
355
+ appendBusLog(`> ${text}\n`);
356
+ busInputValue = "";
357
+ busInputCursor = 0;
358
+ sendBusMessage(viewingAgent, text);
359
+ renderBusView();
360
+ }
361
+
362
+ function handleBusAgentKey(ch, key = {}) {
363
+ if (currentView !== "agent" || !agentViewUsesBus) return false;
364
+ const keyName = key && key.name;
365
+
366
+ if (keyName === "down") return false;
367
+
368
+ if (keyName === "escape") {
369
+ exitAgentView();
370
+ return true;
371
+ }
372
+ if (keyName === "return" || keyName === "enter") {
373
+ if (key && (key.shift || key.meta)) {
374
+ insertBusInput("\n");
375
+ } else {
376
+ submitBusInput();
377
+ }
378
+ return true;
379
+ }
380
+ if (key && key.ctrl && keyName === "u") {
381
+ clearBusInput();
382
+ return true;
383
+ }
384
+ if (key && key.ctrl && keyName === "a") {
385
+ busInputCursor = 0;
386
+ renderBusView();
387
+ return true;
388
+ }
389
+ if (key && key.ctrl && keyName === "e") {
390
+ busInputCursor = busInputValue.length;
391
+ renderBusView();
392
+ return true;
393
+ }
394
+ if (keyName === "left") {
395
+ busInputCursor = Math.max(0, busInputCursor - 1);
396
+ renderBusView();
397
+ return true;
398
+ }
399
+ if (keyName === "right") {
400
+ busInputCursor = Math.min(busInputValue.length, busInputCursor + 1);
401
+ renderBusView();
402
+ return true;
403
+ }
404
+ if (keyName === "home") {
405
+ busInputCursor = 0;
406
+ renderBusView();
407
+ return true;
408
+ }
409
+ if (keyName === "end") {
410
+ busInputCursor = busInputValue.length;
411
+ renderBusView();
412
+ return true;
413
+ }
414
+ if (keyName === "backspace") {
415
+ deleteBusInputBeforeCursor();
416
+ return true;
417
+ }
418
+ if (keyName === "delete") {
419
+ if (busInputCursor < busInputValue.length) {
420
+ busInputValue = busInputValue.slice(0, busInputCursor) + busInputValue.slice(busInputCursor + 1);
421
+ renderBusView();
422
+ }
423
+ return true;
424
+ }
425
+ if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
426
+ insertBusInput(ch.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
427
+ return true;
428
+ }
429
+ const insertChar = (ch && ch.length === 1)
430
+ ? ch
431
+ : (keyName && keyName.length === 1 ? keyName : "");
432
+ if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
433
+ insertBusInput(insertChar);
434
+ return true;
435
+ }
436
+ return true;
437
+ }
438
+
202
439
  function sendRawToAgent(data) {
203
440
  sendRaw(data);
204
441
  }
@@ -219,6 +456,11 @@ function createAgentViewController(options = {}) {
219
456
  const cleaned = text
220
457
  .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
221
458
  .replace(/\x1b\[(?:[?>=]?[0-9]*c|[?]?6n|5n)/g, "");
459
+ if (agentViewUsesBus) {
460
+ appendBusLog(cleaned);
461
+ renderBusView();
462
+ return;
463
+ }
222
464
  if (cleaned) processStdout.write(cleaned);
223
465
  if (agentBarVisible) {
224
466
  const rows = getRows();
@@ -244,7 +486,11 @@ function createAgentViewController(options = {}) {
244
486
  const cols = getCols();
245
487
  processStdout.write(`\x1b[1;${rows - 1}r`);
246
488
  sendResize(cols, Math.max(1, rows - 1));
247
- renderAgentDashboard();
489
+ if (agentViewUsesBus) {
490
+ renderBusView();
491
+ } else {
492
+ renderAgentDashboard();
493
+ }
248
494
  return true;
249
495
  }
250
496
 
@@ -270,6 +516,9 @@ function createAgentViewController(options = {}) {
270
516
 
271
517
  function setAgentOutputSuppressed(value) {
272
518
  agentOutputSuppressed = Boolean(value);
519
+ if (!agentOutputSuppressed && agentViewUsesBus) {
520
+ renderBusView();
521
+ }
273
522
  }
274
523
 
275
524
  function isAgentBarVisible() {
@@ -295,6 +544,7 @@ function createAgentViewController(options = {}) {
295
544
  writeToAgentTerm,
296
545
  placeAgentCursor,
297
546
  handleResizeInAgentView,
547
+ handleBusAgentKey,
298
548
  };
299
549
  }
300
550
 
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();