u-foo 3.0.9 → 3.0.11

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 (65) hide show
  1. package/README.md +25 -9
  2. package/README.zh-CN.md +22 -9
  3. package/dist/tui/darwin-arm64/ufoo-tui +0 -0
  4. package/dist/tui/darwin-x64/ufoo-tui +0 -0
  5. package/dist/tui/linux-arm64/ufoo-tui +0 -0
  6. package/dist/tui/linux-x64/ufoo-tui +0 -0
  7. package/package.json +12 -4
  8. package/scripts/pack-tui.js +112 -0
  9. package/scripts/postinstall.js +11 -0
  10. package/src/agents/activity/activityReconcile.js +106 -0
  11. package/src/agents/activity/activityStatePublisher.js +31 -2
  12. package/src/agents/activity/index.js +1 -0
  13. package/src/agents/launch/launcher.js +19 -0
  14. package/src/agents/launch/ptyRunner.js +20 -1
  15. package/src/app/chat/ChatController.js +433 -0
  16. package/src/app/chat/agentDirectory.js +63 -0
  17. package/src/app/chat/agentEnter.js +70 -0
  18. package/src/app/chat/agentIdentity.js +50 -0
  19. package/src/app/chat/bootstrap.js +66 -0
  20. package/src/app/chat/commandExecutor.js +108 -0
  21. package/src/app/chat/commands.js +38 -1
  22. package/src/app/chat/dashboardView.js +6 -2
  23. package/src/app/chat/historyStore.js +181 -0
  24. package/src/app/chat/index.js +14 -2
  25. package/src/app/chat/inputSubmitHandler.js +21 -7
  26. package/src/app/chat/ipcBuilders.js +52 -0
  27. package/src/app/chat/multiWindow/paneManager.js +10 -1
  28. package/src/app/chat/multiWindow/renderer.js +1 -1
  29. package/src/app/chat/multiWindow/vtFrame.js +93 -0
  30. package/src/app/chat/streamState.js +182 -0
  31. package/src/app/cli/features/doctor.js +22 -0
  32. package/src/code/UcodeController.js +156 -0
  33. package/src/code/context/planGraphService.js +4 -0
  34. package/src/code/repl.js +4 -3
  35. package/src/code/runtime/taskLoop.js +46 -50
  36. package/src/code/tui.js +13 -2
  37. package/src/code/ucodeSlashDispatch.js +241 -0
  38. package/src/coordination/bus/activate.js +3 -0
  39. package/src/runtime/contracts/schemas/ufoo-ui-v1/envelope.json +28 -0
  40. package/src/runtime/contracts/uiProtocol.js +190 -0
  41. package/src/ui/{ink/chatLogModel.js → chatLogModel.js} +2 -2
  42. package/src/ui/dashboardBridge.js +81 -0
  43. package/src/ui/format/index.js +2 -2
  44. package/src/ui/index.js +8 -4
  45. package/src/ui/multiPaneBusMirror.js +137 -0
  46. package/src/ui/multiWindowHandoff.js +232 -0
  47. package/src/ui/ptyHandoff.js +23 -0
  48. package/src/ui/rustChatHost.js +1520 -0
  49. package/src/ui/rustMultiSession.js +497 -0
  50. package/src/ui/rustUcodeHost.js +999 -0
  51. package/src/ui/scrollbackReplay.js +82 -0
  52. package/src/ui/settingsBridge.js +49 -0
  53. package/src/ui/toolMergeBridge.js +66 -0
  54. package/src/ui/tuiLauncher.js +105 -0
  55. package/src/ui/ucodeStatusLine.js +74 -0
  56. package/src/ui/uiHostServer.js +339 -0
  57. package/src/ui/MIGRATION.md +0 -334
  58. package/src/ui/ink/ChatApp.js +0 -4152
  59. package/src/ui/ink/DashboardBar.js +0 -691
  60. package/src/ui/ink/InkDemo.js +0 -96
  61. package/src/ui/ink/MultilineInput.js +0 -662
  62. package/src/ui/ink/UcodeApp.js +0 -1675
  63. package/src/ui/ink/agentMirror.js +0 -730
  64. package/src/ui/ink/chatReducer.js +0 -473
  65. package/src/ui/runInk.js +0 -66
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Mirror inbound bus events into multi/side internal panes.
5
+ * Rust host uses this so pane VT stays live while the main transcript
6
+ * router still runs.
7
+ */
8
+
9
+ function parseInternalBusPayload(raw = "") {
10
+ let displayMessage = String(raw || "");
11
+ let streamPayload = null;
12
+ try {
13
+ const parsed = JSON.parse(raw);
14
+ if (parsed && typeof parsed === "object" && parsed.reply) {
15
+ displayMessage = parsed.reply;
16
+ } else if (parsed && typeof parsed === "object" && parsed.stream) {
17
+ streamPayload = parsed;
18
+ }
19
+ } catch {
20
+ // Plain text.
21
+ }
22
+ return {
23
+ displayMessage: String(displayMessage || "")
24
+ .replace(/\\r\\n/g, "\n")
25
+ .replace(/\\n/g, "\n")
26
+ .replace(/\\r/g, "\n"),
27
+ streamPayload,
28
+ };
29
+ }
30
+
31
+ function internalStatusLabel(value = "") {
32
+ const state = String(value || "").trim().toLowerCase();
33
+ if (state === "waiting" || state === "waiting_input") return "waiting";
34
+ if (state === "blocked" || state === "error") return "blocked";
35
+ if (state === "busy" || state === "processing" || state === "working") return "working";
36
+ if (state === "idle" || state === "ready") return "ready";
37
+ return state || "ready";
38
+ }
39
+
40
+ function buildAgentAliases(agentId, getMeta = () => ({})) {
41
+ const meta = (() => {
42
+ try { return getMeta(agentId) || {}; } catch { return {}; }
43
+ })();
44
+ return new Set([
45
+ agentId,
46
+ meta.nickname,
47
+ meta.scoped_nickname,
48
+ meta.display_nickname,
49
+ meta.fullId,
50
+ meta.id,
51
+ ].filter(Boolean).map(String));
52
+ }
53
+
54
+ /**
55
+ * @param {object} data — bus event payload (msg.data)
56
+ * @param {object} options
57
+ * @param {Iterable<string>|Set<string>} options.agentIds — internal pane agents to mirror
58
+ * @param {(agentId: string) => object} [options.getMeta]
59
+ * @param {(agentId: string, text: string) => boolean|void} options.writeToPane
60
+ * @returns {boolean} true if at least one pane consumed the event
61
+ */
62
+ function writeMultiPaneBusEvent(data = {}, options = {}) {
63
+ const {
64
+ agentIds = [],
65
+ getMeta = () => ({}),
66
+ writeToPane = () => false,
67
+ } = options;
68
+
69
+ const watched = agentIds instanceof Set
70
+ ? agentIds
71
+ : new Set([...agentIds].map((id) => String(id || "").trim()).filter(Boolean));
72
+ if (!watched.size || typeof writeToPane !== "function") return false;
73
+
74
+ let handled = false;
75
+ for (const agentId of watched) {
76
+ const aliases = buildAgentAliases(agentId, getMeta);
77
+ const publisher = String(data.publisher || (data.event === "broadcast" ? "broadcast" : "bus"));
78
+ const target = String(data.target || data.subscriber || "");
79
+ const fromAgent = aliases.has(publisher);
80
+ const toAgent = aliases.has(target) || aliases.has(String(data.subscriber || ""));
81
+ if (!fromAgent && !toAgent) continue;
82
+
83
+ if (data.silent) {
84
+ handled = true;
85
+ continue;
86
+ }
87
+ // Echo of chat→agent sends already shown as local "> " input.
88
+ if ((data.source === "chat-internal-agent-view" || data.source === "rust-multi-window"
89
+ || data.source === "chat-direct") && toAgent && !fromAgent) {
90
+ handled = true;
91
+ continue;
92
+ }
93
+ if (data.event === "activity_state_changed") {
94
+ const state = internalStatusLabel(data.state || data.activity_state || "");
95
+ const detail = String(data.detail || (data.data && data.data.detail) || data.message || "").trim();
96
+ try {
97
+ writeToPane(agentId, `\r\n[${state}${detail ? ` · ${detail}` : ""}]\r\n`);
98
+ } catch { /* ignore */ }
99
+ handled = true;
100
+ continue;
101
+ }
102
+
103
+ const { displayMessage, streamPayload } = parseInternalBusPayload(data.message || "");
104
+ if (streamPayload) {
105
+ if (!fromAgent) {
106
+ handled = true;
107
+ continue;
108
+ }
109
+ const delta = typeof streamPayload.delta === "string"
110
+ ? streamPayload.delta.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\r/g, "\n")
111
+ : "";
112
+ try {
113
+ if (delta) writeToPane(agentId, delta);
114
+ if (streamPayload.done) writeToPane(agentId, "\r\n");
115
+ } catch { /* ignore */ }
116
+ handled = true;
117
+ continue;
118
+ }
119
+ if (!displayMessage) {
120
+ handled = true;
121
+ continue;
122
+ }
123
+ const prefix = fromAgent ? "* " : "> ";
124
+ try {
125
+ writeToPane(agentId, `${prefix}${displayMessage.replace(/\n/g, "\r\n ")}\r\n`);
126
+ } catch { /* ignore */ }
127
+ handled = true;
128
+ }
129
+ return handled;
130
+ }
131
+
132
+ module.exports = {
133
+ parseInternalBusPayload,
134
+ internalStatusLabel,
135
+ buildAgentAliases,
136
+ writeMultiPaneBusEvent,
137
+ };
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Multi-window handoff after Rust TUI suspend (exit 75, reason=multi).
5
+ * Reuses createMultiWindowController (same VT/pane code as Ink).
6
+ */
7
+
8
+ const { createMultiWindowController } = require("../app/chat/multiWindow");
9
+ const { resolveAgentEnterRequest, resolveInjectSockPathForAgent } = require("../app/chat/agentEnter");
10
+ const { restoreStdinAfterHandoff } = require("./ptyHandoff");
11
+
12
+ function agentIdsFromMeta(activeAgentMeta) {
13
+ if (activeAgentMeta instanceof Map) {
14
+ return Array.from(activeAgentMeta.keys()).map(String).filter(Boolean);
15
+ }
16
+ if (activeAgentMeta && typeof activeAgentMeta === "object") {
17
+ return Object.keys(activeAgentMeta);
18
+ }
19
+ return [];
20
+ }
21
+
22
+ function labelForAgent(activeAgentMeta, agentId) {
23
+ const meta = activeAgentMeta instanceof Map
24
+ ? activeAgentMeta.get(agentId)
25
+ : (activeAgentMeta && activeAgentMeta[agentId]);
26
+ if (!meta || typeof meta !== "object") return agentId;
27
+ return String(meta.display_nickname || meta.nickname || meta.scoped_nickname || agentId);
28
+ }
29
+
30
+ /**
31
+ * @returns {Promise<{ ok: boolean, error?: string }>}
32
+ */
33
+ async function runMultiWindowHandoff({
34
+ projectRoot = process.cwd(),
35
+ activeAgentMeta = new Map(),
36
+ settings = {},
37
+ getChatLogLines = () => [],
38
+ getStatusText = () => "",
39
+ getDashboardLines = () => [],
40
+ onInternalSubmit = null,
41
+ stdin = process.stdin,
42
+ stdout = process.stdout,
43
+ } = {}) {
44
+ const agents = agentIdsFromMeta(activeAgentMeta);
45
+ if (agents.length === 0) {
46
+ return { ok: false, error: "No active agents for multi-window mode" };
47
+ }
48
+
49
+ let terminalFocused = false;
50
+ let done = false;
51
+ let resolveDone = null;
52
+ const finished = new Promise((resolve) => {
53
+ resolveDone = resolve;
54
+ });
55
+
56
+ const originalWrite = stdout.write.bind(stdout);
57
+ const controller = createMultiWindowController({
58
+ processStdout: {
59
+ write: originalWrite,
60
+ rows: stdout.rows,
61
+ columns: stdout.columns,
62
+ },
63
+ getRows: () => stdout.rows || 24,
64
+ getCols: () => stdout.columns || 80,
65
+ getInjectSockPath: (agentId) => resolveInjectSockPathForAgent(projectRoot, agentId),
66
+ getActiveAgents: () => agentIdsFromMeta(activeAgentMeta),
67
+ getAgentPaneOptions: (agentId) => {
68
+ const enterRequest = resolveAgentEnterRequest({
69
+ agentId,
70
+ projectRoot,
71
+ activeAgentMeta,
72
+ settings,
73
+ });
74
+ if (!enterRequest || !enterRequest.useBus) return { mode: "socket" };
75
+ let initialLines = [];
76
+ try {
77
+ const { loadInternalAgentLogHistory } = require("../app/chat/internalAgentLogHistory");
78
+ initialLines = loadInternalAgentLogHistory(projectRoot, agentId, {
79
+ maxEvents: 200,
80
+ maxLines: 200,
81
+ });
82
+ } catch {
83
+ initialLines = [];
84
+ }
85
+ return {
86
+ mode: "internal",
87
+ initialLines: [
88
+ `ufoo internal agent · ${labelForAgent(activeAgentMeta, agentId)}`,
89
+ `agent: ${agentId}`,
90
+ "",
91
+ ...initialLines,
92
+ ],
93
+ };
94
+ },
95
+ getChatLogLines,
96
+ getStatusText,
97
+ getPromptPrefix: () => "› ",
98
+ getCurrentDraft: () => "",
99
+ getCursorPos: () => 0,
100
+ getCompletions: () => ({ items: [], index: -1, windowStart: 0, pageSize: 8 }),
101
+ getAgentLabel: (id) => labelForAgent(activeAgentMeta, id),
102
+ getInternalPaneInfo: (id) => {
103
+ const meta = activeAgentMeta instanceof Map
104
+ ? activeAgentMeta.get(id)
105
+ : (activeAgentMeta && activeAgentMeta[id]);
106
+ return {
107
+ status: String((meta && meta.activity_state) || ""),
108
+ detail: String((meta && meta.activity_detail) || ""),
109
+ input: "",
110
+ cursor: 0,
111
+ };
112
+ },
113
+ getDashboardLines,
114
+ getTerminalFocused: () => terminalFocused,
115
+ freezeScreen: (frozen) => {
116
+ if (frozen) stdout.write = () => true;
117
+ else stdout.write = originalWrite;
118
+ },
119
+ restoreTerminal: () => {
120
+ const rows = stdout.rows || 24;
121
+ originalWrite(`\x1b[1;${rows}r`);
122
+ originalWrite("\x1b[2J\x1b[H");
123
+ },
124
+ onInternalSubmit: (agentId, message) => {
125
+ if (typeof onInternalSubmit === "function") {
126
+ onInternalSubmit(agentId, message);
127
+ }
128
+ },
129
+ onExit: () => {
130
+ done = true;
131
+ if (typeof resolveDone === "function") resolveDone();
132
+ },
133
+ });
134
+
135
+ if (!controller.enter()) {
136
+ return { ok: false, error: "No active agents for multi-window mode" };
137
+ }
138
+
139
+ const wasRaw = typeof stdin.isRaw === "boolean" ? stdin.isRaw : false;
140
+ try {
141
+ if (typeof stdin.setRawMode === "function" && stdin.isTTY) {
142
+ stdin.setRawMode(true);
143
+ }
144
+ if (typeof stdin.resume === "function") stdin.resume();
145
+ } catch {
146
+ // ignore
147
+ }
148
+
149
+ const onResize = () => {
150
+ try {
151
+ controller.handleResize();
152
+ } catch {
153
+ // ignore
154
+ }
155
+ };
156
+ if (stdout && typeof stdout.on === "function") {
157
+ stdout.on("resize", onResize);
158
+ }
159
+
160
+ const onData = (chunk) => {
161
+ if (done) return;
162
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk || ""), "utf8");
163
+ if (buf.length === 0) return;
164
+
165
+ // Ctrl+C → exit multi
166
+ if (buf.includes(0x03)) {
167
+ controller.exit();
168
+ return;
169
+ }
170
+ // Ctrl+Q → exit multi
171
+ if (buf.includes(0x11)) {
172
+ controller.handleKey({ name: "q", ctrl: true, sequence: "" });
173
+ terminalFocused = false;
174
+ return;
175
+ }
176
+ // Ctrl+W → cycle focus (chat chrome ↔ agent panes)
177
+ if (buf.includes(0x17)) {
178
+ const ids = controller.getAgentIds();
179
+ if (ids.length === 0) return;
180
+ if (!terminalFocused) {
181
+ controller.focusAgent(ids[0]);
182
+ terminalFocused = true;
183
+ } else {
184
+ const current = controller.getFocused();
185
+ const idx = current ? ids.indexOf(current) : -1;
186
+ if (idx >= 0 && idx < ids.length - 1) {
187
+ controller.focusAgent(ids[idx + 1]);
188
+ } else {
189
+ terminalFocused = false;
190
+ controller.focusAgent(ids[0]);
191
+ }
192
+ }
193
+ controller.renderAll();
194
+ return;
195
+ }
196
+
197
+ if (terminalFocused) {
198
+ controller.sendInput(buf.toString("utf8"));
199
+ }
200
+ };
201
+
202
+ stdin.on("data", onData);
203
+
204
+ try {
205
+ await finished;
206
+ return { ok: true };
207
+ } finally {
208
+ stdin.removeListener("data", onData);
209
+ if (stdout && typeof stdout.off === "function") {
210
+ stdout.off("resize", onResize);
211
+ } else if (stdout && typeof stdout.removeListener === "function") {
212
+ stdout.removeListener("resize", onResize);
213
+ }
214
+ try {
215
+ if (controller.isActive()) controller.exit();
216
+ } catch {
217
+ // ignore
218
+ }
219
+ try {
220
+ if (typeof stdin.setRawMode === "function" && stdin.isTTY) {
221
+ stdin.setRawMode(wasRaw);
222
+ }
223
+ } catch {
224
+ // ignore
225
+ }
226
+ restoreStdinAfterHandoff(stdin);
227
+ }
228
+ }
229
+
230
+ module.exports = {
231
+ runMultiWindowHandoff,
232
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Stdin restore helpers after any fullscreen handoff (e.g. legacy multi).
5
+ * Agent PTY mirror handoff has been removed — activate / side / multi only.
6
+ */
7
+
8
+ function restoreStdinAfterHandoff(stdin = process.stdin) {
9
+ try {
10
+ if (stdin && typeof stdin.setRawMode === "function" && stdin.isTTY) {
11
+ stdin.setRawMode(false);
12
+ }
13
+ if (stdin && typeof stdin.resume === "function") {
14
+ stdin.resume();
15
+ }
16
+ } catch {
17
+ // ignore — next TUI will reconfigure
18
+ }
19
+ }
20
+
21
+ module.exports = {
22
+ restoreStdinAfterHandoff,
23
+ };