u-foo 2.3.20 → 2.3.22

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.20",
3
+ "version": "2.3.22",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
 
3
3
  const { randomUUID } = require("crypto");
4
- const { loadConfig } = require("../config");
4
+ const {
5
+ loadConfig,
6
+ defaultAgentModelForProvider,
7
+ defaultRouterModelForProvider,
8
+ sameModelProvider,
9
+ } = require("../config");
5
10
  const {
6
11
  resolveRuntimeConfig,
7
12
  resolveCompletionUrl,
@@ -30,6 +35,16 @@ const CODEX_DEFAULT_BASE_URL = "https://chatgpt.com/backend-api/codex";
30
35
  const CODEX_DEFAULT_USER_AGENT = "codex-tui/0.118.0 (Mac OS 26.3.1; arm64) iTerm.app/3.6.9 (codex-tui; 0.118.0)";
31
36
  const CODEX_DEFAULT_ORIGINATOR = "codex-tui";
32
37
 
38
+ function resolveConfiguredModelForProvider(config = {}, provider = "") {
39
+ if (config.routerProvider && sameModelProvider(config.routerProvider, provider)) {
40
+ return config.routerModel;
41
+ }
42
+ if (config.agentProvider && sameModelProvider(config.agentProvider, provider)) {
43
+ return config.agentModel;
44
+ }
45
+ return "";
46
+ }
47
+
33
48
  function buildOpenAiChatRequest({
34
49
  model = "",
35
50
  systemPrompt = "",
@@ -217,7 +232,11 @@ async function resolveUpstreamRuntime({
217
232
  const baseUrl = useCodexResponses
218
233
  ? String(env.UFOO_CODEX_BASE_URL || "").trim() || CODEX_DEFAULT_BASE_URL
219
234
  : String(env.OPENAI_BASE_URL || "").trim() || "https://api.openai.com/v1";
220
- const resolvedModel = String(model || config.routerModel || config.agentModel || "").trim();
235
+ const resolvedModel = String(
236
+ model
237
+ || resolveConfiguredModelForProvider(config, "codex")
238
+ || defaultRouterModelForProvider("codex")
239
+ ).trim();
221
240
  return {
222
241
  provider: "codex",
223
242
  transport: useCodexResponses ? "codex-responses" : "openai-chat",
@@ -237,7 +256,11 @@ async function resolveUpstreamRuntime({
237
256
  env,
238
257
  });
239
258
  const baseUrl = String(env.ANTHROPIC_BASE_URL || "").trim() || "https://api.anthropic.com/v1";
240
- const resolvedModel = String(model || config.routerModel || config.agentModel || "").trim();
259
+ const resolvedModel = String(
260
+ model
261
+ || resolveConfiguredModelForProvider(config, "claude")
262
+ || defaultRouterModelForProvider("claude")
263
+ ).trim();
241
264
  return {
242
265
  provider: "claude",
243
266
  transport: "anthropic-messages",
@@ -252,7 +275,7 @@ async function resolveUpstreamRuntime({
252
275
  const runtime = resolveRuntimeConfig({
253
276
  workspaceRoot: projectRoot,
254
277
  provider: normalizedProvider === "ucode" ? "" : normalizedProvider,
255
- model,
278
+ model: model || resolveConfiguredModelForProvider(config, normalizedProvider) || defaultAgentModelForProvider(config.agentProvider),
256
279
  });
257
280
  const auth = runtime.apiKey ? { apiKey: String(runtime.apiKey || "").trim() } : { headers: {} };
258
281
  return {
@@ -306,14 +306,15 @@ function createAgentViewController(options = {}) {
306
306
  const product = "ClaudeCode";
307
307
  const detail = label ? `${label} · managed headless` : "managed headless";
308
308
  const iconWidth = 9;
309
+ const iconGap = " ";
309
310
  const iconLine = (icon = "", text = "") => {
310
311
  const pad = " ".repeat(Math.max(0, iconWidth - displayWidth(icon)));
311
- return `${CLAUDE_ORANGE}${icon}${ANSI_RESET}${pad}${text}`;
312
+ return `${CLAUDE_ORANGE}${icon}${ANSI_RESET}${pad}${iconGap}${text}`;
312
313
  };
313
314
  const lines = [
314
- iconLine("▐▛███▜▌", `${product}v${packageVersion}`),
315
+ iconLine(" ▐▛███▜▌", `${product}v${packageVersion}`),
315
316
  iconLine("▝▜█████▛▘", detail),
316
- iconLine(" ▘▘▝▝", projectPath),
317
+ iconLine(" ▘▘ ▝▝ ", projectPath),
317
318
  "",
318
319
  ];
319
320
  if (width < 44) return lines;
@@ -9,6 +9,9 @@ const {
9
9
  loadGlobalUcodeConfig,
10
10
  saveGlobalUcodeConfig,
11
11
  normalizeControllerMode,
12
+ SETTINGS_MODEL_DEFAULTS,
13
+ defaultAgentModelForProvider,
14
+ defaultRouterModelForProvider,
12
15
  } = require("../config");
13
16
  const { resolveTransport } = require("../code/nativeRunner");
14
17
  const { resolveDisplayNickname } = require("../daemon/nicknameScope");
@@ -43,17 +46,6 @@ function defaultResolveTerminalApp() {
43
46
  return "";
44
47
  }
45
48
 
46
- const SETTINGS_MODEL_DEFAULTS = Object.freeze({
47
- agent: Object.freeze({
48
- codex: "gpt-5.5",
49
- claude: "opus-4.7",
50
- }),
51
- router: Object.freeze({
52
- codex: "gpt-5.4-mini",
53
- claude: "sonnet-4.7",
54
- }),
55
- });
56
-
57
49
  function normalizeSettingsProvider(value = "", fallback = "codex-cli") {
58
50
  const text = String(value || "").trim().toLowerCase();
59
51
  if (text === "claude" || text === "claude-cli" || text === "claude-code" || text === "anthropic") {
@@ -69,13 +61,8 @@ function agentProviderKey(value = "") {
69
61
  return normalizeSettingsProvider(value) === "claude-cli" ? "claude" : "codex";
70
62
  }
71
63
 
72
- function defaultAgentModelForProvider(value = "") {
73
- return SETTINGS_MODEL_DEFAULTS.agent[agentProviderKey(value)] || SETTINGS_MODEL_DEFAULTS.agent.codex;
74
- }
75
-
76
64
  function defaultGateModelForProvider(value = "") {
77
- const key = agentProviderKey(value);
78
- return SETTINGS_MODEL_DEFAULTS.router[key] || SETTINGS_MODEL_DEFAULTS.router.codex;
65
+ return defaultRouterModelForProvider(value);
79
66
  }
80
67
 
81
68
  function collectHostLaunchRequestContext(env = process.env) {
@@ -113,7 +113,7 @@ const COMMAND_TREE = {
113
113
  loop: { desc: "Set router mode to loop", order: 5 },
114
114
  legacy: { desc: "Set router mode to legacy", order: 6 },
115
115
  shadow: { desc: "Set router mode to shadow", order: 7 },
116
- codex: { desc: "Use Codex gate model (gpt-5.4-mini)", order: 8 },
116
+ codex: { desc: "Use Codex gate model (gpt-5.3-codex-spark)", order: 8 },
117
117
  claude: { desc: "Use Claude gate model (sonnet-4.7)", order: 9 },
118
118
  },
119
119
  },
@@ -354,19 +354,6 @@ function createDaemonMessageRouter(options = {}) {
354
354
  function handleBusMessage(msg) {
355
355
  const data = msg.data || {};
356
356
  if (data.event === "activity_state_changed") {
357
- const publisher = data.publisher && data.publisher !== "unknown"
358
- ? data.publisher
359
- : (data.subscriber || "bus");
360
- const viewingAgent = getViewingAgent();
361
- if (
362
- getCurrentView() === "agent" &&
363
- isAgentViewUsesBus() &&
364
- viewingAgent &&
365
- isAgentEventForViewingAgent(data, viewingAgent, publisher)
366
- ) {
367
- const state = data.state || (data.data && data.data.state) || "";
368
- if (state) writeToAgentTerm(`[state] ${state}\r\n`);
369
- }
370
357
  requestStatus();
371
358
  return true;
372
359
  }
@@ -391,6 +378,18 @@ function createDaemonMessageRouter(options = {}) {
391
378
  if (!displayMessage && !streamPayload) return true;
392
379
 
393
380
  const viewingAgent = getViewingAgent();
381
+ const isOwnInternalPrompt =
382
+ data.source === "chat-internal-agent-view" &&
383
+ viewingAgent &&
384
+ data.target === viewingAgent &&
385
+ publisher !== viewingAgent;
386
+ if (
387
+ getCurrentView() === "agent" &&
388
+ isAgentViewUsesBus() &&
389
+ isOwnInternalPrompt
390
+ ) {
391
+ return true;
392
+ }
394
393
  const isAgentViewTarget =
395
394
  getCurrentView() === "agent" &&
396
395
  isAgentViewUsesBus() &&
package/src/chat/index.js CHANGED
@@ -1062,11 +1062,12 @@ async function runChat(projectRoot, options = {}) {
1062
1062
  : `${prefix}>`;
1063
1063
 
1064
1064
  promptBox.setContent(content);
1065
+ if (!input.parent || !promptBox.parent) return;
1066
+
1065
1067
  promptBox.width = content.length + 1; // content + spacer
1066
1068
  input.left = promptBox.width;
1067
1069
  input.width = `100%-${promptBox.width}`;
1068
1070
 
1069
- if (!input.parent || !promptBox.parent) return;
1070
1071
  resizeInput();
1071
1072
  if (typeof input._updateCursor === "function") {
1072
1073
  input._updateCursor();
package/src/code/agent.js CHANGED
@@ -17,7 +17,7 @@ const {
17
17
  stripLeakedEscapeTags,
18
18
  } = require("./tui");
19
19
  const { stripBlessedTags } = require("../chat/text");
20
- const { loadConfig } = require("../config");
20
+ const { loadConfig, defaultAgentModelForProvider, sameModelProvider } = require("../config");
21
21
  const {
22
22
  resolveSessionId,
23
23
  normalizeSessionId,
@@ -109,12 +109,14 @@ function resolveUcodeProviderModel({
109
109
  || ""
110
110
  ).trim();
111
111
  const resolvedProvider = resolvePlannerProvider(explicitProvider || fallbackProviderFromAgent);
112
+ const configuredModel = sameModelProvider(config.ucodeProvider || config.agentProvider, resolvedProvider)
113
+ ? (config.ucodeModel || config.agentModel)
114
+ : "";
112
115
  const resolvedModel = String(
113
116
  model
114
117
  || process.env.UFOO_UCODE_MODEL
115
- || config.ucodeModel
116
- || config.agentModel
117
- || ""
118
+ || configuredModel
119
+ || defaultAgentModelForProvider(resolvedProvider)
118
120
  ).trim();
119
121
  return {
120
122
  provider: resolvedProvider,
@@ -1,5 +1,5 @@
1
1
  const { randomUUID } = require("crypto");
2
- const { loadConfig } = require("../config");
2
+ const { loadConfig, defaultAgentModelForProvider, sameModelProvider } = require("../config");
3
3
  const { runToolCall } = require("./dispatch");
4
4
  const { getReadToolDescription } = require("./prompts/toolDescriptions/read");
5
5
  const { getWriteToolDescription } = require("./prompts/toolDescriptions/write");
@@ -219,13 +219,15 @@ function resolveRuntimeConfig({ workspaceRoot = process.cwd(), provider = "", mo
219
219
  || configuredProvider
220
220
  || "openai"
221
221
  ) || "openai";
222
+ const configuredModel = sameModelProvider(config.ucodeProvider || config.agentProvider, selectedProvider)
223
+ ? (config.ucodeModel || config.agentModel)
224
+ : "";
222
225
 
223
226
  const selectedModel = String(
224
227
  model
225
228
  || process.env.UFOO_UCODE_MODEL
226
- || config.ucodeModel
227
- || config.agentModel
228
- || ""
229
+ || configuredModel
230
+ || defaultAgentModelForProvider(selectedProvider)
229
231
  ).trim();
230
232
 
231
233
  const defaultBaseUrl = selectedProvider === "anthropic"
package/src/code/tui.js CHANGED
@@ -627,8 +627,8 @@ function runUcodeTui({
627
627
  blessed,
628
628
  currentInputHeight: 4,
629
629
  version: UCODE_VERSION,
630
- logBorder: true,
631
- logScrollbar: true,
630
+ logBorder: false,
631
+ logScrollbar: false,
632
632
  });
633
633
 
634
634
  if (completionPanel && typeof completionPanel.hide === "function") {
@@ -1031,7 +1031,7 @@ function runUcodeTui({
1031
1031
  if (!plain) return;
1032
1032
  const content = ` → ${escapeBlessed(plain)} `;
1033
1033
  const visibleLen = plain.length + 4; // " → " + text + " "
1034
- const boxWidth = (logBox.width || 80) - 2; // subtract border/padding
1034
+ const boxWidth = logBox.width || 80;
1035
1035
  const pad = boxWidth > visibleLen ? " ".repeat(boxWidth - visibleLen) : "";
1036
1036
  logBox.log(`{cyan-bg}{white-fg}${content}${pad}{/white-fg}{/cyan-bg}`);
1037
1037
  logBox.log(""); // Add line break after user input
package/src/config.js CHANGED
@@ -4,6 +4,17 @@ const path = require("path");
4
4
 
5
5
  const UCODE_FIELDS = ["ucodeProvider", "ucodeModel", "ucodeBaseUrl", "ucodeApiKey", "ucodeAgentDir"];
6
6
 
7
+ const SETTINGS_MODEL_DEFAULTS = Object.freeze({
8
+ agent: Object.freeze({
9
+ codex: "gpt-5.5",
10
+ claude: "opus-4.7",
11
+ }),
12
+ router: Object.freeze({
13
+ codex: "gpt-5.3-codex-spark",
14
+ claude: "sonnet-4.7",
15
+ }),
16
+ });
17
+
7
18
  const DEFAULT_CONFIG = {
8
19
  launchMode: "auto",
9
20
  agentProvider: "codex-cli",
@@ -15,6 +26,8 @@ const DEFAULT_CONFIG = {
15
26
  claudeOauthTokenPath: "",
16
27
  claudeOauthRefreshWindowSec: 300,
17
28
  agentModel: "",
29
+ routerProvider: "",
30
+ routerModel: "",
18
31
  autoResume: false,
19
32
  };
20
33
 
@@ -41,6 +54,33 @@ function normalizeAgentProvider(value) {
41
54
  return "codex-cli";
42
55
  }
43
56
 
57
+ function providerKey(value = "") {
58
+ const text = String(value || "").trim().toLowerCase();
59
+ if (text === "claude" || text === "claude-cli" || text === "claude-code" || text === "anthropic") return "claude";
60
+ return "codex";
61
+ }
62
+
63
+ function sameModelProvider(left = "", right = "") {
64
+ return providerKey(left) === providerKey(right);
65
+ }
66
+
67
+ function defaultAgentModelForProvider(value = "") {
68
+ return SETTINGS_MODEL_DEFAULTS.agent[providerKey(value)] || SETTINGS_MODEL_DEFAULTS.agent.codex;
69
+ }
70
+
71
+ function defaultRouterProviderForAgentProvider(value = "") {
72
+ return providerKey(value) === "claude" ? "claude" : "codex";
73
+ }
74
+
75
+ function defaultRouterModelForProvider(value = "") {
76
+ return SETTINGS_MODEL_DEFAULTS.router[providerKey(value)] || SETTINGS_MODEL_DEFAULTS.router.codex;
77
+ }
78
+
79
+ function normalizeModel(value, fallback = "") {
80
+ const text = typeof value === "string" ? value.trim() : "";
81
+ return text || fallback;
82
+ }
83
+
44
84
  function normalizeControllerMode(value) {
45
85
  const raw = String(value || "").trim().toLowerCase();
46
86
  if (raw === "shadow") return "shadow";
@@ -100,11 +140,19 @@ function loadJsonSafe(filePath) {
100
140
  function loadConfig(projectRoot) {
101
141
  try {
102
142
  const raw = loadJsonSafe(configPath(projectRoot));
143
+ const agentProvider = normalizeAgentProvider(raw.agentProvider);
144
+ const routerProvider = normalizeModel(
145
+ raw.routerProvider,
146
+ defaultRouterProviderForAgentProvider(agentProvider)
147
+ );
103
148
  return {
104
149
  ...DEFAULT_CONFIG,
105
150
  ...raw,
106
151
  launchMode: normalizeLaunchMode(raw.launchMode),
107
- agentProvider: normalizeAgentProvider(raw.agentProvider),
152
+ agentProvider,
153
+ agentModel: normalizeModel(raw.agentModel, defaultAgentModelForProvider(agentProvider)),
154
+ routerProvider,
155
+ routerModel: normalizeModel(raw.routerModel, defaultRouterModelForProvider(routerProvider)),
108
156
  controllerMode: Object.prototype.hasOwnProperty.call(raw, "controllerMode")
109
157
  ? normalizeControllerMode(raw.controllerMode)
110
158
  : DEFAULT_CONFIG.controllerMode,
@@ -121,7 +169,15 @@ function loadConfig(projectRoot) {
121
169
  ...loadGlobalUcodeConfig(),
122
170
  };
123
171
  } catch {
124
- return { ...DEFAULT_CONFIG, ...DEFAULT_UCODE_CONFIG };
172
+ const agentProvider = DEFAULT_CONFIG.agentProvider;
173
+ const routerProvider = defaultRouterProviderForAgentProvider(agentProvider);
174
+ return {
175
+ ...DEFAULT_CONFIG,
176
+ agentModel: defaultAgentModelForProvider(agentProvider),
177
+ routerProvider,
178
+ routerModel: defaultRouterModelForProvider(routerProvider),
179
+ ...DEFAULT_UCODE_CONFIG,
180
+ };
125
181
  }
126
182
  }
127
183
 
@@ -152,6 +208,9 @@ function saveConfig(projectRoot, config) {
152
208
  }
153
209
  merged.launchMode = normalizeLaunchMode(merged.launchMode);
154
210
  merged.agentProvider = normalizeAgentProvider(merged.agentProvider);
211
+ merged.agentModel = typeof merged.agentModel === "string" ? merged.agentModel.trim() : "";
212
+ merged.routerProvider = typeof merged.routerProvider === "string" ? merged.routerProvider.trim() : "";
213
+ merged.routerModel = typeof merged.routerModel === "string" ? merged.routerModel.trim() : "";
155
214
  merged.controllerMode = normalizeControllerMode(merged.controllerMode);
156
215
  merged.codexInternalThreadMode = normalizeCodexInternalThreadMode(merged.codexInternalThreadMode);
157
216
  merged.codexAuthPath = normalizeCodexAuthPath(merged.codexAuthPath);
@@ -190,12 +249,17 @@ function saveGlobalUcodeConfig(updates = {}) {
190
249
  }
191
250
 
192
251
  module.exports = {
252
+ SETTINGS_MODEL_DEFAULTS,
193
253
  loadConfig,
194
254
  saveConfig,
195
255
  loadGlobalUcodeConfig,
196
256
  saveGlobalUcodeConfig,
197
257
  normalizeLaunchMode,
198
258
  normalizeAgentProvider,
259
+ sameModelProvider,
260
+ defaultAgentModelForProvider,
261
+ defaultRouterProviderForAgentProvider,
262
+ defaultRouterModelForProvider,
199
263
  normalizeControllerMode,
200
264
  normalizeCodexInternalThreadMode,
201
265
  normalizeCodexAuthPath,
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
 
3
- const { loadConfig } = require("../config");
3
+ const {
4
+ loadConfig,
5
+ defaultRouterProviderForAgentProvider,
6
+ defaultRouterModelForProvider,
7
+ sameModelProvider,
8
+ } = require("../config");
4
9
 
5
10
  const DEFAULT_EXECUTION_PATH = "main";
6
11
  const DEFAULT_CONFIDENCE_THRESHOLD = 0.6;
@@ -122,18 +127,22 @@ function resolveGateRouterConfig({
122
127
  loadConfigImpl = loadConfig,
123
128
  } = {}) {
124
129
  const config = loadConfigImpl(projectRoot || process.cwd());
130
+ const requestProvider = requestMeta.router_provider || env.UFOO_AGENT_ROUTER_PROVIDER;
131
+ const provider = String(
132
+ requestProvider
133
+ || config.routerProvider
134
+ || defaultRouterProviderForAgentProvider(config.agentProvider)
135
+ ).trim();
136
+ const configuredRouterModel = !requestProvider || sameModelProvider(config.routerProvider, provider)
137
+ ? config.routerModel
138
+ : "";
125
139
  return {
126
- provider: String(
127
- requestMeta.router_provider
128
- || env.UFOO_AGENT_ROUTER_PROVIDER
129
- || config.routerProvider
130
- || "ucode"
131
- ).trim(),
140
+ provider,
132
141
  model: String(
133
142
  requestMeta.router_model
134
143
  || env.UFOO_AGENT_ROUTER_MODEL
135
- || config.routerModel
136
- || ""
144
+ || configuredRouterModel
145
+ || defaultRouterModelForProvider(provider)
137
146
  ).trim(),
138
147
  timeoutMs: toPositiveNumber(
139
148
  requestMeta.router_timeout_ms
@@ -889,22 +889,6 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
889
889
  }
890
890
  }
891
891
 
892
- function emitRecentWatchedEvents(agentId, limit = 80) {
893
- if (!agentId) return;
894
- const previous = new Set(state.watchedAgents);
895
- state.watchedAgents.add(agentId);
896
- const aliases = buildWatchedAliases();
897
- state.watchedAgents = previous;
898
- const matches = [];
899
- const files = getEventFiles().slice(-3);
900
- for (const file of files) {
901
- for (const evt of readEventFile(file)) {
902
- if (isWatchedEvent(evt, aliases)) matches.push(evt);
903
- }
904
- }
905
- for (const evt of matches.slice(-limit)) emitBusEvent(evt);
906
- }
907
-
908
892
  function pollWatchedEvents() {
909
893
  if (state.watchedAgents.size === 0) {
910
894
  state.lastEventSeq = readCurrentSeq();
@@ -1032,7 +1016,6 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
1032
1016
  watchAgent(agentId, enabled = true) {
1033
1017
  if (!agentId) return;
1034
1018
  if (enabled) {
1035
- emitRecentWatchedEvents(agentId);
1036
1019
  state.watchedAgents.add(agentId);
1037
1020
  state.lastEventSeq = Math.max(state.lastEventSeq, readCurrentSeq());
1038
1021
  } else {
package/src/daemon/run.js CHANGED
@@ -1,14 +1,17 @@
1
1
  const path = require("path");
2
2
  const { startDaemon, stopDaemon, isRunning } = require("./index");
3
- const { loadConfig } = require("../config");
3
+ const { loadConfig, defaultAgentModelForProvider } = require("../config");
4
4
 
5
5
  function runDaemonCli(argv) {
6
6
  const cmd = argv[1] || "start";
7
7
  const projectRoot = process.cwd();
8
8
  const config = loadConfig(projectRoot);
9
- const provider = process.env.UFOO_AGENT_PROVIDER || config.agentProvider || "codex-cli";
9
+ const envProvider = process.env.UFOO_AGENT_PROVIDER;
10
+ const provider = envProvider || config.agentProvider || "codex-cli";
10
11
  const model =
11
- process.env.UFOO_AGENT_MODEL || config.agentModel || (provider === "claude-cli" ? "opus" : "");
12
+ process.env.UFOO_AGENT_MODEL
13
+ || (envProvider && envProvider !== config.agentProvider ? "" : config.agentModel)
14
+ || defaultAgentModelForProvider(provider);
12
15
  const resumeMode = process.env.UFOO_FORCE_RESUME === "1" ? "force" : "auto";
13
16
  const launchMode = config.launchMode || "terminal";
14
17