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
package/src/code/tui.js CHANGED
@@ -16,7 +16,6 @@ const {
16
16
  filterSelectableAgents,
17
17
  findLogicalLineEnd,
18
18
  findLogicalLineStart,
19
- formatHighlightedUserInput,
20
19
  formatPendingElapsed,
21
20
  loadActiveAgents,
22
21
  moveCursorByWord,
@@ -24,7 +23,6 @@ const {
24
23
  moveCursorToVisualLineBoundary,
25
24
  moveCursorVertically,
26
25
  normalizeBashToolCommand,
27
- normalizeModelLabel,
28
26
  normalizeToolMergeEntry,
29
27
  parseActiveAgentsFromBusStatus,
30
28
  renderLogLinesWithMarkdown,
@@ -36,1468 +34,18 @@ const {
36
34
  stripLeakedEscapeTags,
37
35
  } = fmt;
38
36
 
39
- function safeRead(getter, fallback = undefined) {
40
- try {
41
- return getter();
42
- } catch {
43
- return fallback;
44
- }
45
- }
46
-
47
- function resolveLogContentWidth({ logBox = null, screen = null, fallback = 80 } = {}) {
48
- const coords = safeRead(() => logBox && typeof logBox._getCoords === "function" ? logBox._getCoords() : null, null);
49
- if (coords && Number.isFinite(coords.xl) && Number.isFinite(coords.xi)) {
50
- return Math.max(1, coords.xl - coords.xi);
51
- }
52
- const width = safeRead(() => logBox && logBox.width, null);
53
- if (typeof width === "number") return Math.max(1, width);
54
- const screenWidth = safeRead(() => screen && screen.width, null);
55
- if (typeof screenWidth === "number") return Math.max(1, screenWidth);
56
- const screenCols = safeRead(() => screen && screen.cols, null);
57
- if (typeof screenCols === "number") return Math.max(1, screenCols);
58
- return Math.max(1, fallback);
59
- }
60
-
61
- function escapeBlessedLiteral(text) {
62
- const raw = String(text == null ? "" : text);
63
- const safe = raw.replace(/\{\/escape\}/g, "{open}/escape{close}");
64
- return `{escape}${safe}{/escape}`;
65
- }
66
-
67
- function buildUcodeBannerBlessedLines({
68
- model = "",
69
- engine = "ufoo-core",
70
- nickname = "",
71
- agentId = "",
72
- workspaceRoot = "",
73
- sessionId = "",
74
- width = 0,
75
- } = {}) {
76
- const modelLabel = normalizeModelLabel(model);
77
- void width;
78
- void engine;
79
- void nickname;
80
- void agentId;
81
-
82
- const path = require("path");
83
- const os = require("os");
84
- const currentDir = workspaceRoot || process.cwd();
85
- const homeDir = os.homedir();
86
-
87
- let shortPath = currentDir;
88
- if (currentDir.startsWith(homeDir)) {
89
- shortPath = currentDir.replace(homeDir, "~");
90
- }
91
- shortPath = path.normalize(shortPath);
92
-
93
- const logoLines = UCODE_BANNER_LINES.map(
94
- (line) => `{cyan-fg}${escapeBlessedLiteral(line)}{/cyan-fg}`
95
- );
96
- const infoLines = [
97
- `{gray-fg}Version:{/gray-fg} {cyan-fg}{bold}${escapeBlessedLiteral(UCODE_VERSION)}{/bold}{/cyan-fg}`,
98
- `{gray-fg}Model:{/gray-fg} {yellow-fg}${escapeBlessedLiteral(modelLabel)}{/yellow-fg}`,
99
- `{gray-fg}Dictionary:{/gray-fg} {gray-fg}${escapeBlessedLiteral(shortPath)}{/gray-fg}`,
100
- ];
101
- const normalizedSessionId = String(sessionId || "").trim();
102
- if (normalizedSessionId) {
103
- infoLines.push(`{gray-fg}Session:{/gray-fg} {gray-fg}${escapeBlessedLiteral(normalizedSessionId)}{/gray-fg}`);
104
- }
105
- const logoPadding = " ".repeat(
106
- UCODE_BANNER_LINES.reduce((max, line) => Math.max(max, String(line || "").length), 0)
107
- );
108
- const rows = Math.max(logoLines.length, infoLines.length);
109
-
110
- return Array.from({ length: rows }, (_, index) => {
111
- const logoLine = logoLines[index] || logoPadding;
112
- const info = infoLines[index] || "";
113
- return ` ${logoLine} ${info}`;
114
- });
115
- }
116
-
117
37
  function runUcodeTui(props = {}) {
118
- if (String(process.env.UFOO_TUI || "").trim().toLowerCase() === "blessed") {
119
- return runUcodeBlessedTui(props);
120
- }
121
- const { runUcodeInkTui } = require("../ui/components/UcodeApp");
38
+ const { runUcodeInkTui } = require("../ui/ink/UcodeApp");
122
39
  return runUcodeInkTui(props);
123
40
  }
124
41
 
125
- function runUcodeBlessedTui({
126
- stdin = process.stdin,
127
- stdout = process.stdout,
128
- runSingleCommand = () => ({ kind: "empty" }),
129
- runNaturalLanguageTask = async () => ({ ok: true, summary: "ok" }),
130
- runUbusCommand = async () => ({ ok: false, error: "ubus unsupported", summary: "" }),
131
- formatNlResult = () => "ok",
132
- workspaceRoot = process.cwd(),
133
- state = {},
134
- resumeSessionState = () => ({ ok: false, error: "resume unsupported", sessionId: "", restoredMessages: 0 }),
135
- persistSessionState = () => ({ ok: true }),
136
- autoBus = {},
137
- } = {}) {
138
- return new Promise((resolve) => {
139
- const blessed = require("blessed");
140
- const { execFileSync } = require("child_process");
141
- const { createChatLayout } = require("../chat/layout");
142
- const { computeDashboardContent } = require("../chat/dashboardView");
143
- const { escapeBlessed, stripBlessedTags } = require("../chat/text");
144
- const currentSubscriberId = String(process.env.UFOO_SUBSCRIBER_ID || "").trim();
145
- const autoBusEnabled = Boolean(autoBus && autoBus.enabled);
146
- const autoBusSubscriberId = String((autoBus && autoBus.subscriberId) || currentSubscriberId || "").trim();
147
- const getAutoBusPendingCount = typeof (autoBus && autoBus.getPendingCount) === "function"
148
- ? autoBus.getPendingCount
149
- : () => 0;
150
-
151
- let closing = false;
152
- let chain = Promise.resolve();
153
- let statusInterval = null;
154
- let statusIndex = 0;
155
- let activeAgents = [];
156
- let activeAgentMetaMap = new Map();
157
- let targetAgent = null;
158
- let selectedAgentIndex = -1;
159
- let agentListWindowStart = 0;
160
- let agentSelectionMode = false;
161
- let pendingTask = null;
162
- const backgroundTasks = new Map();
163
- let backgroundSeq = 0;
164
- const logRenderState = { inCodeBlock: false };
165
- const inputHistory = [];
166
- let historyIndex = -1;
167
- let activeToolMerge = null;
168
- let lastMergedToolGroup = null;
169
- let toolMergeId = 0;
170
- let cursorPos = 0;
171
- let preferredCol = null;
172
- let currentInputHeight = 4;
173
- const MIN_INPUT_CONTENT_HEIGHT = 1;
174
- const MAX_INPUT_CONTENT_HEIGHT = 8;
175
- const DASHBOARD_HEIGHT = 1;
176
- let autoBusTimer = null;
177
- let autoBusQueued = false;
178
- let autoBusError = "";
179
- const inputMath = require("../chat/inputMath");
180
-
181
- const {
182
- screen,
183
- logBox,
184
- statusLine,
185
- completionPanel,
186
- dashboard,
187
- inputTopLine,
188
- promptBox,
189
- input,
190
- } = createChatLayout({
191
- blessed,
192
- currentInputHeight: 4,
193
- version: UCODE_VERSION,
194
- logBorder: false,
195
- logScrollbar: false,
196
- });
197
-
198
- if (completionPanel && typeof completionPanel.hide === "function") {
199
- completionPanel.hide();
200
- }
201
-
202
- const getAgentTag = (agent) => {
203
- if (!agent) return "";
204
- if (agent.id) return `${agent.type}:${agent.id.slice(0, 6)}`;
205
- return agent.type;
206
- };
207
-
208
- const getAgentLabel = (id) => {
209
- const meta = activeAgentMetaMap.get(id);
210
- if (!meta) return id;
211
- if (meta.nickname) return meta.nickname;
212
- return getAgentTag(meta);
213
- };
214
-
215
- const refreshAgents = () => {
216
- const list = filterSelectableAgents(
217
- loadActiveAgents(workspaceRoot),
218
- currentSubscriberId
219
- );
220
- activeAgents = list.map((agent) => agent.fullId);
221
- activeAgentMetaMap = new Map(list.map((agent) => [agent.fullId, agent]));
222
- if (targetAgent && !activeAgentMetaMap.has(targetAgent)) {
223
- targetAgent = null;
224
- }
225
- selectedAgentIndex = targetAgent ? activeAgents.indexOf(targetAgent) : -1;
226
- };
227
-
228
- const setPrompt = () => {
229
- const content = targetAgent ? `>@${getAgentLabel(targetAgent)}` : ">";
230
- promptBox.setContent(content);
231
- const plain = stripBlessedTags(content);
232
- promptBox.width = Math.max(2, plain.length + 1);
233
- input.left = promptBox.width;
234
- input.width = `100%-${promptBox.width}`;
235
- resizeInput();
236
- };
237
-
238
- // --- Cursor position helpers (mirrors chat inputListenerController) ---
239
- const getInnerWidth = () => {
240
- const promptWidth = typeof promptBox.width === "number" ? promptBox.width : 2;
241
- return inputMath.getInnerWidth({ input, screen, promptWidth });
242
- };
243
-
244
- const getWrapWidth = () => inputMath.getWrapWidth(input, getInnerWidth());
245
-
246
- const resetPreferredCol = () => {
247
- preferredCol = null;
248
- };
249
-
250
- const ensureInputCursorVisible = () => {
251
- const innerWidth = getWrapWidth();
252
- if (innerWidth <= 0) return;
253
- const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
254
- const visibleRows = Math.max(1, input.height || 1);
255
- const { row } = inputMath.getCursorRowCol(input.value || "", cursorPos, innerWidth, (v) => input.strWidth(v));
256
- let base = input.childBase || 0;
257
- const maxBase = Math.max(0, totalRows - visibleRows);
258
- if (row < base) base = row;
259
- else if (row >= base + visibleRows) base = row - visibleRows + 1;
260
- if (base > maxBase) base = maxBase;
261
- if (base < 0) base = 0;
262
- if (base !== input.childBase) {
263
- input.childBase = base;
264
- if (typeof input.scrollTo === "function") input.scrollTo(base);
265
- }
266
- };
267
-
268
- const resizeInput = () => {
269
- const innerWidth = getWrapWidth();
270
- if (innerWidth <= 0) return;
271
- const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
272
- const contentHeight = Math.min(
273
- MAX_INPUT_CONTENT_HEIGHT,
274
- Math.max(MIN_INPUT_CONTENT_HEIGHT, totalRows)
275
- );
276
- const targetHeight = contentHeight + DASHBOARD_HEIGHT + 2;
277
- if (targetHeight !== currentInputHeight) {
278
- currentInputHeight = targetHeight;
279
- input.height = contentHeight;
280
- promptBox.height = contentHeight;
281
- if (inputTopLine) inputTopLine.bottom = currentInputHeight - 1;
282
- }
283
- statusLine.bottom = currentInputHeight;
284
- logBox.height = Math.max(1, screen.height - currentInputHeight - 1);
285
- ensureInputCursorVisible();
286
- };
287
-
288
- const renderInput = () => {
289
- resizeInput();
290
- ensureInputCursorVisible();
291
- input._updateCursor();
292
- screen.render();
293
- };
294
-
295
- const setCursor = (nextPos) => {
296
- cursorPos = clampCursorPos(nextPos, input.value || "");
297
- ensureInputCursorVisible();
298
- input._updateCursor();
299
- screen.render();
300
- };
301
-
302
- const setInputValue = (value) => {
303
- input.setValue(value || "");
304
- cursorPos = (value || "").length;
305
- resetPreferredCol();
306
- renderInput();
307
- };
308
-
309
- const replaceInputRange = (start, end, replacement = "") => {
310
- const value = input.value || "";
311
- const safeStart = clampCursorPos(start, value);
312
- const safeEnd = clampCursorPos(end, value);
313
- const from = Math.min(safeStart, safeEnd);
314
- const to = Math.max(safeStart, safeEnd);
315
- input.value = value.slice(0, from) + String(replacement || "") + value.slice(to);
316
- cursorPos = from + String(replacement || "").length;
317
- resetPreferredCol();
318
- renderInput();
319
- };
320
-
321
- const insertTextAtCursor = (text = "") => {
322
- const normalized = inputMath.normalizePaste(text);
323
- if (!normalized) return;
324
- replaceInputRange(cursorPos, cursorPos, normalized);
325
- };
326
-
327
- const deleteBeforeCursor = () => {
328
- if (cursorPos <= 0) return;
329
- replaceInputRange(cursorPos - 1, cursorPos, "");
330
- };
331
-
332
- const deleteAtCursor = () => {
333
- const value = input.value || "";
334
- if (cursorPos >= value.length) return;
335
- replaceInputRange(cursorPos, cursorPos + 1, "");
336
- };
337
-
338
- const deleteToBoundary = (boundary) => {
339
- const value = input.value || "";
340
- const innerWidth = getWrapWidth();
341
- const target = boundary === "end"
342
- ? moveCursorToVisualLineBoundary({
343
- cursorPos,
344
- inputValue: value,
345
- width: innerWidth,
346
- boundary: "end",
347
- strWidth: (v) => input.strWidth(v),
348
- })
349
- : moveCursorToVisualLineBoundary({
350
- cursorPos,
351
- inputValue: value,
352
- width: innerWidth,
353
- boundary: "start",
354
- strWidth: (v) => input.strWidth(v),
355
- });
356
- if (target === cursorPos && boundary === "end" && value[cursorPos] === "\n") {
357
- replaceInputRange(cursorPos, cursorPos + 1, "");
358
- return;
359
- }
360
- if (target === cursorPos && boundary === "start" && value[cursorPos - 1] === "\n") {
361
- replaceInputRange(cursorPos - 1, cursorPos, "");
362
- return;
363
- }
364
- replaceInputRange(Math.min(cursorPos, target), Math.max(cursorPos, target), "");
365
- };
366
-
367
- // Override _updateCursor to use our tracked cursorPos
368
- input._updateCursor = function () {
369
- if (this.screen.focused !== this) return;
370
- let lpos;
371
- try { lpos = this._getCoords(); } catch { return; }
372
- if (!lpos) return;
373
- const innerWidth = getWrapWidth();
374
- if (innerWidth <= 0) return;
375
- ensureInputCursorVisible();
376
- const { row, col } = inputMath.getCursorRowCol(this.value || "", cursorPos, innerWidth, (v) => this.strWidth(v));
377
- const scrollOffset = this.childBase || 0;
378
- const displayRow = row - scrollOffset;
379
- const safeCol = Math.min(Math.max(0, col), innerWidth - 1);
380
- const cy = lpos.yi + displayRow;
381
- const cx = lpos.xi + safeCol;
382
- this.screen.program.cup(cy, cx);
383
- this.screen.program.showCursor();
384
- };
385
-
386
- // Override _listener to support cursor-aware editing
387
- let lastKeyRef = null;
388
- let skipSubmitKeyRef = null;
389
- input._listener = function (ch, key) {
390
- const keyName = key && key.name;
391
-
392
- // Dedup: blessed delivers the same key object via element 'keypress' event
393
- // from both readInput's __listener binding and screen's focused.emit('keypress').
394
- // Use object identity to skip the duplicate delivery.
395
- if (key && key === lastKeyRef) return;
396
- lastKeyRef = key || null;
397
-
398
- if (keyName === "escape") return;
399
-
400
- if (keyName === "return" || keyName === "enter") {
401
- const value = this.value || "";
402
- if (key && (key.shift || key.meta)) {
403
- insertTextAtCursor("\n");
404
- skipSubmitKeyRef = key || true;
405
- return;
406
- }
407
- if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
408
- replaceInputRange(cursorPos - 1, cursorPos, "\n");
409
- skipSubmitKeyRef = key || true;
410
- return;
411
- }
412
- return;
413
- }
414
-
415
- // Arrow keys handled by input.key() handlers below
416
- if (keyName === "left" || keyName === "right" || keyName === "up" || keyName === "down") return;
417
-
418
- if (key && key.ctrl) {
419
- if (keyName === "a") {
420
- setCursor(moveCursorToVisualLineBoundary({
421
- cursorPos,
422
- inputValue: this.value || "",
423
- width: getWrapWidth(),
424
- boundary: "start",
425
- strWidth: (v) => this.strWidth(v),
426
- }));
427
- resetPreferredCol();
428
- return;
429
- }
430
- if (keyName === "e") {
431
- setCursor(moveCursorToVisualLineBoundary({
432
- cursorPos,
433
- inputValue: this.value || "",
434
- width: getWrapWidth(),
435
- boundary: "end",
436
- strWidth: (v) => this.strWidth(v),
437
- }));
438
- resetPreferredCol();
439
- return;
440
- }
441
- if (keyName === "b") {
442
- setCursor(moveCursorHorizontally(cursorPos, this.value || "", "left"));
443
- resetPreferredCol();
444
- return;
445
- }
446
- if (keyName === "f") {
447
- setCursor(moveCursorHorizontally(cursorPos, this.value || "", "right"));
448
- resetPreferredCol();
449
- return;
450
- }
451
- if (keyName === "d") {
452
- deleteAtCursor();
453
- return;
454
- }
455
- if (keyName === "h") {
456
- deleteBeforeCursor();
457
- return;
458
- }
459
- if (keyName === "k") {
460
- deleteToBoundary("end");
461
- return;
462
- }
463
- if (keyName === "u") {
464
- deleteToBoundary("start");
465
- return;
466
- }
467
- if (keyName === "w") {
468
- const next = deleteWordBeforeCursor(this.value || "", cursorPos);
469
- this.value = next.value;
470
- cursorPos = next.cursorPos;
471
- resetPreferredCol();
472
- renderInput();
473
- return;
474
- }
475
- }
476
-
477
- if (key && key.meta) {
478
- if (keyName === "b") {
479
- setCursor(moveCursorByWord(this.value || "", cursorPos, "backward"));
480
- resetPreferredCol();
481
- return;
482
- }
483
- if (keyName === "f") {
484
- setCursor(moveCursorByWord(this.value || "", cursorPos, "forward"));
485
- resetPreferredCol();
486
- return;
487
- }
488
- if (keyName === "d") {
489
- const end = moveCursorByWord(this.value || "", cursorPos, "forward");
490
- replaceInputRange(cursorPos, end, "");
491
- return;
492
- }
493
- }
494
-
495
- if (keyName === "backspace") {
496
- if (key && (key.meta || key.ctrl)) {
497
- const next = deleteWordBeforeCursor(this.value || "", cursorPos);
498
- this.value = next.value;
499
- cursorPos = next.cursorPos;
500
- resetPreferredCol();
501
- renderInput();
502
- } else {
503
- deleteBeforeCursor();
504
- }
505
- return;
506
- }
507
-
508
- if (keyName === "delete") {
509
- if (key && key.meta) {
510
- deleteToBoundary("end");
511
- } else {
512
- deleteAtCursor();
513
- }
514
- return;
515
- }
516
-
517
- if (keyName === "home") {
518
- setCursor(moveCursorToVisualLineBoundary({
519
- cursorPos,
520
- inputValue: this.value || "",
521
- width: getWrapWidth(),
522
- boundary: "start",
523
- strWidth: (v) => this.strWidth(v),
524
- }));
525
- resetPreferredCol();
526
- return;
527
- }
528
-
529
- if (keyName === "end") {
530
- setCursor(moveCursorToVisualLineBoundary({
531
- cursorPos,
532
- inputValue: this.value || "",
533
- width: getWrapWidth(),
534
- boundary: "end",
535
- strWidth: (v) => this.strWidth(v),
536
- }));
537
- resetPreferredCol();
538
- return;
539
- }
540
-
541
- if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
542
- insertTextAtCursor(ch);
543
- return;
544
- }
545
-
546
- // Normal character insertion at cursor position
547
- const insertChar = (ch && ch.length === 1) ? ch : (keyName && keyName.length === 1 ? keyName : null);
548
- if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
549
- insertTextAtCursor(insertChar);
550
- }
551
- };
552
-
553
- const renderDashboard = () => {
554
- let hint = "No target agents";
555
- if (activeAgents.length > 0) {
556
- if (targetAgent) {
557
- hint = `↓ select ${getAgentLabel(targetAgent)} · ←/→ switch · ↑ clear`;
558
- } else {
559
- hint = "↓ select target · ←/→ switch";
560
- }
561
- }
562
- const computed = computeDashboardContent({
563
- focusMode: "dashboard",
564
- dashboardView: "agents",
565
- activeAgents,
566
- selectedAgentIndex,
567
- agentListWindowStart,
568
- maxAgentWindow: 4,
569
- getAgentLabel,
570
- dashHints: { agents: hint, agentsEmpty: hint },
571
- });
572
- agentListWindowStart = computed.windowStart;
573
- dashboard.setContent(computed.content);
574
- screen.render();
575
- };
576
-
577
- const logText = (text = "") => {
578
- activeToolMerge = null;
579
- firstToolInGroup = true; // Reset tool group flag when switching back to text
580
- const sanitized = stripLeakedEscapeTags(text);
581
- const lines = renderLogLinesWithMarkdown(
582
- sanitized,
583
- logRenderState,
584
- escapeBlessed
585
- );
586
- for (const line of lines) {
587
- logBox.log(line);
588
- }
589
- screen.render();
590
- };
591
-
592
- const logUserInput = (text = "") => {
593
- activeToolMerge = null;
594
- const line = formatHighlightedUserInput(text, {
595
- width: resolveLogContentWidth({ logBox, screen, fallback: (stdout && stdout.columns) || 80 }),
596
- escapeText: escapeBlessed,
597
- });
598
- if (!line) return;
599
- logBox.log(line);
600
- logBox.log(""); // Add line break after user input
601
- screen.render();
602
- };
603
-
604
- const logControlAction = (text = "") => {
605
- activeToolMerge = null;
606
- const plain = String(text || "").trim();
607
- if (!plain) return;
608
- logBox.log(`{gray-fg}⚙{/gray-fg} ${escapeBlessed(plain)}`);
609
- screen.render();
610
- };
611
-
612
- const summarizeToolDetail = (tool = "", args = {}, payload = {}) => {
613
- const toolName = String(tool || "").trim().toLowerCase();
614
- const argObj = args && typeof args === "object" ? args : {};
615
- const resObj = payload && typeof payload === "object" ? payload : {};
616
-
617
- if (toolName === "read") {
618
- const target = String(resObj.path || argObj.path || argObj.file || "").trim();
619
- const lineInfo = Number.isFinite(resObj.totalLines) ? `${resObj.totalLines} lines` : "";
620
- return [target, lineInfo].filter(Boolean).join(" · ");
621
- }
622
- if (toolName === "write") {
623
- const target = String(resObj.path || argObj.path || argObj.file || "").trim();
624
- const mode = String(resObj.mode || argObj.mode || (argObj.append ? "append" : "overwrite")).trim();
625
- const bytes = Number.isFinite(resObj.bytes) ? `${resObj.bytes} bytes` : "";
626
- return [target, mode, bytes].filter(Boolean).join(" · ");
627
- }
628
- if (toolName === "edit") {
629
- const target = String(resObj.path || argObj.path || argObj.file || "").trim();
630
- const replacements = Number.isFinite(resObj.replacements) ? `${resObj.replacements} replacements` : "";
631
- return [target, replacements].filter(Boolean).join(" · ");
632
- }
633
- if (toolName === "bash") {
634
- return normalizeBashToolCommand(argObj, resObj);
635
- }
636
- return "";
637
- };
638
-
639
- const truncateText = (text = "", maxLength = 80) => {
640
- const str = String(text || "");
641
- if (str.length <= maxLength) return str;
642
- return str.slice(0, maxLength - 3) + "...";
643
- };
644
-
645
- const renderSingleToolEntryLine = (entry = {}) => {
646
- const item = normalizeToolMergeEntry(entry);
647
- const marker = item.isError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
648
- const summary = buildMergedToolSummaryText([item]);
649
- const truncated = truncateText(summary, 100);
650
- return `${marker} ${escapeBlessed(truncated)}`;
651
- };
652
-
653
- const renderCollapsedToolMergeLine = (entries = []) => {
654
- const summary = buildMergedToolSummaryText(entries);
655
- const hasError = entries.some((item) => normalizeToolMergeEntry(item).isError);
656
- const marker = hasError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
657
- return `${marker} ${escapeBlessed(summary)} {gray-fg}(Ctrl+O expand){/gray-fg}`;
658
- };
659
-
660
- let firstToolInGroup = true;
661
-
662
- const logToolHint = (entry = {}, payload = {}) => {
663
- const tool = String(entry.tool || "").trim().toLowerCase();
664
- if (!tool) return;
665
- const resObj = payload && typeof payload === "object" ? payload : {};
666
- const isError = String(entry.phase || "").trim().toLowerCase() === "error" || resObj.ok === false;
667
- const detail = summarizeToolDetail(tool, entry.args, resObj);
668
- const errorText = String(entry.error || resObj.error || "").trim();
669
-
670
- const toolEntry = normalizeToolMergeEntry({
671
- tool,
672
- detail,
673
- isError,
674
- errorText,
675
- });
676
-
677
- if (activeToolMerge) {
678
- activeToolMerge.entries.push(toolEntry);
679
- // Only show collapsed format for 2+ tool calls
680
- if (activeToolMerge.entries.length === 2) {
681
- // Convert first single line to collapsed format
682
- logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
683
- } else if (activeToolMerge.entries.length > 2) {
684
- logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
685
- }
686
- if (activeToolMerge.entries.length > 1) {
687
- lastMergedToolGroup = activeToolMerge;
688
- }
689
- } else {
690
- // Add line break before first tool call
691
- if (firstToolInGroup) {
692
- logBox.log("");
693
- firstToolInGroup = false;
694
- }
695
- logBox.log(renderSingleToolEntryLine(toolEntry));
696
- activeToolMerge = {
697
- id: ++toolMergeId,
698
- lineIndex: logBox.getLines().length - 1,
699
- entries: [toolEntry],
700
- expanded: false,
701
- };
702
- }
703
- screen.render();
704
- };
705
-
706
- const renderSingleMarkdownLine = (rawLine = "", options = {}) => {
707
- const preview = Boolean(options.preview);
708
- const renderState = preview
709
- ? { inCodeBlock: Boolean(logRenderState.inCodeBlock) }
710
- : logRenderState;
711
- const rendered = renderLogLinesWithMarkdown(rawLine, renderState, escapeBlessed);
712
- return rendered[0] || "";
713
- };
714
-
715
- const createNlStreamState = () => {
716
- activeToolMerge = null;
717
- firstToolInGroup = true; // Reset flag for new response
718
- logBox.log(""); // Add empty line to start the response
719
- return {
720
- lineIndex: logBox.getLines().length - 1,
721
- buffer: "",
722
- full: "",
723
- seenVisibleContent: false,
724
- };
725
- };
726
-
727
- const appendNlStreamDelta = (streamState, delta) => {
728
- if (!streamState) return;
729
- const chunk = stripLeakedEscapeTags(String(delta || ""));
730
- if (!chunk) return;
731
-
732
- streamState.full += chunk;
733
- streamState.buffer += chunk;
734
-
735
- const parts = streamState.buffer.split("\n");
736
- if (parts.length > 1) {
737
- const completed = parts.slice(0, -1);
738
- for (const line of completed) {
739
- const hasVisible = /[^\s]/.test(line);
740
- if (!streamState.seenVisibleContent && !hasVisible) {
741
- continue;
742
- }
743
- if (hasVisible) {
744
- streamState.seenVisibleContent = true;
745
- }
746
- const rendered = renderSingleMarkdownLine(line);
747
- logBox.setLine(streamState.lineIndex, rendered);
748
- logBox.pushLine("");
749
- streamState.lineIndex = logBox.getLines().length - 1;
750
- }
751
- streamState.buffer = parts[parts.length - 1];
752
- }
753
-
754
- const previewHasVisible = /[^\s]/.test(streamState.buffer);
755
- if (!streamState.seenVisibleContent && !previewHasVisible) {
756
- return;
757
- }
758
- if (previewHasVisible) {
759
- streamState.seenVisibleContent = true;
760
- }
761
- const previewLine = renderSingleMarkdownLine(streamState.buffer, { preview: true });
762
- logBox.setLine(streamState.lineIndex, previewLine);
763
- screen.render();
764
- };
765
-
766
- const finalizeNlStream = (streamState) => {
767
- if (!streamState) return { lastChar: "" };
768
- streamState.buffer = stripLeakedEscapeTags(streamState.buffer);
769
- const rendered = renderSingleMarkdownLine(streamState.buffer);
770
- logBox.setLine(streamState.lineIndex, rendered);
771
- screen.render();
772
- const full = String(streamState.full || "");
773
- return { lastChar: full ? full.charAt(full.length - 1) : "" };
774
- };
775
-
776
- const updateStatus = (message = "", type = "thinking", options = {}) => {
777
- const getBackgroundSuffix = () => {
778
- if (!backgroundTasks || backgroundTasks.size === 0) return "";
779
- let running = 0;
780
- let done = 0;
781
- let failed = 0;
782
- for (const task of backgroundTasks.values()) {
783
- const status = String(task && task.status || "").trim().toLowerCase();
784
- if (status === "running") running += 1;
785
- else if (status === "done") done += 1;
786
- else if (status === "failed") failed += 1;
787
- }
788
- const total = running + done + failed;
789
- if (total <= 0) return "";
790
- return ` · BG ${running}/${done}/${failed}`;
791
- };
792
- if (statusInterval) {
793
- clearInterval(statusInterval);
794
- statusInterval = null;
795
- }
796
- if (!message) {
797
- statusLine.setContent(escapeBlessed(`UCODE · Ready · Enter send · Shift/Alt+Enter newline · PgUp/PgDn log · Ctrl+O tools${getBackgroundSuffix()}`));
798
- screen.render();
799
- return;
800
- }
801
- const showTimer = Boolean(options.showTimer);
802
- const startedAt = Number.isFinite(options.startedAt) ? options.startedAt : Date.now();
803
- const indicators = STATUS_INDICATORS[type] || STATUS_INDICATORS.thinking;
804
- statusIndex = 0;
805
- const draw = () => {
806
- const indicator = indicators[statusIndex % indicators.length];
807
- const timerText = showTimer
808
- ? ` (${formatPendingElapsed(Date.now() - startedAt)},esc cancel)`
809
- : "";
810
- statusLine.setContent(escapeBlessed(`${indicator} ${message}${timerText}${getBackgroundSuffix()}`));
811
- statusIndex += 1;
812
- screen.render();
813
- };
814
- draw();
815
- if (type !== "none") {
816
- statusInterval = setInterval(draw, 100);
817
- }
818
- };
819
-
820
- const closeWithCode = (code = 0) => {
821
- if (closing) return;
822
- closing = true;
823
- if (autoBusTimer) {
824
- clearInterval(autoBusTimer);
825
- autoBusTimer = null;
826
- }
827
- if (statusInterval) {
828
- clearInterval(statusInterval);
829
- statusInterval = null;
830
- }
831
- if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
832
- try {
833
- pendingTask.abortController.abort();
834
- } catch {
835
- // ignore
836
- }
837
- }
838
- try {
839
- screen.destroy();
840
- } catch {
841
- // ignore
842
- }
843
- resolve({ code });
844
- };
845
-
846
- const runAutoBusOnce = async () => {
847
- if (!autoBusEnabled || closing || pendingTask) return;
848
- if (Number(getAutoBusPendingCount()) <= 0) {
849
- autoBusError = "";
850
- return;
851
- }
852
-
853
- // Set pending state for autoBus tasks
854
- const abortController = new AbortController();
855
- pendingTask = {
856
- abortController,
857
- startedAt: Date.now(),
858
- };
859
- updateStatus("Processing bus messages...", "thinking", {
860
- showTimer: true,
861
- startedAt: pendingTask.startedAt,
862
- });
863
-
864
- try {
865
- const ubusResult = await runUbusCommand(state, {
866
- workspaceRoot,
867
- subscriberId: autoBusSubscriberId,
868
- signal: abortController.signal,
869
- onMessageReceived: (msg) => {
870
- // Display the incoming message immediately
871
- const { extractAgentNickname } = require("./agent");
872
- const nickname = extractAgentNickname(msg.from) || msg.from;
873
- logText(`${nickname}: ${msg.task}`);
874
- // Update status to show we're working on this specific task
875
- updateStatus("Working on task...", "thinking", {
876
- showTimer: true,
877
- startedAt: pendingTask.startedAt,
878
- });
879
- },
880
- });
881
-
882
- if (!ubusResult.ok) {
883
- const nextError = String(ubusResult.error || "ubus failed");
884
- if (nextError !== autoBusError) {
885
- autoBusError = nextError;
886
- logText(`Error: ${nextError}`);
887
- }
888
- return;
889
- }
890
- autoBusError = "";
891
- if (ubusResult.handled > 0) {
892
- // Display only the replies (tasks were already shown via onMessageReceived)
893
- if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
894
- const { extractAgentNickname } = require("./agent");
895
- for (const exchange of ubusResult.messageExchanges) {
896
- const nickname = extractAgentNickname(exchange.from) || exchange.from;
897
- // Only show the reply since task was already displayed
898
- logText(`@${nickname} ${exchange.reply}`);
899
- }
900
- }
901
- const persisted = persistSessionState(state);
902
- if (!persisted || persisted.ok === false) {
903
- logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
904
- }
905
- }
906
- } finally {
907
- // Clear pending state
908
- pendingTask = null;
909
- updateStatus("", "none");
910
- }
911
- };
912
-
913
- const scheduleAutoBus = () => {
914
- if (!autoBusEnabled || closing || autoBusQueued || pendingTask) return;
915
- if (Number(getAutoBusPendingCount()) <= 0) return;
916
- autoBusQueued = true;
917
- chain = chain
918
- .then(() => runAutoBusOnce())
919
- .catch(() => {})
920
- .finally(() => {
921
- autoBusQueued = false;
922
- });
923
- };
924
-
925
- const resolveTargetToken = (token = "") => {
926
- const text = String(token || "").trim();
927
- if (!text) return "";
928
-
929
- if (text.includes(":")) {
930
- const match = activeAgents.find((id) => id === text || id.startsWith(text));
931
- if (match) return match;
932
- }
933
-
934
- const normalized = text.toLowerCase();
935
- for (const id of activeAgents) {
936
- const meta = activeAgentMetaMap.get(id);
937
- if (!meta) continue;
938
- const nick = String(meta.nickname || "").toLowerCase();
939
- if (nick && (nick === normalized || nick.startsWith(normalized))) return id;
940
- }
941
- return "";
942
- };
943
-
944
- const executeLine = async (line) => {
945
- const normalizedLine = String(line || "").replace(/\r?\n/g, " ").trim();
946
- if (!normalizedLine) return;
947
- logUserInput(normalizedLine);
948
-
949
- refreshAgents();
950
-
951
- let actualLine = normalizedLine;
952
- let isBusMessage = false;
953
-
954
- if (targetAgent) {
955
- isBusMessage = true;
956
- }
957
-
958
- const mentionMatch = normalizedLine.match(/^@(\S+)\s+(.+)$/);
959
- if (mentionMatch) {
960
- const [, token, message] = mentionMatch;
961
- const resolved = resolveTargetToken(token);
962
- if (resolved) {
963
- isBusMessage = true;
964
- actualLine = message;
965
- targetAgent = resolved;
966
- selectedAgentIndex = activeAgents.indexOf(resolved);
967
- setPrompt();
968
- renderDashboard();
969
- }
970
- }
971
-
972
- if (isBusMessage && targetAgent) {
973
- updateStatus("Sending message...", "typing");
974
- try {
975
- execFileSync("ufoo", ["bus", "send", targetAgent, actualLine], {
976
- cwd: workspaceRoot,
977
- encoding: "utf8",
978
- });
979
- updateStatus("", "none");
980
- logText(`✓ Message sent to ${getAgentLabel(targetAgent)}`);
981
- } catch (err) {
982
- updateStatus("", "none");
983
- const msg = err && err.message ? err.message : "unknown error";
984
- logText(`Failed to send message: ${msg}`);
985
- }
986
- targetAgent = null;
987
- selectedAgentIndex = -1;
988
- agentSelectionMode = false;
989
- setPrompt();
990
- renderDashboard();
991
- return;
992
- }
993
-
994
- const runtimeWorkspace = String((state && state.workspaceRoot) || workspaceRoot || process.cwd());
995
- const result = runSingleCommand(actualLine, runtimeWorkspace);
996
- if (result.kind === "empty") return;
997
- if (result.kind === "exit") {
998
- closeWithCode(0);
999
- return;
1000
- }
1001
- if (result.kind === "tool") {
1002
- const payload = result.result && typeof result.result === "object" ? result.result : {};
1003
- logToolHint({
1004
- tool: result.tool,
1005
- args: result.args,
1006
- phase: payload.ok === false ? "error" : "end",
1007
- error: payload.error || "",
1008
- }, payload);
1009
- return;
1010
- }
1011
- if (result.kind === "probe") {
1012
- return;
1013
- }
1014
- if (result.kind === "help" || result.kind === "error") {
1015
- logText(result.output || "");
1016
- return;
1017
- }
1018
- if (result.kind === "ubus") {
1019
- updateStatus("Checking bus messages...", "typing");
1020
- const ubusResult = await runUbusCommand(state, {
1021
- workspaceRoot,
1022
- onMessageReceived: (msg) => {
1023
- // Display the incoming message immediately
1024
- const { extractAgentNickname } = require("./agent");
1025
- const nickname = extractAgentNickname(msg.from) || msg.from;
1026
- logText(`${nickname}: ${msg.task}`);
1027
- },
1028
- });
1029
- updateStatus("", "none");
1030
- if (!ubusResult.ok) {
1031
- logText(`Error: ${ubusResult.error}`);
1032
- return;
1033
- }
1034
-
1035
- // Display only the replies (tasks were already shown via onMessageReceived)
1036
- if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
1037
- const { extractAgentNickname } = require("./agent");
1038
- for (const exchange of ubusResult.messageExchanges) {
1039
- const nickname = extractAgentNickname(exchange.from) || exchange.from;
1040
- // Only show the reply since task was already displayed
1041
- logText(`@${nickname} ${exchange.reply}`);
1042
- }
1043
- } else if (ubusResult.handled === 0) {
1044
- logText("ubus: no pending messages.");
1045
- }
1046
- const persisted = persistSessionState(state);
1047
- if (!persisted || persisted.ok === false) {
1048
- logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
1049
- }
1050
- return;
1051
- }
1052
- if (result.kind === "resume") {
1053
- const resumed = resumeSessionState(state, result.sessionId, workspaceRoot);
1054
- if (!resumed.ok) {
1055
- logText(`Error: ${resumed.error}`);
1056
- return;
1057
- }
1058
- logText(`Resumed session ${resumed.sessionId} (${resumed.restoredMessages} messages).`);
1059
- return;
1060
- }
1061
-
1062
- if (result.kind === "nl_bg") {
1063
- backgroundSeq += 1;
1064
- const jobId = `bg-${Date.now().toString(36)}-${backgroundSeq.toString(36)}`;
1065
- const taskRecord = {
1066
- id: jobId,
1067
- task: result.task,
1068
- status: "running",
1069
- startedAt: Date.now(),
1070
- summary: "",
1071
- };
1072
- backgroundTasks.set(jobId, taskRecord);
1073
- updateStatus("", "none");
1074
- logText(`[${jobId}] started in background.`);
1075
-
1076
- const bgState = {
1077
- workspaceRoot: state.workspaceRoot,
1078
- provider: state.provider,
1079
- model: state.model,
1080
- engine: state.engine,
1081
- context: state.context,
1082
- nlMessages: Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [],
1083
- sessionId: "",
1084
- timeoutMs: state.timeoutMs,
1085
- jsonOutput: false,
1086
- };
1087
-
1088
- Promise.resolve()
1089
- .then(() => runNaturalLanguageTask(result.task, bgState))
1090
- .then((nlResult) => {
1091
- taskRecord.status = nlResult && nlResult.ok ? "done" : "failed";
1092
- taskRecord.finishedAt = Date.now();
1093
- taskRecord.summary = String(formatNlResult(nlResult, false) || "").trim();
1094
- const title = taskRecord.status === "done" ? "done" : "failed";
1095
- logText(`[${jobId}] ${title}: ${taskRecord.summary || "no summary"}`);
1096
- })
1097
- .catch((err) => {
1098
- taskRecord.status = "failed";
1099
- taskRecord.finishedAt = Date.now();
1100
- taskRecord.summary = err && err.message ? String(err.message) : "background task failed";
1101
- logText(`[${jobId}] failed: ${taskRecord.summary}`);
1102
- })
1103
- .finally(() => {
1104
- updateStatus("", "none");
1105
- screen.render();
1106
- });
1107
- return;
1108
- }
1109
-
1110
- if (result.kind === "nl") {
1111
- const abortController = new AbortController();
1112
- const escapeStripper = createEscapeTagStripper();
1113
- pendingTask = {
1114
- abortController,
1115
- startedAt: Date.now(),
1116
- };
1117
- const TOOL_LABELS = {
1118
- read: "Reading file",
1119
- write: "Writing file",
1120
- edit: "Editing file",
1121
- bash: "Running command",
1122
- };
1123
- const setNlStatus = (msg) => {
1124
- updateStatus(msg, "thinking", {
1125
- showTimer: true,
1126
- startedAt: pendingTask.startedAt,
1127
- });
1128
- };
1129
- setNlStatus("Waiting for model...");
1130
- let streamState = null;
1131
- let renderedToolLogCount = 0;
1132
- let nlResult = null;
1133
- try {
1134
- nlResult = await runNaturalLanguageTask(result.task, state, {
1135
- signal: abortController.signal,
1136
- onPhase: (event) => {
1137
- if (!event || typeof event !== "object") return;
1138
- if (event.type === "request_start") {
1139
- setNlStatus("Waiting for model...");
1140
- } else if (event.type === "thinking_delta") {
1141
- setNlStatus("Thinking...");
1142
- } else if (event.type === "text_delta") {
1143
- setNlStatus("Generating response...");
1144
- } else if (event.type === "tool_request") {
1145
- const label = TOOL_LABELS[String(event.name || "").toLowerCase()] || `Calling ${event.name}`;
1146
- setNlStatus(`${label}...`);
1147
- }
1148
- },
1149
- onDelta: (delta) => {
1150
- const text = escapeStripper.write(String(delta || ""));
1151
- if (!text) return;
1152
- if (!streamState) {
1153
- streamState = createNlStreamState();
1154
- }
1155
- appendNlStreamDelta(streamState, text);
1156
- },
1157
- onToolLog: (entry) => {
1158
- renderedToolLogCount += 1;
1159
- if (entry && entry.tool && entry.phase === "start") {
1160
- const label = TOOL_LABELS[String(entry.tool || "").toLowerCase()] || `Calling ${entry.tool}`;
1161
- setNlStatus(`${label}...`);
1162
- }
1163
- logToolHint(entry);
1164
- },
1165
- });
1166
- const tail = escapeStripper.flush();
1167
- if (tail) {
1168
- if (!streamState) {
1169
- streamState = createNlStreamState();
1170
- }
1171
- appendNlStreamDelta(streamState, tail);
1172
- }
1173
- let finalStreamInfo = { lastChar: "" };
1174
- if (streamState) {
1175
- finalStreamInfo = finalizeNlStream(streamState);
1176
- }
1177
- if (Array.isArray(nlResult && nlResult.logs) && nlResult.logs.length > renderedToolLogCount) {
1178
- for (const entry of nlResult.logs.slice(renderedToolLogCount)) {
1179
- logToolHint(entry);
1180
- }
1181
- }
1182
- const streamed = Boolean(nlResult && nlResult.streamed);
1183
- const hasVisibleStreamText = Boolean(
1184
- streamState
1185
- && typeof streamState.full === "string"
1186
- && /[^\s]/.test(streamState.full)
1187
- );
1188
- const streamLastChar = nlResult && typeof nlResult.streamLastChar === "string"
1189
- ? nlResult.streamLastChar.slice(-1)
1190
- : finalStreamInfo.lastChar;
1191
- if (streamed && hasVisibleStreamText && streamLastChar !== "\n") {
1192
- logBox.log("");
1193
- screen.render();
1194
- }
1195
- const shouldSkipSummary = Boolean(streamed && nlResult && nlResult.ok && hasVisibleStreamText);
1196
- if (!shouldSkipSummary) {
1197
- logText(formatNlResult(nlResult, false));
1198
- }
1199
- const persisted = persistSessionState(state);
1200
- if (!persisted || persisted.ok === false) {
1201
- logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
1202
- }
1203
- } finally {
1204
- pendingTask = null;
1205
- updateStatus("", "none");
1206
- }
1207
- }
1208
- };
1209
-
1210
- const submitInput = (value = "") => {
1211
- const raw = String(value || "");
1212
- const trimmed = raw.trim();
1213
- input.setValue("");
1214
- cursorPos = 0;
1215
- resetPreferredCol();
1216
- resizeInput();
1217
- screen.render();
1218
- agentSelectionMode = false;
1219
-
1220
- if (trimmed) {
1221
- inputHistory.push(trimmed);
1222
- }
1223
- historyIndex = inputHistory.length;
1224
-
1225
- chain = chain
1226
- .then(() => executeLine(raw))
1227
- .catch((err) => {
1228
- updateStatus("", "none");
1229
- logText(`Error: ${err && err.message ? err.message : "agent loop failed"}`);
1230
- })
1231
- .finally(() => {
1232
- if (closing) return;
1233
- refreshAgents();
1234
- setPrompt();
1235
- renderDashboard();
1236
- input.focus();
1237
- screen.render();
1238
- });
1239
- };
1240
-
1241
- input.key(["enter"], (ch, key) => {
1242
- if (skipSubmitKeyRef && (!key || skipSubmitKeyRef === key || skipSubmitKeyRef === true)) {
1243
- skipSubmitKeyRef = null;
1244
- return false;
1245
- }
1246
- submitInput(input.getValue());
1247
- return false;
1248
- });
1249
- input.key(["up"], () => {
1250
- const currentValue = input.getValue();
1251
- if (shouldClearAgentSelectionOnUp({
1252
- agentSelectionMode,
1253
- inputValue: currentValue,
1254
- })) {
1255
- targetAgent = null;
1256
- selectedAgentIndex = -1;
1257
- agentSelectionMode = false;
1258
- setPrompt();
1259
- renderDashboard();
1260
- // Target selection cleared - removed redundant log
1261
- input.focus();
1262
- return false;
1263
- }
1264
- if (currentValue) {
1265
- const move = moveCursorVertically({
1266
- cursorPos,
1267
- inputValue: currentValue,
1268
- width: getWrapWidth(),
1269
- direction: "up",
1270
- preferredCol,
1271
- strWidth: (v) => input.strWidth(v),
1272
- });
1273
- preferredCol = move.preferredCol;
1274
- if (move.moved) {
1275
- setCursor(move.nextCursorPos);
1276
- return false;
1277
- }
1278
- }
1279
- if (inputHistory.length === 0) return false;
1280
- historyIndex = Math.max(0, historyIndex - 1);
1281
- setInputValue(inputHistory[historyIndex] || "");
1282
- return false;
1283
- });
1284
- input.key(["down"], () => {
1285
- const currentValue = input.getValue();
1286
- if (currentValue) {
1287
- const move = moveCursorVertically({
1288
- cursorPos,
1289
- inputValue: currentValue,
1290
- width: getWrapWidth(),
1291
- direction: "down",
1292
- preferredCol,
1293
- strWidth: (v) => input.strWidth(v),
1294
- });
1295
- preferredCol = move.preferredCol;
1296
- if (move.moved) {
1297
- setCursor(move.nextCursorPos);
1298
- return false;
1299
- }
1300
- }
1301
- const historyTransition = resolveHistoryDownTransition({
1302
- inputHistory,
1303
- historyIndex,
1304
- currentValue,
1305
- });
1306
- if (historyTransition.moved) {
1307
- historyIndex = historyTransition.nextHistoryIndex;
1308
- setInputValue(historyTransition.nextValue);
1309
- return false;
1310
- }
1311
-
1312
- if (shouldEnterAgentSelection(currentValue)) {
1313
- const cachedAgents = Array.isArray(activeAgents) ? activeAgents.slice() : [];
1314
- const cachedMeta = activeAgentMetaMap instanceof Map ? new Map(activeAgentMetaMap) : new Map();
1315
- if (!agentSelectionMode) {
1316
- refreshAgents();
1317
- }
1318
- if (!agentSelectionMode && activeAgents.length === 0 && cachedAgents.length > 0) {
1319
- activeAgents = cachedAgents;
1320
- activeAgentMetaMap = cachedMeta;
1321
- }
1322
- const decision = resolveAgentSelectionOnDown({
1323
- agentSelectionMode,
1324
- selectedAgentIndex,
1325
- totalAgents: activeAgents.length,
1326
- });
1327
- if (decision.action === "enter") {
1328
- selectedAgentIndex = decision.index;
1329
- targetAgent = activeAgents[selectedAgentIndex];
1330
- agentSelectionMode = true;
1331
- setPrompt();
1332
- renderDashboard();
1333
- // Removed redundant target selection log
1334
- input.focus();
1335
- return false;
1336
- }
1337
- if (decision.action === "hold") {
1338
- return false;
1339
- }
1340
- }
1341
- return false;
1342
- });
1343
- input.key(["left"], () => {
1344
- const currentValue = input.getValue();
1345
- if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
1346
- if (activeAgents.length === 0) refreshAgents();
1347
- if (activeAgents.length === 0) return false;
1348
- selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "left");
1349
- targetAgent = activeAgents[selectedAgentIndex];
1350
- setPrompt();
1351
- renderDashboard();
1352
- // Removed redundant target switch log
1353
- input.focus();
1354
- return false;
1355
- }
1356
- const next = moveCursorHorizontally(cursorPos, currentValue, "left");
1357
- if (next !== cursorPos) {
1358
- setCursor(next);
1359
- resetPreferredCol();
1360
- }
1361
- return false;
1362
- });
1363
- input.key(["right"], () => {
1364
- const currentValue = input.getValue();
1365
- if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
1366
- if (activeAgents.length === 0) refreshAgents();
1367
- if (activeAgents.length === 0) return false;
1368
- selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "right");
1369
- targetAgent = activeAgents[selectedAgentIndex];
1370
- setPrompt();
1371
- renderDashboard();
1372
- // Removed redundant target switch log
1373
- input.focus();
1374
- return false;
1375
- }
1376
- const next = moveCursorHorizontally(cursorPos, currentValue, "right");
1377
- if (next !== cursorPos) {
1378
- setCursor(next);
1379
- resetPreferredCol();
1380
- }
1381
- return false;
1382
- });
1383
-
1384
- screen.key(["tab"], () => {
1385
- refreshAgents();
1386
- if (activeAgents.length === 0) return;
1387
- if (selectedAgentIndex < 0) selectedAgentIndex = 0;
1388
- else selectedAgentIndex = (selectedAgentIndex + 1) % activeAgents.length;
1389
- targetAgent = activeAgents[selectedAgentIndex];
1390
- agentSelectionMode = true;
1391
- setPrompt();
1392
- renderDashboard();
1393
- // Removed redundant target switch log
1394
- input.focus();
1395
- });
1396
- screen.key(["S-tab"], () => {
1397
- refreshAgents();
1398
- if (activeAgents.length === 0) return;
1399
- if (selectedAgentIndex < 0) selectedAgentIndex = 0;
1400
- else selectedAgentIndex = (selectedAgentIndex - 1 + activeAgents.length) % activeAgents.length;
1401
- targetAgent = activeAgents[selectedAgentIndex];
1402
- agentSelectionMode = true;
1403
- setPrompt();
1404
- renderDashboard();
1405
- // Removed redundant target switch log
1406
- input.focus();
1407
- });
1408
- screen.key(["C-o"], () => {
1409
- if (!lastMergedToolGroup || lastMergedToolGroup.expanded) return;
1410
- if (!Array.isArray(lastMergedToolGroup.entries) || lastMergedToolGroup.entries.length < 2) return;
1411
- const lines = buildMergedToolExpandedLines(lastMergedToolGroup.entries);
1412
- for (let i = 0; i < lines.length; i += 1) {
1413
- const branch = i === lines.length - 1 ? "└" : "│";
1414
- logBox.log(`{gray-fg}${branch}{/gray-fg} ${escapeBlessed(lines[i])}`);
1415
- }
1416
- lastMergedToolGroup.expanded = true;
1417
- if (activeToolMerge && activeToolMerge.id === lastMergedToolGroup.id) {
1418
- activeToolMerge = null;
1419
- }
1420
- screen.render();
1421
- });
1422
- screen.key(["pageup"], () => {
1423
- logBox.scroll(-Math.max(1, Math.floor((logBox.height || 10) / 2)));
1424
- screen.render();
1425
- });
1426
- screen.key(["pagedown"], () => {
1427
- logBox.scroll(Math.max(1, Math.floor((logBox.height || 10) / 2)));
1428
- screen.render();
1429
- });
1430
- input.key(["escape"], () => {
1431
- if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
1432
- try {
1433
- pendingTask.abortController.abort();
1434
- } catch {
1435
- // ignore
1436
- }
1437
- logControlAction("Cancellation requested. Stopping the current task...");
1438
- updateStatus("Cancelling...", "waiting", {
1439
- showTimer: true,
1440
- startedAt: pendingTask.startedAt,
1441
- });
1442
- return false;
1443
- }
1444
- targetAgent = null;
1445
- selectedAgentIndex = -1;
1446
- agentSelectionMode = false;
1447
- setInputValue("");
1448
- setPrompt();
1449
- renderDashboard();
1450
- // Target selection cleared - removed redundant log
1451
- input.focus();
1452
- return false;
1453
- });
1454
- screen.key(["C-c"], () => closeWithCode(0));
1455
- screen.on("resize", () => {
1456
- renderDashboard();
1457
- screen.render();
1458
- });
1459
-
1460
- const nickname = process.env.UFOO_NICKNAME || "";
1461
- const subscriberId = currentSubscriberId;
1462
- const agentId = subscriberId.includes(":") ? subscriberId.split(":")[1] : "";
1463
- const bannerLines = buildUcodeBannerBlessedLines({
1464
- model: state.model || process.env.UFOO_UCODE_MODEL || "",
1465
- engine: state.engine || "ufoo-core",
1466
- nickname,
1467
- agentId,
1468
- workspaceRoot,
1469
- sessionId: state.sessionId || "",
1470
- width: (stdout && stdout.columns) || 80,
1471
- });
1472
- for (const line of bannerLines) {
1473
- logBox.log(String(line || ""));
1474
- }
1475
- logBox.log("");
1476
-
1477
- refreshAgents();
1478
- setPrompt();
1479
- updateStatus("", "none");
1480
- renderDashboard();
1481
- if (autoBusEnabled) {
1482
- autoBusTimer = setInterval(() => {
1483
- scheduleAutoBus();
1484
- }, 800);
1485
- scheduleAutoBus();
1486
- }
1487
- input.focus();
1488
- screen.render();
1489
- });
1490
- }
1491
-
1492
42
  module.exports = {
43
+ STATUS_INDICATORS,
1493
44
  UCODE_BANNER_LINES,
1494
45
  UCODE_VERSION,
1495
46
  StreamBuffer,
1496
47
  displayCellWidth,
1497
- resolveLogContentWidth,
1498
- formatHighlightedUserInput,
1499
48
  buildUcodeBannerLines,
1500
- buildUcodeBannerBlessedLines,
1501
49
  parseActiveAgentsFromBusStatus,
1502
50
  shouldUseUcodeTui,
1503
51
  renderLogLinesWithMarkdown,
@@ -1522,5 +70,6 @@ module.exports = {
1522
70
  normalizeToolMergeEntry,
1523
71
  buildMergedToolSummaryText,
1524
72
  buildMergedToolExpandedLines,
73
+ loadActiveAgents,
1525
74
  runUcodeTui,
1526
75
  };