u-foo 3.0.24 → 3.0.26
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/dist/tui/darwin-arm64/ufoo-tui +0 -0
- package/dist/tui/darwin-x64/ufoo-tui +0 -0
- package/dist/tui/linux-arm64/ufoo-tui +0 -0
- package/dist/tui/linux-x64/ufoo-tui +0 -0
- package/package.json +1 -1
- package/src/code/UcodeController.js +2 -10
- package/src/code/agent.js +105 -26
- package/src/code/context/assembler.js +0 -102
- package/src/code/context/transcript.js +5 -0
- package/src/code/conversation/sessionJournal.js +351 -0
- package/src/code/nativeRunner.js +26 -0
- package/src/code/sessionStore.js +9 -21
- package/src/code/taskDecomposer.js +7 -3
- package/src/code/ucodeSlashDispatch.js +1 -1
- package/src/runtime/contracts/uiProtocol.js +2 -0
- package/src/ui/format/index.js +105 -36
- package/src/ui/format/markdownRenderer.js +2 -2
- package/src/ui/rustChatHost.js +2 -0
- package/src/ui/rustUcodeHost.js +106 -30
- package/src/ui/toolMergeBridge.js +5 -7
package/src/ui/format/index.js
CHANGED
|
@@ -154,17 +154,18 @@ function buildUcodeBannerLines({
|
|
|
154
154
|
}
|
|
155
155
|
shortPath = path.normalize(shortPath);
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
// The logo is literal content. The renderer receives the completed banner
|
|
158
|
+
// row as a dedicated raw entry, so the logo never goes through log styling.
|
|
159
|
+
const logoLines = UCODE_BANNER_LINES;
|
|
158
160
|
const infoLines = [];
|
|
159
|
-
infoLines.push(
|
|
160
|
-
infoLines.push(`${chalk.dim("Model:")} ${chalk.yellow(modelLabel)}`);
|
|
161
|
+
infoLines.push(`Model: ${modelLabel}`);
|
|
161
162
|
if (planMode) {
|
|
162
|
-
infoLines.push(
|
|
163
|
+
infoLines.push("Mode: PLAN");
|
|
163
164
|
}
|
|
164
|
-
infoLines.push(
|
|
165
|
+
infoLines.push(`Dictionary: ${shortPath}`);
|
|
165
166
|
const normalizedSessionId = String(sessionId || "").trim();
|
|
166
167
|
if (normalizedSessionId) {
|
|
167
|
-
infoLines.push(
|
|
168
|
+
infoLines.push(`Session: ${normalizedSessionId}`);
|
|
168
169
|
}
|
|
169
170
|
const logoPadding = " ".repeat(
|
|
170
171
|
UCODE_BANNER_LINES.reduce((max, line) => Math.max(max, String(line || "").length), 0)
|
|
@@ -174,7 +175,7 @@ function buildUcodeBannerLines({
|
|
|
174
175
|
return Array.from({ length: rows }, (_, index) => {
|
|
175
176
|
const logoLine = logoLines[index] || logoPadding;
|
|
176
177
|
const info = infoLines[index] || "";
|
|
177
|
-
return
|
|
178
|
+
return `${logoLine} ${info}`;
|
|
178
179
|
});
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -323,6 +324,66 @@ function toolMessagePreview(message = {}) {
|
|
|
323
324
|
return raw;
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
function parseToolCallArguments(call = {}) {
|
|
328
|
+
const raw = call && call.function && call.function.arguments != null
|
|
329
|
+
? call.function.arguments
|
|
330
|
+
: (call && call.arguments != null ? call.arguments : call && call.input);
|
|
331
|
+
if (raw && typeof raw === "object") return raw;
|
|
332
|
+
if (typeof raw !== "string" || !raw.trim()) return {};
|
|
333
|
+
try {
|
|
334
|
+
const parsed = JSON.parse(raw);
|
|
335
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
336
|
+
} catch {
|
|
337
|
+
return {};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function formatToolDisplayName(tool = "") {
|
|
342
|
+
const words = String(tool || "tool")
|
|
343
|
+
.trim()
|
|
344
|
+
.split(/[_\-\s]+/)
|
|
345
|
+
.filter(Boolean);
|
|
346
|
+
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ") || "Tool";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function formatUcodeToolLine(tool = "", args = {}, payload = {}) {
|
|
350
|
+
const name = String(tool || "tool").trim().toLowerCase() || "tool";
|
|
351
|
+
const detail = normalizeToolLogDetail(name, args, payload);
|
|
352
|
+
return `• ${formatToolDisplayName(name)}${detail ? ` ${detail}` : ""}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function collapseConsecutiveUcodeToolEntries(entries = []) {
|
|
356
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
357
|
+
const collapsed = [];
|
|
358
|
+
for (let index = 0; index < source.length;) {
|
|
359
|
+
const entry = source[index];
|
|
360
|
+
if (!entry || entry.kind !== "tool") {
|
|
361
|
+
collapsed.push(entry);
|
|
362
|
+
index += 1;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let end = index + 1;
|
|
367
|
+
while (end < source.length && source[end] && source[end].kind === "tool") end += 1;
|
|
368
|
+
const group = source.slice(index, end);
|
|
369
|
+
if (group.length === 1) {
|
|
370
|
+
collapsed.push(entry);
|
|
371
|
+
} else {
|
|
372
|
+
const detail = group
|
|
373
|
+
.map((item) => String(item.text || "").replace(/^•\s*/, ""))
|
|
374
|
+
.join("\n");
|
|
375
|
+
collapsed.push({
|
|
376
|
+
...entry,
|
|
377
|
+
text: `${String(entry.text || "• Tool")} · +${group.length - 1} calls (Ctrl+O expand)`,
|
|
378
|
+
detail,
|
|
379
|
+
expanded: false,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
index = end;
|
|
383
|
+
}
|
|
384
|
+
return collapsed;
|
|
385
|
+
}
|
|
386
|
+
|
|
326
387
|
/**
|
|
327
388
|
* Collapse attached-image prompt prefixes / base64 blobs for TUI log display.
|
|
328
389
|
*/
|
|
@@ -355,9 +416,6 @@ function buildUcodeSessionLogEntries(messages = [], options = {}) {
|
|
|
355
416
|
: { inCodeBlock: false };
|
|
356
417
|
const idPrefix = String(options.idPrefix || "h");
|
|
357
418
|
let seq = Number.isFinite(options.startSeq) ? Math.max(0, Math.floor(options.startSeq)) : 0;
|
|
358
|
-
const maxToolPreviewLines = Number.isFinite(options.maxToolPreviewLines)
|
|
359
|
-
? Math.max(1, Math.floor(options.maxToolPreviewLines))
|
|
360
|
-
: 4;
|
|
361
419
|
const entries = [];
|
|
362
420
|
|
|
363
421
|
const pushLines = (text, kind) => {
|
|
@@ -373,14 +431,12 @@ function buildUcodeSessionLogEntries(messages = [], options = {}) {
|
|
|
373
431
|
} else {
|
|
374
432
|
lines = source.split(/\r?\n/);
|
|
375
433
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
seq += 1;
|
|
383
|
-
}
|
|
434
|
+
entries.push({
|
|
435
|
+
id: `${idPrefix}-${seq}`,
|
|
436
|
+
text: lines.map((line) => String(line || "")).join("\n"),
|
|
437
|
+
kind,
|
|
438
|
+
});
|
|
439
|
+
seq += 1;
|
|
384
440
|
};
|
|
385
441
|
|
|
386
442
|
for (const message of list) {
|
|
@@ -390,37 +446,35 @@ function buildUcodeSessionLogEntries(messages = [], options = {}) {
|
|
|
390
446
|
const text = redactUserMessageForLog(messageContentText(message));
|
|
391
447
|
if (!text.trim()) continue;
|
|
392
448
|
const lines = text.split(/\r?\n/);
|
|
393
|
-
lines.
|
|
394
|
-
pushLines(index === 0 ? `› ${line}` : line, "user");
|
|
395
|
-
});
|
|
449
|
+
pushLines(lines.map((line, index) => (index === 0 ? `› ${line}` : line)).join("\n"), "user");
|
|
396
450
|
continue;
|
|
397
451
|
}
|
|
398
452
|
if (role === "assistant") {
|
|
399
453
|
const text = messageContentText(message);
|
|
400
454
|
if (text) pushLines(text, "assistant");
|
|
401
455
|
const calls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
})
|
|
409
|
-
.map((name) => name.trim())
|
|
410
|
-
.filter(Boolean);
|
|
411
|
-
if (names.length > 0) pushLines(`⚙ ${names.join(" · ")}`, "system");
|
|
456
|
+
for (const call of calls) {
|
|
457
|
+
if (!call || typeof call !== "object") continue;
|
|
458
|
+
const name = String(
|
|
459
|
+
(call.function && call.function.name) || call.name || "tool"
|
|
460
|
+
).trim();
|
|
461
|
+
pushLines(formatUcodeToolLine(name, parseToolCallArguments(call)), "tool");
|
|
412
462
|
}
|
|
413
463
|
continue;
|
|
414
464
|
}
|
|
415
465
|
if (role === "tool") {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
466
|
+
// The corresponding assistant tool_call already carries the command or
|
|
467
|
+
// path. Tool-result artifacts are internal storage references and do
|
|
468
|
+
// not belong in the restored transcript presentation.
|
|
469
|
+
continue;
|
|
420
470
|
}
|
|
421
471
|
}
|
|
422
472
|
|
|
423
|
-
return {
|
|
473
|
+
return {
|
|
474
|
+
entries: collapseConsecutiveUcodeToolEntries(entries),
|
|
475
|
+
nextSeq: seq,
|
|
476
|
+
markdownState,
|
|
477
|
+
};
|
|
424
478
|
}
|
|
425
479
|
|
|
426
480
|
function shouldEnterAgentSelection(inputValue = "") {
|
|
@@ -826,6 +880,19 @@ function buildToolMergeRowText(entries = []) {
|
|
|
826
880
|
return `· ${summary}`;
|
|
827
881
|
}
|
|
828
882
|
|
|
883
|
+
function buildUcodeToolRowText(entries = []) {
|
|
884
|
+
const list = Array.isArray(entries)
|
|
885
|
+
? entries.map((item) => normalizeToolMergeEntry(item))
|
|
886
|
+
: [];
|
|
887
|
+
if (list.length === 0) return "• Tool";
|
|
888
|
+
const first = list[0];
|
|
889
|
+
const firstLine = `• ${formatToolDisplayName(first.tool)}${first.detail ? ` ${first.detail}` : ""}`;
|
|
890
|
+
if (list.length === 1) return firstLine;
|
|
891
|
+
const errorCount = list.filter((item) => item.isError).length;
|
|
892
|
+
const errorSuffix = errorCount > 0 ? ` · ${errorCount} error${errorCount === 1 ? "" : "s"}` : "";
|
|
893
|
+
return `${firstLine} · +${list.length - 1} calls${errorSuffix}`;
|
|
894
|
+
}
|
|
895
|
+
|
|
829
896
|
/**
|
|
830
897
|
* Lay out the global-mode project rail inside a single line. Like
|
|
831
898
|
* planAgentsFooter, but with two differences:
|
|
@@ -1316,6 +1383,8 @@ module.exports = {
|
|
|
1316
1383
|
buildMergedToolExpandedLines,
|
|
1317
1384
|
buildMergedToolSummaryText,
|
|
1318
1385
|
buildToolMergeRowText,
|
|
1386
|
+
buildUcodeToolRowText,
|
|
1387
|
+
collapseConsecutiveUcodeToolEntries,
|
|
1319
1388
|
buildCompletions,
|
|
1320
1389
|
buildUcodeBannerLines,
|
|
1321
1390
|
buildUcodeSessionLogEntries,
|
|
@@ -140,7 +140,7 @@ function createBlessedAdapters(escapeFn = (value) => String(value || "")) {
|
|
|
140
140
|
return `{bold}${value}{/bold}`;
|
|
141
141
|
},
|
|
142
142
|
quoteMarker: () => "{gray-fg}│{/gray-fg}",
|
|
143
|
-
bulletMarker: () => "{gray-fg}
|
|
143
|
+
bulletMarker: () => "{gray-fg}-{/gray-fg}",
|
|
144
144
|
orderedMarker: (value) => `{gray-fg}${escape(value)}.{/gray-fg}`,
|
|
145
145
|
rule: () => "{gray-fg}────────────────────────{/gray-fg}",
|
|
146
146
|
fenceOpen: (language) => (
|
|
@@ -181,7 +181,7 @@ function createAnsiAdapters() {
|
|
|
181
181
|
return paint.bold(text);
|
|
182
182
|
},
|
|
183
183
|
quoteMarker: () => paint.gray("│"),
|
|
184
|
-
bulletMarker: () => paint.gray("
|
|
184
|
+
bulletMarker: () => paint.gray("-"),
|
|
185
185
|
orderedMarker: (value) => paint.gray(`${value}.`),
|
|
186
186
|
rule: () => paint.gray("────────────────────────"),
|
|
187
187
|
fenceOpen: (language) => (
|
package/src/ui/rustChatHost.js
CHANGED
|
@@ -39,6 +39,7 @@ const {
|
|
|
39
39
|
resolveDaemonEndpoint,
|
|
40
40
|
routeDaemonRequest,
|
|
41
41
|
} = require("../runtime/daemon/endpoint");
|
|
42
|
+
const PACKAGE_VERSION = require("../../package.json").version;
|
|
42
43
|
|
|
43
44
|
function stripTags(value) {
|
|
44
45
|
return String(value || "").replace(/\{[^}]+\}/g, "");
|
|
@@ -979,6 +980,7 @@ async function runChatRust(projectRoot, options = {}) {
|
|
|
979
980
|
const projects = env.globalMode ? loadGlobalProjectRows(activeProjectRoot) : [];
|
|
980
981
|
return {
|
|
981
982
|
status: "ready",
|
|
983
|
+
package_version: PACKAGE_VERSION,
|
|
982
984
|
footer: agents.footer || "",
|
|
983
985
|
entries: historyToEntries(history),
|
|
984
986
|
input_history: Array.isArray(inputHistory) ? inputHistory.filter(Boolean) : [],
|
package/src/ui/rustUcodeHost.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
} = require("../code/UcodeController");
|
|
18
18
|
const { createEnvelope, encodeMessage } = require("../runtime/contracts/uiProtocol");
|
|
19
19
|
const fmt = require("./format");
|
|
20
|
+
const PACKAGE_VERSION = require("../../package.json").version;
|
|
20
21
|
|
|
21
22
|
function stripTags(value) {
|
|
22
23
|
return String(value || "").replace(/\{[^}]+\}/g, "");
|
|
@@ -162,6 +163,46 @@ function normalizeToolLogEntry(entry = {}) {
|
|
|
162
163
|
return fmt.normalizeToolMergeEntry({ tool, detail, isError, errorText });
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
function splitUcodeBannerRow(line, index) {
|
|
167
|
+
const row = String(line || "");
|
|
168
|
+
const logo = String((fmt.UCODE_BANNER_LINES || [])[index] || "");
|
|
169
|
+
if (logo && row.startsWith(logo)) {
|
|
170
|
+
return { logo, metadata: row.slice(logo.length).trimStart() };
|
|
171
|
+
}
|
|
172
|
+
return { logo: "", metadata: row.trimStart() };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Keep a model's reasoning in one mutable scrollback entry. The TUI owns the
|
|
177
|
+
* collapsed/expanded presentation; the host only sends an initial row plus
|
|
178
|
+
* append-only deltas for that row.
|
|
179
|
+
*/
|
|
180
|
+
function createThinkingLogPublisher(publish, thinkingStatus, streamId) {
|
|
181
|
+
const id = `${String(streamId || "stream")}-thinking`;
|
|
182
|
+
let started = false;
|
|
183
|
+
|
|
184
|
+
function onThinkingDelta(delta) {
|
|
185
|
+
const text = String(delta || "");
|
|
186
|
+
if (!text) return;
|
|
187
|
+
if (!started) {
|
|
188
|
+
started = true;
|
|
189
|
+
publish("thinking.start", { id });
|
|
190
|
+
}
|
|
191
|
+
publish("thinking.delta", { id, text });
|
|
192
|
+
if (thinkingStatus && typeof thinkingStatus.onThinkingDelta === "function") {
|
|
193
|
+
thinkingStatus.onThinkingDelta(text);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function stop() {
|
|
198
|
+
if (thinkingStatus && typeof thinkingStatus.reset === "function") {
|
|
199
|
+
thinkingStatus.reset();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { id, onThinkingDelta, stop };
|
|
204
|
+
}
|
|
205
|
+
|
|
165
206
|
async function runUcodeRust(props = {}) {
|
|
166
207
|
const plan = resolveTuiLaunchPlan({
|
|
167
208
|
mode: props.tuiMode || process.env.UFOO_TUI || "rust",
|
|
@@ -321,25 +362,22 @@ async function runUcodeRust(props = {}) {
|
|
|
321
362
|
} else {
|
|
322
363
|
lines = raw.split(/\r?\n/);
|
|
323
364
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (/\x1b\[/.test(line)) {
|
|
338
|
-
last.text = line;
|
|
339
|
-
}
|
|
340
|
-
publish("transcript.append", last);
|
|
365
|
+
const block = lines.map((line) => String(line || "")).join("\n");
|
|
366
|
+
entrySeq += 1;
|
|
367
|
+
const entry = {
|
|
368
|
+
id: `u-${entrySeq}`,
|
|
369
|
+
kind,
|
|
370
|
+
text: stripTags(block),
|
|
371
|
+
// Keep ANSI in a parallel field if strip removes styling; prefer the
|
|
372
|
+
// rendered block for Rust when it contains escape sequences.
|
|
373
|
+
ansi: block,
|
|
374
|
+
speaker: "",
|
|
375
|
+
};
|
|
376
|
+
if (/\x1b\[/.test(block)) {
|
|
377
|
+
entry.text = block;
|
|
341
378
|
}
|
|
342
|
-
|
|
379
|
+
publish("transcript.append", entry);
|
|
380
|
+
return entry;
|
|
343
381
|
}
|
|
344
382
|
|
|
345
383
|
function replaceTranscript(entries = []) {
|
|
@@ -351,6 +389,8 @@ async function runUcodeRust(props = {}) {
|
|
|
351
389
|
kind: String((entry && entry.kind) || "system"),
|
|
352
390
|
text,
|
|
353
391
|
speaker: String((entry && entry.speaker) || ""),
|
|
392
|
+
detail: String((entry && entry.detail) || ""),
|
|
393
|
+
expanded: Boolean(entry && entry.expanded),
|
|
354
394
|
};
|
|
355
395
|
});
|
|
356
396
|
entrySeq = Math.max(entrySeq, normalized.length);
|
|
@@ -461,13 +501,25 @@ async function runUcodeRust(props = {}) {
|
|
|
461
501
|
);
|
|
462
502
|
return {
|
|
463
503
|
status: "ready",
|
|
504
|
+
package_version: PACKAGE_VERSION,
|
|
464
505
|
footer: agentsSnap.footer,
|
|
465
|
-
entries: banner.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
506
|
+
entries: banner.map((line, idx) => {
|
|
507
|
+
const { logo, metadata } = splitUcodeBannerRow(line, idx);
|
|
508
|
+
return {
|
|
509
|
+
id: `b-${idx}`,
|
|
510
|
+
kind: "banner",
|
|
511
|
+
// Logo and metadata are separate raw renderer segments. Neither
|
|
512
|
+
// ever passes through the ordinary log formatter.
|
|
513
|
+
text: logo,
|
|
514
|
+
detail: metadata,
|
|
515
|
+
speaker: "",
|
|
516
|
+
};
|
|
517
|
+
}).concat([{
|
|
518
|
+
id: `b-${banner.length}`,
|
|
519
|
+
kind: "spacer",
|
|
520
|
+
text: "",
|
|
469
521
|
speaker: "",
|
|
470
|
-
})
|
|
522
|
+
}]),
|
|
471
523
|
input_history: [],
|
|
472
524
|
agents: agentsSnap.agents,
|
|
473
525
|
attachment_count: pendingAttachments.length,
|
|
@@ -509,17 +561,28 @@ async function runUcodeRust(props = {}) {
|
|
|
509
561
|
tools.beginScope();
|
|
510
562
|
thinking.reset();
|
|
511
563
|
publish("stream.start", { id: streamId });
|
|
564
|
+
const thinkingLog = createThinkingLogPublisher(publish, thinking, streamId);
|
|
565
|
+
let responseStarted = false;
|
|
566
|
+
const beginResponse = () => {
|
|
567
|
+
if (responseStarted) return;
|
|
568
|
+
responseStarted = true;
|
|
569
|
+
thinkingLog.stop();
|
|
570
|
+
publish("status.set", { text: "Generating response…", busy: true });
|
|
571
|
+
};
|
|
512
572
|
try {
|
|
513
573
|
const result = await submit(answerText, props.state, {
|
|
514
574
|
signal: abort.signal,
|
|
515
575
|
onDelta: (delta) => {
|
|
576
|
+
beginResponse();
|
|
516
577
|
publish("stream.delta", { id: streamId, text: String(delta || "") });
|
|
517
578
|
},
|
|
518
|
-
onThinkingDelta:
|
|
579
|
+
onThinkingDelta: (delta) => {
|
|
580
|
+
if (!responseStarted) thinkingLog.onThinkingDelta(delta);
|
|
581
|
+
},
|
|
519
582
|
onToolLog: ingestToolLog,
|
|
520
583
|
});
|
|
521
584
|
tools.flush();
|
|
522
|
-
|
|
585
|
+
thinkingLog.stop();
|
|
523
586
|
publish("stream.done", { id: streamId });
|
|
524
587
|
if (!result || result.ok === false) {
|
|
525
588
|
appendLog(`Error: ${(result && result.error) || "resume failed"}`, "error");
|
|
@@ -535,7 +598,7 @@ async function runUcodeRust(props = {}) {
|
|
|
535
598
|
return { ok: true, waiting: false };
|
|
536
599
|
} catch (err) {
|
|
537
600
|
tools.flush();
|
|
538
|
-
|
|
601
|
+
thinkingLog.stop();
|
|
539
602
|
publish("stream.done", { id: streamId });
|
|
540
603
|
if (abort.signal.aborted) {
|
|
541
604
|
appendLog("Task cancelled.", "system");
|
|
@@ -557,20 +620,31 @@ async function runUcodeRust(props = {}) {
|
|
|
557
620
|
tools.beginScope();
|
|
558
621
|
thinking.reset();
|
|
559
622
|
publish("stream.start", { id: streamId });
|
|
623
|
+
const thinkingLog = createThinkingLogPublisher(publish, thinking, streamId);
|
|
624
|
+
let responseStarted = false;
|
|
625
|
+
const beginResponse = () => {
|
|
626
|
+
if (responseStarted) return;
|
|
627
|
+
responseStarted = true;
|
|
628
|
+
thinkingLog.stop();
|
|
629
|
+
publish("status.set", { text: "Generating response…", busy: true });
|
|
630
|
+
};
|
|
560
631
|
try {
|
|
561
632
|
const nlResult = await props.runNaturalLanguageTask(text, props.state, {
|
|
562
633
|
signal: abort.signal,
|
|
563
634
|
onDelta: (delta) => {
|
|
635
|
+
beginResponse();
|
|
564
636
|
publish("stream.delta", { id: streamId, text: String(delta || "") });
|
|
565
637
|
},
|
|
566
|
-
onThinkingDelta:
|
|
638
|
+
onThinkingDelta: (delta) => {
|
|
639
|
+
if (!responseStarted) thinkingLog.onThinkingDelta(delta);
|
|
640
|
+
},
|
|
567
641
|
onToolLog: ingestToolLog,
|
|
568
642
|
onPhase: (event) => {
|
|
569
643
|
if (!event || typeof event !== "object") return;
|
|
570
644
|
if (event.type === "request_start") {
|
|
571
645
|
publish("status.set", { text: "Waiting for model…", busy: true });
|
|
572
646
|
} else if (event.type === "text_delta") {
|
|
573
|
-
|
|
647
|
+
beginResponse();
|
|
574
648
|
} else if (event.type === "tool_request") {
|
|
575
649
|
const tool = String(event.name || "tool").trim() || "tool";
|
|
576
650
|
const label = (fmt.TOOL_LABELS && fmt.TOOL_LABELS[tool.toLowerCase()])
|
|
@@ -580,7 +654,7 @@ async function runUcodeRust(props = {}) {
|
|
|
580
654
|
},
|
|
581
655
|
});
|
|
582
656
|
tools.flush();
|
|
583
|
-
|
|
657
|
+
thinkingLog.stop();
|
|
584
658
|
publish("stream.done", { id: streamId });
|
|
585
659
|
if (nlResult && nlResult.summary) appendLog(nlResult.summary, "system");
|
|
586
660
|
if (typeof props.formatNlResult === "function" && nlResult) {
|
|
@@ -596,7 +670,7 @@ async function runUcodeRust(props = {}) {
|
|
|
596
670
|
return { ok: true, waiting: false, result: nlResult };
|
|
597
671
|
} catch (err) {
|
|
598
672
|
tools.flush();
|
|
599
|
-
|
|
673
|
+
thinkingLog.stop();
|
|
600
674
|
publish("stream.done", { id: streamId });
|
|
601
675
|
if (abort.signal.aborted) {
|
|
602
676
|
appendLog("Task cancelled.", "system");
|
|
@@ -994,6 +1068,8 @@ module.exports = {
|
|
|
994
1068
|
runUcodeRust,
|
|
995
1069
|
buildPlanSetPayload,
|
|
996
1070
|
normalizeToolLogEntry,
|
|
1071
|
+
splitUcodeBannerRow,
|
|
1072
|
+
createThinkingLogPublisher,
|
|
997
1073
|
buildUcodeAgentsSnapshot,
|
|
998
1074
|
buildUcodeCompletionItems,
|
|
999
1075
|
};
|
|
@@ -23,13 +23,11 @@ function createToolMergePublisher(publish) {
|
|
|
23
23
|
merge = null;
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
const summary = fmt.
|
|
26
|
+
const summary = fmt.buildUcodeToolRowText(merge.entries);
|
|
27
27
|
const detail = fmt.buildMergedToolExpandedLines(merge.entries).join("\n");
|
|
28
|
-
const row =
|
|
29
|
-
?
|
|
30
|
-
:
|
|
31
|
-
? `· ${summary} (Ctrl+O expand)`
|
|
32
|
-
: summary);
|
|
28
|
+
const row = merge.entries.length >= 2
|
|
29
|
+
? `${summary} (Ctrl+O expand)`
|
|
30
|
+
: summary;
|
|
33
31
|
publish("tool.group", {
|
|
34
32
|
id: `tool-merge-${merge.id}`,
|
|
35
33
|
summary: row || summary,
|
|
@@ -47,7 +45,7 @@ function createToolMergePublisher(publish) {
|
|
|
47
45
|
if (merge) {
|
|
48
46
|
publish("tool.start", {
|
|
49
47
|
id: `tool-merge-${merge.id}`,
|
|
50
|
-
summary: fmt.
|
|
48
|
+
summary: fmt.buildUcodeToolRowText(merge.entries),
|
|
51
49
|
});
|
|
52
50
|
}
|
|
53
51
|
}
|