u-foo 2.5.15 → 3.0.0

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/code/agent.js +333 -243
  3. package/src/code/commands.js +16 -0
  4. package/src/code/context/assembler.js +18 -13
  5. package/src/code/context/executionSegment.js +97 -119
  6. package/src/code/context/index.js +11 -1
  7. package/src/code/context/planGraph.js +1410 -0
  8. package/src/code/context/planGraphService.js +857 -0
  9. package/src/code/context/planMode.js +398 -0
  10. package/src/code/context/planProjection.js +432 -0
  11. package/src/code/context/promptLayers.js +21 -5
  12. package/src/code/context/stateCommit.js +2 -0
  13. package/src/code/context/toolRuntime.js +172 -0
  14. package/src/code/context/userInteraction.js +457 -0
  15. package/src/code/context/userNudge.js +116 -0
  16. package/src/code/dispatch.js +17 -1
  17. package/src/code/index.js +2 -0
  18. package/src/code/nativeRunner.js +518 -37
  19. package/src/code/repl.js +160 -18
  20. package/src/code/runtime/agentWakeup.js +58 -0
  21. package/src/code/runtime/graphOwner.js +41 -0
  22. package/src/code/runtime/graphYieldRouter.js +42 -0
  23. package/src/code/runtime/index.js +15 -0
  24. package/src/code/runtime/loopMailbox.js +124 -0
  25. package/src/code/runtime/runtimeEvents.js +39 -0
  26. package/src/code/runtime/taskControl.js +565 -0
  27. package/src/code/runtime/taskFocus.js +165 -0
  28. package/src/code/runtime/taskLoop.js +383 -0
  29. package/src/code/runtime/taskRun.js +187 -0
  30. package/src/code/runtime/toolProvenance.js +70 -0
  31. package/src/code/runtime/workspaceLease.js +208 -0
  32. package/src/code/sessionStore.js +0 -10
  33. package/src/code/skills/injection.js +1 -0
  34. package/src/code/taskDecomposer.js +32 -8
  35. package/src/code/tools/askUser.js +11 -0
  36. package/src/code/tools/planGraph.js +29 -0
  37. package/src/ui/format/index.js +25 -1
  38. package/src/ui/format/markdownRenderer.js +224 -2
  39. package/src/ui/ink/UcodeApp.js +285 -22
  40. package/src/code/context/featureFlag.js +0 -13
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Produces either blessed tags (chat / legacy) or chalk ANSI (Ink ucode).
5
5
  * Terminals cannot change font size, so headings/emphasis use color + weight
6
- * instead of literal `#` / `**` markers.
6
+ * instead of literal `#` / `**` markers. GFM pipe tables are aligned into a
7
+ * compact spreadsheet-like grid.
7
8
  */
8
9
 
9
10
  const chalk = require("chalk");
@@ -15,6 +16,116 @@ function stripLeakedEscapeTags(text = "") {
15
16
  return withoutDanglingEscape.replace(/\{\s*\/?\s*e?s?c?a?p?e?[^{}\n]*$/gi, "");
16
17
  }
17
18
 
19
+ /** Visible width for CJK-aware table padding (no ANSI). */
20
+ function visibleWidth(text = "") {
21
+ let width = 0;
22
+ for (const char of String(text || "")) {
23
+ const code = char.codePointAt(0) || 0;
24
+ if (code < 32 || (code >= 0x7f && code < 0xa0)) continue;
25
+ if (
26
+ (code >= 0x1100 && code <= 0x115f)
27
+ || code === 0x2329
28
+ || code === 0x232a
29
+ || (code >= 0x2e80 && code <= 0xa4cf)
30
+ || (code >= 0xac00 && code <= 0xd7a3)
31
+ || (code >= 0xf900 && code <= 0xfaff)
32
+ || (code >= 0xfe10 && code <= 0xfe19)
33
+ || (code >= 0xfe30 && code <= 0xfe6f)
34
+ || (code >= 0xff00 && code <= 0xff60)
35
+ || (code >= 0xffe0 && code <= 0xffe6)
36
+ ) {
37
+ width += 2;
38
+ continue;
39
+ }
40
+ width += 1;
41
+ }
42
+ return width;
43
+ }
44
+
45
+ function isTableSeparatorLine(line = "") {
46
+ const raw = String(line || "").trim();
47
+ if (!raw.includes("-")) return false;
48
+ return /^\|?(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?$/.test(raw);
49
+ }
50
+
51
+ function isTableRowLine(line = "") {
52
+ const raw = String(line || "").trim();
53
+ if (!raw.includes("|")) return false;
54
+ if (isTableSeparatorLine(raw)) return true;
55
+ // Prefer GFM pipe rows (`| a | b |`). Also allow compact `a | b | c`
56
+ // (at least two pipes). Lone prose like `A | B` is not a table row.
57
+ if (/^\|.*\|$/.test(raw)) return true;
58
+ const parts = raw.split("|");
59
+ return parts.length >= 3 && parts.some((p) => p.trim().length > 0);
60
+ }
61
+
62
+ /**
63
+ * Buffers consecutive GFM table rows so they can be rendered as one block
64
+ * (column widths need the full table). Call flush() before non-table output.
65
+ */
66
+ function createMarkdownTableBuffer() {
67
+ const rows = [];
68
+ return {
69
+ get size() {
70
+ return rows.length;
71
+ },
72
+ push(line = "") {
73
+ const raw = String(line == null ? "" : line);
74
+ if (!isTableRowLine(raw)) return false;
75
+ rows.push(raw);
76
+ return true;
77
+ },
78
+ flush() {
79
+ if (rows.length === 0) return null;
80
+ const text = rows.join("\n");
81
+ rows.length = 0;
82
+ return text;
83
+ },
84
+ };
85
+ }
86
+
87
+ function parseTableCells(line = "") {
88
+ let raw = String(line || "").trim();
89
+ if (raw.startsWith("|")) raw = raw.slice(1);
90
+ if (raw.endsWith("|")) raw = raw.slice(0, -1);
91
+ return raw.split("|").map((cell) => cell.trim());
92
+ }
93
+
94
+ function parseSeparatorAlignments(line = "", columnCount = 0) {
95
+ const cells = parseTableCells(line);
96
+ const aligns = cells.map((cell) => {
97
+ const left = cell.startsWith(":");
98
+ const right = cell.endsWith(":");
99
+ if (left && right) return "center";
100
+ if (right) return "right";
101
+ return "left";
102
+ });
103
+ while (aligns.length < columnCount) aligns.push("left");
104
+ return aligns.slice(0, Math.max(columnCount, aligns.length));
105
+ }
106
+
107
+ function alignCell(text = "", width = 0, align = "left") {
108
+ const value = String(text || "");
109
+ const current = visibleWidth(value);
110
+ if (current >= width) return value;
111
+ const pad = width - current;
112
+ if (align === "right") return `${" ".repeat(pad)}${value}`;
113
+ if (align === "center") {
114
+ const left = Math.floor(pad / 2);
115
+ const right = pad - left;
116
+ return `${" ".repeat(left)}${value}${" ".repeat(right)}`;
117
+ }
118
+ return `${value}${" ".repeat(pad)}`;
119
+ }
120
+
121
+ function plainCellWidth(cell = "") {
122
+ const plain = String(cell || "")
123
+ .replace(/\*\*|__/g, "")
124
+ .replace(/`/g, "")
125
+ .replace(/\*/g, "");
126
+ return visibleWidth(plain);
127
+ }
128
+
18
129
  function createBlessedAdapters(escapeFn = (value) => String(value || "")) {
19
130
  const escape = (value) => escapeFn(value);
20
131
  return {
@@ -40,6 +151,11 @@ function createBlessedAdapters(escapeFn = (value) => String(value || "")) {
40
151
  fenceClose: () => "{gray-fg}└{/gray-fg}",
41
152
  fenceBody: (value) => `{gray-fg}│{/gray-fg} {white-fg}${escape(value)}{/white-fg}`,
42
153
  error: (value) => `{red-fg}${value}{/red-fg}`,
154
+ tablePipe: () => "{gray-fg}│{/gray-fg}",
155
+ tableSepCross: () => "{gray-fg}┼{/gray-fg}",
156
+ tableSepH: () => "{gray-fg}─{/gray-fg}",
157
+ tableHeaderCell: (value) => `{bold}{white-fg}${value}{/white-fg}{/bold}`,
158
+ tableCell: (value) => value,
43
159
  };
44
160
  }
45
161
 
@@ -76,6 +192,11 @@ function createAnsiAdapters() {
76
192
  fenceClose: () => paint.gray("└"),
77
193
  fenceBody: (value) => `${paint.gray("│")} ${paint.white(String(value || ""))}`,
78
194
  error: (value) => paint.red(String(value || "")),
195
+ tablePipe: () => paint.gray("│"),
196
+ tableSepCross: () => paint.gray("┼"),
197
+ tableSepH: () => paint.gray("─"),
198
+ tableHeaderCell: (value) => paint.bold.whiteBright(String(value || "")),
199
+ tableCell: (value) => String(value || ""),
79
200
  };
80
201
  }
81
202
 
@@ -161,6 +282,82 @@ function renderInlineMarkdown(input = "", adapters = createBlessedAdapters()) {
161
282
  return out;
162
283
  }
163
284
 
285
+ function renderTableBlock(rawRows = [], adapters = createBlessedAdapters()) {
286
+ if (!Array.isArray(rawRows) || rawRows.length === 0) return [];
287
+
288
+ const rows = [];
289
+ let alignments = [];
290
+ let headerUsed = false;
291
+
292
+ for (let i = 0; i < rawRows.length; i += 1) {
293
+ const line = rawRows[i];
294
+ if (isTableSeparatorLine(line)) {
295
+ if (rows.length > 0 && !headerUsed) {
296
+ rows[rows.length - 1].isHeader = true;
297
+ headerUsed = true;
298
+ }
299
+ alignments = parseSeparatorAlignments(line, Math.max(alignments.length, parseTableCells(line).length));
300
+ continue;
301
+ }
302
+ rows.push({
303
+ cells: parseTableCells(line),
304
+ isHeader: false,
305
+ });
306
+ }
307
+
308
+ if (rows.length === 0) return [];
309
+
310
+ const columnCount = rows.reduce((max, row) => Math.max(max, row.cells.length), 0);
311
+ while (alignments.length < columnCount) alignments.push("left");
312
+
313
+ const widths = Array.from({ length: columnCount }, () => 1);
314
+ for (const row of rows) {
315
+ for (let c = 0; c < columnCount; c += 1) {
316
+ widths[c] = Math.max(widths[c], plainCellWidth(row.cells[c] || ""));
317
+ }
318
+ }
319
+
320
+ const pipe = adapters.tablePipe || (() => "│");
321
+ const sepH = adapters.tableSepH || (() => "─");
322
+ const sepCross = adapters.tableSepCross || (() => "┼");
323
+ const styleHeader = adapters.tableHeaderCell || ((v) => v);
324
+ const styleCell = adapters.tableCell || ((v) => v);
325
+
326
+ const out = [];
327
+ let wroteHeaderSep = false;
328
+
329
+ for (const row of rows) {
330
+ const renderedCells = [];
331
+ for (let c = 0; c < columnCount; c += 1) {
332
+ const rawCell = row.cells[c] || "";
333
+ const inline = renderInlineMarkdown(rawCell, adapters);
334
+ const plain = String(rawCell)
335
+ .replace(/\*\*|__/g, "")
336
+ .replace(/`/g, "")
337
+ .replace(/\*/g, "");
338
+ const alignedPlain = alignCell(plain, widths[c], alignments[c] || "left");
339
+ const padRight = Math.max(0, visibleWidth(alignedPlain) - visibleWidth(plain));
340
+ const styled = row.isHeader ? styleHeader(inline) : styleCell(inline);
341
+ renderedCells.push(`${styled}${" ".repeat(padRight)}`);
342
+ }
343
+ out.push(`${pipe()} ${renderedCells.join(` ${pipe()} `)} ${pipe()}`);
344
+
345
+ if (row.isHeader && !wroteHeaderSep) {
346
+ const segments = widths.map((w) => {
347
+ const unit = sepH();
348
+ // sepH may be a styled string longer than 1 codepoint; repeat by width.
349
+ return unit.repeat(Math.max(1, w));
350
+ });
351
+ out.push(
352
+ `${pipe()}${sepH()}${segments.join(`${sepH()}${sepCross()}${sepH()}`)}${sepH()}${pipe()}`,
353
+ );
354
+ wroteHeaderSep = true;
355
+ }
356
+ }
357
+
358
+ return out;
359
+ }
360
+
164
361
  function renderMarkdownLinesWithAdapters(text = "", state = {}, adapters = createBlessedAdapters()) {
165
362
  const renderState = state && typeof state === "object" ? state : {};
166
363
  if (typeof renderState.inCodeBlock !== "boolean") {
@@ -170,7 +367,8 @@ function renderMarkdownLinesWithAdapters(text = "", state = {}, adapters = creat
170
367
  const lines = String(text || "").split(/\r?\n/);
171
368
  const out = [];
172
369
 
173
- for (const line of lines) {
370
+ for (let index = 0; index < lines.length; index += 1) {
371
+ const line = lines[index];
174
372
  const raw = stripLeakedEscapeTags(String(line || ""));
175
373
  const fenceMatch = raw.match(/^(\s*)(`{3,}|~{3,})(.*)$/);
176
374
  if (fenceMatch) {
@@ -190,6 +388,24 @@ function renderMarkdownLinesWithAdapters(text = "", state = {}, adapters = creat
190
388
  continue;
191
389
  }
192
390
 
391
+ // GFM table block — collect contiguous pipe rows for column alignment.
392
+ if (isTableRowLine(raw)) {
393
+ const block = [raw];
394
+ let look = index + 1;
395
+ while (look < lines.length) {
396
+ const nextRaw = stripLeakedEscapeTags(String(lines[look] || ""));
397
+ if (!isTableRowLine(nextRaw)) break;
398
+ block.push(nextRaw);
399
+ look += 1;
400
+ }
401
+ const hasSep = block.some((row) => isTableSeparatorLine(row));
402
+ if (hasSep || block.length >= 2) {
403
+ out.push(...renderTableBlock(block, adapters));
404
+ index = look - 1;
405
+ continue;
406
+ }
407
+ }
408
+
193
409
  if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(raw)) {
194
410
  out.push(adapters.rule());
195
411
  continue;
@@ -261,4 +477,10 @@ module.exports = {
261
477
  renderMarkdownLinesWithAdapters,
262
478
  createBlessedAdapters,
263
479
  createAnsiAdapters,
480
+ isTableRowLine,
481
+ isTableSeparatorLine,
482
+ parseTableCells,
483
+ renderTableBlock,
484
+ createMarkdownTableBuffer,
485
+ visibleWidth,
264
486
  };