u-foo 1.0.6 → 1.1.9

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 +44 -4
  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 +11 -2
  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 +154 -0
  64. package/src/chat/index.js +935 -2909
  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 +132 -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 +1580 -0
  98. package/src/config.js +47 -1
  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 +661 -488
  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 = state.agentProvider === "claude-cli" ? 1 : 0;
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,154 @@
1
+ const { clampAgentWindowWithSelection } = require("./agentDirectory");
2
+
3
+ const DEFAULT_MODE_OPTIONS = ["terminal", "tmux", "internal"];
4
+
5
+ function providerLabel(value) {
6
+ return value === "claude-cli" ? "claude" : "codex";
7
+ }
8
+
9
+ function assistantLabel(value) {
10
+ if (value === "codex") return "codex";
11
+ if (value === "claude") return "claude";
12
+ if (value === "ufoo") return "ufoo";
13
+ return "auto";
14
+ }
15
+
16
+ function ensureAtPrefix(value) {
17
+ const text = String(value || "").trim();
18
+ if (!text) return text;
19
+ return text.startsWith("@") ? text : `@${text}`;
20
+ }
21
+
22
+ function computeDashboardContent(options = {}) {
23
+ const {
24
+ focusMode = "input",
25
+ dashboardView = "agents",
26
+ activeAgents = [],
27
+ selectedAgentIndex = -1,
28
+ agentListWindowStart = 0,
29
+ maxAgentWindow = 4,
30
+ getAgentLabel = (id) => id,
31
+ launchMode = "terminal",
32
+ agentProvider = "codex-cli",
33
+ assistantEngine = "auto",
34
+ selectedModeIndex = 0,
35
+ selectedProviderIndex = 0,
36
+ selectedAssistantIndex = 0,
37
+ selectedResumeIndex = 0,
38
+ cronTasks = [],
39
+ providerOptions = [],
40
+ assistantOptions = [],
41
+ resumeOptions = [],
42
+ dashHints = {},
43
+ modeOptions = DEFAULT_MODE_OPTIONS,
44
+ } = options;
45
+
46
+ let content = " ";
47
+ let windowStart = agentListWindowStart;
48
+
49
+ if (focusMode === "dashboard") {
50
+ if (dashboardView === "mode") {
51
+ const modeParts = modeOptions.map((mode, i) => {
52
+ if (i === selectedModeIndex) {
53
+ return `{inverse}${mode}{/inverse}`;
54
+ }
55
+ return `{cyan-fg}${mode}{/cyan-fg}`;
56
+ });
57
+ content += `{gray-fg}Mode:{/gray-fg} ${modeParts.join(" ")}`;
58
+ content += ` {gray-fg}│ ${dashHints.mode || ""}{/gray-fg}`;
59
+ return { content, windowStart };
60
+ }
61
+
62
+ if (dashboardView === "provider") {
63
+ const providerParts = providerOptions.map((opt, i) => {
64
+ if (i === selectedProviderIndex) {
65
+ return `{inverse}${opt.label}{/inverse}`;
66
+ }
67
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
68
+ });
69
+ content += `{gray-fg}Agent:{/gray-fg} ${providerParts.join(" ")}`;
70
+ content += ` {gray-fg}│ ${dashHints.provider || ""}{/gray-fg}`;
71
+ return { content, windowStart };
72
+ }
73
+
74
+ if (dashboardView === "assistant") {
75
+ const assistantParts = assistantOptions.map((opt, i) => {
76
+ if (i === selectedAssistantIndex) {
77
+ return `{inverse}${opt.label}{/inverse}`;
78
+ }
79
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
80
+ });
81
+ content += `{gray-fg}Assistant:{/gray-fg} ${assistantParts.join(" ")}`;
82
+ content += ` {gray-fg}│ ${dashHints.assistant || ""}{/gray-fg}`;
83
+ return { content, windowStart };
84
+ }
85
+
86
+ if (dashboardView === "resume") {
87
+ const resumeParts = resumeOptions.map((opt, i) => {
88
+ if (i === selectedResumeIndex) {
89
+ return `{inverse}${opt.label}{/inverse}`;
90
+ }
91
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
92
+ });
93
+ content += `{gray-fg}Resume:{/gray-fg} ${resumeParts.join(" ")}`;
94
+ content += ` {gray-fg}│ ${dashHints.resume || ""}{/gray-fg}`;
95
+ return { content, windowStart };
96
+ }
97
+
98
+ if (dashboardView === "cron") {
99
+ const items = Array.isArray(cronTasks) ? cronTasks : [];
100
+ const summary = items.length > 0
101
+ ? items.map((item) => item.summary || item.id || "").filter(Boolean).join(", ")
102
+ : "none";
103
+ content += `{gray-fg}Cron:{/gray-fg} {cyan-fg}${summary}{/cyan-fg}`;
104
+ content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
105
+ return { content, windowStart };
106
+ }
107
+
108
+ if (activeAgents.length > 0) {
109
+ windowStart = clampAgentWindowWithSelection({
110
+ activeCount: activeAgents.length,
111
+ maxWindow: maxAgentWindow,
112
+ windowStart,
113
+ selectionIndex: selectedAgentIndex,
114
+ });
115
+ const maxItems = Math.max(1, Math.min(maxAgentWindow, activeAgents.length));
116
+ const start = windowStart;
117
+ const end = start + maxItems;
118
+ const visibleAgents = activeAgents.slice(start, end);
119
+ const agentParts = visibleAgents.map((agent, i) => {
120
+ const absoluteIndex = start + i;
121
+ const label = ensureAtPrefix(getAgentLabel(agent));
122
+ if (absoluteIndex === selectedAgentIndex) {
123
+ return `{inverse}${label}{/inverse}`;
124
+ }
125
+ return `{cyan-fg}${label}{/cyan-fg}`;
126
+ });
127
+ const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
128
+ const rightMore = end < activeAgents.length ? " {gray-fg}>{/gray-fg}" : "";
129
+ content += `{gray-fg}Agents:{/gray-fg} ${leftMore}${agentParts.join(" ")}${rightMore}`;
130
+ content += ` {gray-fg}│ ${dashHints.agents || ""}{/gray-fg}`;
131
+ } else {
132
+ content += "{gray-fg}Agents:{/gray-fg} {cyan-fg}none{/cyan-fg}";
133
+ content += ` {gray-fg}│ ${dashHints.agentsEmpty || ""}{/gray-fg}`;
134
+ }
135
+ return { content, windowStart };
136
+ }
137
+
138
+ const agents = activeAgents.length > 0
139
+ ? activeAgents.slice(0, 3).map((id) => ensureAtPrefix(getAgentLabel(id))).join(", ") + (activeAgents.length > 3 ? ` +${activeAgents.length - 3}` : "")
140
+ : "none";
141
+ content += `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`;
142
+ content += ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`;
143
+ content += ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`;
144
+ content += ` {gray-fg}Assistant:{/gray-fg} {cyan-fg}${assistantLabel(assistantEngine)}{/cyan-fg}`;
145
+ content += ` {gray-fg}Cron:{/gray-fg} {cyan-fg}${Array.isArray(cronTasks) ? cronTasks.length : 0}{/cyan-fg}`;
146
+
147
+ return { content, windowStart };
148
+ }
149
+
150
+ module.exports = {
151
+ computeDashboardContent,
152
+ providerLabel,
153
+ assistantLabel,
154
+ };