u-foo 2.5.14 → 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.
- package/package.json +1 -1
- package/src/agents/prompts/native/environment.js +20 -8
- package/src/code/agent.js +517 -112
- package/src/code/commands.js +77 -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 +703 -0
- package/src/code/context/executionSegment.js +292 -0
- package/src/code/context/index.js +28 -0
- package/src/code/context/planGraph.js +1410 -0
- package/src/code/context/planGraphService.js +857 -0
- package/src/code/context/planMode.js +398 -0
- package/src/code/context/planProjection.js +432 -0
- package/src/code/context/projectSnapshot.js +201 -0
- package/src/code/context/promptLayers.js +175 -0
- package/src/code/context/reducers.js +328 -0
- package/src/code/context/stableJson.js +29 -0
- package/src/code/context/stateCommit.js +414 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/transcript.js +182 -0
- package/src/code/context/transcriptSync.js +106 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/context/workingSet.js +323 -0
- package/src/code/dispatch.js +20 -1
- package/src/code/index.js +8 -0
- package/src/code/modelCommand.js +87 -0
- package/src/code/nativeRunner.js +625 -34
- package/src/code/repl.js +196 -50
- package/src/code/runtime/agentWakeup.js +58 -0
- package/src/code/runtime/graphOwner.js +41 -0
- package/src/code/runtime/graphYieldRouter.js +42 -0
- package/src/code/runtime/index.js +15 -0
- package/src/code/runtime/loopMailbox.js +124 -0
- package/src/code/runtime/runtimeEvents.js +39 -0
- package/src/code/runtime/taskControl.js +565 -0
- package/src/code/runtime/taskFocus.js +165 -0
- package/src/code/runtime/taskLoop.js +383 -0
- package/src/code/runtime/taskRun.js +187 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +208 -0
- package/src/code/sessionStore.js +217 -15
- package/src/code/skills/index.js +10 -0
- package/src/code/skills/injection.js +66 -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 +56 -2
- package/src/code/tools/artifactRead.js +40 -0
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/code/tui.js +2 -0
- package/src/code/usageStore.js +15 -0
- package/src/ui/format/index.js +285 -45
- package/src/ui/format/markdownRenderer.js +436 -71
- package/src/ui/ink/ChatApp.js +39 -8
- package/src/ui/ink/UcodeApp.js +592 -43
- 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
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { runAskUserTool } = require("../context/userInteraction");
|
|
4
|
+
|
|
5
|
+
function runAskUserToolDispatch(args = {}, options = {}) {
|
|
6
|
+
return runAskUserTool(args, options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
runAskUserTool: runAskUserToolDispatch,
|
|
11
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
runPlanGraphCommand,
|
|
5
|
+
normalizePlanGraphCommand,
|
|
6
|
+
} = require("../context/planGraphService");
|
|
7
|
+
|
|
8
|
+
function runPlanGraphTool(args = {}, options = {}) {
|
|
9
|
+
const command = normalizePlanGraphCommand(args) || args;
|
|
10
|
+
const result = runPlanGraphCommand(command, {
|
|
11
|
+
executionState: options.executionState,
|
|
12
|
+
runTool: options.runTool,
|
|
13
|
+
autoAdvance: options.autoAdvance !== false,
|
|
14
|
+
parallel: options.parallel !== false,
|
|
15
|
+
knownTools: options.knownTools,
|
|
16
|
+
maxNodeRuns: options.maxNodeRuns,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const payload = result.modelPayload || result;
|
|
20
|
+
return {
|
|
21
|
+
ok: payload.status === "accepted",
|
|
22
|
+
...payload,
|
|
23
|
+
executionState: result.executionState,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
runPlanGraphTool,
|
|
29
|
+
};
|
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;
|
|
@@ -128,7 +129,16 @@ function normalizeModelLabel(model = "") {
|
|
|
128
129
|
return "default";
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function buildUcodeBannerLines({
|
|
132
|
+
function buildUcodeBannerLines({
|
|
133
|
+
model = "",
|
|
134
|
+
engine = "ufoo-core",
|
|
135
|
+
nickname = "",
|
|
136
|
+
agentId = "",
|
|
137
|
+
workspaceRoot = "",
|
|
138
|
+
sessionId = "",
|
|
139
|
+
width = 0,
|
|
140
|
+
planMode = false,
|
|
141
|
+
} = {}) {
|
|
132
142
|
const modelLabel = normalizeModelLabel(model);
|
|
133
143
|
void width;
|
|
134
144
|
void engine;
|
|
@@ -150,6 +160,9 @@ function buildUcodeBannerLines({ model = "", engine = "ufoo-core", nickname = ""
|
|
|
150
160
|
const infoLines = [];
|
|
151
161
|
infoLines.push(`${chalk.dim("Version:")} ${chalk.cyan.bold(UCODE_VERSION)}`);
|
|
152
162
|
infoLines.push(`${chalk.dim("Model:")} ${chalk.yellow(modelLabel)}`);
|
|
163
|
+
if (planMode) {
|
|
164
|
+
infoLines.push(`${chalk.dim("Mode:")} ${chalk.magenta.bold("PLAN")}`);
|
|
165
|
+
}
|
|
153
166
|
infoLines.push(`${chalk.dim("Dictionary:")} ${chalk.gray(shortPath)}`);
|
|
154
167
|
const normalizedSessionId = String(sessionId || "").trim();
|
|
155
168
|
if (normalizedSessionId) {
|
|
@@ -248,6 +261,130 @@ function renderLogLinesWithMarkdown(text = "", state = {}, escapeFn = (value) =>
|
|
|
248
261
|
return renderMarkdownLines(text, state, escapeFn);
|
|
249
262
|
}
|
|
250
263
|
|
|
264
|
+
function renderLogLinesWithMarkdownAnsi(text = "", state = {}) {
|
|
265
|
+
const { renderMarkdownLinesAnsi } = require("./markdownRenderer");
|
|
266
|
+
return renderMarkdownLinesAnsi(text, state);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function createMarkdownTableBuffer() {
|
|
270
|
+
const { createMarkdownTableBuffer: create } = require("./markdownRenderer");
|
|
271
|
+
return create();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isTableRowLine(line = "") {
|
|
275
|
+
const { isTableRowLine: check } = require("./markdownRenderer");
|
|
276
|
+
return check(line);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function messageContentText(message = {}) {
|
|
280
|
+
if (!message || typeof message !== "object") return "";
|
|
281
|
+
const content = message.content;
|
|
282
|
+
if (typeof content === "string") return content;
|
|
283
|
+
if (Array.isArray(content)) {
|
|
284
|
+
return content.map((part) => {
|
|
285
|
+
if (typeof part === "string") return part;
|
|
286
|
+
if (part && typeof part === "object" && part.text != null) return String(part.text);
|
|
287
|
+
return "";
|
|
288
|
+
}).join("");
|
|
289
|
+
}
|
|
290
|
+
return "";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function toolMessagePreview(message = {}) {
|
|
294
|
+
const raw = messageContentText(message).trim();
|
|
295
|
+
if (!raw) return "";
|
|
296
|
+
try {
|
|
297
|
+
const parsed = JSON.parse(raw);
|
|
298
|
+
if (parsed && typeof parsed === "object") {
|
|
299
|
+
if (parsed.preview) return String(parsed.preview);
|
|
300
|
+
if (parsed.artifactId) return `artifact:${parsed.artifactId}`;
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
// plain tool text
|
|
304
|
+
}
|
|
305
|
+
return raw;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Convert persisted nlMessages into ucode TUI log rows for resume/history.
|
|
310
|
+
* Applies the shared ANSI markdown renderer so restored assistant text matches
|
|
311
|
+
* live streaming output.
|
|
312
|
+
*/
|
|
313
|
+
function buildUcodeSessionLogEntries(messages = [], options = {}) {
|
|
314
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
315
|
+
const markdownState = options.markdownState && typeof options.markdownState === "object"
|
|
316
|
+
? options.markdownState
|
|
317
|
+
: { inCodeBlock: false };
|
|
318
|
+
const idPrefix = String(options.idPrefix || "h");
|
|
319
|
+
let seq = Number.isFinite(options.startSeq) ? Math.max(0, Math.floor(options.startSeq)) : 0;
|
|
320
|
+
const maxToolPreviewLines = Number.isFinite(options.maxToolPreviewLines)
|
|
321
|
+
? Math.max(1, Math.floor(options.maxToolPreviewLines))
|
|
322
|
+
: 4;
|
|
323
|
+
const entries = [];
|
|
324
|
+
|
|
325
|
+
const pushLines = (text, kind) => {
|
|
326
|
+
const source = String(text == null ? "" : text);
|
|
327
|
+
let lines;
|
|
328
|
+
if (kind === "assistant" || kind === "error") {
|
|
329
|
+
try {
|
|
330
|
+
lines = renderLogLinesWithMarkdownAnsi(source, markdownState);
|
|
331
|
+
if (!Array.isArray(lines) || lines.length === 0) lines = source.split(/\r?\n/);
|
|
332
|
+
} catch {
|
|
333
|
+
lines = source.split(/\r?\n/);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
lines = source.split(/\r?\n/);
|
|
337
|
+
}
|
|
338
|
+
for (const line of lines) {
|
|
339
|
+
entries.push({
|
|
340
|
+
id: `${idPrefix}-${seq}`,
|
|
341
|
+
text: String(line || ""),
|
|
342
|
+
kind,
|
|
343
|
+
});
|
|
344
|
+
seq += 1;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
for (const message of list) {
|
|
349
|
+
if (!message || typeof message !== "object") continue;
|
|
350
|
+
const role = String(message.role || "").trim().toLowerCase();
|
|
351
|
+
if (role === "user") {
|
|
352
|
+
const text = messageContentText(message);
|
|
353
|
+
if (!text.trim()) continue;
|
|
354
|
+
const lines = text.split(/\r?\n/);
|
|
355
|
+
lines.forEach((line, index) => {
|
|
356
|
+
pushLines(index === 0 ? `› ${line}` : line, "user");
|
|
357
|
+
});
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (role === "assistant") {
|
|
361
|
+
const text = messageContentText(message);
|
|
362
|
+
if (text) pushLines(text, "assistant");
|
|
363
|
+
const calls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
364
|
+
if (calls.length > 0) {
|
|
365
|
+
const names = calls
|
|
366
|
+
.map((call) => {
|
|
367
|
+
if (!call || typeof call !== "object") return "";
|
|
368
|
+
if (call.function && call.function.name) return String(call.function.name);
|
|
369
|
+
return String(call.name || "");
|
|
370
|
+
})
|
|
371
|
+
.map((name) => name.trim())
|
|
372
|
+
.filter(Boolean);
|
|
373
|
+
if (names.length > 0) pushLines(`⚙ ${names.join(" · ")}`, "system");
|
|
374
|
+
}
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (role === "tool") {
|
|
378
|
+
const preview = toolMessagePreview(message);
|
|
379
|
+
if (!preview.trim()) continue;
|
|
380
|
+
const clipped = preview.split(/\r?\n/).slice(0, maxToolPreviewLines).join("\n");
|
|
381
|
+
pushLines(clipped, "toolDetail");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return { entries, nextSeq: seq, markdownState };
|
|
386
|
+
}
|
|
387
|
+
|
|
251
388
|
function shouldEnterAgentSelection(inputValue = "") {
|
|
252
389
|
const text = String(inputValue || "");
|
|
253
390
|
const trimmed = text.trim();
|
|
@@ -514,6 +651,39 @@ function normalizeBashToolCommand(args = {}, payload = {}) {
|
|
|
514
651
|
return [command, code].filter(Boolean).join(" · ");
|
|
515
652
|
}
|
|
516
653
|
|
|
654
|
+
function shortenPathDetail(value = "", maxChars = 72) {
|
|
655
|
+
const text = String(value || "").trim().replace(/\\/g, "/");
|
|
656
|
+
if (!text) return "";
|
|
657
|
+
const limit = Number.isFinite(maxChars) && maxChars > 8 ? Math.floor(maxChars) : 72;
|
|
658
|
+
if (text.length <= limit) return text;
|
|
659
|
+
return `…${text.slice(-(limit - 1))}`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function normalizeToolLogDetail(tool = "", args = {}, payload = {}) {
|
|
663
|
+
const name = String(tool || "").trim().toLowerCase();
|
|
664
|
+
const argObj = args && typeof args === "object" ? args : {};
|
|
665
|
+
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
666
|
+
|
|
667
|
+
if (name === "bash") return normalizeBashToolCommand(argObj, resObj);
|
|
668
|
+
|
|
669
|
+
if (name === "read" || name === "write" || name === "edit") {
|
|
670
|
+
const pathText = String(argObj.path || resObj.path || "").trim();
|
|
671
|
+
return shortenPathDetail(pathText);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (name === "artifact_read") {
|
|
675
|
+
const artifactId = String(argObj.artifactId || argObj.id || resObj.artifactId || "").trim();
|
|
676
|
+
const rangeBits = [];
|
|
677
|
+
if (argObj.startLine != null) rangeBits.push(`L${argObj.startLine}`);
|
|
678
|
+
if (argObj.endLine != null) rangeBits.push(`L${argObj.endLine}`);
|
|
679
|
+
const range = rangeBits.length > 0 ? rangeBits.join("-") : "";
|
|
680
|
+
return [artifactId, range].filter(Boolean).join(" · ");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const fallback = String(argObj.path || argObj.command || argObj.cmd || "").trim();
|
|
684
|
+
return shortenPathDetail(fallback);
|
|
685
|
+
}
|
|
686
|
+
|
|
517
687
|
function normalizeToolMergeEntry(entry = {}) {
|
|
518
688
|
const source = entry && typeof entry === "object" ? entry : {};
|
|
519
689
|
const tool = String(source.tool || "").trim().toLowerCase() || "tool";
|
|
@@ -822,6 +992,7 @@ function buildCompletions({
|
|
|
822
992
|
commandTree = null,
|
|
823
993
|
groupTemplates = [],
|
|
824
994
|
soloProfiles = [],
|
|
995
|
+
argumentLists = null,
|
|
825
996
|
limit = 8,
|
|
826
997
|
} = {}) {
|
|
827
998
|
const raw = String(text || "");
|
|
@@ -830,9 +1001,14 @@ function buildCompletions({
|
|
|
830
1001
|
const endsWithWhitespace = /\s$/.test(trimmed);
|
|
831
1002
|
|
|
832
1003
|
if (trimmed.startsWith("/")) {
|
|
833
|
-
const
|
|
834
|
-
const head =
|
|
835
|
-
const tail =
|
|
1004
|
+
const tokenParts = trimmed.trimEnd().split(/\s+/).filter(Boolean);
|
|
1005
|
+
const head = tokenParts[0] || ""; // "/launch"
|
|
1006
|
+
const tail = tokenParts.slice(1);
|
|
1007
|
+
const headKey = head.startsWith("/") ? head : `/${head}`;
|
|
1008
|
+
const headNode = commandTree && typeof commandTree === "object" ? commandTree[headKey] : null;
|
|
1009
|
+
const argListForHead = argumentLists && typeof argumentLists === "object"
|
|
1010
|
+
? argumentLists[headKey]
|
|
1011
|
+
: null;
|
|
836
1012
|
|
|
837
1013
|
// Dynamic argument completion for /group run <alias> and
|
|
838
1014
|
// /solo run <profile>. These pull from runtime sources (group
|
|
@@ -842,7 +1018,7 @@ function buildCompletions({
|
|
|
842
1018
|
: (head === "/solo" && tail[0] === "run")
|
|
843
1019
|
? soloProfiles
|
|
844
1020
|
: null;
|
|
845
|
-
if (dynList && (tail.length >= 2 ||
|
|
1021
|
+
if (dynList && (tail.length >= 2 || (tail[0] === "run" && endsWithWhitespace))) {
|
|
846
1022
|
const partial = String(tail[1] || "").toLowerCase();
|
|
847
1023
|
const out = [];
|
|
848
1024
|
for (const item of (Array.isArray(dynList) ? dynList : [])) {
|
|
@@ -867,49 +1043,91 @@ function buildCompletions({
|
|
|
867
1043
|
}
|
|
868
1044
|
|
|
869
1045
|
// 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
|
-
|
|
1046
|
+
if ((tail.length >= 1 || (endsWithWhitespace && headNode && headNode.children)) && commandTree) {
|
|
1047
|
+
let node = headNode;
|
|
1048
|
+
if (!node || typeof node !== "object") {
|
|
1049
|
+
// fall through
|
|
1050
|
+
} else if (node.children && typeof node.children === "object") {
|
|
1051
|
+
const walkTail = endsWithWhitespace && tail.length === 0
|
|
1052
|
+
? []
|
|
1053
|
+
: (endsWithWhitespace ? tail : tail.slice(0, -1));
|
|
1054
|
+
const partial = endsWithWhitespace && tail.length === 0
|
|
1055
|
+
? ""
|
|
1056
|
+
: (endsWithWhitespace ? "" : String(tail[tail.length - 1] || "").toLowerCase());
|
|
1057
|
+
let walkNode = node;
|
|
1058
|
+
let walkOk = true;
|
|
1059
|
+
for (const segment of walkTail) {
|
|
1060
|
+
const next = walkNode.children && walkNode.children[segment];
|
|
1061
|
+
if (!next) {
|
|
1062
|
+
walkOk = false;
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
walkNode = next;
|
|
1066
|
+
}
|
|
1067
|
+
const children = walkOk ? (walkNode && walkNode.children) : null;
|
|
1068
|
+
if (children && typeof children === "object") {
|
|
1069
|
+
const prefixSoFar = walkTail.length > 0
|
|
1070
|
+
? `${head} ${walkTail.join(" ")}`
|
|
1071
|
+
: head;
|
|
1072
|
+
const entries = Object.keys(children).map((name) => ({
|
|
1073
|
+
name,
|
|
1074
|
+
...children[name],
|
|
1075
|
+
}));
|
|
1076
|
+
entries.sort((a, b) => {
|
|
1077
|
+
const orderA = Number.isFinite(a.order) ? a.order : 999;
|
|
1078
|
+
const orderB = Number.isFinite(b.order) ? b.order : 999;
|
|
1079
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1080
|
+
return a.name.localeCompare(b.name);
|
|
1081
|
+
});
|
|
1082
|
+
const out = [];
|
|
1083
|
+
for (const entry of entries) {
|
|
1084
|
+
if (partial && !entry.name.toLowerCase().startsWith(partial)) continue;
|
|
1085
|
+
const hasDynamicArguments = (head === "/group" && entry.name === "run")
|
|
1086
|
+
|| (head === "/solo" && entry.name === "run")
|
|
1087
|
+
|| Boolean(entry.hasArguments);
|
|
1088
|
+
out.push({
|
|
1089
|
+
kind: "subcommand",
|
|
1090
|
+
label: `${prefixSoFar} ${entry.name}`.trim(),
|
|
1091
|
+
replace: `${prefixSoFar} ${entry.name} `.replace(/^\s+/, ""),
|
|
1092
|
+
description: String(entry.desc || entry.summary || entry.description || ""),
|
|
1093
|
+
hasChildren: Boolean((entry.children && typeof entry.children === "object") || hasDynamicArguments),
|
|
1094
|
+
});
|
|
1095
|
+
if (out.length >= limit) break;
|
|
1096
|
+
}
|
|
1097
|
+
if (!endsWithWhitespace && out.length === 1) {
|
|
1098
|
+
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
1099
|
+
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
1100
|
+
}
|
|
1101
|
+
return out;
|
|
1102
|
+
}
|
|
881
1103
|
}
|
|
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
|
-
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Generic top-level argument lists (e.g. /resume <session-id>).
|
|
1107
|
+
if (
|
|
1108
|
+
Array.isArray(argListForHead)
|
|
1109
|
+
&& argListForHead.length > 0
|
|
1110
|
+
&& !(headNode && headNode.children)
|
|
1111
|
+
&& (endsWithWhitespace || tail.length >= 1)
|
|
1112
|
+
) {
|
|
1113
|
+
if (tail.length > 1) return [];
|
|
1114
|
+
const partial = String(tail[0] || "").toLowerCase();
|
|
898
1115
|
const out = [];
|
|
899
|
-
for (const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1116
|
+
for (const item of argListForHead) {
|
|
1117
|
+
const id = String((item && (item.alias || item.cmd || item.id || item.name)) || item || "");
|
|
1118
|
+
if (!id) continue;
|
|
1119
|
+
if (partial && !id.toLowerCase().startsWith(partial)) continue;
|
|
1120
|
+
const desc = String((item && (item.desc || item.summary || item.description || item.source)) || "");
|
|
903
1121
|
out.push({
|
|
904
|
-
kind: "
|
|
905
|
-
label: `${
|
|
906
|
-
replace: `${
|
|
907
|
-
description:
|
|
908
|
-
hasChildren:
|
|
1122
|
+
kind: "argument",
|
|
1123
|
+
label: `${head} ${id}`,
|
|
1124
|
+
replace: `${head} ${id} `,
|
|
1125
|
+
description: desc,
|
|
1126
|
+
hasChildren: false,
|
|
909
1127
|
});
|
|
910
1128
|
if (out.length >= limit) break;
|
|
911
1129
|
}
|
|
912
|
-
if (
|
|
1130
|
+
if (partial && out.length === 1) {
|
|
913
1131
|
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
914
1132
|
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
915
1133
|
}
|
|
@@ -920,8 +1138,16 @@ function buildCompletions({
|
|
|
920
1138
|
const after = trimmed.slice(1);
|
|
921
1139
|
const prefix = after.toLowerCase();
|
|
922
1140
|
const list = Array.isArray(commands) ? commands : [];
|
|
1141
|
+
const sorted = list.slice().sort((a, b) => {
|
|
1142
|
+
const orderA = Number.isFinite(a && a.order) ? a.order : 999;
|
|
1143
|
+
const orderB = Number.isFinite(b && b.order) ? b.order : 999;
|
|
1144
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1145
|
+
const nameA = String((a && a.cmd) || a || "");
|
|
1146
|
+
const nameB = String((b && b.cmd) || b || "");
|
|
1147
|
+
return nameA.localeCompare(nameB);
|
|
1148
|
+
});
|
|
923
1149
|
const out = [];
|
|
924
|
-
for (const item of
|
|
1150
|
+
for (const item of sorted) {
|
|
925
1151
|
// Registry entries already include the leading '/' in `cmd`. Strip
|
|
926
1152
|
// it before matching the user's prefix and put it back when we
|
|
927
1153
|
// render so we don't end up with '//cron'.
|
|
@@ -930,18 +1156,27 @@ function buildCompletions({
|
|
|
930
1156
|
const lower = bare.toLowerCase();
|
|
931
1157
|
if (!bare) continue;
|
|
932
1158
|
if (!lower.startsWith(prefix)) continue;
|
|
1159
|
+
const node = commandTree && commandTree[`/${bare}`];
|
|
1160
|
+
const hasChildren = Boolean(
|
|
1161
|
+
(node && node.children && typeof node.children === "object")
|
|
1162
|
+
|| (node && node.hasArguments)
|
|
1163
|
+
|| (argumentLists && Array.isArray(argumentLists[`/${bare}`]) && argumentLists[`/${bare}`].length > 0),
|
|
1164
|
+
);
|
|
933
1165
|
out.push({
|
|
934
1166
|
kind: "command",
|
|
935
1167
|
label: `/${bare}`,
|
|
936
1168
|
replace: `/${bare} `,
|
|
937
1169
|
description: String((item && (item.desc || item.summary || item.description)) || ""),
|
|
938
|
-
hasChildren
|
|
1170
|
+
hasChildren,
|
|
1171
|
+
optionalArguments: Boolean(node && node.optionalArguments),
|
|
939
1172
|
});
|
|
940
1173
|
if (out.length >= limit) break;
|
|
941
1174
|
}
|
|
942
1175
|
if (!endsWithWhitespace && out.length === 1) {
|
|
943
1176
|
const candidate = String(out[0].replace || "").trim().replace(/^\//, "").toLowerCase();
|
|
944
|
-
|
|
1177
|
+
// Exact bare command: close the popup so Enter submits. Commands with
|
|
1178
|
+
// optionalArguments (e.g. /model) are valid both bare and with an arg.
|
|
1179
|
+
if (candidate === prefix && (!out[0].hasChildren || out[0].optionalArguments)) return [];
|
|
945
1180
|
}
|
|
946
1181
|
return out;
|
|
947
1182
|
}
|
|
@@ -994,6 +1229,7 @@ module.exports = {
|
|
|
994
1229
|
buildToolMergeRowText,
|
|
995
1230
|
buildCompletions,
|
|
996
1231
|
buildUcodeBannerLines,
|
|
1232
|
+
buildUcodeSessionLogEntries,
|
|
997
1233
|
charDisplayWidth,
|
|
998
1234
|
clampCursorPos,
|
|
999
1235
|
createEscapeTagStripper,
|
|
@@ -1012,11 +1248,15 @@ module.exports = {
|
|
|
1012
1248
|
moveCursorVertically,
|
|
1013
1249
|
normalizeBashToolCommand,
|
|
1014
1250
|
normalizeModelLabel,
|
|
1251
|
+
normalizeToolLogDetail,
|
|
1015
1252
|
normalizeToolMergeEntry,
|
|
1016
1253
|
parseActiveAgentsFromBusStatus,
|
|
1017
1254
|
planAgentsFooter,
|
|
1018
1255
|
planProjectsRail,
|
|
1019
1256
|
renderLogLinesWithMarkdown,
|
|
1257
|
+
renderLogLinesWithMarkdownAnsi,
|
|
1258
|
+
createMarkdownTableBuffer,
|
|
1259
|
+
isTableRowLine,
|
|
1020
1260
|
resolveAgentSelectionOnDown,
|
|
1021
1261
|
resolveHistoryDownTransition,
|
|
1022
1262
|
shouldClearAgentSelectionOnUp,
|