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.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/src/agents/prompts/native/tasks.js +4 -1
  3. package/src/app/chat/commandExecutor.js +111 -1
  4. package/src/app/chat/commands.js +2 -1
  5. package/src/app/chat/daemonMessageRouter.js +1 -1
  6. package/src/app/chat/inputSubmitHandler.js +3 -2
  7. package/src/code/agent.js +17 -3
  8. package/src/code/commands.js +3 -3
  9. package/src/code/context/executionSegment.js +5 -0
  10. package/src/code/context/planMode.js +8 -1
  11. package/src/code/context/promptLayers.js +10 -9
  12. package/src/code/dispatch.js +4 -0
  13. package/src/code/index.js +2 -0
  14. package/src/code/modelCommand.js +199 -23
  15. package/src/code/nativeRunner.js +299 -225
  16. package/src/code/protocol/controlPlane.js +93 -0
  17. package/src/code/protocol/faultHarness.js +90 -0
  18. package/src/code/protocol/index.js +20 -0
  19. package/src/code/protocol/loopEvents.js +102 -0
  20. package/src/code/protocol/materialize.js +107 -0
  21. package/src/code/protocol/messageFixtures.js +116 -0
  22. package/src/code/protocol/ownership.js +147 -0
  23. package/src/code/protocol/protocolValidator.js +165 -0
  24. package/src/code/protocol/suspension.js +173 -0
  25. package/src/code/protocol/toolCallLedger.js +222 -0
  26. package/src/code/protocol/transitions.js +97 -0
  27. package/src/code/providers/anthropicMessagesTransport.js +93 -0
  28. package/src/code/providers/index.js +8 -0
  29. package/src/code/providers/modelsCatalog.js +304 -0
  30. package/src/code/providers/openaiChatTransport.js +98 -0
  31. package/src/code/providers/transportContract.js +46 -0
  32. package/src/code/repl.js +45 -29
  33. package/src/code/runtime/taskControl.js +177 -53
  34. package/src/code/runtime/taskFocus.js +30 -10
  35. package/src/code/runtime/taskLoop.js +25 -3
  36. package/src/code/runtime/taskRun.js +172 -2
  37. package/src/code/runtime/workspaceLease.js +41 -0
  38. package/src/code/sessionStore.js +1 -0
  39. package/src/code/taskRoute.js +73 -0
  40. package/src/code/thinkingLevels.js +132 -0
  41. package/src/code/tools/taskRun.js +118 -0
  42. package/src/config.js +10 -1
  43. package/src/ui/format/index.js +48 -3
  44. package/src/ui/ink/ChatApp.js +137 -25
  45. package/src/ui/ink/UcodeApp.js +38 -30
  46. package/src/ui/ink/chatLogModel.js +238 -32
  47. package/src/ui/ink/chatReducer.js +18 -6
@@ -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 to the global config so the next launch keeps it.
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
- const output = previous && previous !== next
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
- function suggestUcodeModels(state = {}) {
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
- let defaults = ["gpt-5.4", "gpt-5.3", "o3", "o4-mini"];
68
- if (provider.includes("anthropic") || provider.includes("claude")) {
69
- defaults = ["claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5"];
70
- } else if (provider.includes("kimi") || provider.includes("moonshot")) {
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 ? "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
  };