u-foo 2.4.8 → 2.4.9
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/code/tui.js +4 -0
- package/src/ui/format/index.js +41 -0
- package/src/ui/ink/UcodeApp.js +26 -12
package/package.json
CHANGED
package/src/code/tui.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
StreamBuffer,
|
|
6
6
|
UCODE_BANNER_LINES,
|
|
7
7
|
UCODE_VERSION,
|
|
8
|
+
appendToolMergeEntry,
|
|
8
9
|
buildMergedToolExpandedLines,
|
|
9
10
|
buildMergedToolSummaryText,
|
|
10
11
|
buildUcodeBannerLines,
|
|
@@ -31,6 +32,7 @@ const {
|
|
|
31
32
|
shouldClearAgentSelectionOnUp,
|
|
32
33
|
shouldEnterAgentSelection,
|
|
33
34
|
shouldUseUcodeTui,
|
|
35
|
+
splitStreamingLogChunk,
|
|
34
36
|
stripLeakedEscapeTags,
|
|
35
37
|
} = fmt;
|
|
36
38
|
|
|
@@ -64,8 +66,10 @@ module.exports = {
|
|
|
64
66
|
resolveHistoryDownTransition,
|
|
65
67
|
filterSelectableAgents,
|
|
66
68
|
stripLeakedEscapeTags,
|
|
69
|
+
splitStreamingLogChunk,
|
|
67
70
|
createEscapeTagStripper,
|
|
68
71
|
formatPendingElapsed,
|
|
72
|
+
appendToolMergeEntry,
|
|
69
73
|
normalizeBashToolCommand,
|
|
70
74
|
normalizeToolMergeEntry,
|
|
71
75
|
buildMergedToolSummaryText,
|
package/src/ui/format/index.js
CHANGED
|
@@ -518,6 +518,24 @@ function normalizeToolMergeEntry(entry = {}) {
|
|
|
518
518
|
};
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
function appendToolMergeEntry(currentMerge = null, entry = {}, scope = 0, nextId = 1) {
|
|
522
|
+
const toolEntry = normalizeToolMergeEntry(entry);
|
|
523
|
+
const current = currentMerge && typeof currentMerge === "object" ? currentMerge : null;
|
|
524
|
+
const normalizedScope = Number.isFinite(Number(scope)) ? Number(scope) : 0;
|
|
525
|
+
if (current && current.scope === normalizedScope && Array.isArray(current.entries)) {
|
|
526
|
+
return {
|
|
527
|
+
...current,
|
|
528
|
+
entries: current.entries.concat([toolEntry]),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
id: Number.isFinite(Number(nextId)) ? Number(nextId) : 1,
|
|
533
|
+
scope: normalizedScope,
|
|
534
|
+
entries: [toolEntry],
|
|
535
|
+
expanded: false,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
521
539
|
function buildMergedToolSummaryText(entries = []) {
|
|
522
540
|
const list = Array.isArray(entries)
|
|
523
541
|
? entries.map((item) => normalizeToolMergeEntry(item))
|
|
@@ -551,6 +569,27 @@ function buildMergedToolExpandedLines(entries = []) {
|
|
|
551
569
|
});
|
|
552
570
|
}
|
|
553
571
|
|
|
572
|
+
function splitStreamingLogChunk(buffer = "", chunk = "", options = {}) {
|
|
573
|
+
const previous = String(buffer || "");
|
|
574
|
+
const text = String(chunk || "");
|
|
575
|
+
const combined = `${previous}${text}`;
|
|
576
|
+
const parts = combined.split(/\r?\n/);
|
|
577
|
+
const lines = parts.slice(0, -1);
|
|
578
|
+
const dropLeadingBlank = Boolean(options.dropLeadingBlank) && previous === "";
|
|
579
|
+
|
|
580
|
+
if (dropLeadingBlank) {
|
|
581
|
+
while (lines.length > 0 && lines[0] === "") {
|
|
582
|
+
lines.shift();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
lines,
|
|
588
|
+
buffer: parts[parts.length - 1] || "",
|
|
589
|
+
sawVisible: /[^\s]/.test(text),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
554
593
|
// Composed live-row text for an in-flight tool group: shows the merged
|
|
555
594
|
// summary, plus a "(Ctrl+O expand)" hint once at least two entries are
|
|
556
595
|
// present.
|
|
@@ -937,6 +976,7 @@ module.exports = {
|
|
|
937
976
|
TOOL_LABELS,
|
|
938
977
|
UCODE_BANNER_LINES,
|
|
939
978
|
UCODE_VERSION,
|
|
979
|
+
appendToolMergeEntry,
|
|
940
980
|
buildMergedToolExpandedLines,
|
|
941
981
|
buildMergedToolSummaryText,
|
|
942
982
|
buildToolMergeRowText,
|
|
@@ -970,5 +1010,6 @@ module.exports = {
|
|
|
970
1010
|
shouldClearAgentSelectionOnUp,
|
|
971
1011
|
shouldEnterAgentSelection,
|
|
972
1012
|
shouldUseUcodeTui,
|
|
1013
|
+
splitStreamingLogChunk,
|
|
973
1014
|
stripLeakedEscapeTags,
|
|
974
1015
|
};
|
package/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -78,6 +78,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
78
78
|
const { stdout } = useStdout();
|
|
79
79
|
const lineSeqRef = useRef(banner.length + 1);
|
|
80
80
|
const mergeIdRef = useRef(0);
|
|
81
|
+
const toolMergeScopeRef = useRef(0);
|
|
81
82
|
|
|
82
83
|
const targetAgent = agentSelectionMode && selectedAgentIndex >= 0
|
|
83
84
|
? agents[selectedAgentIndex]
|
|
@@ -261,13 +262,12 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
261
262
|
const toolEntry = fmt.normalizeToolMergeEntry({ tool, detail, isError, errorText });
|
|
262
263
|
|
|
263
264
|
setActiveMerge((current) => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} else {
|
|
265
|
+
const scope = toolMergeScopeRef.current;
|
|
266
|
+
const isNewScope = !(current && current.scope === scope);
|
|
267
|
+
if (isNewScope) {
|
|
268
268
|
mergeIdRef.current += 1;
|
|
269
|
-
next = { id: mergeIdRef.current, entries: [toolEntry], expanded: false };
|
|
270
269
|
}
|
|
270
|
+
const next = fmt.appendToolMergeEntry(current, toolEntry, scope, mergeIdRef.current);
|
|
271
271
|
if (next.entries.length >= 2) lastMergeRef.current = next;
|
|
272
272
|
return next;
|
|
273
273
|
});
|
|
@@ -313,6 +313,8 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
313
313
|
const executeLine = useCallback(async (rawValue) => {
|
|
314
314
|
const normalized = String(rawValue || "").replace(/\r?\n/g, " ").trim();
|
|
315
315
|
if (!normalized) return;
|
|
316
|
+
toolMergeScopeRef.current += 1;
|
|
317
|
+
flushActiveMerge();
|
|
316
318
|
appendLogLine(`› ${normalized}`);
|
|
317
319
|
|
|
318
320
|
const runtimeWorkspace = String(
|
|
@@ -459,6 +461,8 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
459
461
|
setNlStatus("Waiting for model...");
|
|
460
462
|
let streamBuf = "";
|
|
461
463
|
let sawStreamText = false;
|
|
464
|
+
let streamStarted = false;
|
|
465
|
+
let dropLeadingStreamBlank = false;
|
|
462
466
|
let nlResult = null;
|
|
463
467
|
try {
|
|
464
468
|
nlResult = await props.runNaturalLanguageTask(result.task, props.state, {
|
|
@@ -477,13 +481,21 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
477
481
|
onDelta: (delta) => {
|
|
478
482
|
const text = String(delta || "");
|
|
479
483
|
if (!text) return;
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
484
|
+
if (!streamStarted) {
|
|
485
|
+
flushActiveMerge();
|
|
486
|
+
streamStarted = true;
|
|
487
|
+
}
|
|
488
|
+
const split = fmt.splitStreamingLogChunk(streamBuf, text, {
|
|
489
|
+
dropLeadingBlank: dropLeadingStreamBlank,
|
|
490
|
+
});
|
|
491
|
+
if (split.sawVisible) {
|
|
492
|
+
sawStreamText = true;
|
|
493
|
+
dropLeadingStreamBlank = false;
|
|
494
|
+
}
|
|
495
|
+
for (const line of split.lines) {
|
|
496
|
+
appendLogLine(line);
|
|
485
497
|
}
|
|
486
|
-
streamBuf =
|
|
498
|
+
streamBuf = split.buffer;
|
|
487
499
|
},
|
|
488
500
|
onToolLog: (entry) => {
|
|
489
501
|
if (!entry || typeof entry !== "object") return;
|
|
@@ -491,6 +503,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
491
503
|
const label = fmt.TOOL_LABELS[String(entry.tool || "").toLowerCase()] ||
|
|
492
504
|
`Calling ${entry.tool}`;
|
|
493
505
|
setNlStatus(`${label}...`);
|
|
506
|
+
dropLeadingStreamBlank = true;
|
|
494
507
|
}
|
|
495
508
|
logToolHint(entry, entry.result);
|
|
496
509
|
},
|
|
@@ -516,6 +529,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
516
529
|
const summary = props.formatNlResult(nlResult, false);
|
|
517
530
|
if (summary) appendLogText(summary);
|
|
518
531
|
}
|
|
532
|
+
flushActiveMerge();
|
|
519
533
|
try {
|
|
520
534
|
const persisted = props.persistSessionState(props.state);
|
|
521
535
|
if (persisted && persisted.ok === false) {
|
|
@@ -531,7 +545,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
531
545
|
default:
|
|
532
546
|
if (result.output) appendLogText(result.output);
|
|
533
547
|
}
|
|
534
|
-
}, [appendLogLine, appendLogText, exit, props, logToolHint]);
|
|
548
|
+
}, [appendLogLine, appendLogText, exit, props, logToolHint, flushActiveMerge]);
|
|
535
549
|
// ^ `props` is captured by the createUcodeApp closure on a single mount,
|
|
536
550
|
// so its reference is stable across renders even though it looks like a
|
|
537
551
|
// changing dep to React's exhaustive-deps lint.
|