u-foo 1.0.6 → 1.2.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 (149) hide show
  1. package/README.md +247 -23
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +168 -28
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +157 -0
  64. package/src/chat/index.js +938 -2910
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +133 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1587 -0
  98. package/src/config.js +50 -2
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +662 -489
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. package/src/bus/API_DESIGN.md +0 -204
@@ -0,0 +1,480 @@
1
+ const DEFAULT_MODE_OPTIONS = ["terminal", "tmux", "internal"];
2
+
3
+ function createDashboardKeyController(options = {}) {
4
+ const {
5
+ state,
6
+ existsSync = () => false,
7
+ getInjectSockPath = () => "",
8
+ getAgentAdapter = () => null,
9
+ activateAgent = () => {},
10
+ requestCloseAgent = () => {},
11
+ enterAgentView = () => {},
12
+ exitAgentView = () => {},
13
+ setAgentBarVisible = () => {},
14
+ requestAgentSnapshot = () => {},
15
+ clearTargetAgent = () => {},
16
+ restoreTargetFromSelection = () => {},
17
+ exitDashboardMode = () => {},
18
+ setLaunchMode = () => {},
19
+ setAgentProvider = () => {},
20
+ setAssistantEngine = () => {},
21
+ setAutoResume = () => {},
22
+ clampAgentWindow = () => {},
23
+ clampAgentWindowWithSelection = () => {},
24
+ renderDashboard = () => {},
25
+ renderAgentDashboard = () => {},
26
+ renderScreen = () => {},
27
+ setScreenGrabKeys = () => {},
28
+ modeOptions = DEFAULT_MODE_OPTIONS,
29
+ } = options;
30
+
31
+ if (!state || typeof state !== "object") {
32
+ throw new Error("createDashboardKeyController requires a mutable state object");
33
+ }
34
+
35
+ function renderDashboardAndScreen() {
36
+ renderDashboard();
37
+ renderScreen();
38
+ }
39
+
40
+ function getAgentCapabilities(agentId) {
41
+ const adapter = getAgentAdapter(agentId);
42
+ return adapter && adapter.capabilities ? adapter.capabilities : null;
43
+ }
44
+
45
+ function supportsActivate(agentId) {
46
+ const caps = getAgentCapabilities(agentId);
47
+ return Boolean(caps && caps.supportsActivate);
48
+ }
49
+
50
+ function supportsSocket(agentId) {
51
+ const caps = getAgentCapabilities(agentId);
52
+ return Boolean(caps && caps.supportsSocketProtocol);
53
+ }
54
+
55
+ function withAgentInputFocus() {
56
+ state.focusMode = "input";
57
+ state.agentOutputSuppressed = false;
58
+ setAgentBarVisible(true);
59
+ }
60
+
61
+ function activateExternalAgent(agentId) {
62
+ try {
63
+ activateAgent(agentId);
64
+ } catch {
65
+ // Activation is best-effort.
66
+ }
67
+ }
68
+
69
+ function switchAgentView(agentId) {
70
+ withAgentInputFocus();
71
+ enterAgentView(agentId);
72
+ }
73
+
74
+ function exitAgentDashboardToInput() {
75
+ withAgentInputFocus();
76
+ renderAgentDashboard();
77
+ requestAgentSnapshot();
78
+ }
79
+
80
+ function handleAgentDashboardKey(key) {
81
+ const totalItems = 1 + state.activeAgents.length;
82
+
83
+ if (key.name === "left") {
84
+ if (state.selectedAgentIndex > 0) {
85
+ state.selectedAgentIndex -= 1;
86
+ }
87
+ clampAgentWindowWithSelection(state.selectedAgentIndex > 0 ? state.selectedAgentIndex - 1 : -1);
88
+ renderAgentDashboard();
89
+ return true;
90
+ }
91
+
92
+ if (key.name === "right") {
93
+ if (state.selectedAgentIndex < totalItems - 1) {
94
+ state.selectedAgentIndex += 1;
95
+ }
96
+ clampAgentWindowWithSelection(state.selectedAgentIndex > 0 ? state.selectedAgentIndex - 1 : -1);
97
+ renderAgentDashboard();
98
+ return true;
99
+ }
100
+
101
+ if (key.name === "enter" || key.name === "return") {
102
+ if (state.selectedAgentIndex === 0) {
103
+ exitAgentView();
104
+ return true;
105
+ }
106
+
107
+ const agentId = state.activeAgents[state.selectedAgentIndex - 1];
108
+ if (!agentId) {
109
+ return true;
110
+ }
111
+
112
+ if (agentId === state.viewingAgent) {
113
+ exitAgentDashboardToInput();
114
+ return true;
115
+ }
116
+
117
+ if (supportsActivate(agentId)) {
118
+ exitAgentView();
119
+ activateExternalAgent(agentId);
120
+ return true;
121
+ }
122
+
123
+ switchAgentView(agentId);
124
+ return true;
125
+ }
126
+
127
+ if (key.name === "up") {
128
+ exitAgentDashboardToInput();
129
+ return true;
130
+ }
131
+
132
+ if (key.name === "x" && key.ctrl) {
133
+ if (state.selectedAgentIndex <= 0 || state.selectedAgentIndex > state.activeAgents.length) {
134
+ return true;
135
+ }
136
+
137
+ const agentId = state.activeAgents[state.selectedAgentIndex - 1];
138
+ const remaining = state.activeAgents.filter((id) => id !== agentId);
139
+ const nextIndex = remaining.length > 0
140
+ ? Math.min(state.selectedAgentIndex - 1, remaining.length - 1)
141
+ : -1;
142
+ const nextAgent = nextIndex >= 0 ? remaining[nextIndex] : null;
143
+
144
+ if (agentId === state.viewingAgent) {
145
+ if (nextAgent) {
146
+ if (supportsActivate(nextAgent)) {
147
+ exitAgentView();
148
+ activateExternalAgent(nextAgent);
149
+ } else {
150
+ withAgentInputFocus();
151
+ state.selectedAgentIndex = nextIndex + 1;
152
+ enterAgentView(nextAgent);
153
+ }
154
+ } else {
155
+ exitAgentView();
156
+ }
157
+ } else if (nextAgent) {
158
+ state.selectedAgentIndex = nextIndex + 1;
159
+ renderAgentDashboard();
160
+ } else {
161
+ state.selectedAgentIndex = 0;
162
+ renderAgentDashboard();
163
+ }
164
+
165
+ requestCloseAgent(agentId);
166
+ return true;
167
+ }
168
+
169
+ return true;
170
+ }
171
+
172
+ function handleModeKey(key) {
173
+ if (key.name === "left") {
174
+ state.selectedModeIndex = state.selectedModeIndex <= 0 ? modeOptions.length - 1 : state.selectedModeIndex - 1;
175
+ renderDashboardAndScreen();
176
+ return true;
177
+ }
178
+
179
+ if (key.name === "right") {
180
+ state.selectedModeIndex = state.selectedModeIndex >= modeOptions.length - 1 ? 0 : state.selectedModeIndex + 1;
181
+ renderDashboardAndScreen();
182
+ return true;
183
+ }
184
+
185
+ if (key.name === "down") {
186
+ state.dashboardView = "provider";
187
+ state.selectedProviderIndex = Math.max(0, (state.providerOptions || []).findIndex((opt) => opt.value === state.agentProvider));
188
+ renderDashboardAndScreen();
189
+ return true;
190
+ }
191
+
192
+ if (key.name === "up") {
193
+ state.dashboardView = "agents";
194
+ restoreTargetFromSelection();
195
+ renderDashboardAndScreen();
196
+ return true;
197
+ }
198
+
199
+ if (key.name === "enter" || key.name === "return") {
200
+ const mode = modeOptions[state.selectedModeIndex];
201
+ if (mode) setLaunchMode(mode);
202
+ exitDashboardMode(false);
203
+ return true;
204
+ }
205
+
206
+ if (key.name === "escape") {
207
+ exitDashboardMode(false);
208
+ return true;
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ function handleProviderKey(key) {
215
+ if (key.name === "left") {
216
+ state.selectedProviderIndex = state.selectedProviderIndex <= 0
217
+ ? state.providerOptions.length - 1
218
+ : state.selectedProviderIndex - 1;
219
+ renderDashboardAndScreen();
220
+ return true;
221
+ }
222
+
223
+ if (key.name === "right") {
224
+ state.selectedProviderIndex = state.selectedProviderIndex >= state.providerOptions.length - 1
225
+ ? 0
226
+ : state.selectedProviderIndex + 1;
227
+ renderDashboardAndScreen();
228
+ return true;
229
+ }
230
+
231
+ if (key.name === "down") {
232
+ state.dashboardView = "assistant";
233
+ const list = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
234
+ const nextIndex = list.findIndex((opt) => opt.value === state.assistantEngine);
235
+ state.selectedAssistantIndex = nextIndex >= 0 ? nextIndex : 0;
236
+ renderDashboardAndScreen();
237
+ return true;
238
+ }
239
+
240
+ if (key.name === "up") {
241
+ state.dashboardView = "mode";
242
+ renderDashboardAndScreen();
243
+ return true;
244
+ }
245
+
246
+ if (key.name === "enter" || key.name === "return") {
247
+ const selected = state.providerOptions[state.selectedProviderIndex];
248
+ if (selected) setAgentProvider(selected.value);
249
+ exitDashboardMode(false);
250
+ return true;
251
+ }
252
+
253
+ if (key.name === "escape") {
254
+ exitDashboardMode(false);
255
+ return true;
256
+ }
257
+
258
+ return true;
259
+ }
260
+
261
+ function handleAssistantKey(key) {
262
+ const options = Array.isArray(state.assistantOptions) ? state.assistantOptions : [];
263
+ if (options.length === 0) {
264
+ if (key.name === "up") {
265
+ state.dashboardView = "provider";
266
+ renderDashboardAndScreen();
267
+ return true;
268
+ }
269
+ if (key.name === "escape" || key.name === "enter" || key.name === "return") {
270
+ exitDashboardMode(false);
271
+ return true;
272
+ }
273
+ return true;
274
+ }
275
+
276
+ if (key.name === "left") {
277
+ state.selectedAssistantIndex = state.selectedAssistantIndex <= 0
278
+ ? options.length - 1
279
+ : state.selectedAssistantIndex - 1;
280
+ renderDashboardAndScreen();
281
+ return true;
282
+ }
283
+
284
+ if (key.name === "right") {
285
+ state.selectedAssistantIndex = state.selectedAssistantIndex >= options.length - 1
286
+ ? 0
287
+ : state.selectedAssistantIndex + 1;
288
+ renderDashboardAndScreen();
289
+ return true;
290
+ }
291
+
292
+ if (key.name === "up") {
293
+ state.dashboardView = "provider";
294
+ renderDashboardAndScreen();
295
+ return true;
296
+ }
297
+
298
+ if (key.name === "down") {
299
+ state.dashboardView = "cron";
300
+ renderDashboardAndScreen();
301
+ return true;
302
+ }
303
+
304
+ if (key.name === "enter" || key.name === "return") {
305
+ const selected = options[state.selectedAssistantIndex];
306
+ if (selected) setAssistantEngine(selected.value);
307
+ exitDashboardMode(false);
308
+ return true;
309
+ }
310
+
311
+ if (key.name === "escape") {
312
+ exitDashboardMode(false);
313
+ return true;
314
+ }
315
+
316
+ return true;
317
+ }
318
+
319
+ function handleCronKey(key) {
320
+ if (key.name === "up") {
321
+ state.dashboardView = "assistant";
322
+ renderDashboardAndScreen();
323
+ return true;
324
+ }
325
+
326
+ if (key.name === "x" && key.ctrl) {
327
+ exitDashboardMode(false);
328
+ return true;
329
+ }
330
+
331
+ if (key.name === "escape" || key.name === "enter" || key.name === "return") {
332
+ exitDashboardMode(false);
333
+ return true;
334
+ }
335
+
336
+ return true;
337
+ }
338
+
339
+ function handleResumeKey(key) {
340
+ if (key.name === "left") {
341
+ state.selectedResumeIndex = state.selectedResumeIndex <= 0
342
+ ? state.resumeOptions.length - 1
343
+ : state.selectedResumeIndex - 1;
344
+ renderDashboardAndScreen();
345
+ return true;
346
+ }
347
+
348
+ if (key.name === "right") {
349
+ state.selectedResumeIndex = state.selectedResumeIndex >= state.resumeOptions.length - 1
350
+ ? 0
351
+ : state.selectedResumeIndex + 1;
352
+ renderDashboardAndScreen();
353
+ return true;
354
+ }
355
+
356
+ if (key.name === "up") {
357
+ state.dashboardView = "provider";
358
+ renderDashboardAndScreen();
359
+ return true;
360
+ }
361
+
362
+ if (key.name === "enter" || key.name === "return") {
363
+ const selected = state.resumeOptions[state.selectedResumeIndex];
364
+ if (selected) setAutoResume(selected.value);
365
+ exitDashboardMode(false);
366
+ return true;
367
+ }
368
+
369
+ if (key.name === "escape") {
370
+ exitDashboardMode(false);
371
+ return true;
372
+ }
373
+
374
+ return true;
375
+ }
376
+
377
+ function handleAgentsKey(key) {
378
+ if (key.name === "left") {
379
+ if (state.activeAgents.length > 0 && state.selectedAgentIndex > 0) {
380
+ state.selectedAgentIndex -= 1;
381
+ clampAgentWindow();
382
+ syncTargetFromSelection();
383
+ renderDashboardAndScreen();
384
+ }
385
+ return true;
386
+ }
387
+
388
+ if (key.name === "right") {
389
+ if (state.activeAgents.length > 0 && state.selectedAgentIndex < state.activeAgents.length - 1) {
390
+ state.selectedAgentIndex += 1;
391
+ clampAgentWindow();
392
+ syncTargetFromSelection();
393
+ renderDashboardAndScreen();
394
+ }
395
+ return true;
396
+ }
397
+
398
+ if (key.name === "down") {
399
+ clearTargetAgent();
400
+ state.dashboardView = "mode";
401
+ state.selectedModeIndex = state.launchMode === "internal" ? 2 : (state.launchMode === "tmux" ? 1 : 0);
402
+ renderDashboardAndScreen();
403
+ return true;
404
+ }
405
+
406
+ if (key.name === "up" || key.name === "escape") {
407
+ clearTargetAgent();
408
+ exitDashboardMode(false);
409
+ return true;
410
+ }
411
+
412
+ if (key.name === "x" && key.ctrl) {
413
+ if (state.selectedAgentIndex >= 0 && state.selectedAgentIndex < state.activeAgents.length) {
414
+ const agentId = state.activeAgents[state.selectedAgentIndex];
415
+ requestCloseAgent(agentId);
416
+ clearTargetAgent();
417
+ exitDashboardMode(false);
418
+ }
419
+ return true;
420
+ }
421
+
422
+ if (key.name === "enter" || key.name === "return") {
423
+ if (state.selectedAgentIndex >= 0 && state.selectedAgentIndex < state.activeAgents.length) {
424
+ const agentId = state.activeAgents[state.selectedAgentIndex];
425
+ if (supportsActivate(agentId)) {
426
+ clearTargetAgent();
427
+ exitDashboardMode(false);
428
+ activateExternalAgent(agentId);
429
+ return true;
430
+ }
431
+
432
+ const sockPath = getInjectSockPath(agentId);
433
+ if (supportsSocket(agentId) && existsSync(sockPath)) {
434
+ clearTargetAgent();
435
+ state.focusMode = "input";
436
+ state.dashboardView = "agents";
437
+ state.selectedAgentIndex = -1;
438
+ setScreenGrabKeys(false);
439
+ enterAgentView(agentId);
440
+ return true;
441
+ }
442
+ }
443
+
444
+ exitDashboardMode(false);
445
+ return true;
446
+ }
447
+
448
+ return false;
449
+ }
450
+
451
+ function syncTargetFromSelection() {
452
+ if (typeof options.syncTargetFromSelection === "function") {
453
+ options.syncTargetFromSelection();
454
+ }
455
+ }
456
+
457
+ function handleDashboardKey(key) {
458
+ if (!key || state.focusMode !== "dashboard") return false;
459
+
460
+ if (state.currentView === "agent") {
461
+ return handleAgentDashboardKey(key);
462
+ }
463
+
464
+ if (state.dashboardView === "mode") return handleModeKey(key);
465
+ if (state.dashboardView === "provider") return handleProviderKey(key);
466
+ if (state.dashboardView === "assistant") return handleAssistantKey(key);
467
+ if (state.dashboardView === "resume") return handleResumeKey(key);
468
+ if (state.dashboardView === "cron") return handleCronKey(key);
469
+
470
+ return handleAgentsKey(key);
471
+ }
472
+
473
+ return {
474
+ handleDashboardKey,
475
+ };
476
+ }
477
+
478
+ module.exports = {
479
+ createDashboardKeyController,
480
+ };
@@ -0,0 +1,157 @@
1
+ const { clampAgentWindowWithSelection } = require("./agentDirectory");
2
+
3
+ const DEFAULT_MODE_OPTIONS = ["terminal", "tmux", "internal"];
4
+
5
+ function providerLabel(value) {
6
+ if (value === "claude-cli") return "claude";
7
+ if (value === "ucode" || value === "ufoo" || value === "ufoo-code") return "ucode";
8
+ return "codex";
9
+ }
10
+
11
+ function assistantLabel(value) {
12
+ if (value === "codex") return "codex";
13
+ if (value === "claude") return "claude";
14
+ if (value === "ufoo") return "ucode";
15
+ if (value === "ucode") return "ucode";
16
+ return "auto";
17
+ }
18
+
19
+ function ensureAtPrefix(value) {
20
+ const text = String(value || "").trim();
21
+ if (!text) return text;
22
+ return text.startsWith("@") ? text : `@${text}`;
23
+ }
24
+
25
+ function computeDashboardContent(options = {}) {
26
+ const {
27
+ focusMode = "input",
28
+ dashboardView = "agents",
29
+ activeAgents = [],
30
+ selectedAgentIndex = -1,
31
+ agentListWindowStart = 0,
32
+ maxAgentWindow = 4,
33
+ getAgentLabel = (id) => id,
34
+ launchMode = "terminal",
35
+ agentProvider = "codex-cli",
36
+ assistantEngine = "auto",
37
+ selectedModeIndex = 0,
38
+ selectedProviderIndex = 0,
39
+ selectedAssistantIndex = 0,
40
+ selectedResumeIndex = 0,
41
+ cronTasks = [],
42
+ providerOptions = [],
43
+ assistantOptions = [],
44
+ resumeOptions = [],
45
+ dashHints = {},
46
+ modeOptions = DEFAULT_MODE_OPTIONS,
47
+ } = options;
48
+
49
+ let content = " ";
50
+ let windowStart = agentListWindowStart;
51
+
52
+ if (focusMode === "dashboard") {
53
+ if (dashboardView === "mode") {
54
+ const modeParts = modeOptions.map((mode, i) => {
55
+ if (i === selectedModeIndex) {
56
+ return `{inverse}${mode}{/inverse}`;
57
+ }
58
+ return `{cyan-fg}${mode}{/cyan-fg}`;
59
+ });
60
+ content += `{gray-fg}Mode:{/gray-fg} ${modeParts.join(" ")}`;
61
+ content += ` {gray-fg}│ ${dashHints.mode || ""}{/gray-fg}`;
62
+ return { content, windowStart };
63
+ }
64
+
65
+ if (dashboardView === "provider") {
66
+ const providerParts = providerOptions.map((opt, i) => {
67
+ if (i === selectedProviderIndex) {
68
+ return `{inverse}${opt.label}{/inverse}`;
69
+ }
70
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
71
+ });
72
+ content += `{gray-fg}Agent:{/gray-fg} ${providerParts.join(" ")}`;
73
+ content += ` {gray-fg}│ ${dashHints.provider || ""}{/gray-fg}`;
74
+ return { content, windowStart };
75
+ }
76
+
77
+ if (dashboardView === "assistant") {
78
+ const assistantParts = assistantOptions.map((opt, i) => {
79
+ if (i === selectedAssistantIndex) {
80
+ return `{inverse}${opt.label}{/inverse}`;
81
+ }
82
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
83
+ });
84
+ content += `{gray-fg}Assistant:{/gray-fg} ${assistantParts.join(" ")}`;
85
+ content += ` {gray-fg}│ ${dashHints.assistant || ""}{/gray-fg}`;
86
+ return { content, windowStart };
87
+ }
88
+
89
+ if (dashboardView === "resume") {
90
+ const resumeParts = resumeOptions.map((opt, i) => {
91
+ if (i === selectedResumeIndex) {
92
+ return `{inverse}${opt.label}{/inverse}`;
93
+ }
94
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
95
+ });
96
+ content += `{gray-fg}Resume:{/gray-fg} ${resumeParts.join(" ")}`;
97
+ content += ` {gray-fg}│ ${dashHints.resume || ""}{/gray-fg}`;
98
+ return { content, windowStart };
99
+ }
100
+
101
+ if (dashboardView === "cron") {
102
+ const items = Array.isArray(cronTasks) ? cronTasks : [];
103
+ const summary = items.length > 0
104
+ ? items.map((item) => item.summary || item.id || "").filter(Boolean).join(", ")
105
+ : "none";
106
+ content += `{gray-fg}Cron:{/gray-fg} {cyan-fg}${summary}{/cyan-fg}`;
107
+ content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
108
+ return { content, windowStart };
109
+ }
110
+
111
+ if (activeAgents.length > 0) {
112
+ windowStart = clampAgentWindowWithSelection({
113
+ activeCount: activeAgents.length,
114
+ maxWindow: maxAgentWindow,
115
+ windowStart,
116
+ selectionIndex: selectedAgentIndex,
117
+ });
118
+ const maxItems = Math.max(1, Math.min(maxAgentWindow, activeAgents.length));
119
+ const start = windowStart;
120
+ const end = start + maxItems;
121
+ const visibleAgents = activeAgents.slice(start, end);
122
+ const agentParts = visibleAgents.map((agent, i) => {
123
+ const absoluteIndex = start + i;
124
+ const label = ensureAtPrefix(getAgentLabel(agent));
125
+ if (absoluteIndex === selectedAgentIndex) {
126
+ return `{inverse}${label}{/inverse}`;
127
+ }
128
+ return `{cyan-fg}${label}{/cyan-fg}`;
129
+ });
130
+ const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
131
+ const rightMore = end < activeAgents.length ? " {gray-fg}>{/gray-fg}" : "";
132
+ content += `{gray-fg}Agents:{/gray-fg} ${leftMore}${agentParts.join(" ")}${rightMore}`;
133
+ content += ` {gray-fg}│ ${dashHints.agents || ""}{/gray-fg}`;
134
+ } else {
135
+ content += "{gray-fg}Agents:{/gray-fg} {cyan-fg}none{/cyan-fg}";
136
+ content += ` {gray-fg}│ ${dashHints.agentsEmpty || ""}{/gray-fg}`;
137
+ }
138
+ return { content, windowStart };
139
+ }
140
+
141
+ const agents = activeAgents.length > 0
142
+ ? activeAgents.slice(0, 3).map((id) => ensureAtPrefix(getAgentLabel(id))).join(", ") + (activeAgents.length > 3 ? ` +${activeAgents.length - 3}` : "")
143
+ : "none";
144
+ content += `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`;
145
+ content += ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`;
146
+ content += ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`;
147
+ content += ` {gray-fg}Assistant:{/gray-fg} {cyan-fg}${assistantLabel(assistantEngine)}{/cyan-fg}`;
148
+ content += ` {gray-fg}Cron:{/gray-fg} {cyan-fg}${Array.isArray(cronTasks) ? cronTasks.length : 0}{/cyan-fg}`;
149
+
150
+ return { content, windowStart };
151
+ }
152
+
153
+ module.exports = {
154
+ computeDashboardContent,
155
+ providerLabel,
156
+ assistantLabel,
157
+ };