u-foo 1.1.9 โ†’ 1.2.1

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,37 +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)
29
62
  ```
30
63
 
31
- To import a local `pi-mono` checkout as a reference snapshot (reference-only):
64
+ ## Example Workflow
32
65
 
33
66
  ```bash
34
- npm run import:pi-mono -- /path/to/pi-mono
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
35
84
  ```
36
85
 
37
86
  Native self-developed implementation lives under `src/code`.
@@ -52,17 +101,51 @@ ucode-core run-once --json
52
101
  ucode-core list --json
53
102
  ```
54
103
 
55
- Configure `ucode` provider/model/API in `.ufoo/config.json` (ufoo-managed):
104
+ ## Agent Configuration
105
+
106
+ Configure AI providers in `.ufoo/config.json`:
56
107
 
108
+ ### ucode Configuration (Self-developed Assistant)
57
109
  ```json
58
110
  {
59
- "ucodeProvider": "openai",
60
- "ucodeModel": "gpt-5.1-codex",
111
+ "ucodeProvider": "openai", // or "anthropic", "azure", etc.
112
+ "ucodeModel": "gpt-4-turbo-preview",
61
113
  "ucodeBaseUrl": "https://api.openai.com/v1",
62
114
  "ucodeApiKey": "sk-***"
63
115
  }
64
116
  ```
65
117
 
118
+ ### Claude Configuration
119
+ ```json
120
+ {
121
+ "claudeProvider": "claude-cli", // Uses Claude CLI
122
+ "claudeModel": "claude-3-opus" // or "claude-3-sonnet"
123
+ }
124
+ ```
125
+
126
+ ### Codex Configuration
127
+ ```json
128
+ {
129
+ "codexProvider": "codex-cli", // Uses Codex CLI
130
+ "codexModel": "gpt-4" // or "gpt-4-turbo-preview"
131
+ }
132
+ ```
133
+
134
+ ### Complete Example
135
+ ```json
136
+ {
137
+ "launchMode": "internal",
138
+ "ucodeProvider": "openai",
139
+ "ucodeModel": "gpt-4-turbo-preview",
140
+ "ucodeBaseUrl": "https://api.openai.com/v1",
141
+ "ucodeApiKey": "sk-***",
142
+ "claudeProvider": "claude-cli",
143
+ "claudeModel": "claude-3-opus",
144
+ "codexProvider": "codex-cli",
145
+ "codexModel": "gpt-4"
146
+ }
147
+ ```
148
+
66
149
  `ucode` writes these into a dedicated runtime directory (`.ufoo/agent/ucode/pi-agent`) and uses them for native planner/engine calls.
67
150
 
68
151
  ## Architecture
@@ -89,21 +172,43 @@ Bus state lives in `.ufoo/agent/all-agents.json` (metadata), `.ufoo/bus/*` (queu
89
172
 
90
173
  ## Commands
91
174
 
175
+ ### Core Commands
92
176
  | Command | Description |
93
177
  |---------|-------------|
178
+ | `ufoo` | Launch chat interface (default) |
179
+ | `ufoo chat` | Launch interactive multi-agent chat UI |
94
180
  | `ufoo init` | Initialize .ufoo in current project |
95
181
  | `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) |
182
+ | `ufoo doctor` | Check installation health |
183
+
184
+ ### Agent Management
185
+ | Command | Description |
186
+ |---------|-------------|
187
+ | `ufoo daemon start` | Start ufoo daemon |
188
+ | `ufoo daemon stop` | Stop ufoo daemon |
189
+ | `ufoo daemon status` | Check daemon status |
190
+ | `ufoo resume [nickname]` | Resume agent sessions |
191
+
192
+ ### Event Bus
193
+ | Command | Description |
194
+ |---------|-------------|
195
+ | `ufoo bus join` | Join event bus (auto by agent wrappers) |
100
196
  | `ufoo bus send <id> <msg>` | Send message to agent |
101
197
  | `ufoo bus check <id>` | Check pending messages |
102
- | `ufoo bus status` | Show bus status |
198
+ | `ufoo bus status` | Show bus status and online agents |
199
+
200
+ ### Context & Decisions
201
+ | Command | Description |
202
+ |---------|-------------|
103
203
  | `ufoo ctx decisions -l` | List all decisions |
104
204
  | `ufoo ctx decisions -n 1` | Show latest decision |
205
+ | `ufoo ctx decisions new <title>` | Create new decision |
206
+
207
+ ### Skills
208
+ | Command | Description |
209
+ |---------|-------------|
105
210
  | `ufoo skills list` | List available skills |
106
- | `ufoo doctor` | Check installation health |
211
+ | `ufoo skills show <skill>` | Show skill details |
107
212
 
108
213
  Notes:
109
214
  - Claude CLI headless agent uses `--dangerously-skip-permissions`.
@@ -117,7 +222,7 @@ ufoo/
117
222
  โ”‚ โ”œโ”€โ”€ ufoo.js # Node wrapper
118
223
  โ”‚ โ”œโ”€โ”€ uclaude # Claude Code wrapper
119
224
  โ”‚ โ”œโ”€โ”€ ucodex # Codex wrapper
120
- โ”‚ โ””โ”€โ”€ ucode # ufoo core wrapper
225
+ โ”‚ โ””โ”€โ”€ ucode # ucode assistant wrapper
121
226
  โ”œโ”€โ”€ SKILLS/ # Global skills (uinit, ustatus)
122
227
  โ”œโ”€โ”€ src/
123
228
  โ”‚ โ”œโ”€โ”€ bus/ # Event bus implementation (JS)
@@ -152,6 +257,37 @@ your-project/
152
257
  โ””โ”€โ”€ CLAUDE.md # โ†’ AGENTS.md
153
258
  ```
154
259
 
260
+ ## Chat Interface
261
+
262
+ The interactive chat UI provides a centralized hub for agent management:
263
+
264
+ ### Features
265
+ - **Real-time Communication** - See all agent messages in one place
266
+ - **Agent Dashboard** - Monitor online status, session IDs, and nicknames
267
+ - **Direct Messaging** - Use `@agent-name` to target specific agents
268
+ - **Command Completion** - Tab completion for commands and agent names
269
+ - **Mouse Support** - Toggle with `Ctrl+M` for scrolling vs text selection
270
+ - **Session History** - Persistent message history across sessions
271
+
272
+ ### Keyboard Shortcuts
273
+ | Key | Action |
274
+ |-----|--------|
275
+ | `Tab` | Auto-complete commands/agents |
276
+ | `Ctrl+C` | Exit chat |
277
+ | `Ctrl+M` | Toggle mouse mode |
278
+ | `Ctrl+L` | Clear screen |
279
+ | `Ctrl+R` | Refresh agent list |
280
+ | `โ†‘/โ†“` | Navigate command history |
281
+
282
+ ### Chat Commands
283
+ | Command | Description |
284
+ |---------|-------------|
285
+ | `/help` | Show available commands |
286
+ | `/agents` | List online agents |
287
+ | `/clear` | Clear chat history |
288
+ | `/settings` | Configure chat preferences |
289
+ | `@agent-name <message>` | Send to specific agent |
290
+
155
291
  ## Agent Communication
156
292
 
157
293
  Agents communicate via the event bus:
@@ -178,9 +314,10 @@ Built-in skills triggered by slash commands:
178
314
 
179
315
  ## Requirements
180
316
 
181
- - macOS (for Terminal.app/iTerm2 injection features)
182
- - Node.js >= 18 (optional, for npm global install)
183
- - Bash 4+
317
+ - **macOS** - Required for Terminal.app/iTerm2 integration
318
+ - **Node.js >= 18** - For npm installation and JavaScript runtime
319
+ - **Bash 4+** - For shell scripts and command execution
320
+ - **Terminal** - iTerm2 or Terminal.app for agent launching
184
321
 
185
322
  ## Codex CLI Notes
186
323
 
@@ -195,21 +332,62 @@ ufoo chat # daemon auto-starts
195
332
 
196
333
  ## Development
197
334
 
335
+ ### Setup
198
336
  ```bash
199
- # Local development
200
- ./bin/ufoo --help
337
+ # Clone the repository
338
+ git clone https://github.com/Icyoung/ufoo.git
339
+ cd ufoo
340
+
341
+ # Install dependencies
342
+ npm install
201
343
 
202
- # Or via Node
344
+ # Link for local development
203
345
  npm link
204
- ufoo --help
346
+
347
+ # Run tests
348
+ npm test
205
349
  ```
206
350
 
351
+ ### Contributing
352
+ - Fork the repository
353
+ - Create a feature branch (`git checkout -b feature/amazing-feature`)
354
+ - Commit your changes (`git commit -m 'Add amazing feature'`)
355
+ - Push to the branch (`git push origin feature/amazing-feature`)
356
+ - Open a Pull Request
357
+
358
+ ### Project Structure
359
+ - `src/` - Core JavaScript implementation
360
+ - `bin/` - CLI entry points
361
+ - `modules/` - Modular features (bus, context, etc.)
362
+ - `test/` - Unit and integration tests
363
+ - `SKILLS/` - Agent skill definitions
364
+
207
365
  ## License
208
366
 
209
367
  UNLICENSED (Private)
210
368
 
211
369
  ## Recent Changes
212
370
 
371
+ ### ๐ŸŽจ UCode Branding & UI Improvements (2026-02-15)
372
+
373
+ Enhanced ucode agent branding consistency and fixed UI rendering issues:
374
+
375
+ **Features:**
376
+ - **Consistent Branding** - ucode agents now display as "ucode-1" instead of "ufoo-code-1"
377
+ - **Banner Normalization** - Agent type shows "ucode" in launch banners
378
+ - **UI Width Fix** - Resolved terminal width inconsistency causing background overflow
379
+ - **Immediate Prompt Display** - ucode TUI now shows incoming prompts instantly (not waiting for response)
380
+
381
+ **Technical Details:**
382
+ - `src/bus/nickname.js` - Maps ufoo-code โ†’ ucode for nickname generation
383
+ - `src/utils/banner.js` - Normalized agent type display
384
+ - `src/chat/layout.js` - Fixed blessed log component width handling
385
+ - `src/code/tui.js` - Added onMessageReceived callback for instant display
386
+
387
+ **Version:** v1.1.9
388
+
389
+ ---
390
+
213
391
  ### ๐Ÿš€ Smart Ready Detection & PTY Wrapper (2026-02-06)
214
392
 
215
393
  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.1",
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)}`);
@@ -2,15 +2,7 @@
2
2
 
3
3
  `src/code/` is the self-developed core area for `ucode`.
4
4
 
5
- ## Reference Repo (pi-mono)
6
-
7
- - Local reference repo path: `/Users/icy/Code/pi-mono`
8
- - Upstream reference URL: `https://github.com/badlogic/pi-mono`
9
- - Primary reference package: `https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent`
10
-
11
- Note:
12
- - `pi-mono` is reference-only for architecture and behavior alignment.
13
- - `ucode` NL core and TUI/runtime orchestration are implemented in `src/code/`.
5
+ `ucode` NL core and TUI/runtime orchestration are implemented in `src/code/`.
14
6
 
15
7
  ## Current native scope (phase 1)
16
8
 
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) {