u-foo 2.3.32 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +157 -213
  2. package/README.zh-CN.md +151 -197
  3. package/SKILLS/ufoo/SKILL.md +8 -8
  4. package/bin/uagy.js +69 -0
  5. package/bin/uclaude.js +2 -2
  6. package/bin/ucode.js +4 -4
  7. package/bin/ucodex.js +2 -2
  8. package/bin/ufoo.js +5 -23
  9. package/modules/AGENTS.template.md +1 -1
  10. package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
  11. package/package.json +5 -5
  12. package/scripts/chat-app-smoke.js +1 -1
  13. package/scripts/global-chat-switch-benchmark.js +5 -5
  14. package/scripts/ink-demo.js +1 -1
  15. package/scripts/ink-smoke.js +1 -1
  16. package/scripts/ucode-app-smoke.js +1 -1
  17. package/src/{agent → agents/activity}/activityDetector.js +39 -2
  18. package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
  19. package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
  20. package/src/{agent → agents/activity}/activityTracker.js +1 -1
  21. package/src/agents/activity/index.js +8 -0
  22. package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
  23. package/src/agents/controller/index.js +8 -0
  24. package/src/{agent → agents/controller}/loopObservability.js +2 -2
  25. package/src/{agent → agents/controller}/loopRuntime.js +1 -1
  26. package/src/{agent → agents/controller}/ufooAgent.js +9 -9
  27. package/src/agents/index.js +10 -0
  28. package/src/agents/internal/index.js +3 -0
  29. package/src/{agent → agents/internal}/internalRunner.js +45 -22
  30. package/src/agents/launch/agyConversation.js +159 -0
  31. package/src/agents/launch/index.js +12 -0
  32. package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
  33. package/src/{agent → agents/launch}/launcher.js +64 -21
  34. package/src/{agent → agents/launch}/notifier.js +23 -12
  35. package/src/{agent → agents/launch}/ptyRunner.js +44 -12
  36. package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
  37. package/src/{agent → agents/launch}/publisherRouting.js +1 -1
  38. package/src/{agent → agents/launch}/readyDetector.js +23 -0
  39. package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
  40. package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
  41. package/src/agents/prompts/index.js +8 -0
  42. package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
  43. package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
  44. package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
  45. package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
  46. package/src/agents/providers/index.js +13 -0
  47. package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
  48. package/src/{chat → app/chat}/agentSockets.js +1 -1
  49. package/src/{chat → app/chat}/commandExecutor.js +50 -26
  50. package/src/{chat → app/chat}/commands.js +119 -5
  51. package/src/{chat → app/chat}/daemonConnection.js +1 -1
  52. package/src/{chat → app/chat}/daemonMessageRouter.js +45 -3
  53. package/src/{chat → app/chat}/dashboardView.js +2 -1
  54. package/src/app/chat/index.js +6 -0
  55. package/src/{chat → app/chat}/inputSubmitHandler.js +4 -13
  56. package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
  57. package/src/app/chat/multiWindow/index.js +268 -0
  58. package/src/app/chat/multiWindow/paneLayout.js +84 -0
  59. package/src/app/chat/multiWindow/paneManager.js +299 -0
  60. package/src/app/chat/multiWindow/renderer.js +384 -0
  61. package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
  62. package/src/{chat → app/chat}/transport.js +1 -1
  63. package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
  64. package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
  65. package/src/{init/index.js → app/cli/features/init.js} +14 -32
  66. package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
  67. package/src/app/cli/index.js +9 -0
  68. package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
  69. package/src/{cli.js → app/cli/run.js} +59 -57
  70. package/src/app/index.js +6 -0
  71. package/src/code/agent.js +10 -9
  72. package/src/code/index.js +2 -0
  73. package/src/code/launcher/index.js +9 -0
  74. package/src/{agent → code/launcher}/ucode.js +7 -8
  75. package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
  76. package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
  77. package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
  78. package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
  79. package/src/code/nativeRunner.js +4 -4
  80. package/src/code/tui.js +3 -1454
  81. package/src/config.js +15 -2
  82. package/src/{bus → coordination/bus}/activate.js +2 -2
  83. package/src/{bus → coordination/bus}/daemon.js +15 -5
  84. package/src/coordination/bus/envelope.js +173 -0
  85. package/src/{bus → coordination/bus}/index.js +7 -3
  86. package/src/{bus → coordination/bus}/inject.js +11 -3
  87. package/src/{bus → coordination/bus}/message.js +1 -1
  88. package/src/coordination/bus/messageMeta.js +130 -0
  89. package/src/coordination/bus/promptEnvelope.js +65 -0
  90. package/src/{bus → coordination/bus}/shake.js +1 -1
  91. package/src/{bus → coordination/bus}/store.js +3 -3
  92. package/src/{bus → coordination/bus}/subscriber.js +2 -2
  93. package/src/{bus → coordination/bus}/utils.js +2 -2
  94. package/src/{history → coordination/history}/inputTimeline.js +5 -5
  95. package/src/coordination/index.js +10 -0
  96. package/src/{memory → coordination/memory}/historySearch.js +1 -1
  97. package/src/{memory → coordination/memory}/index.js +3 -3
  98. package/src/{report → coordination/report}/store.js +2 -2
  99. package/src/{status → coordination/status}/index.js +3 -3
  100. package/src/online/bridge.js +2 -2
  101. package/src/{controller → orchestration/controller}/flags.js +1 -1
  102. package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
  103. package/src/orchestration/controller/index.js +10 -0
  104. package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
  105. package/src/orchestration/groups/bootstrap.js +3 -0
  106. package/src/orchestration/groups/index.js +10 -0
  107. package/src/orchestration/groups/promptProfiles.js +3 -0
  108. package/src/{group → orchestration/groups}/templates.js +1 -1
  109. package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
  110. package/src/orchestration/index.js +7 -0
  111. package/src/orchestration/solo/index.js +3 -0
  112. package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
  113. package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
  114. package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
  115. package/src/{daemon → runtime/daemon}/index.js +105 -53
  116. package/src/{daemon → runtime/daemon}/ipcServer.js +1 -1
  117. package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
  118. package/src/{daemon → runtime/daemon}/ops.js +48 -61
  119. package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
  120. package/src/{daemon → runtime/daemon}/promptRequest.js +7 -7
  121. package/src/runtime/daemon/providerSessions.js +230 -0
  122. package/src/{daemon → runtime/daemon}/reporting.js +4 -4
  123. package/src/{daemon → runtime/daemon}/run.js +4 -4
  124. package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
  125. package/src/{daemon → runtime/daemon}/status.js +5 -5
  126. package/src/runtime/index.js +10 -0
  127. package/src/{projects → runtime/projects}/registry.js +1 -1
  128. package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
  129. package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
  130. package/src/tools/handlers/common.js +1 -1
  131. package/src/tools/handlers/listAgents.js +1 -1
  132. package/src/tools/handlers/memory.js +3 -3
  133. package/src/tools/handlers/readBusSummary.js +1 -1
  134. package/src/tools/handlers/readOpenDecisions.js +1 -1
  135. package/src/tools/handlers/readProjectRegistry.js +1 -1
  136. package/src/tools/handlers/readPromptHistory.js +2 -2
  137. package/src/tools/schemaFixtures.js +1 -1
  138. package/src/ui/MIGRATION.md +42 -88
  139. package/src/ui/format/index.js +5 -28
  140. package/src/ui/index.js +1 -1
  141. package/src/ui/{components → ink}/ChatApp.js +812 -88
  142. package/src/ui/ink/DashboardBar.js +685 -0
  143. package/src/ui/{components → ink}/MultilineInput.js +230 -5
  144. package/src/ui/{components → ink}/UcodeApp.js +16 -7
  145. package/src/ui/{components → ink}/agentMirror.js +24 -19
  146. package/src/ui/{components → ink}/chatReducer.js +29 -7
  147. package/src/bus/messageMeta.js +0 -52
  148. package/src/chat/agentViewController.js +0 -1072
  149. package/src/chat/chatLogController.js +0 -138
  150. package/src/chat/completionController.js +0 -533
  151. package/src/chat/dashboardKeyController.js +0 -533
  152. package/src/chat/index.js +0 -2222
  153. package/src/chat/inputHistoryController.js +0 -135
  154. package/src/chat/inputListenerController.js +0 -470
  155. package/src/chat/layout.js +0 -186
  156. package/src/chat/pasteController.js +0 -81
  157. package/src/chat/statusLineController.js +0 -223
  158. package/src/chat/streamTracker.js +0 -156
  159. package/src/code/config +0 -0
  160. package/src/daemon/providerSessions.js +0 -488
  161. package/src/terminal/adapters/internalPtyAdapter.js +0 -42
  162. package/src/ui/components/DashboardBar.js +0 -417
  163. /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
  164. /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
  165. /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
  166. /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
  167. /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
  168. /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
  169. /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
  170. /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
  171. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
  172. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
  173. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
  174. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
  175. /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
  176. /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
  177. /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
  178. /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
  179. /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
  180. /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
  181. /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
  182. /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
  183. /package/src/{agent → agents/providers}/credentials/index.js +0 -0
  184. /package/src/{chat → app/chat}/agentBar.js +0 -0
  185. /package/src/{chat → app/chat}/agentDirectory.js +0 -0
  186. /package/src/{chat → app/chat}/cronScheduler.js +0 -0
  187. /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
  188. /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
  189. /package/src/{chat → app/chat}/daemonTransport.js +0 -0
  190. /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
  191. /package/src/{chat → app/chat}/inputMath.js +0 -0
  192. /package/src/{chat → app/chat}/projectCloseController.js +0 -0
  193. /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
  194. /package/src/{chat → app/chat}/settingsController.js +0 -0
  195. /package/src/{chat → app/chat}/shellCommand.js +0 -0
  196. /package/src/{chat → app/chat}/text.js +0 -0
  197. /package/src/{chat → app/chat}/transientAgentState.js +0 -0
  198. /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
  199. /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
  200. /package/src/{bus → coordination/bus}/nickname.js +0 -0
  201. /package/src/{bus → coordination/bus}/queue.js +0 -0
  202. /package/src/{context → coordination/context}/decisions.js +0 -0
  203. /package/src/{context → coordination/context}/doctor.js +0 -0
  204. /package/src/{context → coordination/context}/index.js +0 -0
  205. /package/src/{context → coordination/context}/sync.js +0 -0
  206. /package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +0 -0
  207. /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
  208. /package/src/{ufoo → coordination/state}/paths.js +0 -0
  209. /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
  210. /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
  211. /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
  212. /package/src/{group → orchestration/groups}/diagram.js +0 -0
  213. /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
  214. /package/src/{solo → orchestration/solo}/commands.js +0 -0
  215. /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
  216. /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
  217. /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
  218. /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
  219. /package/src/{utils → runtime/process}/nodeExecutable.js +0 -0
  220. /package/src/{projects → runtime/projects}/identity.js +0 -0
  221. /package/src/{projects → runtime/projects}/index.js +0 -0
  222. /package/src/{projects → runtime/projects}/projectId.js +0 -0
  223. /package/src/{projects → runtime/projects}/runtimes.js +0 -0
  224. /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
  225. /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
  226. /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
  227. /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
  228. /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
  229. /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
  230. /package/src/{terminal → runtime/terminal}/detect.js +0 -0
  231. /package/src/{terminal → runtime/terminal}/index.js +0 -0
  232. /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
  233. /package/src/{utils → ui/format}/banner.js +0 -0
  234. /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
  235. /package/src/ui/{components → ink}/InkDemo.js +0 -0
@@ -0,0 +1,299 @@
1
+ const fs = require("fs");
2
+ const net = require("net");
3
+ const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../../../runtime/contracts/ptySocketContract");
4
+ const { createVirtualTerminal } = require("./virtualTerminal");
5
+
6
+ function createPaneManager(options = {}) {
7
+ const {
8
+ getInjectSockPath = () => "",
9
+ onPaneOutput = () => {},
10
+ onInternalSubmit = () => {},
11
+ } = options;
12
+
13
+ const panes = new Map();
14
+ let focusedAgent = null;
15
+
16
+ function addAgent(agentId, cols, rows, options = {}) {
17
+ if (panes.has(agentId)) return;
18
+ const vt = createVirtualTerminal(cols, rows);
19
+ const mode = options.mode === "internal" ? "internal" : "socket";
20
+ const pane = {
21
+ agentId,
22
+ mode,
23
+ vt,
24
+ outputClient: null,
25
+ inputClient: null,
26
+ buffer: "",
27
+ internalInput: "",
28
+ internalCursor: 0,
29
+ };
30
+ panes.set(agentId, pane);
31
+ if (mode === "internal") {
32
+ const initialOutput = Array.isArray(options.initialLines)
33
+ ? options.initialLines.join("\r\n")
34
+ : String(options.initialOutput || "");
35
+ if (initialOutput) pane.vt.write(`${initialOutput}\r\n`);
36
+ onPaneOutput(pane.agentId);
37
+ } else {
38
+ connectOutput(pane);
39
+ }
40
+ if (!focusedAgent) focusedAgent = agentId;
41
+ }
42
+
43
+ function removeAgent(agentId) {
44
+ const pane = panes.get(agentId);
45
+ if (!pane) return;
46
+ disconnect(pane);
47
+ panes.delete(agentId);
48
+ if (focusedAgent === agentId) {
49
+ const keys = [...panes.keys()];
50
+ focusedAgent = keys.length > 0 ? keys[0] : null;
51
+ }
52
+ }
53
+
54
+ function connectOutput(pane) {
55
+ const sockPath = getInjectSockPath(pane.agentId);
56
+ if (!sockPath || !fs.existsSync(sockPath)) {
57
+ pane.vt.write("\x1b[33m[waiting]\x1b[0m inject.sock not found\r\n");
58
+ onPaneOutput(pane.agentId);
59
+ return;
60
+ }
61
+
62
+ try {
63
+ const client = net.createConnection(sockPath, () => {
64
+ const { cols, rows } = pane.vt.getScreen();
65
+ client.write(JSON.stringify({
66
+ type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
67
+ cols,
68
+ rows,
69
+ }) + "\n");
70
+ client.write(JSON.stringify({
71
+ type: PTY_SOCKET_MESSAGE_TYPES.SUBSCRIBE,
72
+ mode: PTY_SOCKET_SUBSCRIBE_MODES.FULL,
73
+ }) + "\n");
74
+ });
75
+
76
+ client.on("data", (data) => {
77
+ pane.buffer += data.toString("utf8");
78
+ const lines = pane.buffer.split("\n");
79
+ pane.buffer = lines.pop() || "";
80
+ for (const line of lines) {
81
+ if (!line.trim()) continue;
82
+ try {
83
+ const msg = JSON.parse(line);
84
+ if (msg.type === PTY_SOCKET_MESSAGE_TYPES.OUTPUT) {
85
+ if (msg.data) {
86
+ pane.vt.write(msg.data);
87
+ onPaneOutput(pane.agentId);
88
+ }
89
+ } else if (msg.type === PTY_SOCKET_MESSAGE_TYPES.REPLAY) {
90
+ if (msg.data) {
91
+ pane.vt.write(msg.data);
92
+ onPaneOutput(pane.agentId);
93
+ }
94
+ } else if (msg.type === PTY_SOCKET_MESSAGE_TYPES.SNAPSHOT) {
95
+ if (msg.data) {
96
+ pane.vt.write(msg.data);
97
+ onPaneOutput(pane.agentId);
98
+ }
99
+ }
100
+ } catch {
101
+ // ignore malformed messages or render errors
102
+ }
103
+ }
104
+ });
105
+
106
+ client.on("error", (err) => {
107
+ pane.outputClient = null;
108
+ pane.vt.write(`\r\n\x1b[31m[connection error]\x1b[0m ${err && err.message ? err.message : "socket error"}\r\n`);
109
+ onPaneOutput(pane.agentId);
110
+ });
111
+ client.on("close", () => {
112
+ pane.outputClient = null;
113
+ pane.vt.write("\r\n\x1b[33m[disconnected]\x1b[0m\r\n");
114
+ onPaneOutput(pane.agentId);
115
+ });
116
+ pane.outputClient = client;
117
+ } catch (err) {
118
+ pane.vt.write(`\x1b[31m[connection error]\x1b[0m ${err && err.message ? err.message : "connection failed"}\r\n`);
119
+ onPaneOutput(pane.agentId);
120
+ }
121
+ }
122
+
123
+ function disconnect(pane) {
124
+ if (pane.outputClient) {
125
+ try {
126
+ pane.outputClient.removeAllListeners();
127
+ pane.outputClient.destroy();
128
+ } catch {}
129
+ pane.outputClient = null;
130
+ }
131
+ if (pane.inputClient) {
132
+ try {
133
+ pane.inputClient.removeAllListeners();
134
+ pane.inputClient.destroy();
135
+ } catch {}
136
+ pane.inputClient = null;
137
+ }
138
+ }
139
+
140
+ function sendInput(data) {
141
+ if (!focusedAgent) return;
142
+ const pane = panes.get(focusedAgent);
143
+ if (!pane) return;
144
+ if (pane.mode === "internal") {
145
+ handleInternalInput(pane, data);
146
+ return;
147
+ }
148
+ const sockPath = getInjectSockPath(pane.agentId);
149
+ if (!sockPath) return;
150
+
151
+ if (!pane.inputClient || pane.inputClient.destroyed) {
152
+ try {
153
+ const client = net.createConnection(sockPath);
154
+ pane.inputClient = client;
155
+ client.on("error", () => { pane.inputClient = null; });
156
+ client.on("close", () => { pane.inputClient = null; });
157
+ client.once("connect", () => {
158
+ try {
159
+ client.write(JSON.stringify({
160
+ type: PTY_SOCKET_MESSAGE_TYPES.RAW,
161
+ data,
162
+ }) + "\n");
163
+ } catch {}
164
+ });
165
+ } catch { return; }
166
+ return;
167
+ }
168
+
169
+ try {
170
+ pane.inputClient.write(JSON.stringify({
171
+ type: PTY_SOCKET_MESSAGE_TYPES.RAW,
172
+ data,
173
+ }) + "\n");
174
+ } catch {
175
+ pane.inputClient = null;
176
+ }
177
+ }
178
+
179
+ function previousInputBoundary(text = "", cursor = 0) {
180
+ const source = String(text || "");
181
+ const target = Math.max(0, Math.min(source.length, cursor));
182
+ let previous = 0;
183
+ for (const char of Array.from(source)) {
184
+ const next = previous + char.length;
185
+ if (next >= target) break;
186
+ previous = next;
187
+ }
188
+ return previous;
189
+ }
190
+
191
+ function handleInternalInput(pane, data) {
192
+ const raw = String(data || "");
193
+ if (!raw) return;
194
+ if (raw === "\r" || raw === "\n") {
195
+ const message = String(pane.internalInput || "").trim();
196
+ pane.internalInput = "";
197
+ pane.internalCursor = 0;
198
+ if (message) {
199
+ pane.vt.write(`\r\n> ${message.replace(/\r?\n/g, "\r\n ")}\r\n`);
200
+ try { onInternalSubmit(pane.agentId, message); } catch {}
201
+ }
202
+ onPaneOutput(pane.agentId);
203
+ return;
204
+ }
205
+ if (raw === "\x7f" || raw === "\b" || raw === "\x08") {
206
+ if (pane.internalCursor > 0) {
207
+ const start = previousInputBoundary(pane.internalInput, pane.internalCursor);
208
+ pane.internalInput = pane.internalInput.slice(0, start) + pane.internalInput.slice(pane.internalCursor);
209
+ pane.internalCursor = start;
210
+ onPaneOutput(pane.agentId);
211
+ }
212
+ return;
213
+ }
214
+ if (raw === "\x1b[D") {
215
+ pane.internalCursor = previousInputBoundary(pane.internalInput, pane.internalCursor);
216
+ onPaneOutput(pane.agentId);
217
+ return;
218
+ }
219
+ if (raw === "\x1b[C") {
220
+ const tail = pane.internalInput.slice(pane.internalCursor);
221
+ const nextChar = Array.from(tail)[0] || "";
222
+ pane.internalCursor = Math.min(pane.internalInput.length, pane.internalCursor + nextChar.length);
223
+ onPaneOutput(pane.agentId);
224
+ return;
225
+ }
226
+ if (raw === "\x1b[A" || raw === "\x1b[B" || raw === "\t" || raw === "\x1b") return;
227
+
228
+ const clean = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/g, "");
229
+ if (!clean) return;
230
+ pane.internalInput = pane.internalInput.slice(0, pane.internalCursor) + clean + pane.internalInput.slice(pane.internalCursor);
231
+ pane.internalCursor += clean.length;
232
+ onPaneOutput(pane.agentId);
233
+ }
234
+
235
+ function sendResize(agentId, cols, rows) {
236
+ const pane = panes.get(agentId);
237
+ if (!pane) return;
238
+ pane.vt.resize(cols, rows);
239
+ if (pane.mode === "internal") return;
240
+ const sockPath = getInjectSockPath(pane.agentId);
241
+ if ((!pane.outputClient || pane.outputClient.destroyed) && sockPath && fs.existsSync(sockPath)) {
242
+ connectOutput(pane);
243
+ return;
244
+ }
245
+ if (pane.outputClient && !pane.outputClient.destroyed) {
246
+ try {
247
+ pane.outputClient.write(JSON.stringify({
248
+ type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
249
+ cols,
250
+ rows,
251
+ }) + "\n");
252
+ } catch {}
253
+ }
254
+ }
255
+
256
+ function cycleFocus() {
257
+ const keys = [...panes.keys()];
258
+ if (keys.length === 0) return;
259
+ const idx = keys.indexOf(focusedAgent);
260
+ focusedAgent = keys[(idx + 1) % keys.length];
261
+ return focusedAgent;
262
+ }
263
+
264
+ function getFocused() { return focusedAgent; }
265
+ function setFocused(agentId) { if (panes.has(agentId)) focusedAgent = agentId; }
266
+ function getPane(agentId) { return panes.get(agentId) || null; }
267
+ function getAllPanes() { return [...panes.values()]; }
268
+ function getAgentIds() { return [...panes.keys()]; }
269
+ function writeToPane(agentId, data) {
270
+ const pane = panes.get(agentId);
271
+ if (!pane) return false;
272
+ pane.vt.write(data);
273
+ onPaneOutput(pane.agentId);
274
+ return true;
275
+ }
276
+
277
+ function disconnectAll() {
278
+ for (const pane of panes.values()) disconnect(pane);
279
+ panes.clear();
280
+ focusedAgent = null;
281
+ }
282
+
283
+ return {
284
+ addAgent,
285
+ removeAgent,
286
+ sendInput,
287
+ sendResize,
288
+ cycleFocus,
289
+ getFocused,
290
+ setFocused,
291
+ getPane,
292
+ getAllPanes,
293
+ getAgentIds,
294
+ writeToPane,
295
+ disconnectAll,
296
+ };
297
+ }
298
+
299
+ module.exports = { createPaneManager };
@@ -0,0 +1,384 @@
1
+ const BOX = { h: "─", v: "│", tl: "┌", tr: "┐", bl: "└", br: "┘", t: "┬", b: "┴", l: "├", r: "┤", x: "┼" };
2
+
3
+ function createRenderer(options = {}) {
4
+ const {
5
+ write: rawWrite = process.stdout.write.bind(process.stdout),
6
+ } = options;
7
+
8
+ function write(data) {
9
+ try { rawWrite(data); } catch {}
10
+ }
11
+
12
+ function moveTo(row, col) {
13
+ return `\x1b[${row + 1};${col + 1}H`;
14
+ }
15
+
16
+ function attrToAnsi(attr) {
17
+ const parts = [];
18
+ if (attr.bold) parts.push("1");
19
+ if (attr.dim) parts.push("2");
20
+ if (attr.italic) parts.push("3");
21
+ if (attr.underline) parts.push("4");
22
+ if (attr.inverse) parts.push("7");
23
+ if (attr.fgRgb) {
24
+ parts.push(`38;2;${attr.fgRgb[0]};${attr.fgRgb[1]};${attr.fgRgb[2]}`);
25
+ } else if (attr.fg !== 7) {
26
+ if (attr.fg < 8) parts.push(String(30 + attr.fg));
27
+ else if (attr.fg < 16) parts.push(String(90 + attr.fg - 8));
28
+ else parts.push(`38;5;${attr.fg}`);
29
+ }
30
+ if (attr.bgRgb) {
31
+ parts.push(`48;2;${attr.bgRgb[0]};${attr.bgRgb[1]};${attr.bgRgb[2]}`);
32
+ } else if (attr.bg > 0) {
33
+ if (attr.bg < 8) parts.push(String(40 + attr.bg));
34
+ else if (attr.bg < 16) parts.push(String(100 + attr.bg - 8));
35
+ else parts.push(`48;5;${attr.bg}`);
36
+ }
37
+ return parts.length > 0 ? `\x1b[${parts.join(";")}m` : "";
38
+ }
39
+
40
+ function isWide(ch) {
41
+ const code = ch.codePointAt(0);
42
+ if (code < 0x1100) return false;
43
+ return (
44
+ (code >= 0x1100 && code <= 0x115f) ||
45
+ (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
46
+ (code >= 0xac00 && code <= 0xd7a3) ||
47
+ (code >= 0xf900 && code <= 0xfaff) ||
48
+ (code >= 0xfe10 && code <= 0xfe6f) ||
49
+ (code >= 0xff01 && code <= 0xff60) ||
50
+ (code >= 0xffe0 && code <= 0xffe6) ||
51
+ (code >= 0x20000 && code <= 0x2fffd) ||
52
+ (code >= 0x30000 && code <= 0x3fffd) ||
53
+ (code >= 0x1f300 && code <= 0x1f9ff)
54
+ );
55
+ }
56
+
57
+ function renderPane(vt, pane, focused, label) {
58
+ const { buffer, rows, cols, cursorRow, cursorCol } = vt.getScreen();
59
+ const { top, left, width, height } = pane;
60
+
61
+ let out = "";
62
+ const borderColor = focused ? "\x1b[1;36m" : "\x1b[90m";
63
+ const reset = "\x1b[0m";
64
+
65
+ const labelText = label ? ` ${label} ` : "";
66
+ const topLine = BOX.tl + labelText +
67
+ BOX.h.repeat(Math.max(0, width - 2 - labelText.length)) + BOX.tr;
68
+ out += moveTo(top, left) + borderColor + topLine + reset;
69
+
70
+ const innerWidth = width - 2;
71
+ const innerHeight = height - 2;
72
+ for (let r = 0; r < innerHeight; r++) {
73
+ out += moveTo(top + 1 + r, left) + borderColor + BOX.v + reset;
74
+ const bufRow = r < rows ? buffer[r] : null;
75
+ let lastAttr = "";
76
+ let col = 0;
77
+ let c = 0;
78
+ while (col < innerWidth && c < (bufRow ? cols : 0)) {
79
+ const cell = bufRow[c];
80
+ if (cell.wideContinuation) {
81
+ c++;
82
+ continue;
83
+ }
84
+ const atCursor = focused && r === cursorRow && c === cursorCol;
85
+ const w = isWide(cell.char) ? 2 : 1;
86
+ if (col + w > innerWidth) break;
87
+ const attr = atCursor ? { ...cell.attr, inverse: !cell.attr.inverse } : cell.attr;
88
+ const ansi = attrToAnsi(attr);
89
+ if (ansi !== lastAttr) {
90
+ out += reset + ansi;
91
+ lastAttr = ansi;
92
+ }
93
+ out += cell.char || " ";
94
+ col += w;
95
+ c++;
96
+ }
97
+ if (lastAttr) out += reset;
98
+ if (col < innerWidth) out += " ".repeat(innerWidth - col);
99
+ out += borderColor + BOX.v + reset;
100
+ }
101
+
102
+ const botLine = BOX.bl + BOX.h.repeat(Math.max(0, width - 2)) + BOX.br;
103
+ out += moveTo(top + height - 1, left) + borderColor + botLine + reset;
104
+
105
+ write(out);
106
+ }
107
+
108
+ function renderCells(cells, maxWidth, cursorCol = -1) {
109
+ let out = "";
110
+ let lastAttr = "";
111
+ let col = 0;
112
+ let c = 0;
113
+ while (col < maxWidth && c < cells.length) {
114
+ const cell = cells[c];
115
+ if (cell.wideContinuation) {
116
+ c++;
117
+ continue;
118
+ }
119
+ const w = isWide(cell.char) ? 2 : 1;
120
+ if (col + w > maxWidth) break;
121
+ const atCursor = c === cursorCol;
122
+ const attr = atCursor ? { ...cell.attr, inverse: !cell.attr.inverse } : cell.attr;
123
+ const ansi = attrToAnsi(attr);
124
+ if (ansi !== lastAttr) {
125
+ out += "\x1b[0m" + ansi;
126
+ lastAttr = ansi;
127
+ }
128
+ out += cell.char || " ";
129
+ col += w;
130
+ c++;
131
+ }
132
+ if (lastAttr) out += "\x1b[0m";
133
+ if (col < maxWidth) out += " ".repeat(maxWidth - col);
134
+ return out;
135
+ }
136
+
137
+ function renderPlainLine(text, width, color = "") {
138
+ const reset = "\x1b[0m";
139
+ const raw = String(text || "");
140
+ const truncated = truncateVisible(raw, width);
141
+ const pad = Math.max(0, width - visibleLength(truncated));
142
+ return `${color}${truncated}${" ".repeat(pad)}${reset}`;
143
+ }
144
+
145
+ function renderInternalPane(vt, pane, focused, info = {}) {
146
+ const { buffer, rows, cols } = vt.getScreen();
147
+ const { top, left, width, height } = pane;
148
+
149
+ let out = "";
150
+ const borderColor = focused ? "\x1b[1;36m" : "\x1b[90m";
151
+ const reset = "\x1b[0m";
152
+ const cyan = "\x1b[36m";
153
+ const gray = "\x1b[90m";
154
+ const red = "\x1b[31m";
155
+ const magenta = "\x1b[35m";
156
+
157
+ const label = info.label || "";
158
+ const labelText = label ? ` ${label} ` : "";
159
+ const topLine = BOX.tl + labelText +
160
+ BOX.h.repeat(Math.max(0, width - 2 - labelText.length)) + BOX.tr;
161
+ out += moveTo(top, left) + borderColor + topLine + reset;
162
+
163
+ const innerWidth = width - 2;
164
+ const innerHeight = height - 2;
165
+ const chromeRows = innerHeight >= 6 ? 3 : 0;
166
+ const logHeight = Math.max(1, innerHeight - chromeRows);
167
+ let lastContentRow = -1;
168
+ for (let r = rows - 1; r >= 0; r--) {
169
+ const line = buffer[r] || [];
170
+ const hasContent = line.some((cell) => cell && !cell.wideContinuation && cell.char && cell.char !== " ");
171
+ if (hasContent) {
172
+ lastContentRow = r;
173
+ break;
174
+ }
175
+ }
176
+ const sourceStart = lastContentRow >= 0
177
+ ? Math.max(0, lastContentRow - logHeight + 1)
178
+ : Math.max(0, rows - logHeight);
179
+
180
+ for (let r = 0; r < logHeight; r++) {
181
+ const bufRow = buffer[sourceStart + r] || [];
182
+ out += moveTo(top + 1 + r, left) + borderColor + BOX.v + reset;
183
+ out += renderCells(bufRow, innerWidth);
184
+ out += borderColor + BOX.v + reset;
185
+ }
186
+
187
+ if (chromeRows > 0) {
188
+ const status = String(info.status || "ready").toLowerCase();
189
+ const detail = String(info.detail || "").trim();
190
+ const statusColor = status === "blocked" || status === "error" ? red : (status === "ready" || status === "idle" ? gray : cyan);
191
+ const statusLabel = status === "idle" ? "ready" : status;
192
+ const statusText = `ufoo · ${label || "agent"} · ${statusLabel}${detail ? ` · ${detail}` : ""}`;
193
+ const input = String(info.input || "");
194
+ const inputCursor = Number.isFinite(info.cursor) ? Math.max(0, Math.min(input.length, info.cursor)) : input.length;
195
+ const before = input.slice(0, inputCursor);
196
+ const cursorChar = inputCursor < input.length ? input[inputCursor] : " ";
197
+ const after = inputCursor < input.length ? input.slice(inputCursor + 1) : "";
198
+ const baseRow = top + 1 + logHeight;
199
+
200
+ out += moveTo(baseRow, left) + borderColor + BOX.v + reset +
201
+ renderPlainLine(statusText, innerWidth, statusColor) +
202
+ borderColor + BOX.v + reset;
203
+ out += moveTo(baseRow + 1, left) + borderColor + BOX.v + reset +
204
+ gray + BOX.h.repeat(innerWidth) + reset +
205
+ borderColor + BOX.v + reset;
206
+ out += moveTo(baseRow + 2, left) + borderColor + BOX.v + reset +
207
+ renderPlainLine(`${magenta}› ${reset}${before}\x1b[7m${cursorChar}${reset}${after}`, innerWidth) +
208
+ borderColor + BOX.v + reset;
209
+ }
210
+
211
+ const botLine = BOX.bl + BOX.h.repeat(Math.max(0, width - 2)) + BOX.br;
212
+ out += moveTo(top + height - 1, left) + borderColor + botLine + reset;
213
+
214
+ write(out);
215
+ }
216
+
217
+ function stripControl(str) {
218
+ return str.replace(/[\x00-\x08\x0a-\x1f]/g, "");
219
+ }
220
+
221
+ function visibleLength(str) {
222
+ return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length;
223
+ }
224
+
225
+ function truncateVisible(str, maxWidth) {
226
+ let visible = 0;
227
+ let i = 0;
228
+ while (i < str.length && visible < maxWidth) {
229
+ if (str[i] === "\x1b" && str[i + 1] === "[") {
230
+ const start = i;
231
+ i += 2;
232
+ while (i < str.length && str[i] >= "\x20" && str[i] <= "\x3f") i++;
233
+ if (i < str.length) i++;
234
+ continue;
235
+ }
236
+ visible++;
237
+ i++;
238
+ }
239
+ return str.slice(0, i);
240
+ }
241
+
242
+ function classifyLogLine(raw = "") {
243
+ const clean = stripControl(raw)
244
+ .replace(/\{\/?[^{}\n]+\}/g, "")
245
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
246
+ .replace(/`([^`]+)`/g, "$1");
247
+ const trimmed = clean.trim();
248
+ if (!trimmed) return { kind: "spacer", text: " " };
249
+ if (/^[█▀▄ ]+$/.test(trimmed) || /^ufoo chat/i.test(trimmed)) return { kind: "banner", text: clean };
250
+ if (/^───.*───$/.test(trimmed)) return { kind: "divider", text: clean };
251
+ if (/^(error:|✗|failed\b)/i.test(trimmed)) return { kind: "error", marker: "!", speaker: "error", body: clean.replace(/^(error:\s*)/i, "") };
252
+ if (/^(✓|✔|done\b|closed\b)/i.test(trimmed)) return { kind: "success", marker: "✓", body: clean.replace(/^[✓✔]\s*/, "") };
253
+ const dot = clean.match(/^([^·:\n]{1,34})\s+·\s+(.*)$/);
254
+ if (dot) {
255
+ const speaker = dot[1].trim();
256
+ return {
257
+ kind: speaker.toLowerCase() === "ufoo" ? "assistant" : "agent",
258
+ marker: speaker.toLowerCase() === "ufoo" ? "◆" : "●",
259
+ speaker,
260
+ body: dot[2] || " ",
261
+ };
262
+ }
263
+ const colon = clean.match(/^([A-Za-z0-9_.:@/-]{1,34}):\s+(.*)$/);
264
+ if (colon) return { kind: "agent", marker: "●", speaker: colon[1], body: colon[2] || " " };
265
+ return { kind: "plain", marker: "│", body: clean };
266
+ }
267
+
268
+ function formatChatLogLine(raw = "", width = 80) {
269
+ const row = classifyLogLine(raw);
270
+ const reset = "\x1b[0m";
271
+ if (row.kind === "spacer") return " ".repeat(width);
272
+ if (row.kind === "banner") return `\x1b[36;1m${truncateVisible(row.text, width)}${reset}`;
273
+ if (row.kind === "divider") return `\x1b[90m${truncateVisible(row.text, width)}${reset}`;
274
+
275
+ const palette = {
276
+ assistant: { marker: "\x1b[36m", speaker: "\x1b[37;1m", body: "" },
277
+ agent: { marker: "\x1b[36m", speaker: "\x1b[36m", body: "" },
278
+ error: { marker: "\x1b[31;1m", speaker: "\x1b[31m", body: "\x1b[31m" },
279
+ success: { marker: "\x1b[32m", speaker: "\x1b[32m", body: "\x1b[32m" },
280
+ plain: { marker: "\x1b[90m", speaker: "\x1b[90m", body: "" },
281
+ };
282
+ const colors = palette[row.kind] || palette.plain;
283
+ const speaker = row.speaker ? `${colors.speaker}${row.speaker}${reset}\x1b[90m · ${reset}` : "";
284
+ const line = `${colors.marker}${row.marker || "│"}${reset} ${speaker}${colors.body || ""}${row.body || row.text || " "}${reset}`;
285
+ return truncateVisible(line, width);
286
+ }
287
+
288
+ function renderChatLog(pane, lines) {
289
+ const { top, left, width, height } = pane;
290
+ const innerWidth = width - 1;
291
+ let out = "";
292
+ const dim = "\x1b[90m";
293
+ const reset = "\x1b[0m";
294
+
295
+ for (let r = 0; r < height; r++) {
296
+ out += moveTo(top + r, left);
297
+ const idx = lines.length - height + r;
298
+ const raw = (idx >= 0 && idx < lines.length) ? lines[idx] || "" : "";
299
+ const truncated = formatChatLogLine(raw, innerWidth);
300
+ const pad = Math.max(0, innerWidth - visibleLength(truncated));
301
+ out += truncated + reset + " ".repeat(pad);
302
+ out += dim + BOX.v + reset;
303
+ }
304
+ write(out);
305
+ }
306
+
307
+ function renderSeparator(pane, highlighted) {
308
+ const { top, left, width } = pane;
309
+ const reset = "\x1b[0m";
310
+ const color = highlighted ? "\x1b[36m" : "\x1b[90m";
311
+ write(moveTo(top, left) + color + "─".repeat(width) + reset);
312
+ }
313
+
314
+ function renderStatusLine(pane, text) {
315
+ const { top, left, width } = pane;
316
+ const reset = "\x1b[0m";
317
+ const dim = "\x1b[90m";
318
+ const truncated = truncateVisible(text || "", width);
319
+ const pad = Math.max(0, width - visibleLength(truncated));
320
+ write(moveTo(top, left) + dim + truncated + " ".repeat(pad) + reset);
321
+ }
322
+
323
+ function renderDashboard(pane, lines) {
324
+ const { top, left, width } = pane;
325
+ const reset = "\x1b[0m";
326
+ for (let i = 0; i < lines.length; i++) {
327
+ const line = lines[i] || "";
328
+ const pad = Math.max(0, width - visibleLength(line));
329
+ write(moveTo(top + i, left) + line + " ".repeat(pad) + reset);
330
+ }
331
+ }
332
+
333
+ function renderInputPrompt(pane, prefix, draft, cursor) {
334
+ const { top, left, width } = pane;
335
+ const reset = "\x1b[0m";
336
+ const cyan = "\x1b[36m";
337
+ const inverse = "\x1b[7m";
338
+ const prefixStr = prefix || "› ";
339
+ const text = draft || "";
340
+ const cursorPos = typeof cursor === "number" ? Math.max(0, Math.min(text.length, cursor)) : text.length;
341
+ const before = text.slice(0, cursorPos);
342
+ const cursorChar = cursorPos < text.length ? text[cursorPos] : " ";
343
+ const after = cursorPos < text.length ? text.slice(cursorPos + 1) : "";
344
+ const promptLine = cyan + prefixStr + reset + before + inverse + cursorChar + reset + after;
345
+ const truncated = truncateVisible(promptLine, width);
346
+ const pad = Math.max(0, width - visibleLength(truncated));
347
+ write(moveTo(top, left) + truncated + " ".repeat(pad) + reset);
348
+ }
349
+
350
+ function hideCursor() { write("\x1b[?25l"); }
351
+ function showCursor() { write("\x1b[?25h"); }
352
+ function clear() { write("\x1b[2J\x1b[H"); }
353
+
354
+ function clearRows(top, count, width, left = 0) {
355
+ const rows = Math.max(0, Number(count) || 0);
356
+ const cols = Math.max(0, Number(width) || 0);
357
+ if (rows === 0 || cols === 0) return;
358
+ let out = "";
359
+ const blank = " ".repeat(cols);
360
+ for (let i = 0; i < rows; i++) {
361
+ out += moveTo(top + i, left) + blank;
362
+ }
363
+ write(out);
364
+ }
365
+
366
+ return {
367
+ renderPane,
368
+ renderInternalPane,
369
+ renderChatLog,
370
+ renderSeparator,
371
+ renderStatusLine,
372
+ renderDashboard,
373
+ renderInputPrompt,
374
+ hideCursor,
375
+ showCursor,
376
+ clear,
377
+ clearRows,
378
+ moveTo,
379
+ write,
380
+ visibleLength,
381
+ };
382
+ }
383
+
384
+ module.exports = { createRenderer };