u-foo 2.5.13 → 2.5.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 +1 -1
- package/src/agents/prompts/native/environment.js +20 -8
- package/src/code/agent.js +339 -24
- package/src/code/commands.js +61 -0
- package/src/code/context/artifactGc.js +292 -0
- package/src/code/context/artifactIndex.js +161 -0
- package/src/code/context/artifacts.js +183 -0
- package/src/code/context/assembler.js +698 -0
- package/src/code/context/executionSegment.js +314 -0
- package/src/code/context/featureFlag.js +13 -0
- package/src/code/context/index.js +18 -0
- package/src/code/context/projectSnapshot.js +201 -0
- package/src/code/context/promptLayers.js +159 -0
- package/src/code/context/reducers.js +328 -0
- package/src/code/context/stableJson.js +29 -0
- package/src/code/context/stateCommit.js +412 -0
- package/src/code/context/transcript.js +182 -0
- package/src/code/context/transcriptSync.js +106 -0
- package/src/code/context/workingSet.js +323 -0
- package/src/code/dispatch.js +4 -1
- package/src/code/index.js +6 -0
- package/src/code/modelCommand.js +87 -0
- package/src/code/nativeRunner.js +187 -31
- package/src/code/repl.js +36 -32
- package/src/code/sessionStore.js +227 -15
- package/src/code/skills/index.js +10 -0
- package/src/code/skills/injection.js +65 -3
- package/src/code/skills/loader.js +21 -0
- package/src/code/skills/manifest.js +87 -0
- package/src/code/skills/render.js +15 -1
- package/src/code/taskDecomposer.js +32 -2
- package/src/code/tools/artifactRead.js +40 -0
- package/src/code/tui.js +2 -0
- package/src/code/usageStore.js +15 -0
- package/src/ui/format/index.js +260 -44
- package/src/ui/format/markdownRenderer.js +215 -72
- package/src/ui/ink/ChatApp.js +39 -8
- package/src/ui/ink/UcodeApp.js +408 -55
- package/src/ui/ink/chatLogModel.js +102 -21
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { loadArtifact, readArtifactSlice } = require("../context/artifacts");
|
|
4
|
+
|
|
5
|
+
function runArtifactReadTool(args = {}, options = {}) {
|
|
6
|
+
const workspaceRoot = options.workspaceRoot || process.cwd();
|
|
7
|
+
const sessionId = String(options.sessionId || args.sessionId || "").trim();
|
|
8
|
+
const artifactId = String(args.artifactId || args.id || "").trim();
|
|
9
|
+
if (!artifactId) {
|
|
10
|
+
return { ok: false, error: "artifactId is required" };
|
|
11
|
+
}
|
|
12
|
+
if (!sessionId) {
|
|
13
|
+
return { ok: false, error: "sessionId is required for artifact_read" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const loaded = loadArtifact(workspaceRoot, sessionId, artifactId);
|
|
17
|
+
if (!loaded.ok || !loaded.artifact) {
|
|
18
|
+
return { ok: false, error: loaded.error || "artifact not found", artifactId };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const selector = {};
|
|
22
|
+
if (args.startLine !== undefined) selector.startLine = args.startLine;
|
|
23
|
+
if (args.endLine !== undefined) selector.endLine = args.endLine;
|
|
24
|
+
if (args.maxChars !== undefined) selector.maxChars = args.maxChars;
|
|
25
|
+
if (args.tailLines !== undefined) selector.tailLines = args.tailLines;
|
|
26
|
+
|
|
27
|
+
const slice = readArtifactSlice(loaded.artifact, selector);
|
|
28
|
+
return {
|
|
29
|
+
ok: slice.ok !== false,
|
|
30
|
+
artifactId,
|
|
31
|
+
content: slice.content || "",
|
|
32
|
+
range: slice.range,
|
|
33
|
+
truncated: Boolean(slice.truncated),
|
|
34
|
+
error: slice.error || "",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
runArtifactReadTool,
|
|
40
|
+
};
|
package/src/code/tui.js
CHANGED
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
normalizeToolMergeEntry,
|
|
28
28
|
parseActiveAgentsFromBusStatus,
|
|
29
29
|
renderLogLinesWithMarkdown,
|
|
30
|
+
renderLogLinesWithMarkdownAnsi,
|
|
30
31
|
resolveAgentSelectionOnDown,
|
|
31
32
|
resolveHistoryDownTransition,
|
|
32
33
|
shouldClearAgentSelectionOnUp,
|
|
@@ -51,6 +52,7 @@ module.exports = {
|
|
|
51
52
|
parseActiveAgentsFromBusStatus,
|
|
52
53
|
shouldUseUcodeTui,
|
|
53
54
|
renderLogLinesWithMarkdown,
|
|
55
|
+
renderLogLinesWithMarkdownAnsi,
|
|
54
56
|
shouldEnterAgentSelection,
|
|
55
57
|
resolveAgentSelectionOnDown,
|
|
56
58
|
cycleAgentSelectionIndex,
|
package/src/code/usageStore.js
CHANGED
|
@@ -92,10 +92,25 @@ function summarizeSessionUsage({ workspaceRoot = process.cwd(), sessionId = "" }
|
|
|
92
92
|
return summary;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function formatSessionUsageStatus(summary = {}) {
|
|
96
|
+
const source = summary && typeof summary === "object" ? summary : {};
|
|
97
|
+
const input = Number(source.input) || 0;
|
|
98
|
+
const output = Number(source.output) || 0;
|
|
99
|
+
const cacheRead = Number(source.cacheRead) || 0;
|
|
100
|
+
const cacheCreation = Number(source.cacheCreation) || 0;
|
|
101
|
+
const denominator = cacheRead + input;
|
|
102
|
+
const hitRate = denominator > 0 ? (cacheRead / denominator) * 100 : 0;
|
|
103
|
+
return [
|
|
104
|
+
`Session tokens: input=${input} output=${output} cache_read=${cacheRead} cache_creation=${cacheCreation}`,
|
|
105
|
+
`Cache hit rate: ${hitRate.toFixed(1)}% (cache_read/(cache_read+input))`,
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
95
109
|
module.exports = {
|
|
96
110
|
getUsageFilePath,
|
|
97
111
|
buildUsageRecord,
|
|
98
112
|
appendUsageRecord,
|
|
99
113
|
createUsageSummary,
|
|
100
114
|
summarizeSessionUsage,
|
|
115
|
+
formatSessionUsageStatus,
|
|
101
116
|
};
|
package/src/ui/format/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const TOOL_LABELS = {
|
|
|
41
41
|
write: "Writing file",
|
|
42
42
|
edit: "Editing file",
|
|
43
43
|
bash: "Running command",
|
|
44
|
+
artifact_read: "Reading artifact",
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
const ANSI_PATTERN = /\x1B\[[0-9;?]*[ -/]*[@-~]/g;
|
|
@@ -248,6 +249,120 @@ function renderLogLinesWithMarkdown(text = "", state = {}, escapeFn = (value) =>
|
|
|
248
249
|
return renderMarkdownLines(text, state, escapeFn);
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
function renderLogLinesWithMarkdownAnsi(text = "", state = {}) {
|
|
253
|
+
const { renderMarkdownLinesAnsi } = require("./markdownRenderer");
|
|
254
|
+
return renderMarkdownLinesAnsi(text, state);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function messageContentText(message = {}) {
|
|
258
|
+
if (!message || typeof message !== "object") return "";
|
|
259
|
+
const content = message.content;
|
|
260
|
+
if (typeof content === "string") return content;
|
|
261
|
+
if (Array.isArray(content)) {
|
|
262
|
+
return content.map((part) => {
|
|
263
|
+
if (typeof part === "string") return part;
|
|
264
|
+
if (part && typeof part === "object" && part.text != null) return String(part.text);
|
|
265
|
+
return "";
|
|
266
|
+
}).join("");
|
|
267
|
+
}
|
|
268
|
+
return "";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function toolMessagePreview(message = {}) {
|
|
272
|
+
const raw = messageContentText(message).trim();
|
|
273
|
+
if (!raw) return "";
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(raw);
|
|
276
|
+
if (parsed && typeof parsed === "object") {
|
|
277
|
+
if (parsed.preview) return String(parsed.preview);
|
|
278
|
+
if (parsed.artifactId) return `artifact:${parsed.artifactId}`;
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
// plain tool text
|
|
282
|
+
}
|
|
283
|
+
return raw;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Convert persisted nlMessages into ucode TUI log rows for resume/history.
|
|
288
|
+
* Applies the shared ANSI markdown renderer so restored assistant text matches
|
|
289
|
+
* live streaming output.
|
|
290
|
+
*/
|
|
291
|
+
function buildUcodeSessionLogEntries(messages = [], options = {}) {
|
|
292
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
293
|
+
const markdownState = options.markdownState && typeof options.markdownState === "object"
|
|
294
|
+
? options.markdownState
|
|
295
|
+
: { inCodeBlock: false };
|
|
296
|
+
const idPrefix = String(options.idPrefix || "h");
|
|
297
|
+
let seq = Number.isFinite(options.startSeq) ? Math.max(0, Math.floor(options.startSeq)) : 0;
|
|
298
|
+
const maxToolPreviewLines = Number.isFinite(options.maxToolPreviewLines)
|
|
299
|
+
? Math.max(1, Math.floor(options.maxToolPreviewLines))
|
|
300
|
+
: 4;
|
|
301
|
+
const entries = [];
|
|
302
|
+
|
|
303
|
+
const pushLines = (text, kind) => {
|
|
304
|
+
const source = String(text == null ? "" : text);
|
|
305
|
+
let lines;
|
|
306
|
+
if (kind === "assistant" || kind === "error") {
|
|
307
|
+
try {
|
|
308
|
+
lines = renderLogLinesWithMarkdownAnsi(source, markdownState);
|
|
309
|
+
if (!Array.isArray(lines) || lines.length === 0) lines = source.split(/\r?\n/);
|
|
310
|
+
} catch {
|
|
311
|
+
lines = source.split(/\r?\n/);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
lines = source.split(/\r?\n/);
|
|
315
|
+
}
|
|
316
|
+
for (const line of lines) {
|
|
317
|
+
entries.push({
|
|
318
|
+
id: `${idPrefix}-${seq}`,
|
|
319
|
+
text: String(line || ""),
|
|
320
|
+
kind,
|
|
321
|
+
});
|
|
322
|
+
seq += 1;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
for (const message of list) {
|
|
327
|
+
if (!message || typeof message !== "object") continue;
|
|
328
|
+
const role = String(message.role || "").trim().toLowerCase();
|
|
329
|
+
if (role === "user") {
|
|
330
|
+
const text = messageContentText(message);
|
|
331
|
+
if (!text.trim()) continue;
|
|
332
|
+
const lines = text.split(/\r?\n/);
|
|
333
|
+
lines.forEach((line, index) => {
|
|
334
|
+
pushLines(index === 0 ? `› ${line}` : line, "user");
|
|
335
|
+
});
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (role === "assistant") {
|
|
339
|
+
const text = messageContentText(message);
|
|
340
|
+
if (text) pushLines(text, "assistant");
|
|
341
|
+
const calls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
342
|
+
if (calls.length > 0) {
|
|
343
|
+
const names = calls
|
|
344
|
+
.map((call) => {
|
|
345
|
+
if (!call || typeof call !== "object") return "";
|
|
346
|
+
if (call.function && call.function.name) return String(call.function.name);
|
|
347
|
+
return String(call.name || "");
|
|
348
|
+
})
|
|
349
|
+
.map((name) => name.trim())
|
|
350
|
+
.filter(Boolean);
|
|
351
|
+
if (names.length > 0) pushLines(`⚙ ${names.join(" · ")}`, "system");
|
|
352
|
+
}
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (role === "tool") {
|
|
356
|
+
const preview = toolMessagePreview(message);
|
|
357
|
+
if (!preview.trim()) continue;
|
|
358
|
+
const clipped = preview.split(/\r?\n/).slice(0, maxToolPreviewLines).join("\n");
|
|
359
|
+
pushLines(clipped, "toolDetail");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { entries, nextSeq: seq, markdownState };
|
|
364
|
+
}
|
|
365
|
+
|
|
251
366
|
function shouldEnterAgentSelection(inputValue = "") {
|
|
252
367
|
const text = String(inputValue || "");
|
|
253
368
|
const trimmed = text.trim();
|
|
@@ -514,6 +629,39 @@ function normalizeBashToolCommand(args = {}, payload = {}) {
|
|
|
514
629
|
return [command, code].filter(Boolean).join(" · ");
|
|
515
630
|
}
|
|
516
631
|
|
|
632
|
+
function shortenPathDetail(value = "", maxChars = 72) {
|
|
633
|
+
const text = String(value || "").trim().replace(/\\/g, "/");
|
|
634
|
+
if (!text) return "";
|
|
635
|
+
const limit = Number.isFinite(maxChars) && maxChars > 8 ? Math.floor(maxChars) : 72;
|
|
636
|
+
if (text.length <= limit) return text;
|
|
637
|
+
return `…${text.slice(-(limit - 1))}`;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function normalizeToolLogDetail(tool = "", args = {}, payload = {}) {
|
|
641
|
+
const name = String(tool || "").trim().toLowerCase();
|
|
642
|
+
const argObj = args && typeof args === "object" ? args : {};
|
|
643
|
+
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
644
|
+
|
|
645
|
+
if (name === "bash") return normalizeBashToolCommand(argObj, resObj);
|
|
646
|
+
|
|
647
|
+
if (name === "read" || name === "write" || name === "edit") {
|
|
648
|
+
const pathText = String(argObj.path || resObj.path || "").trim();
|
|
649
|
+
return shortenPathDetail(pathText);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (name === "artifact_read") {
|
|
653
|
+
const artifactId = String(argObj.artifactId || argObj.id || resObj.artifactId || "").trim();
|
|
654
|
+
const rangeBits = [];
|
|
655
|
+
if (argObj.startLine != null) rangeBits.push(`L${argObj.startLine}`);
|
|
656
|
+
if (argObj.endLine != null) rangeBits.push(`L${argObj.endLine}`);
|
|
657
|
+
const range = rangeBits.length > 0 ? rangeBits.join("-") : "";
|
|
658
|
+
return [artifactId, range].filter(Boolean).join(" · ");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const fallback = String(argObj.path || argObj.command || argObj.cmd || "").trim();
|
|
662
|
+
return shortenPathDetail(fallback);
|
|
663
|
+
}
|
|
664
|
+
|
|
517
665
|
function normalizeToolMergeEntry(entry = {}) {
|
|
518
666
|
const source = entry && typeof entry === "object" ? entry : {};
|
|
519
667
|
const tool = String(source.tool || "").trim().toLowerCase() || "tool";
|
|
@@ -822,6 +970,7 @@ function buildCompletions({
|
|
|
822
970
|
commandTree = null,
|
|
823
971
|
groupTemplates = [],
|
|
824
972
|
soloProfiles = [],
|
|
973
|
+
argumentLists = null,
|
|
825
974
|
limit = 8,
|
|
826
975
|
} = {}) {
|
|
827
976
|
const raw = String(text || "");
|
|
@@ -830,9 +979,14 @@ function buildCompletions({
|
|
|
830
979
|
const endsWithWhitespace = /\s$/.test(trimmed);
|
|
831
980
|
|
|
832
981
|
if (trimmed.startsWith("/")) {
|
|
833
|
-
const
|
|
834
|
-
const head =
|
|
835
|
-
const tail =
|
|
982
|
+
const tokenParts = trimmed.trimEnd().split(/\s+/).filter(Boolean);
|
|
983
|
+
const head = tokenParts[0] || ""; // "/launch"
|
|
984
|
+
const tail = tokenParts.slice(1);
|
|
985
|
+
const headKey = head.startsWith("/") ? head : `/${head}`;
|
|
986
|
+
const headNode = commandTree && typeof commandTree === "object" ? commandTree[headKey] : null;
|
|
987
|
+
const argListForHead = argumentLists && typeof argumentLists === "object"
|
|
988
|
+
? argumentLists[headKey]
|
|
989
|
+
: null;
|
|
836
990
|
|
|
837
991
|
// Dynamic argument completion for /group run <alias> and
|
|
838
992
|
// /solo run <profile>. These pull from runtime sources (group
|
|
@@ -842,7 +996,7 @@ function buildCompletions({
|
|
|
842
996
|
: (head === "/solo" && tail[0] === "run")
|
|
843
997
|
? soloProfiles
|
|
844
998
|
: null;
|
|
845
|
-
if (dynList && (tail.length >= 2 ||
|
|
999
|
+
if (dynList && (tail.length >= 2 || (tail[0] === "run" && endsWithWhitespace))) {
|
|
846
1000
|
const partial = String(tail[1] || "").toLowerCase();
|
|
847
1001
|
const out = [];
|
|
848
1002
|
for (const item of (Array.isArray(dynList) ? dynList : [])) {
|
|
@@ -867,49 +1021,91 @@ function buildCompletions({
|
|
|
867
1021
|
}
|
|
868
1022
|
|
|
869
1023
|
// Sub-command completion: "/cmd <prefix>" or "/cmd sub <prefix>".
|
|
870
|
-
if (tail.length >= 1 && commandTree) {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
1024
|
+
if ((tail.length >= 1 || (endsWithWhitespace && headNode && headNode.children)) && commandTree) {
|
|
1025
|
+
let node = headNode;
|
|
1026
|
+
if (!node || typeof node !== "object") {
|
|
1027
|
+
// fall through
|
|
1028
|
+
} else if (node.children && typeof node.children === "object") {
|
|
1029
|
+
const walkTail = endsWithWhitespace && tail.length === 0
|
|
1030
|
+
? []
|
|
1031
|
+
: (endsWithWhitespace ? tail : tail.slice(0, -1));
|
|
1032
|
+
const partial = endsWithWhitespace && tail.length === 0
|
|
1033
|
+
? ""
|
|
1034
|
+
: (endsWithWhitespace ? "" : String(tail[tail.length - 1] || "").toLowerCase());
|
|
1035
|
+
let walkNode = node;
|
|
1036
|
+
let walkOk = true;
|
|
1037
|
+
for (const segment of walkTail) {
|
|
1038
|
+
const next = walkNode.children && walkNode.children[segment];
|
|
1039
|
+
if (!next) {
|
|
1040
|
+
walkOk = false;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
walkNode = next;
|
|
1044
|
+
}
|
|
1045
|
+
const children = walkOk ? (walkNode && walkNode.children) : null;
|
|
1046
|
+
if (children && typeof children === "object") {
|
|
1047
|
+
const prefixSoFar = walkTail.length > 0
|
|
1048
|
+
? `${head} ${walkTail.join(" ")}`
|
|
1049
|
+
: head;
|
|
1050
|
+
const entries = Object.keys(children).map((name) => ({
|
|
1051
|
+
name,
|
|
1052
|
+
...children[name],
|
|
1053
|
+
}));
|
|
1054
|
+
entries.sort((a, b) => {
|
|
1055
|
+
const orderA = Number.isFinite(a.order) ? a.order : 999;
|
|
1056
|
+
const orderB = Number.isFinite(b.order) ? b.order : 999;
|
|
1057
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1058
|
+
return a.name.localeCompare(b.name);
|
|
1059
|
+
});
|
|
1060
|
+
const out = [];
|
|
1061
|
+
for (const entry of entries) {
|
|
1062
|
+
if (partial && !entry.name.toLowerCase().startsWith(partial)) continue;
|
|
1063
|
+
const hasDynamicArguments = (head === "/group" && entry.name === "run")
|
|
1064
|
+
|| (head === "/solo" && entry.name === "run")
|
|
1065
|
+
|| Boolean(entry.hasArguments);
|
|
1066
|
+
out.push({
|
|
1067
|
+
kind: "subcommand",
|
|
1068
|
+
label: `${prefixSoFar} ${entry.name}`.trim(),
|
|
1069
|
+
replace: `${prefixSoFar} ${entry.name} `.replace(/^\s+/, ""),
|
|
1070
|
+
description: String(entry.desc || entry.summary || entry.description || ""),
|
|
1071
|
+
hasChildren: Boolean((entry.children && typeof entry.children === "object") || hasDynamicArguments),
|
|
1072
|
+
});
|
|
1073
|
+
if (out.length >= limit) break;
|
|
1074
|
+
}
|
|
1075
|
+
if (!endsWithWhitespace && out.length === 1) {
|
|
1076
|
+
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
1077
|
+
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
1078
|
+
}
|
|
1079
|
+
return out;
|
|
1080
|
+
}
|
|
881
1081
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const orderA = Number.isFinite(a.order) ? a.order : 999;
|
|
894
|
-
const orderB = Number.isFinite(b.order) ? b.order : 999;
|
|
895
|
-
if (orderA !== orderB) return orderA - orderB;
|
|
896
|
-
return a.name.localeCompare(b.name);
|
|
897
|
-
});
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Generic top-level argument lists (e.g. /resume <session-id>).
|
|
1085
|
+
if (
|
|
1086
|
+
Array.isArray(argListForHead)
|
|
1087
|
+
&& argListForHead.length > 0
|
|
1088
|
+
&& !(headNode && headNode.children)
|
|
1089
|
+
&& (endsWithWhitespace || tail.length >= 1)
|
|
1090
|
+
) {
|
|
1091
|
+
if (tail.length > 1) return [];
|
|
1092
|
+
const partial = String(tail[0] || "").toLowerCase();
|
|
898
1093
|
const out = [];
|
|
899
|
-
for (const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1094
|
+
for (const item of argListForHead) {
|
|
1095
|
+
const id = String((item && (item.alias || item.cmd || item.id || item.name)) || item || "");
|
|
1096
|
+
if (!id) continue;
|
|
1097
|
+
if (partial && !id.toLowerCase().startsWith(partial)) continue;
|
|
1098
|
+
const desc = String((item && (item.desc || item.summary || item.description || item.source)) || "");
|
|
903
1099
|
out.push({
|
|
904
|
-
kind: "
|
|
905
|
-
label: `${
|
|
906
|
-
replace: `${
|
|
907
|
-
description:
|
|
908
|
-
hasChildren:
|
|
1100
|
+
kind: "argument",
|
|
1101
|
+
label: `${head} ${id}`,
|
|
1102
|
+
replace: `${head} ${id} `,
|
|
1103
|
+
description: desc,
|
|
1104
|
+
hasChildren: false,
|
|
909
1105
|
});
|
|
910
1106
|
if (out.length >= limit) break;
|
|
911
1107
|
}
|
|
912
|
-
if (
|
|
1108
|
+
if (partial && out.length === 1) {
|
|
913
1109
|
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
914
1110
|
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
915
1111
|
}
|
|
@@ -920,8 +1116,16 @@ function buildCompletions({
|
|
|
920
1116
|
const after = trimmed.slice(1);
|
|
921
1117
|
const prefix = after.toLowerCase();
|
|
922
1118
|
const list = Array.isArray(commands) ? commands : [];
|
|
1119
|
+
const sorted = list.slice().sort((a, b) => {
|
|
1120
|
+
const orderA = Number.isFinite(a && a.order) ? a.order : 999;
|
|
1121
|
+
const orderB = Number.isFinite(b && b.order) ? b.order : 999;
|
|
1122
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1123
|
+
const nameA = String((a && a.cmd) || a || "");
|
|
1124
|
+
const nameB = String((b && b.cmd) || b || "");
|
|
1125
|
+
return nameA.localeCompare(nameB);
|
|
1126
|
+
});
|
|
923
1127
|
const out = [];
|
|
924
|
-
for (const item of
|
|
1128
|
+
for (const item of sorted) {
|
|
925
1129
|
// Registry entries already include the leading '/' in `cmd`. Strip
|
|
926
1130
|
// it before matching the user's prefix and put it back when we
|
|
927
1131
|
// render so we don't end up with '//cron'.
|
|
@@ -930,18 +1134,27 @@ function buildCompletions({
|
|
|
930
1134
|
const lower = bare.toLowerCase();
|
|
931
1135
|
if (!bare) continue;
|
|
932
1136
|
if (!lower.startsWith(prefix)) continue;
|
|
1137
|
+
const node = commandTree && commandTree[`/${bare}`];
|
|
1138
|
+
const hasChildren = Boolean(
|
|
1139
|
+
(node && node.children && typeof node.children === "object")
|
|
1140
|
+
|| (node && node.hasArguments)
|
|
1141
|
+
|| (argumentLists && Array.isArray(argumentLists[`/${bare}`]) && argumentLists[`/${bare}`].length > 0),
|
|
1142
|
+
);
|
|
933
1143
|
out.push({
|
|
934
1144
|
kind: "command",
|
|
935
1145
|
label: `/${bare}`,
|
|
936
1146
|
replace: `/${bare} `,
|
|
937
1147
|
description: String((item && (item.desc || item.summary || item.description)) || ""),
|
|
938
|
-
hasChildren
|
|
1148
|
+
hasChildren,
|
|
1149
|
+
optionalArguments: Boolean(node && node.optionalArguments),
|
|
939
1150
|
});
|
|
940
1151
|
if (out.length >= limit) break;
|
|
941
1152
|
}
|
|
942
1153
|
if (!endsWithWhitespace && out.length === 1) {
|
|
943
1154
|
const candidate = String(out[0].replace || "").trim().replace(/^\//, "").toLowerCase();
|
|
944
|
-
|
|
1155
|
+
// Exact bare command: close the popup so Enter submits. Commands with
|
|
1156
|
+
// optionalArguments (e.g. /model) are valid both bare and with an arg.
|
|
1157
|
+
if (candidate === prefix && (!out[0].hasChildren || out[0].optionalArguments)) return [];
|
|
945
1158
|
}
|
|
946
1159
|
return out;
|
|
947
1160
|
}
|
|
@@ -994,6 +1207,7 @@ module.exports = {
|
|
|
994
1207
|
buildToolMergeRowText,
|
|
995
1208
|
buildCompletions,
|
|
996
1209
|
buildUcodeBannerLines,
|
|
1210
|
+
buildUcodeSessionLogEntries,
|
|
997
1211
|
charDisplayWidth,
|
|
998
1212
|
clampCursorPos,
|
|
999
1213
|
createEscapeTagStripper,
|
|
@@ -1012,11 +1226,13 @@ module.exports = {
|
|
|
1012
1226
|
moveCursorVertically,
|
|
1013
1227
|
normalizeBashToolCommand,
|
|
1014
1228
|
normalizeModelLabel,
|
|
1229
|
+
normalizeToolLogDetail,
|
|
1015
1230
|
normalizeToolMergeEntry,
|
|
1016
1231
|
parseActiveAgentsFromBusStatus,
|
|
1017
1232
|
planAgentsFooter,
|
|
1018
1233
|
planProjectsRail,
|
|
1019
1234
|
renderLogLinesWithMarkdown,
|
|
1235
|
+
renderLogLinesWithMarkdownAnsi,
|
|
1020
1236
|
resolveAgentSelectionOnDown,
|
|
1021
1237
|
resolveHistoryDownTransition,
|
|
1022
1238
|
shouldClearAgentSelectionOnUp,
|