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 +206 -28
- package/package.json +1 -1
- package/src/agent/ufooAgent.js +157 -26
- package/src/chat/agentBar.js +3 -3
- package/src/chat/dashboardKeyController.js +1 -1
- package/src/chat/dashboardView.js +5 -2
- package/src/chat/index.js +5 -3
- package/src/chat/layout.js +1 -1
- package/src/chat/settingsController.js +2 -1
- package/src/code/README.md +1 -9
- package/src/code/tui.js +7 -0
- package/src/config.js +3 -1
- package/src/daemon/index.js +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,86 @@
|
|
|
1
1
|
# ufoo
|
|
2
2
|
|
|
3
|
-
Multi-agent AI collaboration
|
|
3
|
+
๐ค Multi-agent AI collaboration framework for orchestrating Claude Code, OpenAI Codex, and custom AI agents.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/u-foo)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[](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
|
|
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
|
-
##
|
|
35
|
+
## Installation
|
|
15
36
|
|
|
16
37
|
```bash
|
|
17
|
-
#
|
|
18
|
-
|
|
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
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
64
|
+
## Example Workflow
|
|
32
65
|
|
|
33
66
|
```bash
|
|
34
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
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
|
|
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 #
|
|
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
|
|
182
|
-
- Node.js >= 18
|
|
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
|
-
#
|
|
200
|
-
|
|
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
|
-
#
|
|
344
|
+
# Link for local development
|
|
203
345
|
npm link
|
|
204
|
-
|
|
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
package/src/agent/ufooAgent.js
CHANGED
|
@@ -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
|
|
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 (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
|
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
|
|
package/src/chat/agentBar.js
CHANGED
|
@@ -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;
|
|
25
|
-
: "\x1b[
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 "
|
|
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 =
|
|
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: "
|
|
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 =
|
|
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)
|
package/src/chat/layout.js
CHANGED
|
@@ -58,7 +58,7 @@ function createChatLayout(options = {}) {
|
|
|
58
58
|
tags: true,
|
|
59
59
|
content: "",
|
|
60
60
|
});
|
|
61
|
-
const bannerText = `{bold}UFOO{/bold} ยท
|
|
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(
|
|
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/README.md
CHANGED
|
@@ -2,15 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`src/code/` is the self-developed core area for `ucode`.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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) {
|
package/src/daemon/index.js
CHANGED
|
@@ -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) {
|