u-foo 3.0.0 → 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/agent.js +17 -3
- package/src/code/commands.js +3 -3
- package/src/code/context/executionSegment.js +5 -0
- package/src/code/context/planMode.js +8 -1
- package/src/code/context/promptLayers.js +10 -9
- package/src/code/dispatch.js +4 -0
- package/src/code/index.js +2 -0
- package/src/code/modelCommand.js +199 -23
- package/src/code/nativeRunner.js +299 -225
- package/src/code/protocol/controlPlane.js +93 -0
- package/src/code/protocol/faultHarness.js +90 -0
- package/src/code/protocol/index.js +20 -0
- package/src/code/protocol/loopEvents.js +102 -0
- package/src/code/protocol/materialize.js +107 -0
- package/src/code/protocol/messageFixtures.js +116 -0
- package/src/code/protocol/ownership.js +147 -0
- package/src/code/protocol/protocolValidator.js +165 -0
- package/src/code/protocol/suspension.js +173 -0
- package/src/code/protocol/toolCallLedger.js +222 -0
- package/src/code/protocol/transitions.js +97 -0
- package/src/code/providers/anthropicMessagesTransport.js +93 -0
- package/src/code/providers/index.js +8 -0
- package/src/code/providers/modelsCatalog.js +304 -0
- package/src/code/providers/openaiChatTransport.js +98 -0
- package/src/code/providers/transportContract.js +46 -0
- package/src/code/repl.js +45 -29
- package/src/code/runtime/taskControl.js +177 -53
- package/src/code/runtime/taskFocus.js +30 -10
- package/src/code/runtime/taskLoop.js +25 -3
- package/src/code/runtime/taskRun.js +172 -2
- package/src/code/runtime/workspaceLease.js +41 -0
- package/src/code/sessionStore.js +1 -0
- package/src/code/taskRoute.js +73 -0
- 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 +38 -30
- package/src/ui/ink/chatLogModel.js +238 -32
- package/src/ui/ink/chatReducer.js +18 -6
package/src/code/modelCommand.js
CHANGED
|
@@ -1,37 +1,176 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const { saveGlobalUcodeConfig } = require("../config");
|
|
3
|
+
const { saveGlobalUcodeConfig, loadGlobalUcodeConfig } = require("../config");
|
|
4
|
+
const {
|
|
5
|
+
listProviderModels,
|
|
6
|
+
confirmModelSupported,
|
|
7
|
+
} = require("./providers/modelsCatalog");
|
|
8
|
+
const {
|
|
9
|
+
normalizeThinkingLevel,
|
|
10
|
+
suggestThinkingLevels,
|
|
11
|
+
applyThinkingLevelToEnv,
|
|
12
|
+
resolveThinkingFromEnvAndConfig,
|
|
13
|
+
DEFAULT_THINKING_LEVEL,
|
|
14
|
+
} = require("./thinkingLevels");
|
|
15
|
+
|
|
16
|
+
function fallbackModelSuggestions(provider = "") {
|
|
17
|
+
const text = String(provider || "").trim().toLowerCase();
|
|
18
|
+
if (text.includes("anthropic") || text.includes("claude")) {
|
|
19
|
+
return ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"];
|
|
20
|
+
}
|
|
21
|
+
if (text.includes("kimi") || text.includes("moonshot")) {
|
|
22
|
+
return ["k3", "kimi-k2.5", "moonshot-v1-128k"];
|
|
23
|
+
}
|
|
24
|
+
return ["gpt-5.4", "gpt-5.3", "o3", "o4-mini"];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveModelRuntime(state = {}, options = {}) {
|
|
28
|
+
// Lazy require: nativeRunner pulls agent/repl paths that can load modelCommand.
|
|
29
|
+
const { resolveRuntimeConfig } = require("./nativeRunner");
|
|
30
|
+
return resolveRuntimeConfig({
|
|
31
|
+
workspaceRoot: options.workspaceRoot || process.cwd(),
|
|
32
|
+
provider: options.provider || (state && state.provider) || "",
|
|
33
|
+
model: options.model || (state && state.model) || "",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function currentThinkingLevel(state = {}) {
|
|
38
|
+
let configLevel = "";
|
|
39
|
+
try {
|
|
40
|
+
configLevel = String((loadGlobalUcodeConfig() || {}).ucodeThinking || "").trim();
|
|
41
|
+
} catch {
|
|
42
|
+
configLevel = "";
|
|
43
|
+
}
|
|
44
|
+
const fromState = normalizeThinkingLevel(state && state.thinking);
|
|
45
|
+
const resolved = resolveThinkingFromEnvAndConfig({
|
|
46
|
+
env: process.env,
|
|
47
|
+
configLevel: fromState || configLevel,
|
|
48
|
+
});
|
|
49
|
+
if (resolved.level) return resolved.level;
|
|
50
|
+
if (resolved.source === "env-budget") {
|
|
51
|
+
// Approximate a named level for the secondary menu highlight.
|
|
52
|
+
const budget = Number(resolved.budgetTokens) || 0;
|
|
53
|
+
if (budget <= 0) return "off";
|
|
54
|
+
if (budget <= 3000) return "low";
|
|
55
|
+
if (budget <= 16000) return "medium";
|
|
56
|
+
if (budget <= 40000) return "high";
|
|
57
|
+
return "max";
|
|
58
|
+
}
|
|
59
|
+
return DEFAULT_THINKING_LEVEL;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function persistThinkingLevel(state = {}, level = "") {
|
|
63
|
+
const normalized = normalizeThinkingLevel(level);
|
|
64
|
+
if (!normalized) return "";
|
|
65
|
+
if (state && typeof state === "object") state.thinking = normalized;
|
|
66
|
+
try {
|
|
67
|
+
saveGlobalUcodeConfig({ ucodeThinking: normalized });
|
|
68
|
+
} catch {
|
|
69
|
+
// best-effort
|
|
70
|
+
}
|
|
71
|
+
applyThinkingLevelToEnv(normalized, process.env);
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch models from the configured provider's /models route.
|
|
77
|
+
*/
|
|
78
|
+
async function listUcodeModels(state = {}, options = {}) {
|
|
79
|
+
const runtime = resolveModelRuntime(state, options);
|
|
80
|
+
const listed = await listProviderModels({
|
|
81
|
+
...runtime,
|
|
82
|
+
fetchImpl: options.fetchImpl,
|
|
83
|
+
timeoutMs: options.timeoutMs,
|
|
84
|
+
skipCache: options.skipCache === true,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
...listed,
|
|
88
|
+
provider: runtime.provider,
|
|
89
|
+
transport: runtime.transport,
|
|
90
|
+
baseUrl: runtime.baseUrl,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
4
93
|
|
|
5
94
|
/**
|
|
6
95
|
* Apply /model show|set against the live session state.
|
|
7
|
-
* Persists ucodeModel
|
|
96
|
+
* Persists ucodeModel / ucodeThinking so the next launch keeps them.
|
|
97
|
+
* Set validates against the provider models route when available.
|
|
98
|
+
*
|
|
99
|
+
* result.shape:
|
|
100
|
+
* { action: "show" }
|
|
101
|
+
* { action: "set", model, thinking? }
|
|
8
102
|
*/
|
|
9
|
-
function applyUcodeModelCommand(state = {}, result = {}) {
|
|
103
|
+
async function applyUcodeModelCommand(state = {}, result = {}, options = {}) {
|
|
10
104
|
const action = String((result && result.action) || "").trim().toLowerCase();
|
|
11
105
|
if (action === "show") {
|
|
12
106
|
const model = String((state && state.model) || "").trim() || "(unset)";
|
|
13
107
|
const provider = String((state && state.provider) || "").trim() || "(unset)";
|
|
108
|
+
const thinking = currentThinkingLevel(state);
|
|
109
|
+
const lines = [
|
|
110
|
+
`model: ${model}`,
|
|
111
|
+
`provider: ${provider}`,
|
|
112
|
+
`thinking: ${thinking}`,
|
|
113
|
+
"usage: /model <model-id> [off|low|medium|high|max]",
|
|
114
|
+
];
|
|
115
|
+
try {
|
|
116
|
+
const listed = await listUcodeModels(state, options);
|
|
117
|
+
if (listed.ok && listed.models.length > 0) {
|
|
118
|
+
const sample = listed.models.slice(0, 12);
|
|
119
|
+
lines.push(`models route: ${listed.url}`);
|
|
120
|
+
lines.push(`available (${listed.models.length}): ${sample.join(", ")}${listed.models.length > 12 ? "…" : ""}`);
|
|
121
|
+
} else if (listed.error) {
|
|
122
|
+
lines.push(`models route: ${listed.error}`);
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
lines.push(`models route: ${err && err.message ? err.message : "unavailable"}`);
|
|
126
|
+
}
|
|
14
127
|
return {
|
|
15
128
|
ok: true,
|
|
16
129
|
error: "",
|
|
17
|
-
output:
|
|
18
|
-
`model: ${model}`,
|
|
19
|
-
`provider: ${provider}`,
|
|
20
|
-
"usage: /model <model-id>",
|
|
21
|
-
].join("\n"),
|
|
130
|
+
output: lines.join("\n"),
|
|
22
131
|
model: String((state && state.model) || "").trim(),
|
|
132
|
+
thinking,
|
|
23
133
|
};
|
|
24
134
|
}
|
|
25
135
|
if (action === "set") {
|
|
26
136
|
const next = String((result && result.model) || "").trim();
|
|
137
|
+
const thinkingRaw = String((result && result.thinking) || "").trim();
|
|
138
|
+
const thinkingNext = normalizeThinkingLevel(thinkingRaw);
|
|
27
139
|
if (!next) {
|
|
28
140
|
return {
|
|
29
141
|
ok: false,
|
|
30
|
-
error: "usage: /model [model-id]",
|
|
31
|
-
output: "usage: /model [model-id]",
|
|
142
|
+
error: "usage: /model [model-id] [off|low|medium|high|max]",
|
|
143
|
+
output: "usage: /model [model-id] [off|low|medium|high|max]",
|
|
32
144
|
};
|
|
33
145
|
}
|
|
146
|
+
if (thinkingRaw && !thinkingNext) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: `unknown thinking level "${thinkingRaw}" (use off|low|medium|high|max)`,
|
|
150
|
+
output: `unknown thinking level "${thinkingRaw}" (use off|low|medium|high|max)`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const runtime = resolveModelRuntime(state, { ...options, model: next });
|
|
155
|
+
const confirmation = await confirmModelSupported({
|
|
156
|
+
...runtime,
|
|
157
|
+
model: next,
|
|
158
|
+
fetchImpl: options.fetchImpl,
|
|
159
|
+
timeoutMs: options.timeoutMs,
|
|
160
|
+
skipCache: options.skipCache === true,
|
|
161
|
+
strict: options.strict === true,
|
|
162
|
+
});
|
|
163
|
+
if (!confirmation.allowed) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: confirmation.error || `model "${next}" is not supported`,
|
|
167
|
+
output: confirmation.error || `model "${next}" is not supported`,
|
|
168
|
+
models: confirmation.models,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
34
172
|
const previous = String((state && state.model) || "").trim();
|
|
173
|
+
const previousThinking = currentThinkingLevel(state);
|
|
35
174
|
if (state && typeof state === "object") state.model = next;
|
|
36
175
|
try {
|
|
37
176
|
saveGlobalUcodeConfig({ ucodeModel: next });
|
|
@@ -43,45 +182,82 @@ function applyUcodeModelCommand(state = {}, result = {}) {
|
|
|
43
182
|
} catch {
|
|
44
183
|
// ignore env write failures
|
|
45
184
|
}
|
|
46
|
-
|
|
185
|
+
|
|
186
|
+
const lines = [];
|
|
187
|
+
const modelOutput = previous && previous !== next
|
|
47
188
|
? `model switched: ${previous} → ${next}`
|
|
48
189
|
: `model set: ${next}`;
|
|
190
|
+
lines.push(modelOutput);
|
|
191
|
+
|
|
192
|
+
let appliedThinking = "";
|
|
193
|
+
if (thinkingNext) {
|
|
194
|
+
appliedThinking = persistThinkingLevel(state, thinkingNext);
|
|
195
|
+
if (previousThinking && previousThinking !== appliedThinking) {
|
|
196
|
+
lines.push(`thinking: ${previousThinking} → ${appliedThinking}`);
|
|
197
|
+
} else {
|
|
198
|
+
lines.push(`thinking: ${appliedThinking}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (confirmation.warning) lines.push(`note: ${confirmation.warning}`);
|
|
203
|
+
if (confirmation.ok && confirmation.models.length > 0) {
|
|
204
|
+
lines.push(`confirmed via models route (${confirmation.models.length} available)`);
|
|
205
|
+
}
|
|
49
206
|
return {
|
|
50
207
|
ok: true,
|
|
51
208
|
error: "",
|
|
52
|
-
output,
|
|
209
|
+
output: lines.join("\n"),
|
|
53
210
|
model: next,
|
|
54
211
|
previous,
|
|
212
|
+
thinking: appliedThinking || previousThinking,
|
|
213
|
+
warning: confirmation.warning || "",
|
|
214
|
+
models: confirmation.models,
|
|
55
215
|
};
|
|
56
216
|
}
|
|
57
217
|
return {
|
|
58
218
|
ok: false,
|
|
59
|
-
error: "usage: /model [model-id]",
|
|
60
|
-
output: "usage: /model [model-id]",
|
|
219
|
+
error: "usage: /model [model-id] [off|low|medium|high|max]",
|
|
220
|
+
output: "usage: /model [model-id] [off|low|medium|high|max]",
|
|
61
221
|
};
|
|
62
222
|
}
|
|
63
223
|
|
|
64
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Build /model completion rows. Prefer a live models-route catalog when
|
|
226
|
+
* provided; otherwise fall back to a small hardcoded list.
|
|
227
|
+
* Models are marked hasChildren so the TUI opens a thinking-intensity
|
|
228
|
+
* secondary menu after the id is chosen.
|
|
229
|
+
*/
|
|
230
|
+
function suggestUcodeModels(state = {}, options = {}) {
|
|
65
231
|
const current = String((state && state.model) || "").trim();
|
|
66
232
|
const provider = String((state && state.provider) || "").trim().toLowerCase();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
defaults = ["kimi-k2.5", "moonshot-v1-128k"];
|
|
72
|
-
}
|
|
233
|
+
const remote = Array.isArray(options.models)
|
|
234
|
+
? options.models.map((item) => String(item || "").trim()).filter(Boolean)
|
|
235
|
+
: [];
|
|
236
|
+
const defaults = remote.length > 0 ? remote : fallbackModelSuggestions(provider);
|
|
73
237
|
const ids = [];
|
|
74
238
|
if (current) ids.push(current);
|
|
75
239
|
for (const id of defaults) {
|
|
76
240
|
if (id && !ids.includes(id)) ids.push(id);
|
|
77
241
|
}
|
|
78
|
-
return ids.map((id) => ({
|
|
242
|
+
return ids.slice(0, 40).map((id) => ({
|
|
79
243
|
id,
|
|
80
|
-
desc: id === current
|
|
244
|
+
desc: id === current
|
|
245
|
+
? "current · pick thinking next"
|
|
246
|
+
: (remote.length > 0 ? "models route · pick thinking next" : "pick thinking next"),
|
|
247
|
+
hasChildren: true,
|
|
81
248
|
}));
|
|
82
249
|
}
|
|
83
250
|
|
|
251
|
+
function suggestUcodeThinkingLevels(state = {}) {
|
|
252
|
+
return suggestThinkingLevels({ current: currentThinkingLevel(state) });
|
|
253
|
+
}
|
|
254
|
+
|
|
84
255
|
module.exports = {
|
|
85
256
|
applyUcodeModelCommand,
|
|
86
257
|
suggestUcodeModels,
|
|
258
|
+
suggestUcodeThinkingLevels,
|
|
259
|
+
listUcodeModels,
|
|
260
|
+
fallbackModelSuggestions,
|
|
261
|
+
currentThinkingLevel,
|
|
262
|
+
persistThinkingLevel,
|
|
87
263
|
};
|