u-foo 3.0.1 → 3.0.2
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/tasks.js +4 -1
- package/src/app/chat/commandExecutor.js +111 -1
- package/src/app/chat/commands.js +2 -1
- package/src/app/chat/daemonMessageRouter.js +1 -1
- package/src/app/chat/inputSubmitHandler.js +3 -2
- package/src/code/commands.js +3 -3
- package/src/code/context/promptLayers.js +10 -9
- package/src/code/dispatch.js +4 -0
- package/src/code/modelCommand.js +199 -23
- package/src/code/nativeRunner.js +157 -19
- package/src/code/protocol/protocolValidator.js +3 -3
- package/src/code/providers/index.js +1 -0
- package/src/code/providers/modelsCatalog.js +304 -0
- package/src/code/repl.js +37 -8
- package/src/code/runtime/taskControl.js +177 -53
- package/src/code/runtime/taskFocus.js +30 -10
- package/src/code/runtime/taskLoop.js +12 -1
- package/src/code/runtime/taskRun.js +10 -1
- package/src/code/thinkingLevels.js +132 -0
- package/src/code/tools/taskRun.js +118 -0
- package/src/config.js +10 -1
- package/src/ui/format/index.js +48 -3
- package/src/ui/ink/ChatApp.js +137 -25
- package/src/ui/ink/UcodeApp.js +28 -3
- package/src/ui/ink/chatLogModel.js +238 -32
- package/src/ui/ink/chatReducer.js +18 -6
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
startStandaloneTask,
|
|
5
|
+
cancelTask,
|
|
6
|
+
failTask,
|
|
7
|
+
completeTaskFromLoop,
|
|
8
|
+
} = require("../runtime/taskControl");
|
|
9
|
+
const { getTaskRun } = require("../runtime/taskRun");
|
|
10
|
+
const { emptyExecutionState } = require("../context/executionSegment");
|
|
11
|
+
|
|
12
|
+
function normalizeTaskRunCommand(args = {}) {
|
|
13
|
+
if (!args || typeof args !== "object") return null;
|
|
14
|
+
const operation = String(args.operation || args.op || "").trim().toLowerCase();
|
|
15
|
+
if (!operation) return null;
|
|
16
|
+
return {
|
|
17
|
+
operation,
|
|
18
|
+
objective: args.objective,
|
|
19
|
+
title: args.title,
|
|
20
|
+
taskRunId: args.taskRunId || args.task_run_id,
|
|
21
|
+
nodeId: args.nodeId || args.node_id,
|
|
22
|
+
reason: args.reason,
|
|
23
|
+
result: args.result,
|
|
24
|
+
commandId: args.commandId || args.command_id,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runTaskRunTool(args = {}, options = {}) {
|
|
29
|
+
const command = normalizeTaskRunCommand(args) || args;
|
|
30
|
+
const operation = String(command.operation || "").trim().toLowerCase();
|
|
31
|
+
const executionState = options.executionState && typeof options.executionState === "object"
|
|
32
|
+
? options.executionState
|
|
33
|
+
: emptyExecutionState();
|
|
34
|
+
const commandId = String(command.commandId || "").trim();
|
|
35
|
+
const runTool = options.runTool || null;
|
|
36
|
+
const knownTools = options.knownTools || null;
|
|
37
|
+
|
|
38
|
+
let payload;
|
|
39
|
+
if (operation === "start") {
|
|
40
|
+
payload = startStandaloneTask(executionState, {
|
|
41
|
+
objective: command.objective,
|
|
42
|
+
title: command.title,
|
|
43
|
+
commandId,
|
|
44
|
+
runTool,
|
|
45
|
+
knownTools,
|
|
46
|
+
processImmediately: options.processImmediately !== false,
|
|
47
|
+
});
|
|
48
|
+
} else if (operation === "cancel") {
|
|
49
|
+
payload = cancelTask(executionState, {
|
|
50
|
+
taskRunId: command.taskRunId,
|
|
51
|
+
nodeId: command.nodeId,
|
|
52
|
+
reason: command.reason,
|
|
53
|
+
commandId,
|
|
54
|
+
});
|
|
55
|
+
} else if (operation === "fail") {
|
|
56
|
+
payload = failTask(executionState, {
|
|
57
|
+
taskRunId: command.taskRunId,
|
|
58
|
+
nodeId: command.nodeId,
|
|
59
|
+
reason: command.reason,
|
|
60
|
+
commandId,
|
|
61
|
+
});
|
|
62
|
+
} else if (operation === "complete") {
|
|
63
|
+
payload = completeTaskFromLoop(executionState, {
|
|
64
|
+
taskRunId: command.taskRunId,
|
|
65
|
+
result: command.result,
|
|
66
|
+
commandId,
|
|
67
|
+
});
|
|
68
|
+
} else if (operation === "inspect") {
|
|
69
|
+
const run = getTaskRun(executionState, command.taskRunId);
|
|
70
|
+
if (!run) {
|
|
71
|
+
payload = {
|
|
72
|
+
status: "rejected",
|
|
73
|
+
ok: false,
|
|
74
|
+
errors: [{ code: "TASK_RUN_NOT_FOUND", message: "task run missing" }],
|
|
75
|
+
};
|
|
76
|
+
} else {
|
|
77
|
+
payload = {
|
|
78
|
+
status: "accepted",
|
|
79
|
+
ok: true,
|
|
80
|
+
taskRun: {
|
|
81
|
+
id: run.id,
|
|
82
|
+
kind: run.kind || "",
|
|
83
|
+
status: run.status,
|
|
84
|
+
phase: run.phase,
|
|
85
|
+
objective: run.objective || "",
|
|
86
|
+
title: run.title || "",
|
|
87
|
+
parentGraphId: run.parentGraphId || "",
|
|
88
|
+
parentNodeId: run.parentNodeId || "",
|
|
89
|
+
childGraphId: run.childGraphId || "",
|
|
90
|
+
result: run.result,
|
|
91
|
+
error: run.error,
|
|
92
|
+
changedFiles: Array.isArray(run.changedFiles) ? run.changedFiles.slice() : [],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
payload = {
|
|
98
|
+
status: "rejected",
|
|
99
|
+
ok: false,
|
|
100
|
+
errors: [{
|
|
101
|
+
code: "UNKNOWN_TASK_RUN_OP",
|
|
102
|
+
message: `unknown task_run operation: ${operation || "(empty)"}`,
|
|
103
|
+
}],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const ok = payload && payload.ok !== false && payload.status !== "rejected";
|
|
108
|
+
return {
|
|
109
|
+
ok,
|
|
110
|
+
...payload,
|
|
111
|
+
executionState,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
normalizeTaskRunCommand,
|
|
117
|
+
runTaskRunTool,
|
|
118
|
+
};
|
package/src/config.js
CHANGED
|
@@ -2,7 +2,14 @@ const fs = require("fs");
|
|
|
2
2
|
const os = require("os");
|
|
3
3
|
const path = require("path");
|
|
4
4
|
|
|
5
|
-
const UCODE_FIELDS = [
|
|
5
|
+
const UCODE_FIELDS = [
|
|
6
|
+
"ucodeProvider",
|
|
7
|
+
"ucodeModel",
|
|
8
|
+
"ucodeBaseUrl",
|
|
9
|
+
"ucodeApiKey",
|
|
10
|
+
"ucodeAgentDir",
|
|
11
|
+
"ucodeThinking",
|
|
12
|
+
];
|
|
6
13
|
|
|
7
14
|
const SETTINGS_MODEL_DEFAULTS = Object.freeze({
|
|
8
15
|
agent: Object.freeze({
|
|
@@ -47,6 +54,7 @@ const DEFAULT_UCODE_CONFIG = {
|
|
|
47
54
|
ucodeBaseUrl: "",
|
|
48
55
|
ucodeApiKey: "",
|
|
49
56
|
ucodeAgentDir: "",
|
|
57
|
+
ucodeThinking: "",
|
|
50
58
|
};
|
|
51
59
|
|
|
52
60
|
function normalizeLaunchMode(value) {
|
|
@@ -251,6 +259,7 @@ function loadGlobalUcodeConfig() {
|
|
|
251
259
|
ucodeBaseUrl: typeof raw.ucodeBaseUrl === "string" ? raw.ucodeBaseUrl : "",
|
|
252
260
|
ucodeApiKey: typeof raw.ucodeApiKey === "string" ? raw.ucodeApiKey : "",
|
|
253
261
|
ucodeAgentDir: typeof raw.ucodeAgentDir === "string" ? raw.ucodeAgentDir : "",
|
|
262
|
+
ucodeThinking: typeof raw.ucodeThinking === "string" ? raw.ucodeThinking : "",
|
|
254
263
|
};
|
|
255
264
|
}
|
|
256
265
|
|
package/src/ui/format/index.js
CHANGED
|
@@ -1103,13 +1103,52 @@ function buildCompletions({
|
|
|
1103
1103
|
}
|
|
1104
1104
|
}
|
|
1105
1105
|
|
|
1106
|
-
// Generic top-level argument lists (e.g. /resume <session-id
|
|
1106
|
+
// Generic top-level argument lists (e.g. /resume <session-id>,
|
|
1107
|
+
// /model <id> [thinking]). /model supports a secondary intensity menu.
|
|
1107
1108
|
if (
|
|
1108
1109
|
Array.isArray(argListForHead)
|
|
1109
1110
|
&& argListForHead.length > 0
|
|
1110
1111
|
&& !(headNode && headNode.children)
|
|
1111
1112
|
&& (endsWithWhitespace || tail.length >= 1)
|
|
1112
1113
|
) {
|
|
1114
|
+
const secondaryList = argumentLists && typeof argumentLists === "object"
|
|
1115
|
+
? argumentLists[`${headKey}/thinking`] || argumentLists[`${headKey}/think`]
|
|
1116
|
+
: null;
|
|
1117
|
+
const supportsThinkingMenu = head === "/model" && Array.isArray(secondaryList) && secondaryList.length > 0;
|
|
1118
|
+
|
|
1119
|
+
// Secondary menu: "/model <id> " or "/model <id> <partial>"
|
|
1120
|
+
if (supportsThinkingMenu && (
|
|
1121
|
+
(tail.length === 1 && endsWithWhitespace)
|
|
1122
|
+
|| tail.length >= 2
|
|
1123
|
+
)) {
|
|
1124
|
+
if (tail.length > 2) return [];
|
|
1125
|
+
const modelId = String(tail[0] || "").trim();
|
|
1126
|
+
if (!modelId) return [];
|
|
1127
|
+
const partial = tail.length >= 2 && !endsWithWhitespace
|
|
1128
|
+
? String(tail[1] || "").toLowerCase()
|
|
1129
|
+
: "";
|
|
1130
|
+
const out = [];
|
|
1131
|
+
for (const item of secondaryList) {
|
|
1132
|
+
const id = String((item && (item.alias || item.cmd || item.id || item.name)) || item || "");
|
|
1133
|
+
if (!id) continue;
|
|
1134
|
+
if (partial && !id.toLowerCase().startsWith(partial)) continue;
|
|
1135
|
+
const desc = String((item && (item.desc || item.summary || item.description || item.source)) || "");
|
|
1136
|
+
out.push({
|
|
1137
|
+
kind: "argument",
|
|
1138
|
+
label: `${head} ${modelId} ${id}`,
|
|
1139
|
+
replace: `${head} ${modelId} ${id}`,
|
|
1140
|
+
description: desc,
|
|
1141
|
+
hasChildren: false,
|
|
1142
|
+
});
|
|
1143
|
+
if (out.length >= limit) break;
|
|
1144
|
+
}
|
|
1145
|
+
if (partial && out.length === 1) {
|
|
1146
|
+
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
1147
|
+
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
1148
|
+
}
|
|
1149
|
+
return out;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1113
1152
|
if (tail.length > 1) return [];
|
|
1114
1153
|
const partial = String(tail[0] || "").toLowerCase();
|
|
1115
1154
|
const out = [];
|
|
@@ -1118,17 +1157,23 @@ function buildCompletions({
|
|
|
1118
1157
|
if (!id) continue;
|
|
1119
1158
|
if (partial && !id.toLowerCase().startsWith(partial)) continue;
|
|
1120
1159
|
const desc = String((item && (item.desc || item.summary || item.description || item.source)) || "");
|
|
1160
|
+
const hasChildren = Boolean(
|
|
1161
|
+
(item && item.hasChildren)
|
|
1162
|
+
|| supportsThinkingMenu
|
|
1163
|
+
);
|
|
1121
1164
|
out.push({
|
|
1122
1165
|
kind: "argument",
|
|
1123
1166
|
label: `${head} ${id}`,
|
|
1124
|
-
|
|
1167
|
+
// Trailing space keeps the popup open for the thinking submenu.
|
|
1168
|
+
replace: hasChildren ? `${head} ${id} ` : `${head} ${id} `,
|
|
1125
1169
|
description: desc,
|
|
1126
|
-
hasChildren
|
|
1170
|
+
hasChildren,
|
|
1127
1171
|
});
|
|
1128
1172
|
if (out.length >= limit) break;
|
|
1129
1173
|
}
|
|
1130
1174
|
if (partial && out.length === 1) {
|
|
1131
1175
|
const candidate = String(out[0].replace || "").trim().split(/\s+/).pop() || "";
|
|
1176
|
+
// Keep the popup open when the sole match still has a submenu.
|
|
1132
1177
|
if (candidate.toLowerCase() === partial && !out[0].hasChildren) return [];
|
|
1133
1178
|
}
|
|
1134
1179
|
return out;
|
package/src/ui/ink/ChatApp.js
CHANGED
|
@@ -165,38 +165,58 @@ function loadChatHistory(projectRoot, cap = 200, options = {}) {
|
|
|
165
165
|
const raw = fs.readFileSync(file, "utf8");
|
|
166
166
|
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
167
167
|
const out = [];
|
|
168
|
-
const pushLine = (line = "") => {
|
|
168
|
+
const pushLine = (line = "", sourceType = "") => {
|
|
169
169
|
const value = String(line || "");
|
|
170
170
|
if (!value.trim()) {
|
|
171
|
-
if (out.length > 0
|
|
171
|
+
if (out.length > 0) {
|
|
172
|
+
const last = out[out.length - 1];
|
|
173
|
+
const lastText = typeof last === "object" ? last.text : last;
|
|
174
|
+
if (lastText !== "") out.push({ text: "", sourceType: sourceType || "system" });
|
|
175
|
+
}
|
|
172
176
|
return;
|
|
173
177
|
}
|
|
174
|
-
out.push(value);
|
|
178
|
+
out.push(sourceType ? { text: value, sourceType } : value);
|
|
175
179
|
};
|
|
176
180
|
for (const line of lines) {
|
|
177
181
|
try {
|
|
178
182
|
const entry = JSON.parse(line);
|
|
179
183
|
if (!entry) continue;
|
|
180
184
|
if (entry.type === "spacer") {
|
|
181
|
-
pushLine("");
|
|
185
|
+
pushLine("", "system");
|
|
182
186
|
continue;
|
|
183
187
|
}
|
|
184
188
|
const text = String(entry.text || "");
|
|
185
189
|
if (!text) continue;
|
|
190
|
+
const sourceType = String(entry.type || "");
|
|
186
191
|
// Strip blessed-tag markup that the legacy log writer used; ink
|
|
187
192
|
// can't render those tags and we don't want them shown literally.
|
|
188
193
|
const stripped = text.replace(/\{[^{}]+\}/g, "");
|
|
189
194
|
for (const renderedLine of normalizeInkLogLines(stripped)) {
|
|
190
|
-
pushLine(renderedLine);
|
|
195
|
+
pushLine(renderedLine, sourceType);
|
|
191
196
|
}
|
|
192
197
|
} catch {
|
|
193
198
|
// ignore malformed lines
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
|
-
while (out.length > 0
|
|
197
|
-
|
|
201
|
+
while (out.length > 0) {
|
|
202
|
+
const first = out[0];
|
|
203
|
+
const firstText = typeof first === "object" ? first.text : first;
|
|
204
|
+
if (firstText !== "") break;
|
|
205
|
+
out.shift();
|
|
206
|
+
}
|
|
207
|
+
while (out.length > 0) {
|
|
208
|
+
const last = out[out.length - 1];
|
|
209
|
+
const lastText = typeof last === "object" ? last.text : last;
|
|
210
|
+
if (lastText !== "") break;
|
|
211
|
+
out.pop();
|
|
212
|
+
}
|
|
198
213
|
const capped = out.slice(-cap);
|
|
199
|
-
while (capped.length > 0
|
|
214
|
+
while (capped.length > 0) {
|
|
215
|
+
const first = capped[0];
|
|
216
|
+
const firstText = typeof first === "object" ? first.text : first;
|
|
217
|
+
if (firstText !== "") break;
|
|
218
|
+
capped.shift();
|
|
219
|
+
}
|
|
200
220
|
return capped;
|
|
201
221
|
} catch {
|
|
202
222
|
return [];
|
|
@@ -435,12 +455,26 @@ function createThrottledSender(send, windowMs = 500) {
|
|
|
435
455
|
// Kinds whose log entries render as a margin-bottom "transcript cell" in
|
|
436
456
|
// buildChatLogGroups. Kept in sync with canAppendToChatLogGroup in
|
|
437
457
|
// chatLogModel.js.
|
|
438
|
-
const STATIC_GROUPABLE_KINDS = new Set([
|
|
458
|
+
const STATIC_GROUPABLE_KINDS = new Set([
|
|
459
|
+
"assistant",
|
|
460
|
+
"agent",
|
|
461
|
+
"report",
|
|
462
|
+
"success",
|
|
463
|
+
"error",
|
|
464
|
+
"meta",
|
|
465
|
+
"system",
|
|
466
|
+
"plain",
|
|
467
|
+
]);
|
|
439
468
|
|
|
440
469
|
// Shared row colors for both the dynamic (stream) and <Static> renderers.
|
|
470
|
+
// Aligned with ucode LOG_LINE_TEXT_PROPS: user green+bold, system dim gray,
|
|
471
|
+
// team bus/agent cyan, ufoo assistant white/bold marker.
|
|
441
472
|
const CHAT_LOG_ROW_PALETTE = {
|
|
473
|
+
user: { marker: "green", speaker: "green", body: "green", bold: true },
|
|
442
474
|
assistant: { marker: "cyan", speaker: "white", body: undefined, bold: true },
|
|
443
475
|
agent: { marker: "cyan", speaker: "cyan", body: undefined, bold: false },
|
|
476
|
+
report: { marker: "yellow", speaker: "yellow", body: undefined, bold: false },
|
|
477
|
+
system: { marker: "gray", speaker: "gray", body: "gray", bold: false, dim: true },
|
|
444
478
|
error: { marker: "red", speaker: "red", body: "red", bold: true },
|
|
445
479
|
success: { marker: "green", speaker: "green", body: "green", bold: false },
|
|
446
480
|
divider: { marker: "gray", speaker: "gray", body: "gray", bold: false },
|
|
@@ -460,21 +494,39 @@ function decorateStaticLogEntry(prev, entry) {
|
|
|
460
494
|
const markdownState = prev && prev.markdownState && typeof prev.markdownState === "object"
|
|
461
495
|
? { inCodeBlock: Boolean(prev.markdownState.inCodeBlock) }
|
|
462
496
|
: { inCodeBlock: false };
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
497
|
+
const source = entry && typeof entry === "object" ? entry : { text: entry };
|
|
498
|
+
const sourceText = source.text != null ? String(source.text) : String(entry || "");
|
|
499
|
+
const sourceType = String(source.sourceType || source.type || "");
|
|
500
|
+
const meta = source.meta && typeof source.meta === "object" ? source.meta : {};
|
|
501
|
+
const row = buildChatLogLineModel({
|
|
502
|
+
...source,
|
|
503
|
+
text: sourceText,
|
|
504
|
+
sourceType,
|
|
505
|
+
meta,
|
|
506
|
+
}, { markdownState, sourceType, meta });
|
|
467
507
|
const continuation = Boolean(
|
|
468
508
|
prev
|
|
469
|
-
&& (
|
|
470
|
-
|
|
509
|
+
&& (
|
|
510
|
+
((row.kind === "plain" || row.kind === "spacer") && STATIC_GROUPABLE_KINDS.has(prev.groupKind))
|
|
511
|
+
|| (prev.groupKind === "user" && row.kind === "user" && row.marker !== "›")
|
|
512
|
+
)
|
|
471
513
|
);
|
|
472
514
|
const groupKind = continuation ? prev.groupKind : row.kind;
|
|
473
515
|
// A gap belongs between visual blocks: only on entries that START a new
|
|
474
516
|
// block, and only when the previous block was a transcript group (whose
|
|
475
517
|
// old dynamic renderer contributed a trailing marginBottom).
|
|
476
|
-
|
|
477
|
-
|
|
518
|
+
// User turns also get a leading gap so › prompts don't sit flush against
|
|
519
|
+
// the previous transcript cell (ucode parity).
|
|
520
|
+
const marginBefore = Boolean(
|
|
521
|
+
!continuation
|
|
522
|
+
&& prev
|
|
523
|
+
&& (
|
|
524
|
+
STATIC_GROUPABLE_KINDS.has(prev.groupKind)
|
|
525
|
+
|| prev.groupKind === "user"
|
|
526
|
+
|| row.kind === "user"
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
return { entry: source, row, groupKind, continuation, marginBefore, markdownState };
|
|
478
530
|
}
|
|
479
531
|
|
|
480
532
|
function createInkStreamState({
|
|
@@ -1315,7 +1367,15 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1315
1367
|
}
|
|
1316
1368
|
const lines = normalizeInkLogLines(text);
|
|
1317
1369
|
if (lines.length === 0) return;
|
|
1318
|
-
|
|
1370
|
+
const payload = lines.map((line, index) => ({
|
|
1371
|
+
text: line,
|
|
1372
|
+
type,
|
|
1373
|
+
sourceType: type,
|
|
1374
|
+
// Attach router meta only on the first physical line so multi-line
|
|
1375
|
+
// bus/reply bodies don't duplicate publisher payloads.
|
|
1376
|
+
meta: index === 0 && meta && typeof meta === "object" ? meta : {},
|
|
1377
|
+
}));
|
|
1378
|
+
dispatch({ type: "log/appendMany", lines: payload });
|
|
1319
1379
|
appendScopedHistory(type, stripBlessedTags(text), meta);
|
|
1320
1380
|
}, [appendScopedHistory, setStatusText]);
|
|
1321
1381
|
|
|
@@ -3505,6 +3565,8 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
3505
3565
|
return buildChatLogGroups(lines.map((line, idx) => ({
|
|
3506
3566
|
id: `s-${idx}`,
|
|
3507
3567
|
text: idx === 0 ? `${prefix}${line}` : ` ${line}`,
|
|
3568
|
+
sourceType: "bus",
|
|
3569
|
+
type: "bus",
|
|
3508
3570
|
})));
|
|
3509
3571
|
}, [state.activeStream]);
|
|
3510
3572
|
|
|
@@ -3512,6 +3574,18 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
3512
3574
|
return null;
|
|
3513
3575
|
}
|
|
3514
3576
|
|
|
3577
|
+
const renderUserLogBody = (bodyText = "") => {
|
|
3578
|
+
const body = String(bodyText || "");
|
|
3579
|
+
const atMatch = body.match(/^@([^\s]+)\s+(.*)$/);
|
|
3580
|
+
if (atMatch) {
|
|
3581
|
+
return {
|
|
3582
|
+
at: atMatch[1],
|
|
3583
|
+
rest: atMatch[2] || "",
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
return { at: "", rest: body };
|
|
3587
|
+
};
|
|
3588
|
+
|
|
3515
3589
|
const renderChatLogEntry = (entry, group) => {
|
|
3516
3590
|
const row = entry && entry.row ? entry.row : buildChatLogLineModel("");
|
|
3517
3591
|
const key = entry && entry.id ? entry.id : `log-${row.body}`;
|
|
@@ -3529,12 +3603,31 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
3529
3603
|
h(Text, { color: colors.body, bold: true, wrap: "truncate" }, row.body),
|
|
3530
3604
|
);
|
|
3531
3605
|
}
|
|
3606
|
+
if (row.kind === "user") {
|
|
3607
|
+
const userBody = renderUserLogBody(row.bodyText);
|
|
3608
|
+
return h(Box, { key, width: "100%", marginBottom: 1 },
|
|
3609
|
+
h(Text, { color: "green", bold: true }, row.markerText || "› "),
|
|
3610
|
+
userBody.at
|
|
3611
|
+
? h(Text, { color: "magenta", bold: true }, `@${userBody.at} `)
|
|
3612
|
+
: null,
|
|
3613
|
+
h(Text, { color: "green", bold: true, wrap: "wrap" }, userBody.rest),
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3532
3616
|
const markerText = entry && entry.continuation
|
|
3533
|
-
? (group && (group.kind === "assistant" || group.kind === "agent") ? " " : " ")
|
|
3617
|
+
? (group && (group.kind === "assistant" || group.kind === "agent" || group.kind === "report") ? " " : " ")
|
|
3534
3618
|
: row.markerText;
|
|
3619
|
+
const bodyProps = {
|
|
3620
|
+
color: colors.body,
|
|
3621
|
+
wrap: "wrap",
|
|
3622
|
+
};
|
|
3623
|
+
if (colors.dim) bodyProps.dimColor = true;
|
|
3535
3624
|
return h(Box, { key, width: "100%" },
|
|
3536
|
-
h(Text, {
|
|
3537
|
-
|
|
3625
|
+
h(Text, {
|
|
3626
|
+
color: colors.marker,
|
|
3627
|
+
bold: row.kind === "error" || row.kind === "assistant",
|
|
3628
|
+
dimColor: Boolean(colors.dim),
|
|
3629
|
+
}, markerText),
|
|
3630
|
+
h(Text, bodyProps,
|
|
3538
3631
|
row.speaker && !(entry && entry.continuation)
|
|
3539
3632
|
? h(Text, { color: colors.speaker, bold: colors.bold }, row.speaker)
|
|
3540
3633
|
: null,
|
|
@@ -3551,7 +3644,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
3551
3644
|
if (entries.length === 0) return null;
|
|
3552
3645
|
const first = entries[0] || {};
|
|
3553
3646
|
const row = first.row || buildChatLogLineModel("");
|
|
3554
|
-
if (row.kind === "spacer" || row.kind === "banner" || row.kind === "divider") {
|
|
3647
|
+
if (row.kind === "spacer" || row.kind === "banner" || row.kind === "divider" || row.kind === "user") {
|
|
3555
3648
|
return renderChatLogEntry(first, group);
|
|
3556
3649
|
}
|
|
3557
3650
|
return h(Box, {
|
|
@@ -3587,12 +3680,31 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
3587
3680
|
h(Text, { color: colors.body, bold: true, wrap: "truncate" }, row.body),
|
|
3588
3681
|
);
|
|
3589
3682
|
}
|
|
3683
|
+
if (row.kind === "user") {
|
|
3684
|
+
const userBody = renderUserLogBody(row.bodyText);
|
|
3685
|
+
return h(Box, { key, width: "100%", marginTop, marginBottom: 1 },
|
|
3686
|
+
h(Text, { color: "green", bold: true }, row.markerText || "› "),
|
|
3687
|
+
userBody.at
|
|
3688
|
+
? h(Text, { color: "magenta", bold: true }, `@${userBody.at} `)
|
|
3689
|
+
: null,
|
|
3690
|
+
h(Text, { color: "green", bold: true, wrap: "wrap" }, userBody.rest),
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3590
3693
|
const markerText = continuation
|
|
3591
|
-
? (groupKind === "assistant" || groupKind === "agent" ? " " : " ")
|
|
3694
|
+
? (groupKind === "assistant" || groupKind === "agent" || groupKind === "report" ? " " : " ")
|
|
3592
3695
|
: row.markerText;
|
|
3696
|
+
const bodyProps = {
|
|
3697
|
+
color: colors.body,
|
|
3698
|
+
wrap: "wrap",
|
|
3699
|
+
};
|
|
3700
|
+
if (colors.dim) bodyProps.dimColor = true;
|
|
3593
3701
|
return h(Box, { key, width: "100%", marginTop },
|
|
3594
|
-
h(Text, {
|
|
3595
|
-
|
|
3702
|
+
h(Text, {
|
|
3703
|
+
color: colors.marker,
|
|
3704
|
+
bold: row.kind === "error" || row.kind === "assistant",
|
|
3705
|
+
dimColor: Boolean(colors.dim),
|
|
3706
|
+
}, markerText),
|
|
3707
|
+
h(Text, bodyProps,
|
|
3596
3708
|
row.speaker && !continuation
|
|
3597
3709
|
? h(Text, { color: colors.speaker, bold: colors.bold }, row.speaker)
|
|
3598
3710
|
: null,
|
package/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -323,14 +323,36 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
323
323
|
|
|
324
324
|
const { UCODE_COMMAND_REGISTRY, UCODE_COMMAND_TREE } = require("../../code/commands");
|
|
325
325
|
const { listSessionSummaries } = require("../../code/sessionStore");
|
|
326
|
-
const { suggestUcodeModels, applyUcodeModelCommand } = require("../../code/modelCommand");
|
|
326
|
+
const { suggestUcodeModels, suggestUcodeThinkingLevels, applyUcodeModelCommand, listUcodeModels } = require("../../code/modelCommand");
|
|
327
327
|
let resumeSessions = [];
|
|
328
328
|
try {
|
|
329
329
|
resumeSessions = listSessionSummaries(props.workspaceRoot || process.cwd(), { limit: 40 });
|
|
330
330
|
} catch {
|
|
331
331
|
resumeSessions = [];
|
|
332
332
|
}
|
|
333
|
-
const
|
|
333
|
+
const [remoteModels, setRemoteModels] = useState([]);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
let cancelled = false;
|
|
336
|
+
(async () => {
|
|
337
|
+
try {
|
|
338
|
+
const listed = await listUcodeModels(props.state || {}, {
|
|
339
|
+
workspaceRoot: props.workspaceRoot || process.cwd(),
|
|
340
|
+
});
|
|
341
|
+
if (!cancelled && listed.ok) {
|
|
342
|
+
setRemoteModels(Array.isArray(listed.models) ? listed.models : []);
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
if (!cancelled) setRemoteModels([]);
|
|
346
|
+
}
|
|
347
|
+
})();
|
|
348
|
+
return () => { cancelled = true; };
|
|
349
|
+
}, [
|
|
350
|
+
props.workspaceRoot,
|
|
351
|
+
props.state && props.state.provider,
|
|
352
|
+
props.state && props.state.model,
|
|
353
|
+
]);
|
|
354
|
+
const modelSuggestions = suggestUcodeModels(props.state || {}, { models: remoteModels });
|
|
355
|
+
const thinkingSuggestions = suggestUcodeThinkingLevels(props.state || {});
|
|
334
356
|
|
|
335
357
|
const completions = fmt.buildCompletions({
|
|
336
358
|
text: draft,
|
|
@@ -341,6 +363,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
341
363
|
argumentLists: {
|
|
342
364
|
"/resume": resumeSessions,
|
|
343
365
|
"/model": modelSuggestions,
|
|
366
|
+
"/model/thinking": thinkingSuggestions,
|
|
344
367
|
},
|
|
345
368
|
limit: 20,
|
|
346
369
|
});
|
|
@@ -541,7 +564,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
541
564
|
return;
|
|
542
565
|
}
|
|
543
566
|
case "model": {
|
|
544
|
-
const applied = applyUcodeModelCommand(props.state || {}, result
|
|
567
|
+
const applied = await applyUcodeModelCommand(props.state || {}, result, {
|
|
568
|
+
workspaceRoot: runtimeWorkspace,
|
|
569
|
+
});
|
|
545
570
|
appendLogText(applied.output || "", applied.ok ? "system" : "error");
|
|
546
571
|
if (applied.ok && result.action === "set" && typeof props.persistSessionState === "function") {
|
|
547
572
|
try {
|