u-foo 2.3.31 → 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 (236) 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 +9 -5
  12. package/scripts/chat-app-smoke.js +30 -0
  13. package/scripts/global-chat-switch-benchmark.js +5 -5
  14. package/scripts/ink-demo.js +23 -0
  15. package/scripts/ink-smoke.js +30 -0
  16. package/scripts/ucode-app-smoke.js +36 -0
  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 +56 -28
  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 +54 -4
  53. package/src/{chat → app/chat}/daemonTransport.js +2 -1
  54. package/src/{chat → app/chat}/dashboardView.js +2 -21
  55. package/src/app/chat/index.js +6 -0
  56. package/src/{chat → app/chat}/inputSubmitHandler.js +38 -13
  57. package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
  58. package/src/app/chat/multiWindow/index.js +268 -0
  59. package/src/app/chat/multiWindow/paneLayout.js +84 -0
  60. package/src/app/chat/multiWindow/paneManager.js +299 -0
  61. package/src/app/chat/multiWindow/renderer.js +384 -0
  62. package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
  63. package/src/{chat → app/chat}/projectCloseController.js +1 -1
  64. package/src/app/chat/shellCommand.js +42 -0
  65. package/src/{chat → app/chat}/transport.js +16 -3
  66. package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
  67. package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
  68. package/src/{init/index.js → app/cli/features/init.js} +14 -32
  69. package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
  70. package/src/app/cli/index.js +9 -0
  71. package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
  72. package/src/{cli.js → app/cli/run.js} +62 -59
  73. package/src/app/index.js +6 -0
  74. package/src/code/agent.js +10 -9
  75. package/src/code/index.js +2 -0
  76. package/src/code/launcher/index.js +9 -0
  77. package/src/{agent → code/launcher}/ucode.js +7 -8
  78. package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
  79. package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
  80. package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
  81. package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
  82. package/src/code/nativeRunner.js +4 -4
  83. package/src/code/taskDecomposer.js +5 -4
  84. package/src/code/tui.js +39 -1997
  85. package/src/config.js +15 -2
  86. package/src/{bus → coordination/bus}/activate.js +2 -2
  87. package/src/{bus → coordination/bus}/daemon.js +15 -5
  88. package/src/coordination/bus/envelope.js +173 -0
  89. package/src/{bus → coordination/bus}/index.js +7 -3
  90. package/src/{bus → coordination/bus}/inject.js +11 -3
  91. package/src/{bus → coordination/bus}/message.js +1 -1
  92. package/src/coordination/bus/messageMeta.js +130 -0
  93. package/src/coordination/bus/promptEnvelope.js +65 -0
  94. package/src/{bus → coordination/bus}/shake.js +1 -1
  95. package/src/{bus → coordination/bus}/store.js +3 -3
  96. package/src/{bus → coordination/bus}/subscriber.js +2 -2
  97. package/src/{bus → coordination/bus}/utils.js +2 -2
  98. package/src/{history → coordination/history}/inputTimeline.js +5 -5
  99. package/src/coordination/index.js +10 -0
  100. package/src/{memory → coordination/memory}/historySearch.js +1 -1
  101. package/src/{memory → coordination/memory}/index.js +3 -3
  102. package/src/{report → coordination/report}/store.js +2 -2
  103. package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +43 -0
  104. package/src/{status → coordination/status}/index.js +3 -3
  105. package/src/online/bridge.js +2 -2
  106. package/src/{controller → orchestration/controller}/flags.js +1 -1
  107. package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
  108. package/src/orchestration/controller/index.js +10 -0
  109. package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
  110. package/src/orchestration/groups/bootstrap.js +3 -0
  111. package/src/orchestration/groups/index.js +10 -0
  112. package/src/orchestration/groups/promptProfiles.js +3 -0
  113. package/src/{group → orchestration/groups}/templates.js +1 -1
  114. package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
  115. package/src/orchestration/index.js +7 -0
  116. package/src/orchestration/solo/index.js +3 -0
  117. package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
  118. package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
  119. package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
  120. package/src/{daemon → runtime/daemon}/index.js +273 -79
  121. package/src/{daemon → runtime/daemon}/ipcServer.js +24 -2
  122. package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
  123. package/src/{daemon → runtime/daemon}/ops.js +48 -61
  124. package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
  125. package/src/{daemon → runtime/daemon}/promptRequest.js +13 -8
  126. package/src/runtime/daemon/providerSessions.js +230 -0
  127. package/src/{daemon → runtime/daemon}/reporting.js +4 -4
  128. package/src/{daemon → runtime/daemon}/run.js +12 -5
  129. package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
  130. package/src/{daemon → runtime/daemon}/status.js +5 -5
  131. package/src/runtime/index.js +10 -0
  132. package/src/runtime/process/nodeExecutable.js +26 -0
  133. package/src/{projects → runtime/projects}/registry.js +1 -1
  134. package/src/{projects → runtime/projects}/runtimes.js +1 -1
  135. package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
  136. package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
  137. package/src/tools/handlers/common.js +1 -1
  138. package/src/tools/handlers/listAgents.js +1 -1
  139. package/src/tools/handlers/memory.js +3 -3
  140. package/src/tools/handlers/readBusSummary.js +1 -1
  141. package/src/tools/handlers/readOpenDecisions.js +1 -1
  142. package/src/tools/handlers/readProjectRegistry.js +1 -1
  143. package/src/tools/handlers/readPromptHistory.js +2 -2
  144. package/src/tools/schemaFixtures.js +1 -1
  145. package/src/ui/MIGRATION.md +336 -0
  146. package/src/ui/format/index.js +974 -0
  147. package/src/ui/index.js +9 -0
  148. package/src/ui/ink/ChatApp.js +3674 -0
  149. package/src/ui/ink/DashboardBar.js +685 -0
  150. package/src/ui/ink/InkDemo.js +96 -0
  151. package/src/ui/ink/MultilineInput.js +612 -0
  152. package/src/ui/ink/UcodeApp.js +822 -0
  153. package/src/ui/ink/agentMirror.js +730 -0
  154. package/src/ui/ink/chatReducer.js +359 -0
  155. package/src/ui/runInk.js +57 -0
  156. package/src/bus/messageMeta.js +0 -52
  157. package/src/chat/agentViewController.js +0 -1072
  158. package/src/chat/chatLogController.js +0 -138
  159. package/src/chat/completionController.js +0 -533
  160. package/src/chat/dashboardKeyController.js +0 -573
  161. package/src/chat/index.js +0 -2214
  162. package/src/chat/inputHistoryController.js +0 -135
  163. package/src/chat/inputListenerController.js +0 -470
  164. package/src/chat/layout.js +0 -186
  165. package/src/chat/pasteController.js +0 -81
  166. package/src/chat/statusLineController.js +0 -223
  167. package/src/chat/streamTracker.js +0 -156
  168. package/src/code/config +0 -0
  169. package/src/daemon/providerSessions.js +0 -488
  170. package/src/terminal/adapters/internalPtyAdapter.js +0 -42
  171. /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
  172. /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
  173. /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
  174. /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
  175. /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
  176. /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
  177. /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
  178. /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
  179. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
  180. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
  181. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
  182. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
  183. /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
  184. /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
  185. /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
  186. /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
  187. /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
  188. /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
  189. /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
  190. /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
  191. /package/src/{agent → agents/providers}/credentials/index.js +0 -0
  192. /package/src/{chat → app/chat}/agentBar.js +0 -0
  193. /package/src/{chat → app/chat}/agentDirectory.js +0 -0
  194. /package/src/{chat → app/chat}/cronScheduler.js +0 -0
  195. /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
  196. /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
  197. /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
  198. /package/src/{chat → app/chat}/inputMath.js +0 -0
  199. /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
  200. /package/src/{chat → app/chat}/settingsController.js +0 -0
  201. /package/src/{chat → app/chat}/text.js +0 -0
  202. /package/src/{chat → app/chat}/transientAgentState.js +0 -0
  203. /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
  204. /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
  205. /package/src/{bus → coordination/bus}/nickname.js +0 -0
  206. /package/src/{bus → coordination/bus}/queue.js +0 -0
  207. /package/src/{context → coordination/context}/decisions.js +0 -0
  208. /package/src/{context → coordination/context}/doctor.js +0 -0
  209. /package/src/{context → coordination/context}/index.js +0 -0
  210. /package/src/{context → coordination/context}/sync.js +0 -0
  211. /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
  212. /package/src/{ufoo → coordination/state}/paths.js +0 -0
  213. /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
  214. /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
  215. /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
  216. /package/src/{group → orchestration/groups}/diagram.js +0 -0
  217. /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
  218. /package/src/{solo → orchestration/solo}/commands.js +0 -0
  219. /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
  220. /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
  221. /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
  222. /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
  223. /package/src/{projects → runtime/projects}/identity.js +0 -0
  224. /package/src/{projects → runtime/projects}/index.js +0 -0
  225. /package/src/{projects → runtime/projects}/projectId.js +0 -0
  226. /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
  227. /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
  228. /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
  229. /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
  230. /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
  231. /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
  232. /package/src/{terminal → runtime/terminal}/detect.js +0 -0
  233. /package/src/{terminal → runtime/terminal}/index.js +0 -0
  234. /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
  235. /package/src/{utils → ui/format}/banner.js +0 -0
  236. /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
@@ -0,0 +1,268 @@
1
+ const { calculatePaneLayout } = require("./paneLayout");
2
+ const { createPaneManager } = require("./paneManager");
3
+ const { createRenderer } = require("./renderer");
4
+
5
+ function createMultiWindowController(options = {}) {
6
+ const {
7
+ processStdout = process.stdout,
8
+ getRows = () => process.stdout.rows || 24,
9
+ getCols = () => process.stdout.columns || 80,
10
+ getInjectSockPath = () => "",
11
+ getActiveAgents = () => [],
12
+ getAgentPaneOptions = () => ({}),
13
+ getChatLogLines = () => [],
14
+ getStatusText = () => "",
15
+ getPromptPrefix = () => "› ",
16
+ getCurrentDraft = () => "",
17
+ getCursorPos = () => 0,
18
+ getCompletions = () => ({ items: [], index: -1, windowStart: 0, pageSize: 8 }),
19
+ getAgentLabel = (id) => id,
20
+ getInternalPaneInfo = () => ({}),
21
+ getDashboardLines = () => [],
22
+ getTerminalFocused = () => false,
23
+ freezeScreen = () => {},
24
+ restoreTerminal = () => {},
25
+ onExit = () => {},
26
+ onFocusAgent = () => {},
27
+ onInternalSubmit = () => {},
28
+ } = options;
29
+
30
+ let active = false;
31
+ let renderThrottleTimer = null;
32
+ let dirtyPanes = new Set();
33
+ let lastCompletionPopup = null;
34
+ const renderer = createRenderer({ write: (d) => processStdout.write(d) });
35
+ const paneManager = createPaneManager({
36
+ getInjectSockPath,
37
+ onInternalSubmit,
38
+ onPaneOutput: (agentId) => {
39
+ if (!active) return;
40
+ if (getTerminalFocused()) {
41
+ renderSinglePane(agentId);
42
+ } else {
43
+ dirtyPanes.add(agentId);
44
+ if (!renderThrottleTimer) {
45
+ renderThrottleTimer = setTimeout(() => {
46
+ renderThrottleTimer = null;
47
+ const panes = [...dirtyPanes];
48
+ dirtyPanes.clear();
49
+ for (const id of panes) renderSinglePane(id);
50
+ }, 200);
51
+ }
52
+ }
53
+ },
54
+ });
55
+
56
+ function enter() {
57
+ const agents = getActiveAgents();
58
+ if (agents.length === 0) return false;
59
+ if (active) return false;
60
+ active = true;
61
+ freezeScreen(true);
62
+ renderer.hideCursor();
63
+ renderer.clear();
64
+ syncAgents();
65
+ renderAll();
66
+ return true;
67
+ }
68
+
69
+ function exit() {
70
+ if (!active) return;
71
+ active = false;
72
+ if (renderThrottleTimer) {
73
+ clearTimeout(renderThrottleTimer);
74
+ renderThrottleTimer = null;
75
+ dirtyPanes.clear();
76
+ }
77
+ paneManager.disconnectAll();
78
+ renderer.showCursor();
79
+ restoreTerminal();
80
+ freezeScreen(false);
81
+ onExit();
82
+ }
83
+
84
+ function syncAgents() {
85
+ const agents = getActiveAgents();
86
+ const current = new Set(paneManager.getAgentIds());
87
+ const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
88
+
89
+ for (let i = 0; i < agents.length; i++) {
90
+ const id = agents[i];
91
+ const pane = layout.agentPanes[i];
92
+ if (!pane) continue;
93
+ const innerW = Math.max(1, pane.width - 2);
94
+ const innerH = Math.max(1, pane.height - 2);
95
+ if (!current.has(id)) {
96
+ paneManager.addAgent(id, innerW, innerH, getAgentPaneOptions(id) || {});
97
+ } else {
98
+ paneManager.sendResize(id, innerW, innerH);
99
+ }
100
+ }
101
+ for (const id of current) {
102
+ if (!agents.includes(id)) {
103
+ paneManager.removeAgent(id);
104
+ }
105
+ }
106
+ }
107
+
108
+ function renderSinglePane(agentId) {
109
+ if (!active) return;
110
+ try {
111
+ const agents = paneManager.getAgentIds();
112
+ const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
113
+ const idx = agents.indexOf(agentId);
114
+ if (idx < 0 || !layout.agentPanes[idx]) return;
115
+ const pane = paneManager.getPane(agentId);
116
+ if (!pane) return;
117
+ const isFocused = getTerminalFocused() && agentId === paneManager.getFocused();
118
+ if (pane.mode === "internal" && typeof renderer.renderInternalPane === "function") {
119
+ renderer.renderInternalPane(pane.vt, layout.agentPanes[idx], isFocused, {
120
+ label: getAgentLabel(agentId),
121
+ ...(getInternalPaneInfo(agentId) || {}),
122
+ input: pane.internalInput || "",
123
+ cursor: pane.internalCursor || 0,
124
+ });
125
+ } else {
126
+ renderer.renderPane(pane.vt, layout.agentPanes[idx], isFocused, getAgentLabel(agentId));
127
+ }
128
+ } catch {
129
+ // swallow render errors to prevent crash
130
+ }
131
+ }
132
+
133
+ function renderAll() {
134
+ if (!active) return;
135
+ try {
136
+ const agents = paneManager.getAgentIds();
137
+ const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
138
+ const cols = getCols();
139
+
140
+ renderer.renderChatLog(layout.chatPane, getChatLogLines());
141
+
142
+ const focused = getTerminalFocused() ? paneManager.getFocused() : null;
143
+ for (let i = 0; i < agents.length; i++) {
144
+ const pane = paneManager.getPane(agents[i]);
145
+ if (!pane || !layout.agentPanes[i]) continue;
146
+ if (pane.mode === "internal" && typeof renderer.renderInternalPane === "function") {
147
+ renderer.renderInternalPane(pane.vt, layout.agentPanes[i], agents[i] === focused, {
148
+ label: getAgentLabel(agents[i]),
149
+ ...(getInternalPaneInfo(agents[i]) || {}),
150
+ input: pane.internalInput || "",
151
+ cursor: pane.internalCursor || 0,
152
+ });
153
+ } else {
154
+ renderer.renderPane(pane.vt, layout.agentPanes[i], agents[i] === focused, getAgentLabel(agents[i]));
155
+ }
156
+ }
157
+
158
+ const chatFocused = !getTerminalFocused();
159
+ if (layout.separatorPane) {
160
+ renderer.renderSeparator(layout.separatorPane, chatFocused);
161
+ }
162
+ if (layout.statusPane) {
163
+ renderer.renderStatusLine(layout.statusPane, getStatusText());
164
+ }
165
+ if (layout.inputPane) {
166
+ renderer.renderInputPrompt(layout.inputPane, getPromptPrefix(), getCurrentDraft(), getCursorPos());
167
+ }
168
+ if (layout.inputSepPane) {
169
+ renderer.renderSeparator(layout.inputSepPane, chatFocused);
170
+ }
171
+ if (layout.dashboardPane) {
172
+ const lines = getDashboardLines();
173
+ renderer.renderDashboard(layout.dashboardPane, lines);
174
+ }
175
+
176
+ const cmp = getCompletions();
177
+ let nextCompletionPopup = null;
178
+ if (lastCompletionPopup && typeof renderer.clearRows === "function") {
179
+ renderer.clearRows(
180
+ lastCompletionPopup.top,
181
+ lastCompletionPopup.height,
182
+ lastCompletionPopup.width,
183
+ lastCompletionPopup.left || 0
184
+ );
185
+ }
186
+ if (cmp && Array.isArray(cmp.items) && cmp.items.length > 0 && layout.inputPane) {
187
+ const start = Math.min(cmp.windowStart || 0, Math.max(0, cmp.items.length - (cmp.pageSize || 8)));
188
+ const end = Math.min(cmp.items.length, start + (cmp.pageSize || 8));
189
+ const visible = cmp.items.slice(start, end);
190
+ const popupTop = layout.inputPane.top - visible.length - 1;
191
+ if (popupTop >= 0) {
192
+ nextCompletionPopup = { top: popupTop, left: 0, width: cols, height: visible.length + 1 };
193
+ renderer.renderSeparator({ top: popupTop, left: 0, width: cols });
194
+ for (let i = 0; i < visible.length; i++) {
195
+ const idx = start + i;
196
+ const selected = idx === cmp.index;
197
+ const label = visible[i].label || "";
198
+ const desc = visible[i].description || "";
199
+ const line = selected
200
+ ? `\x1b[7;36m${label}\x1b[0m \x1b[90m${desc}\x1b[0m`
201
+ : `\x1b[90m${label} ${desc}\x1b[0m`;
202
+ const pad = Math.max(0, cols - renderer.visibleLength(line));
203
+ renderer.write(renderer.moveTo(popupTop + 1 + i, 0) + line + " ".repeat(pad) + "\x1b[0m");
204
+ }
205
+ }
206
+ }
207
+ lastCompletionPopup = nextCompletionPopup;
208
+ } catch {
209
+ // swallow render errors to prevent crash
210
+ }
211
+ }
212
+
213
+ function handleKey(key) {
214
+ if (!active) return false;
215
+
216
+ if (key.name === "c" && key.ctrl) {
217
+ return false;
218
+ }
219
+
220
+ if (key.name === "w" && key.ctrl) {
221
+ paneManager.cycleFocus();
222
+ renderAll();
223
+ return true;
224
+ }
225
+
226
+ if (key.name === "q" && key.ctrl) {
227
+ exit();
228
+ return true;
229
+ }
230
+
231
+ return false;
232
+ }
233
+
234
+ function focusAgent(agentId) {
235
+ if (!active) return;
236
+ const agents = paneManager.getAgentIds();
237
+ if (!agents.includes(agentId)) return;
238
+ paneManager.setFocused(agentId);
239
+ onFocusAgent(agentId);
240
+ renderAll();
241
+ }
242
+
243
+ function handleResize() {
244
+ if (!active) return;
245
+ syncAgents();
246
+ renderer.clear();
247
+ renderAll();
248
+ }
249
+
250
+ function isActive() { return active; }
251
+
252
+ return {
253
+ enter,
254
+ exit,
255
+ isActive,
256
+ handleKey,
257
+ handleResize,
258
+ syncAgents,
259
+ renderAll,
260
+ focusAgent,
261
+ sendInput: (data) => paneManager.sendInput(data),
262
+ writeToPane: (agentId, data) => paneManager.writeToPane(agentId, data),
263
+ getFocused: () => paneManager.getFocused(),
264
+ getAgentIds: () => paneManager.getAgentIds(),
265
+ };
266
+ }
267
+
268
+ module.exports = { createMultiWindowController };
@@ -0,0 +1,84 @@
1
+ function calculatePaneLayout(termCols, termRows, agentCount) {
2
+ const bottomRows = 5;
3
+ const safeRows = termRows - 1;
4
+ const contentHeight = Math.max(1, safeRows - bottomRows);
5
+ const bottomTop = contentHeight;
6
+
7
+ const statusPane = { top: bottomTop, left: 0, width: termCols };
8
+ const separatorPane = { top: bottomTop + 1, left: 0, width: termCols };
9
+ const inputPane = { top: bottomTop + 2, left: 0, width: termCols };
10
+ const inputSepPane = { top: bottomTop + 3, left: 0, width: termCols };
11
+ const dashboardPane = { top: bottomTop + 4, left: 0, width: termCols };
12
+
13
+ if (agentCount <= 0) {
14
+ return {
15
+ separatorPane,
16
+ statusPane,
17
+ inputPane,
18
+ inputSepPane,
19
+ dashboardPane,
20
+ chatPane: { top: 0, left: 0, width: termCols, height: contentHeight },
21
+ agentPanes: [],
22
+ };
23
+ }
24
+
25
+ const chatWidth = Math.floor(termCols / 3);
26
+ const rightLeft = chatWidth + 1;
27
+ const rightWidth = termCols - chatWidth - 1;
28
+
29
+ const chatPane = { top: 0, left: 0, width: chatWidth, height: contentHeight };
30
+
31
+ if (rightWidth < 4 || contentHeight < 3) {
32
+ return {
33
+ separatorPane,
34
+ statusPane,
35
+ inputPane,
36
+ inputSepPane,
37
+ dashboardPane,
38
+ chatPane: { top: 0, left: 0, width: termCols, height: contentHeight },
39
+ agentPanes: [],
40
+ };
41
+ }
42
+
43
+ const agentPanes = layoutAgentPanes(rightLeft, rightWidth, contentHeight, agentCount, 0);
44
+ return { separatorPane, statusPane, inputPane, inputSepPane, dashboardPane, chatPane, agentPanes };
45
+ }
46
+
47
+ function layoutAgentPanes(left, width, height, count, topOffset = 0) {
48
+ if (count === 1) {
49
+ return [{ top: topOffset, left, width, height }];
50
+ }
51
+ if (count === 2) {
52
+ const h1 = Math.floor(height / 2);
53
+ return [
54
+ { top: topOffset, left, width, height: h1 },
55
+ { top: topOffset + h1, left, width, height: height - h1 },
56
+ ];
57
+ }
58
+
59
+ const rowCount = Math.ceil(count / 2);
60
+ const rowHeight = Math.floor(height / rowCount);
61
+ const panes = [];
62
+ let placed = 0;
63
+
64
+ for (let row = 0; row < rowCount; row++) {
65
+ const rowTop = topOffset + row * rowHeight;
66
+ const actualHeight = row === rowCount - 1 ? height - row * rowHeight : rowHeight;
67
+ const remaining = count - placed;
68
+ const isOddRow = remaining % 2 === 1 && row === 0 && count % 2 === 1;
69
+
70
+ if (isOddRow) {
71
+ panes.push({ top: rowTop, left, width, height: actualHeight });
72
+ placed++;
73
+ } else {
74
+ const halfWidth = Math.floor(width / 2);
75
+ panes.push({ top: rowTop, left, width: halfWidth, height: actualHeight });
76
+ panes.push({ top: rowTop, left: left + halfWidth + 1, width: width - halfWidth - 1, height: actualHeight });
77
+ placed += 2;
78
+ }
79
+ }
80
+
81
+ return panes;
82
+ }
83
+
84
+ module.exports = { calculatePaneLayout };
@@ -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 };