u-foo 1.1.9 โ†’ 1.2.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/README.md CHANGED
@@ -1,31 +1,86 @@
1
1
  # ufoo
2
2
 
3
- Multi-agent AI collaboration toolkit for Claude Code and OpenAI Codex.
3
+ ๐Ÿค– Multi-agent AI collaboration framework for orchestrating Claude Code, OpenAI Codex, and custom AI agents.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/u-foo.svg)](https://www.npmjs.com/package/u-foo)
6
+ [![License](https://img.shields.io/badge/license-UNLICENSED-red.svg)](LICENSE)
7
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+ [![Platform](https://img.shields.io/badge/platform-macOS-blue.svg)](https://www.apple.com/macos)
9
+
10
+ ## Why ufoo?
11
+
12
+ ufoo solves the challenge of coordinating multiple AI coding agents:
13
+
14
+ - **๐Ÿ”— Unified Interface** - One chat UI to manage all your AI agents
15
+ - **๐Ÿ“ฌ Message Routing** - Agents can communicate and collaborate via event bus
16
+ - **๐Ÿง  Context Sharing** - Shared decisions and knowledge across agents
17
+ - **๐Ÿš€ Auto-initialization** - Agent wrappers handle setup automatically
18
+ - **๐Ÿ“ Decision Tracking** - Record architectural decisions and trade-offs
19
+ - **โšก Real-time Updates** - See agent status and messages instantly
4
20
 
5
21
  ## Features
6
22
 
23
+ - **Chat Interface** - Interactive multi-agent chat UI (`ufoo chat`)
24
+ - Real-time agent communication and status monitoring
25
+ - Dashboard with agent list, online status, and quick actions
26
+ - Direct messaging to specific agents with `@agent-name`
7
27
  - **Event Bus** - Real-time inter-agent messaging (`ufoo bus`)
8
28
  - **Context Sharing** - Shared decisions and project context (`ufoo ctx`)
9
- - **Agent Wrappers** - Auto-initialization for Claude Code (`uclaude`), Codex (`ucodex`), and ufoo core (`ucode`)
29
+ - **Agent Wrappers** - Auto-initialization for Claude Code (`uclaude`), Codex (`ucodex`), and ucode assistant (`ucode`)
10
30
  - **PTY Wrapper** - Intelligent terminal emulation with ready detection
11
31
  - **Smart Probe Injection** - Waits for agent initialization before injecting commands
32
+ - **Consistent Branding** - Unified agent naming (e.g., ucode-1, claude-1, codex-1)
12
33
  - **Skills System** - Extensible agent capabilities (`ufoo skills`)
13
34
 
14
- ## Quick Start
35
+ ## Installation
15
36
 
16
37
  ```bash
17
- # Clone and link globally
18
- git clone <repo> ~/.ufoo
38
+ # Install globally from npm
39
+ npm install -g u-foo
40
+
41
+ # Or clone from source
42
+ git clone https://github.com/Icyoung/ufoo.git ~/.ufoo
19
43
  cd ~/.ufoo && npm link
44
+ ```
45
+
46
+ ## Quick Start
20
47
 
48
+ ```bash
21
49
  # Initialize a project
22
50
  cd your-project
23
51
  ufoo init
24
52
 
25
- # Or use agent wrappers (auto-init + bus join)
26
- uclaude # instead of 'claude'
27
- ucodex # instead of 'codex'
28
- ucode # ufoo self-developed coding agent entry
53
+ # Launch chat interface (default command)
54
+ ufoo chat
55
+ # or just
56
+ ufoo
57
+
58
+ # Use agent wrappers (auto-init + bus join)
59
+ uclaude # Claude Code wrapper
60
+ ucodex # Codex wrapper
61
+ ucode # ucode assistant (self-developed AI coding agent)
62
+ ```
63
+
64
+ ## Example Workflow
65
+
66
+ ```bash
67
+ # 1. Start the chat interface
68
+ $ ufoo
69
+
70
+ # 2. Launch agents from chat
71
+ > /launch claude
72
+ > /launch ucode
73
+
74
+ # 3. Send tasks to agents
75
+ > @claude-1 Please analyze the current codebase structure
76
+ > @ucode-1 Fix the bug in authentication module
77
+
78
+ # 4. Agents communicate via bus
79
+ claude-1: Analysis complete. Found 3 areas needing refactoring...
80
+ ucode-1: Bug fixed. Running tests...
81
+
82
+ # 5. Check decisions made
83
+ > /decisions
29
84
  ```
30
85
 
31
86
  To import a local `pi-mono` checkout as a reference snapshot (reference-only):
@@ -52,17 +107,51 @@ ucode-core run-once --json
52
107
  ucode-core list --json
53
108
  ```
54
109
 
55
- Configure `ucode` provider/model/API in `.ufoo/config.json` (ufoo-managed):
110
+ ## Agent Configuration
111
+
112
+ Configure AI providers in `.ufoo/config.json`:
56
113
 
114
+ ### ucode Configuration (Self-developed Assistant)
57
115
  ```json
58
116
  {
59
- "ucodeProvider": "openai",
60
- "ucodeModel": "gpt-5.1-codex",
117
+ "ucodeProvider": "openai", // or "anthropic", "azure", etc.
118
+ "ucodeModel": "gpt-4-turbo-preview",
61
119
  "ucodeBaseUrl": "https://api.openai.com/v1",
62
120
  "ucodeApiKey": "sk-***"
63
121
  }
64
122
  ```
65
123
 
124
+ ### Claude Configuration
125
+ ```json
126
+ {
127
+ "claudeProvider": "claude-cli", // Uses Claude CLI
128
+ "claudeModel": "claude-3-opus" // or "claude-3-sonnet"
129
+ }
130
+ ```
131
+
132
+ ### Codex Configuration
133
+ ```json
134
+ {
135
+ "codexProvider": "codex-cli", // Uses Codex CLI
136
+ "codexModel": "gpt-4" // or "gpt-4-turbo-preview"
137
+ }
138
+ ```
139
+
140
+ ### Complete Example
141
+ ```json
142
+ {
143
+ "launchMode": "internal",
144
+ "ucodeProvider": "openai",
145
+ "ucodeModel": "gpt-4-turbo-preview",
146
+ "ucodeBaseUrl": "https://api.openai.com/v1",
147
+ "ucodeApiKey": "sk-***",
148
+ "claudeProvider": "claude-cli",
149
+ "claudeModel": "claude-3-opus",
150
+ "codexProvider": "codex-cli",
151
+ "codexModel": "gpt-4"
152
+ }
153
+ ```
154
+
66
155
  `ucode` writes these into a dedicated runtime directory (`.ufoo/agent/ucode/pi-agent`) and uses them for native planner/engine calls.
67
156
 
68
157
  ## Architecture
@@ -89,21 +178,43 @@ Bus state lives in `.ufoo/agent/all-agents.json` (metadata), `.ufoo/bus/*` (queu
89
178
 
90
179
  ## Commands
91
180
 
181
+ ### Core Commands
92
182
  | Command | Description |
93
183
  |---------|-------------|
184
+ | `ufoo` | Launch chat interface (default) |
185
+ | `ufoo chat` | Launch interactive multi-agent chat UI |
94
186
  | `ufoo init` | Initialize .ufoo in current project |
95
187
  | `ufoo status` | Show banner, unread bus messages, open decisions |
96
- | `ufoo daemon --start|--stop|--status` | Manage ufoo daemon |
97
- | `ufoo chat` | Launch ufoo chat UI (also default when no args) |
98
- | `ufoo resume [nickname]` | Resume agent sessions (optional nickname) |
99
- | `ufoo bus join` | Join event bus (auto by uclaude/ucodex/ucode) |
188
+ | `ufoo doctor` | Check installation health |
189
+
190
+ ### Agent Management
191
+ | Command | Description |
192
+ |---------|-------------|
193
+ | `ufoo daemon start` | Start ufoo daemon |
194
+ | `ufoo daemon stop` | Stop ufoo daemon |
195
+ | `ufoo daemon status` | Check daemon status |
196
+ | `ufoo resume [nickname]` | Resume agent sessions |
197
+
198
+ ### Event Bus
199
+ | Command | Description |
200
+ |---------|-------------|
201
+ | `ufoo bus join` | Join event bus (auto by agent wrappers) |
100
202
  | `ufoo bus send <id> <msg>` | Send message to agent |
101
203
  | `ufoo bus check <id>` | Check pending messages |
102
- | `ufoo bus status` | Show bus status |
204
+ | `ufoo bus status` | Show bus status and online agents |
205
+
206
+ ### Context & Decisions
207
+ | Command | Description |
208
+ |---------|-------------|
103
209
  | `ufoo ctx decisions -l` | List all decisions |
104
210
  | `ufoo ctx decisions -n 1` | Show latest decision |
211
+ | `ufoo ctx decisions new <title>` | Create new decision |
212
+
213
+ ### Skills
214
+ | Command | Description |
215
+ |---------|-------------|
105
216
  | `ufoo skills list` | List available skills |
106
- | `ufoo doctor` | Check installation health |
217
+ | `ufoo skills show <skill>` | Show skill details |
107
218
 
108
219
  Notes:
109
220
  - Claude CLI headless agent uses `--dangerously-skip-permissions`.
@@ -117,7 +228,7 @@ ufoo/
117
228
  โ”‚ โ”œโ”€โ”€ ufoo.js # Node wrapper
118
229
  โ”‚ โ”œโ”€โ”€ uclaude # Claude Code wrapper
119
230
  โ”‚ โ”œโ”€โ”€ ucodex # Codex wrapper
120
- โ”‚ โ””โ”€โ”€ ucode # ufoo core wrapper
231
+ โ”‚ โ””โ”€โ”€ ucode # ucode assistant wrapper
121
232
  โ”œโ”€โ”€ SKILLS/ # Global skills (uinit, ustatus)
122
233
  โ”œโ”€โ”€ src/
123
234
  โ”‚ โ”œโ”€โ”€ bus/ # Event bus implementation (JS)
@@ -152,6 +263,37 @@ your-project/
152
263
  โ””โ”€โ”€ CLAUDE.md # โ†’ AGENTS.md
153
264
  ```
154
265
 
266
+ ## Chat Interface
267
+
268
+ The interactive chat UI provides a centralized hub for agent management:
269
+
270
+ ### Features
271
+ - **Real-time Communication** - See all agent messages in one place
272
+ - **Agent Dashboard** - Monitor online status, session IDs, and nicknames
273
+ - **Direct Messaging** - Use `@agent-name` to target specific agents
274
+ - **Command Completion** - Tab completion for commands and agent names
275
+ - **Mouse Support** - Toggle with `Ctrl+M` for scrolling vs text selection
276
+ - **Session History** - Persistent message history across sessions
277
+
278
+ ### Keyboard Shortcuts
279
+ | Key | Action |
280
+ |-----|--------|
281
+ | `Tab` | Auto-complete commands/agents |
282
+ | `Ctrl+C` | Exit chat |
283
+ | `Ctrl+M` | Toggle mouse mode |
284
+ | `Ctrl+L` | Clear screen |
285
+ | `Ctrl+R` | Refresh agent list |
286
+ | `โ†‘/โ†“` | Navigate command history |
287
+
288
+ ### Chat Commands
289
+ | Command | Description |
290
+ |---------|-------------|
291
+ | `/help` | Show available commands |
292
+ | `/agents` | List online agents |
293
+ | `/clear` | Clear chat history |
294
+ | `/settings` | Configure chat preferences |
295
+ | `@agent-name <message>` | Send to specific agent |
296
+
155
297
  ## Agent Communication
156
298
 
157
299
  Agents communicate via the event bus:
@@ -178,9 +320,10 @@ Built-in skills triggered by slash commands:
178
320
 
179
321
  ## Requirements
180
322
 
181
- - macOS (for Terminal.app/iTerm2 injection features)
182
- - Node.js >= 18 (optional, for npm global install)
183
- - Bash 4+
323
+ - **macOS** - Required for Terminal.app/iTerm2 integration
324
+ - **Node.js >= 18** - For npm installation and JavaScript runtime
325
+ - **Bash 4+** - For shell scripts and command execution
326
+ - **Terminal** - iTerm2 or Terminal.app for agent launching
184
327
 
185
328
  ## Codex CLI Notes
186
329
 
@@ -195,21 +338,62 @@ ufoo chat # daemon auto-starts
195
338
 
196
339
  ## Development
197
340
 
341
+ ### Setup
198
342
  ```bash
199
- # Local development
200
- ./bin/ufoo --help
343
+ # Clone the repository
344
+ git clone https://github.com/Icyoung/ufoo.git
345
+ cd ufoo
346
+
347
+ # Install dependencies
348
+ npm install
201
349
 
202
- # Or via Node
350
+ # Link for local development
203
351
  npm link
204
- ufoo --help
352
+
353
+ # Run tests
354
+ npm test
205
355
  ```
206
356
 
357
+ ### Contributing
358
+ - Fork the repository
359
+ - Create a feature branch (`git checkout -b feature/amazing-feature`)
360
+ - Commit your changes (`git commit -m 'Add amazing feature'`)
361
+ - Push to the branch (`git push origin feature/amazing-feature`)
362
+ - Open a Pull Request
363
+
364
+ ### Project Structure
365
+ - `src/` - Core JavaScript implementation
366
+ - `bin/` - CLI entry points
367
+ - `modules/` - Modular features (bus, context, etc.)
368
+ - `test/` - Unit and integration tests
369
+ - `SKILLS/` - Agent skill definitions
370
+
207
371
  ## License
208
372
 
209
373
  UNLICENSED (Private)
210
374
 
211
375
  ## Recent Changes
212
376
 
377
+ ### ๐ŸŽจ UCode Branding & UI Improvements (2026-02-15)
378
+
379
+ Enhanced ucode agent branding consistency and fixed UI rendering issues:
380
+
381
+ **Features:**
382
+ - **Consistent Branding** - ucode agents now display as "ucode-1" instead of "ufoo-code-1"
383
+ - **Banner Normalization** - Agent type shows "ucode" in launch banners
384
+ - **UI Width Fix** - Resolved terminal width inconsistency causing background overflow
385
+ - **Immediate Prompt Display** - ucode TUI now shows incoming prompts instantly (not waiting for response)
386
+
387
+ **Technical Details:**
388
+ - `src/bus/nickname.js` - Maps ufoo-code โ†’ ucode for nickname generation
389
+ - `src/utils/banner.js` - Normalized agent type display
390
+ - `src/chat/layout.js` - Fixed blessed log component width handling
391
+ - `src/code/tui.js` - Added onMessageReceived callback for instant display
392
+
393
+ **Version:** v1.1.9
394
+
395
+ ---
396
+
213
397
  ### ๐Ÿš€ Smart Ready Detection & PTY Wrapper (2026-02-06)
214
398
 
215
399
  Added intelligent agent initialization detection for reliable probe injection:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
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",
@@ -4,6 +4,11 @@ const { runCliAgent } = require("./cliRunner");
4
4
  const { normalizeCliOutput } = require("./normalizeOutput");
5
5
  const { buildStatus } = require("../daemon/status");
6
6
  const { getUfooPaths } = require("../ufoo/paths");
7
+ const {
8
+ resolveRuntimeConfig,
9
+ resolveCompletionUrl,
10
+ resolveAnthropicMessagesUrl,
11
+ } = require("../code/nativeRunner");
7
12
 
8
13
  function loadSessionState(projectRoot) {
9
14
  const dir = getUfooPaths(projectRoot).agentDir;
@@ -153,6 +158,111 @@ function extractNickname(prompt) {
153
158
  return "";
154
159
  }
155
160
 
161
+ function isUcodeProvider(value = "") {
162
+ const text = String(value || "").trim().toLowerCase();
163
+ return text === "ucode" || text === "ufoo" || text === "ufoo-code";
164
+ }
165
+
166
+ function stripMarkdownFence(text = "") {
167
+ const raw = String(text || "").trim();
168
+ const match = raw.match(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/);
169
+ if (match) return match[1].trim();
170
+ return raw;
171
+ }
172
+
173
+ function clipText(value = "", maxChars = 500) {
174
+ const text = String(value || "");
175
+ if (text.length <= maxChars) return text;
176
+ return `${text.slice(0, maxChars)}...[truncated]`;
177
+ }
178
+
179
+ async function runNativeRouterCall({ projectRoot, prompt, systemPrompt, model: requestedModel, timeoutMs = 120000 }) {
180
+ const runtime = resolveRuntimeConfig({
181
+ workspaceRoot: projectRoot,
182
+ provider: "",
183
+ model: requestedModel,
184
+ });
185
+
186
+ const requestModel = String(runtime.model || "").trim();
187
+ if (!requestModel) {
188
+ return { ok: false, error: "ucode model is not configured" };
189
+ }
190
+
191
+ const isAnthropic = runtime.transport === "anthropic-messages";
192
+ const url = isAnthropic
193
+ ? resolveAnthropicMessagesUrl(runtime.baseUrl)
194
+ : resolveCompletionUrl(runtime.baseUrl);
195
+
196
+ if (!url) {
197
+ return { ok: false, error: "ucode baseUrl is not configured" };
198
+ }
199
+
200
+ const headers = { "content-type": "application/json" };
201
+ let body;
202
+
203
+ if (isAnthropic) {
204
+ headers["anthropic-version"] = "2023-06-01";
205
+ if (runtime.apiKey) headers["x-api-key"] = runtime.apiKey;
206
+ body = JSON.stringify({
207
+ model: requestModel,
208
+ max_tokens: 4096,
209
+ system: String(systemPrompt || ""),
210
+ messages: [{ role: "user", content: String(prompt || "") }],
211
+ temperature: 0,
212
+ });
213
+ } else {
214
+ if (runtime.apiKey) headers.authorization = `Bearer ${runtime.apiKey}`;
215
+ const messages = [];
216
+ if (systemPrompt) messages.push({ role: "system", content: String(systemPrompt) });
217
+ messages.push({ role: "user", content: String(prompt || "") });
218
+ body = JSON.stringify({
219
+ model: requestModel,
220
+ messages,
221
+ temperature: 0,
222
+ });
223
+ }
224
+
225
+ const controller = new AbortController();
226
+ const timer = setTimeout(() => { try { controller.abort(); } catch {} }, timeoutMs);
227
+
228
+ try {
229
+ const response = await fetch(url, {
230
+ method: "POST",
231
+ headers,
232
+ body,
233
+ signal: controller.signal,
234
+ });
235
+
236
+ if (!response.ok) {
237
+ const errBody = await response.text().catch(() => "");
238
+ return { ok: false, error: `provider request failed (${response.status}): ${clipText(errBody)}` };
239
+ }
240
+
241
+ const data = await response.json();
242
+
243
+ let text = "";
244
+ if (isAnthropic) {
245
+ const content = Array.isArray(data.content) ? data.content : [];
246
+ text = content
247
+ .filter((item) => item && item.type === "text")
248
+ .map((item) => String(item.text || ""))
249
+ .join("");
250
+ } else {
251
+ const choice = data.choices && data.choices[0];
252
+ text = choice && choice.message && typeof choice.message.content === "string"
253
+ ? choice.message.content
254
+ : "";
255
+ }
256
+
257
+ return { ok: true, output: text.trim() };
258
+ } catch (err) {
259
+ const message = err && err.message ? err.message : "native router call failed";
260
+ return { ok: false, error: message };
261
+ } finally {
262
+ clearTimeout(timer);
263
+ }
264
+ }
265
+
156
266
  async function runUfooAgent({ projectRoot, prompt, provider, model }) {
157
267
  const state = loadSessionState(projectRoot);
158
268
  const bus = loadBusSummary(projectRoot);
@@ -161,36 +271,57 @@ async function runUfooAgent({ projectRoot, prompt, provider, model }) {
161
271
  const historyPrompt = buildHistoryPrompt(history);
162
272
  const fullPrompt = historyPrompt ? `${historyPrompt}User: ${prompt}` : prompt;
163
273
 
164
- let res = await runCliAgent({
165
- provider,
166
- model,
167
- prompt: fullPrompt,
168
- systemPrompt,
169
- sessionId: state.data?.sessionId,
170
- disableSession: provider === "claude-cli",
171
- cwd: projectRoot,
172
- });
274
+ let res;
173
275
 
174
- if (!res.ok) {
175
- const msg = (res.error || "").toLowerCase();
176
- if (msg.includes("session id") || msg.includes("session-id") || msg.includes("already in use")) {
177
- res = await runCliAgent({
178
- provider,
179
- model,
180
- prompt: fullPrompt,
181
- systemPrompt,
182
- sessionId: undefined,
183
- disableSession: provider === "claude-cli",
184
- cwd: projectRoot,
185
- });
276
+ if (isUcodeProvider(provider)) {
277
+ // Native path: direct HTTP to LLM API, no CLI binary needed
278
+ res = await runNativeRouterCall({
279
+ projectRoot,
280
+ prompt: fullPrompt,
281
+ systemPrompt,
282
+ model,
283
+ });
284
+ if (!res.ok) {
285
+ return { ok: false, error: res.error };
186
286
  }
187
- }
287
+ // Native path returns { ok, output } where output is raw text
288
+ res = { ok: true, output: res.output, sessionId: "" };
289
+ } else {
290
+ // CLI path: spawn codex/claude binary
291
+ res = await runCliAgent({
292
+ provider,
293
+ model,
294
+ prompt: fullPrompt,
295
+ systemPrompt,
296
+ sessionId: state.data?.sessionId,
297
+ disableSession: provider === "claude-cli",
298
+ cwd: projectRoot,
299
+ });
188
300
 
189
- if (!res.ok) {
190
- return { ok: false, error: res.error };
301
+ if (!res.ok) {
302
+ const msg = (res.error || "").toLowerCase();
303
+ if (msg.includes("session id") || msg.includes("session-id") || msg.includes("already in use")) {
304
+ res = await runCliAgent({
305
+ provider,
306
+ model,
307
+ prompt: fullPrompt,
308
+ systemPrompt,
309
+ sessionId: undefined,
310
+ disableSession: provider === "claude-cli",
311
+ cwd: projectRoot,
312
+ });
313
+ }
314
+ }
315
+
316
+ if (!res.ok) {
317
+ return { ok: false, error: res.error };
318
+ }
191
319
  }
192
320
 
193
- const text = normalizeCliOutput(res.output);
321
+ const rawText = isUcodeProvider(provider)
322
+ ? String(res.output || "").trim()
323
+ : normalizeCliOutput(res.output);
324
+ const text = stripMarkdownFence(rawText);
194
325
  let payload = null;
195
326
  try {
196
327
  payload = JSON.parse(text);
@@ -214,7 +345,7 @@ async function runUfooAgent({ projectRoot, prompt, provider, model }) {
214
345
  saveSessionState(projectRoot, {
215
346
  provider,
216
347
  model,
217
- sessionId: res.sessionId,
348
+ sessionId: res.sessionId || "",
218
349
  updated_at: new Date().toISOString(),
219
350
  });
220
351
 
@@ -21,8 +21,8 @@ function computeAgentBar(options = {}) {
21
21
  let windowItems = Math.max(1, Math.min(maxAgentWindow, activeAgents.length));
22
22
  let start = agentListWindowStart;
23
23
  const ufooItem = focusMode === "dashboard" && selectedAgentIndex === 0
24
- ? "\x1b[90;7mufoo\x1b[0m"
25
- : "\x1b[36mufoo\x1b[0m";
24
+ ? "\x1b[90;7mucode\x1b[0m"
25
+ : "\x1b[36mucode\x1b[0m";
26
26
  const ufooLen = stripAnsi(ufooItem).length;
27
27
 
28
28
  const computeStart = (items) => {
@@ -60,7 +60,7 @@ function computeAgentBar(options = {}) {
60
60
  agentParts = visible.map((agent, i) => {
61
61
  const rawLabel = getAgentLabel(agent);
62
62
  const label = maxLabelLen ? truncateLabel(rawLabel, maxLabelLen) : rawLabel;
63
- const idx = s + i + 1; // +1 for ufoo at index 0
63
+ const idx = s + i + 1; // +1 for ucode at index 0
64
64
  if (focusMode === "dashboard" && idx === selectedAgentIndex) {
65
65
  return `\x1b[90;7m${label}\x1b[0m`;
66
66
  }
@@ -184,7 +184,7 @@ function createDashboardKeyController(options = {}) {
184
184
 
185
185
  if (key.name === "down") {
186
186
  state.dashboardView = "provider";
187
- state.selectedProviderIndex = state.agentProvider === "claude-cli" ? 1 : 0;
187
+ state.selectedProviderIndex = Math.max(0, (state.providerOptions || []).findIndex((opt) => opt.value === state.agentProvider));
188
188
  renderDashboardAndScreen();
189
189
  return true;
190
190
  }
@@ -3,13 +3,16 @@ const { clampAgentWindowWithSelection } = require("./agentDirectory");
3
3
  const DEFAULT_MODE_OPTIONS = ["terminal", "tmux", "internal"];
4
4
 
5
5
  function providerLabel(value) {
6
- return value === "claude-cli" ? "claude" : "codex";
6
+ if (value === "claude-cli") return "claude";
7
+ if (value === "ucode" || value === "ufoo" || value === "ufoo-code") return "ucode";
8
+ return "codex";
7
9
  }
8
10
 
9
11
  function assistantLabel(value) {
10
12
  if (value === "codex") return "codex";
11
13
  if (value === "claude") return "claude";
12
- if (value === "ufoo") return "ufoo";
14
+ if (value === "ufoo") return "ucode";
15
+ if (value === "ucode") return "ucode";
13
16
  return "auto";
14
17
  }
15
18
 
package/src/chat/index.js CHANGED
@@ -483,13 +483,14 @@ async function runChat(projectRoot) {
483
483
  const providerOptions = [
484
484
  { label: "codex", value: "codex-cli" },
485
485
  { label: "claude", value: "claude-cli" },
486
+ { label: "ucode", value: "ucode" },
486
487
  ];
487
- let selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
488
+ let selectedProviderIndex = Math.max(0, providerOptions.findIndex((opt) => opt.value === agentProvider));
488
489
  const assistantOptions = [
489
490
  { label: "auto", value: "auto" },
490
491
  { label: "codex", value: "codex" },
491
492
  { label: "claude", value: "claude" },
492
- { label: "ufoo", value: "ufoo" },
493
+ { label: "ucode", value: "ufoo" },
493
494
  ];
494
495
  let selectedAssistantIndex = Math.max(
495
496
  0,
@@ -871,6 +872,7 @@ async function runChat(projectRoot) {
871
872
  selectedAssistantIndex = value;
872
873
  },
873
874
  assistantOptions,
875
+ providerOptions,
874
876
  getAutoResume: () => autoResume,
875
877
  setAutoResumeState: (value) => {
876
878
  autoResume = value;
@@ -981,7 +983,7 @@ async function runChat(projectRoot) {
981
983
  agentListWindowStart = 0;
982
984
  clampAgentWindow();
983
985
  selectedModeIndex = launchMode === "internal" ? 2 : (launchMode === "tmux" ? 1 : 0);
984
- selectedProviderIndex = agentProvider === "claude-cli" ? 1 : 0;
986
+ selectedProviderIndex = Math.max(0, providerOptions.findIndex((opt) => opt.value === agentProvider));
985
987
  selectedAssistantIndex = Math.max(
986
988
  0,
987
989
  assistantOptions.findIndex((opt) => opt.value === assistantEngine)
@@ -58,7 +58,7 @@ function createChatLayout(options = {}) {
58
58
  tags: true,
59
59
  content: "",
60
60
  });
61
- const bannerText = `{bold}UFOO{/bold} ยท Multi-Agent Manager{|}v${version}`;
61
+ const bannerText = `{bold}UFOO{/bold} ยท Chat Manager{|}v${version}`;
62
62
  statusLine.setContent(bannerText);
63
63
 
64
64
  // Command completion panel
@@ -23,6 +23,7 @@ function createSettingsController(options = {}) {
23
23
  setAssistantEngineState = () => {},
24
24
  setSelectedAssistantIndex = () => {},
25
25
  assistantOptions = [],
26
+ providerOptions = [],
26
27
  getAutoResume = () => true,
27
28
  setAutoResumeState = () => {},
28
29
  setSelectedResumeIndex = () => {},
@@ -80,7 +81,7 @@ function createSettingsController(options = {}) {
80
81
  const next = normalizeAgentProvider(provider);
81
82
  if (next === getAgentProvider()) return false;
82
83
  setAgentProviderState(next);
83
- setSelectedProviderIndex(next === "claude-cli" ? 1 : 0);
84
+ setSelectedProviderIndex(Math.max(0, providerOptions.findIndex((opt) => opt.value === next)));
84
85
  saveConfig(projectRoot, { agentProvider: next });
85
86
  clearUfooAgentIdentity();
86
87
  logMessage("status", `{white-fg}โš™{/white-fg} ufoo-agent: ${providerLabel(next)}`);
package/src/code/tui.js CHANGED
@@ -700,9 +700,16 @@ function runUcodeTui({
700
700
 
701
701
  // Override _listener to support cursor-aware editing
702
702
  const origDone = input._done ? input._done.bind(input) : null;
703
+ let lastKeyRef = null;
703
704
  input._listener = function (ch, key) {
704
705
  const keyName = key && key.name;
705
706
 
707
+ // Dedup: blessed delivers the same key object via element 'keypress' event
708
+ // from both readInput's __listener binding and screen's focused.emit('keypress').
709
+ // Use object identity to skip the duplicate delivery.
710
+ if (key && key === lastKeyRef) return;
711
+ lastKeyRef = key || null;
712
+
706
713
  // Let enter/return/escape pass through to blessed key handlers
707
714
  if (keyName === "return" || keyName === "enter" || keyName === "escape") return;
708
715
 
package/src/config.js CHANGED
@@ -25,7 +25,9 @@ function normalizeLaunchMode(value) {
25
25
  }
26
26
 
27
27
  function normalizeAgentProvider(value) {
28
- return value === "claude-cli" ? "claude-cli" : "codex-cli";
28
+ if (value === "claude-cli") return "claude-cli";
29
+ if (value === "ucode" || value === "ufoo" || value === "ufoo-code") return "ucode";
30
+ return "codex-cli";
29
31
  }
30
32
 
31
33
  function normalizeAssistantEngine(value) {
@@ -494,7 +494,7 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain) {
494
494
  try {
495
495
  fs.writeFileSync(debugFile, `Attempting join at ${new Date().toISOString()}\n`, { flag: "a" });
496
496
  // Determine agent type based on provider configuration
497
- const agentType = provider === "codex-cli" ? "codex" : "claude-code";
497
+ const agentType = provider === "codex-cli" ? "codex" : (provider === "ucode" ? "ufoo-code" : "claude-code");
498
498
  // Use fixed ID "ufoo-agent" for daemon's bus identity with explicit nickname
499
499
  const sub = await eventBus.join("ufoo-agent", agentType, "ufoo-agent");
500
500
  if (!sub) {