u-foo 2.3.30 → 2.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +5 -1
  2. package/scripts/chat-app-smoke.js +30 -0
  3. package/scripts/ink-demo.js +23 -0
  4. package/scripts/ink-smoke.js +30 -0
  5. package/scripts/ucode-app-smoke.js +36 -0
  6. package/src/chat/commandExecutor.js +6 -2
  7. package/src/chat/daemonMessageRouter.js +9 -1
  8. package/src/chat/daemonTransport.js +2 -1
  9. package/src/chat/dashboardKeyController.js +0 -40
  10. package/src/chat/dashboardView.js +0 -20
  11. package/src/chat/index.js +9 -1
  12. package/src/chat/inputSubmitHandler.js +34 -0
  13. package/src/chat/projectCloseController.js +1 -1
  14. package/src/chat/shellCommand.js +42 -0
  15. package/src/chat/transport.js +16 -3
  16. package/src/cli.js +4 -3
  17. package/src/code/agent.js +4 -0
  18. package/src/code/nativeRunner.js +74 -0
  19. package/src/code/taskDecomposer.js +5 -4
  20. package/src/code/tui.js +73 -561
  21. package/src/daemon/index.js +169 -27
  22. package/src/daemon/ipcServer.js +23 -1
  23. package/src/daemon/promptRequest.js +6 -1
  24. package/src/daemon/run.js +11 -4
  25. package/src/projects/runtimes.js +1 -1
  26. package/src/ufoo/agentRegistryDiagnostics.js +43 -0
  27. package/src/ui/MIGRATION.md +382 -0
  28. package/src/ui/components/ChatApp.js +2950 -0
  29. package/src/ui/components/DashboardBar.js +417 -0
  30. package/src/ui/components/InkDemo.js +96 -0
  31. package/src/ui/components/MultilineInput.js +387 -0
  32. package/src/ui/components/UcodeApp.js +813 -0
  33. package/src/ui/components/agentMirror.js +725 -0
  34. package/src/ui/components/chatReducer.js +337 -0
  35. package/src/ui/format/index.js +997 -0
  36. package/src/ui/index.js +9 -0
  37. package/src/ui/runInk.js +57 -0
  38. package/src/utils/nodeExecutable.js +26 -0
@@ -0,0 +1,382 @@
1
+ # Ink TUI Migration Plan
2
+
3
+ Status: Ink is the default TUI for chat and ucode. The legacy blessed
4
+ renderer remains available with `UFOO_TUI=blessed` as a temporary fallback.
5
+
6
+ ## Why
7
+
8
+ The legacy chat, ucode and internal-agent TUIs all use blessed. Blessed is
9
+ an unmaintained imperative widget tree with no modern equivalent of React's
10
+ component model, and it's awkward to extend (manual layout math, manual
11
+ redraws, no useful test harness).
12
+
13
+ ink (the React-for-terminals library, what Claude Code, Codex CLI fronts
14
+ and the Gemini CLI all use) gives us declarative components, flexbox
15
+ layout, hooks, and proper isolation of pure logic from rendering.
16
+
17
+ ## Approach
18
+
19
+ - Ink is the default renderer; `UFOO_TUI=blessed` keeps the legacy path
20
+ available while fallback removal is evaluated separately.
21
+ - Pure helpers live in `src/ui/format/` and are shared by both, so behaviour
22
+ parity is enforced by test rather than copy/paste.
23
+ - Components live in `src/ui/components/`, written in plain JS via
24
+ `React.createElement` (no JSX, no build step) so jest stays vanilla.
25
+ - ink is loaded through `src/ui/runInk.js`, a thin CJS→ESM bridge so the
26
+ rest of the codebase stays CommonJS.
27
+
28
+ ## Progress
29
+
30
+ - **P0** ✅ ink + react deps, runtime bridge, `<InkDemo>` smoke harness,
31
+ pure helpers extracted to `src/ui/format/`.
32
+ - **P1** ✅ ucode TUI ported to ink.
33
+ - **P2** ✅ folded into P3.6 (internal agent view).
34
+ - **P3** ✅ chat TUI ported to ink. Daemon
35
+ connection, dashboard (5 views), tool-merge, status spinner, history,
36
+ agent selection, raw-PTY mirror and internal bus agent view are wired.
37
+ - **P4** ✅ parity close-out complete: full `commandExecutor` dispatch,
38
+ `daemonMessageRouter` callback coverage, persisted history, BUS streams,
39
+ transient agent state, loop summary, project rail switching, cron/settings
40
+ dashboard actions, completion popup, and default Ink entrypoints.
41
+
42
+ ## P1 ucode TUI — what's wired
43
+
44
+ | Feature | Status |
45
+ |---|---|
46
+ | Banner + version + session id header | ✅ |
47
+ | Scrolling `<Static>` log (1000 line cap) | ✅ |
48
+ | Multiline input (cursor math, Ctrl+A/E/B/F/D/H/K/U/W, Meta+B/F/D, `\\\n` continuation, Alt+Enter newline, CJK wrap) | ✅ |
49
+ | Up/Down history walk + agent-selection mode | ✅ |
50
+ | Ctrl+C exit, Ctrl+O expand last tool group | ✅ |
51
+ | Tool merge/freeze/expand state machine | ✅ |
52
+ | Spinner + phase status line (request/thinking/text/tool labels) | ✅ |
53
+ | Esc abort with `AbortController` and "Cancelling..." status | ✅ |
54
+ | Agents footer with single-line truncation + `+N more` hint | ✅ |
55
+ | `runSingleCommand` empty/exit/probe/help/error/tool/nl/ubus/resume/nl_bg kinds | ✅ |
56
+ | Background tasks ("BG x/y/z" suffix) | ✅ |
57
+ | ubus / resume / nl_bg branches | ✅ |
58
+ | autoBus polling | ✅ |
59
+
60
+ ## Real-TTY checklist
61
+
62
+ ```sh
63
+ ./bin/ucode.js
64
+ # fallback: UFOO_TUI=blessed ./bin/ucode.js
65
+ ```
66
+
67
+ ### Editor
68
+
69
+ - [ ] Type, see characters appear with the cursor on the next cell.
70
+ - [ ] Backspace deletes one cell back; cursor stays correct on CJK.
71
+ - [ ] Left/Right arrows move the cursor; resetting preferred col.
72
+ - [ ] Up/Down on a single line walks input history.
73
+ - [ ] Up/Down on a multiline value moves between visual rows.
74
+ - [ ] `\` followed by Enter inserts a newline; Enter alone submits.
75
+ - [ ] Alt+Enter inserts a newline.
76
+ - [ ] Ctrl+A / Ctrl+E jump to row start / end.
77
+ - [ ] Ctrl+W deletes the previous word (also Meta+Backspace).
78
+ - [ ] Long pasted text doesn't lock up the renderer.
79
+ - [ ] Resize the terminal — input frame and footer span the new width.
80
+
81
+ ### Status line
82
+
83
+ - [ ] Shows `UCODE · Ready` while idle.
84
+ - [ ] Shows a spinning indicator + phase ("Waiting for model...",
85
+ "Thinking...", "Generating response...", "Calling X...") during
86
+ `runNaturalLanguageTask`.
87
+ - [ ] Appends `(<elapsed> s, esc cancel)` when a task is in flight.
88
+ - [ ] Esc on a running task flips to "Cancelling..." then back to Ready.
89
+
90
+ ### Tool calls
91
+
92
+ - [ ] Single tool call renders one line (`· tool · detail`).
93
+ - [ ] Two+ consecutive tool calls collapse to one row + `(Ctrl+O expand)`.
94
+ - [ ] Ctrl+O expands the most recent group with `│`/`└` branch markers
95
+ and only fires once per group.
96
+ - [ ] When text arrives between tool calls, the previous group freezes
97
+ into the log and a fresh group starts on the next tool call.
98
+
99
+ ### Agents footer
100
+
101
+ - [ ] Shows `Agents: none │ No target agents` when nothing's online.
102
+ - [ ] Shows `Agents: @x @y ... │ ↓ select target · ←/→ switch` otherwise.
103
+ - [ ] At narrow widths, the row stays single-line, drops trailing chips
104
+ and emits ` +N more`.
105
+ - [ ] Down enters selection mode (first chip inverse). Left/Right cycle.
106
+ Up exits. Prompt prefix changes to `›@<name> ` when locked.
107
+
108
+ ### Smoke
109
+
110
+ - [ ] `node scripts/ucode-app-smoke.js` exits 0.
111
+ - [ ] `npx jest --silent` shows the pre-existing 5 OAuth failures only;
112
+ every ink suite passes.
113
+
114
+ ## Decision log
115
+
116
+ - **Don't fork ink.** Claude-code-fixed inlines a customised ink under
117
+ `src/ink/`; we don't need React 19 / ConcurrentRoot / IDE bridging,
118
+ so depending on the public `ink@5` keeps maintenance cost low.
119
+ - **Don't add JSX.** `React.createElement` keeps jest CJS happy and
120
+ avoids a build step. We can revisit if any single component grows
121
+ past ~600 lines and readability suffers.
122
+ - **Don't enable `--experimental-vm-modules` for jest.** The risk of
123
+ surprise ESM behaviour across the existing 1800-test suite is too
124
+ high. Render coverage stays in `scripts/*-smoke.js`; component logic
125
+ is exercised by pure-function tests.
126
+ - **Codex isn't a useful reference.** Its TUI is a Rust ratatui app
127
+ (`codex-rs/tui`), not React-based. The architectural principle worth
128
+ borrowing is its hard split between TUI and core protocol.
129
+
130
+ ## P2 dropped, folded into P3
131
+
132
+ The internal-agent view in chat is not an independent program — it's a
133
+ view mode owned by `src/chat/agentViewController.js`. Today it works by
134
+ detaching `screen.children`, writing raw `\x1b[2J` to stdout, and
135
+ flipping `screen.grabKeys`. There is no clean seam to mount an isolated
136
+ ink subtree inside a still-blessed chat host, so attempting P2 in
137
+ isolation would force us to build a stdout-arbitration layer we'd throw
138
+ away once chat itself is on ink. P3.6 ports the agent view as a chat
139
+ sub-mode instead.
140
+
141
+ ## P3 audit (chat TUI surface)
142
+
143
+ Source: `src/chat/index.js` (2215 lines) + ~30 controllers in
144
+ `src/chat/`. Highlights:
145
+
146
+ ### Lifecycle
147
+ - Public entrypoint `runChat(projectRoot, { globalMode })` from
148
+ `src/chat/index.js`. Wires `daemonCoordinator.connect()` then loops
149
+ forever; exit goes through `process.exit(0)` from a screen `destroy`
150
+ hook.
151
+ - Runners injected via closures: `daemonCoordinator.send`,
152
+ `executeCommand`, `inputSubmitHandler.handleSubmit`,
153
+ `daemonMessageRouter.handleMessage`.
154
+
155
+ ### View state machine
156
+ - `dashboardView` ∈ `projects | agents | mode | provider | cron`
157
+ - `focusMode` ∈ `input | dashboard` — toggled by Tab and arrow keys
158
+ - `globalMode` (boolean) + `globalScope` ∈ `controller | project` —
159
+ `globalMode=true` enables a multi-project rail; Esc/Enter walk the
160
+ scope ladder.
161
+ - `enterDashboardMode()` / `exitDashboardMode()` are the two transition
162
+ points; `setGlobalScope()` runs an async, debounced project switch.
163
+
164
+ ### Input
165
+ - Submit accepts `@mention`, `@target`, `/command`, plain text and
166
+ numeric disambiguation (when `pending.disambiguate` is set).
167
+ - Editor keys cover Ctrl+A/E/B/F/D/H/K/U/W, Meta+B/F/D, arrows
168
+ (cursor + history when empty), Esc (3-layer: clear @target →
169
+ exit project scope → cancel input), bracketed paste, Tab (toggle
170
+ dashboard), PgUp/PgDn (scroll log).
171
+ - Completion fires on `/` and `@` with sources from command registry,
172
+ group templates, solo profiles and agent mentions; Up arrow jumps to
173
+ the latest suggestion.
174
+ - History is per-project, persisted to `input-history.jsonl` with
175
+ draft restoration on project switch.
176
+
177
+ ### Daemon stack
178
+ - `daemonTransport` owns the socket path and retry policy.
179
+ - `daemonConnection` owns the queue + lifecycle (`connect`, `send`,
180
+ `requestStatus`, `close`, `markExit`, `switchConnection`,
181
+ `getState`).
182
+ - `daemonCoordinator` orchestrates project switches with a serialised
183
+ Promise chain.
184
+ - `daemonMessageRouter.handleMessage(msg)` is a stateless dispatcher
185
+ that turns daemon responses into log appends, dashboard updates, PTY
186
+ writes and transient agent state changes.
187
+ - `daemonReconnect.restartDaemonFlow()` provides a per-project lock for
188
+ daemon restarts.
189
+
190
+ ### Side controllers
191
+ - `cronScheduler` — `/cron start|stop|list` + the cron dashboard view.
192
+ - `settingsController` — launch mode / agent provider,
193
+ with daemon restart on mode/provider change. `autoResume` stays config/command-driven.
194
+ - `chatLogController` — log buffer + history file replay
195
+ (`loadHistory`, `appendHistory`, `markStreamStart`,
196
+ `setHistoryTarget`, `resetViewState`).
197
+ - `statusLineController` — debounced status line with background-task
198
+ suffix (e.g. `(ufoo-agent processing)`); `queueStatusLine`,
199
+ `resolveStatusLine`, `enqueueBusStatus`, `resolveBusStatus`.
200
+ - `streamTracker` — per-publisher stream state + markdown rendering
201
+ (`beginStream`, `appendStreamDelta`, `finalizeStream`,
202
+ `markPendingDelivery`, `consumePendingDelivery`).
203
+ - `transientAgentState` — TTL-bounded `working / waiting_input /
204
+ blocked` markers per agent.
205
+ - `projectCloseController` — `requestCloseProject(index)` runs daemon
206
+ stop + project switch.
207
+ - `agentDirectory` — agent label resolution + window clamping (pure).
208
+ - `internalAgentLogHistory` — bus log replay for internal agents.
209
+
210
+ ### Internal-agent sub-view (`agentViewController`, 1072 lines)
211
+ - Public API: `getCurrentView`, `getViewingAgent`,
212
+ `isAgentViewUsesBus`, `getAgentInputSuppressUntil`,
213
+ `get/setAgentOutputSuppressed`, `renderAgentDashboard`,
214
+ `setAgentBarVisible`, `enterAgentView(agentId, options)`,
215
+ `exitAgentView`, `sendRawToAgent`, `sendResizeToAgent`,
216
+ `requestAgentSnapshot`, `writeToAgentTerm`, `placeAgentCursor`,
217
+ `handleBusAgentKey`, `handleResizeInAgentView`, `refreshAgentView`.
218
+ - Two render modes inside it: PTY mirror (raw ANSI passthrough +
219
+ cursor placement, `agentSockets.connectOutput/Input`,
220
+ `requestSnapshot`) and an embedded bus subview (own input value,
221
+ cursor, log, animated status indicator).
222
+ - `agentBar.computeAgentBar` renders the agent strip across both
223
+ modes.
224
+ - `agentSockets.createAgentSockets` owns the PTY/bus socket
225
+ lifecycle.
226
+ - Exit/restore reattaches `screen.children`, restores scroll region
227
+ and unfreezes `screen.render`.
228
+
229
+ ### Layout (`createChatLayout`)
230
+ - 9 widgets: screen, logBox, statusLine, completionPanel, dashboard,
231
+ inputBottomLine, promptBox, input, inputTopLine.
232
+ - Geometry rules: dashboard 1-2 lines, input 5-9 lines (autosizes by
233
+ content), log fills the rest. Many `height: "100%-N"` strings →
234
+ ink flex layout in the port.
235
+
236
+ ### Commands
237
+ `/bus`, `/ctx`, `/daemon`, `/doctor`, `/cron`, `/group`, `/init`,
238
+ `/open`, `/launch`, `/project`, `/role`, `/solo`, `/settings`,
239
+ `/help`, plus nested subcommands (`/bus activate|list|rename|send|status`,
240
+ `/cron start|list|stop`, etc.). `commandExecutor.executeCommand(text)`
241
+ is the single dispatch point. `parseCommand(text)`, `parseAtTarget(text)`
242
+ and `shouldEchoCommandInChat(text)` are pure.
243
+
244
+ ### Cross-cutting
245
+ - `text.js`: `escapeBlessed`, `stripBlessedTags`, `stripAnsi`,
246
+ `truncateAnsi`, `decodeEscapedNewlines`. Pervasive — used by every
247
+ log message, status line and dashboard line. The blessed-tag
248
+ helpers (`escapeBlessed`/`stripBlessedTags`) become no-ops in ink;
249
+ the ANSI helpers stay relevant.
250
+ - `rawKeyMap.keyToRaw(ch, key)`: converts ink-style key events to
251
+ PTY bytes for the agent view. Stays as-is.
252
+ - `transport.js`: `startDaemon`, `stopDaemon`, `connectWithRetry`.
253
+ Framework-agnostic, no migration needed.
254
+
255
+ ### Migration concern shortlist (1 per section)
256
+ 1. **Entry**: 50+ `screen.render()` calls turn into React state
257
+ updates; build a `useReducer` so dispatchers are async-safe.
258
+ 2. **State machine**: `setGlobalScope` is async + debounced — model
259
+ it as an effect, not a setter.
260
+ 3. **Input**: cursor math already lives in `src/ui/format` (P0); we
261
+ reuse it.
262
+ 4. **Daemon**: keep `daemonConnection` / `daemonCoordinator` as-is;
263
+ wrap the message router in a `useEffect` subscription that pumps
264
+ into `dispatch`.
265
+ 5. **Side controllers**: keep the controllers as plain modules;
266
+ `useEffect` subscribes/unsubscribes instead of attaching to
267
+ blessed events.
268
+ 6. **Agent view**: ink can't render arbitrary ANSI inside a `<Box>`,
269
+ but it can yield stdout to a "raw mode" component that writes
270
+ straight through during PTY mirror; we'll model it with
271
+ `<Static>`-style raw write or by suspending ink's render and
272
+ passing stdout through, then re-mount on exit.
273
+ 7. **Layout**: replace `height: "100%-N"` with flexbox + `flexGrow`.
274
+ 8. **Commands**: long-running commands run on a serialised promise
275
+ chain like ucode's `runChainRef`.
276
+ 9. **Cross-cutting**: drop blessed-tag helpers from ink call sites;
277
+ ANSI/text helpers stay.
278
+
279
+ ### P3 phase plan
280
+
281
+ | Step | Goal | Status |
282
+ |---|---|---|
283
+ | P3.1 | This audit | ✅ |
284
+ | P3.2 | `UFOO_TUI=ink` switch in `runChat()` | ✅ |
285
+ | P3.3 | ChatApp shell (banner + log + input + status) | ✅ |
286
+ | P3.4 | Five dashboard views as React components | ✅ |
287
+ | P3.5 | Daemon connection + PROMPT/BUS_SEND wiring | ✅ |
288
+ | P3.6 | Raw-PTY internal agent view as a ChatApp mode | ✅ |
289
+ | P3.7 | Real-TTY checklist | ✅ |
290
+
291
+ ## P3 chat TUI — what's wired
292
+
293
+ | Feature | Status |
294
+ |---|---|
295
+ | Banner header (project + global mode + scope) | ✅ |
296
+ | Scrolling `<Static>` log (1000 line cap) | ✅ |
297
+ | Multiline input (P1 MultilineInput component) | ✅ |
298
+ | 5 dashboard views (projects/agents/mode/provider/cron) | ✅ |
299
+ | Tab toggles input/dashboard focus | ✅ |
300
+ | Up/Down history walk + agent selection mode | ✅ |
301
+ | Left/Right cycle agents while selected | ✅ |
302
+ | Spinner + phase status line | ✅ |
303
+ | Tool-merge state machine + Ctrl+O expand | ✅ |
304
+ | Daemon connect / send / status poll | ✅ |
305
+ | `PROMPT` for free text, `BUS_SEND` for `@target` | ✅ |
306
+ | `BUS_SEND_OK` / `RESPONSE` / `ERROR` / `STATUS` / `BUS` envelopes | ✅ |
307
+ | Raw PTY agent mirror (Enter on selected agent, Esc to leave) | ✅ |
308
+ | `daemonMessageRouter` (markdown streams, transient state, bus subview) | ✅ |
309
+ | `commandExecutor` full slash-command dispatch (`/cron`, `/group`, `/role`, `/settings` …) | ✅ |
310
+ | Slash + `@` autocomplete | ✅ |
311
+ | Input history persisted file load/save | ✅ |
312
+ | Cron dashboard actions | ✅ |
313
+ | Settings dashboard actions (launch mode, provider) | ✅ |
314
+
315
+ ## Real-TTY checklist for chat
316
+
317
+ ```sh
318
+ ./bin/ufoo.js chat # project mode
319
+ ./bin/ufoo.js chat --global # global controller mode
320
+ # fallback: UFOO_TUI=blessed ./bin/ufoo.js chat
321
+ ```
322
+
323
+ ### Layout
324
+ - [ ] Banner shows the active project + global/project tag.
325
+ - [ ] `Agents:` footer, status line above input, log fills the rest.
326
+ - [ ] Resize the terminal — input frame and footer stay single-line.
327
+
328
+ ### Input + history
329
+ - [ ] Type, Enter sends a `PROMPT`. Backspace, arrows, Ctrl+A/E etc.
330
+ behave the same as the ucode editor.
331
+ - [ ] Up/Down on an empty draft walks the in-memory history.
332
+ - [ ] `\` + Enter inserts a newline.
333
+ - [ ] Esc clears any active agent selection.
334
+
335
+ ### Daemon
336
+ - [ ] On launch the daemon spawns automatically (look for the socket
337
+ under `~/.ufoo` or your project's `.ufoo`).
338
+ - [ ] Send a free-text message — daemon answers, status flips to
339
+ `Working on task...` and back to Ready.
340
+ - [ ] Type `@<agent> hi` (or select with arrow keys) — message is
341
+ sent via `BUS_SEND`, ack arrives as `✓ Message delivered`.
342
+
343
+ ### Agents footer
344
+ - [ ] Tab into the dashboard, ↓ enters agent selection (first item
345
+ inverse), ←/→ cycles, ↑ exits.
346
+ - [ ] Enter on a selected agent attaches to its PTY (cleared screen +
347
+ scroll region + bottom hint bar). Esc returns to chat without
348
+ losing the previous draft or log.
349
+
350
+ ### Tool-merge
351
+ - [ ] Daemon-driven tool calls collapse and `(Ctrl+O expand)` works
352
+ the same as ucode.
353
+
354
+ ### Smoke
355
+ - [ ] `node scripts/chat-app-smoke.js` exits 0.
356
+ - [ ] `node scripts/ucode-app-smoke.js` exits 0.
357
+ - [ ] `npx jest --silent` shows the pre-existing 5 OAuth failures
358
+ only; every ink suite passes.
359
+
360
+ ## P4 close-out
361
+
362
+ - **STATUS handler fix** — chat now reads `msg.data.active` /
363
+ `msg.data.active_meta` / `msg.data.cron.tasks` so the agents and cron
364
+ counts in the footer actually update.
365
+ - **Slash command dispatch** — `createCommandExecutor` is wired in Ink
366
+ with daemon stop/start/restart, cron IPC, project switching and agent
367
+ activation callbacks.
368
+ - **Input history persistence** — `<projectRoot>/.ufoo/chat/input-history.jsonl`
369
+ is loaded on mount and appended on every submit; format matches the
370
+ blessed inputHistoryController.
371
+ - **Daemon message routing** — Ink routes daemon envelopes through
372
+ `daemonMessageRouter`, including BUS phase status, transient states,
373
+ pending delivery markers, streams, close/launch refreshes and loop
374
+ summary dashboard display.
375
+ - **Inline completion popup** — `/<prefix>` matches commands from
376
+ `COMMAND_REGISTRY`; `@<prefix>` matches the live agents list. Tab
377
+ accepts the top suggestion. Pure helper `buildCompletions` lives in
378
+ `src/ui/format` with full jest coverage.
379
+ - **Exit hygiene** — Ctrl+C now flushes `\x1b[2J\x1b[H` so the shell
380
+ prompt comes back to a clean screen instead of sitting under the
381
+ final ink frame; `runUcodeInkTui` returns `{ code: 0 }` so
382
+ `agent.js`'s `process.exit(res.code)` no longer crashes.