u-foo 1.4.1 → 1.6.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 (47) hide show
  1. package/README.md +21 -0
  2. package/README.zh-CN.md +21 -0
  3. package/bin/ufoo.js +15 -7
  4. package/modules/AGENTS.template.md +4 -102
  5. package/package.json +3 -2
  6. package/scripts/global-chat-switch-benchmark.js +406 -0
  7. package/src/agent/activityDetector.js +328 -0
  8. package/src/agent/activityStatePublisher.js +67 -0
  9. package/src/agent/activityStateWriter.js +40 -0
  10. package/src/agent/internalRunner.js +13 -0
  11. package/src/agent/launcher.js +47 -7
  12. package/src/agent/notifier.js +73 -4
  13. package/src/agent/ptyRunner.js +81 -34
  14. package/src/agent/ufooAgent.js +192 -6
  15. package/src/bus/message.js +1 -9
  16. package/src/bus/subscriber.js +2 -0
  17. package/src/bus/utils.js +10 -0
  18. package/src/chat/agentBar.js +21 -3
  19. package/src/chat/agentViewController.js +2 -0
  20. package/src/chat/chatLogController.js +28 -5
  21. package/src/chat/commandExecutor.js +127 -3
  22. package/src/chat/commands.js +8 -0
  23. package/src/chat/daemonConnection.js +77 -4
  24. package/src/chat/daemonCoordinator.js +36 -0
  25. package/src/chat/daemonMessageRouter.js +22 -0
  26. package/src/chat/daemonTransport.js +47 -5
  27. package/src/chat/daemonTransportDefaults.js +1 -0
  28. package/src/chat/dashboardKeyController.js +89 -1
  29. package/src/chat/dashboardView.js +312 -93
  30. package/src/chat/index.js +683 -41
  31. package/src/chat/inputHistoryController.js +33 -3
  32. package/src/chat/inputListenerController.js +22 -12
  33. package/src/chat/layout.js +12 -7
  34. package/src/chat/projectCloseController.js +119 -0
  35. package/src/chat/projectRuntimes.js +55 -0
  36. package/src/chat/statusLineController.js +52 -6
  37. package/src/chat/streamTracker.js +6 -0
  38. package/src/chat/transport.js +41 -5
  39. package/src/cli.js +167 -4
  40. package/src/daemon/index.js +54 -5
  41. package/src/daemon/ipcServer.js +6 -1
  42. package/src/daemon/ops.js +245 -35
  43. package/src/daemon/status.js +3 -1
  44. package/src/init/index.js +32 -3
  45. package/src/projects/projectId.js +29 -0
  46. package/src/projects/registry.js +279 -0
  47. package/src/ufoo/agentsStore.js +44 -0
@@ -3,6 +3,7 @@ const DEFAULT_MODE_OPTIONS = ["terminal", "tmux", "internal"];
3
3
  function createDashboardKeyController(options = {}) {
4
4
  const {
5
5
  state,
6
+ globalMode = false,
6
7
  existsSync = () => false,
7
8
  getInjectSockPath = () => "",
8
9
  getAgentAdapter = () => null,
@@ -21,6 +22,8 @@ function createDashboardKeyController(options = {}) {
21
22
  setAutoResume = () => {},
22
23
  clampAgentWindow = () => {},
23
24
  clampAgentWindowWithSelection = () => {},
25
+ requestProjectSwitch = () => {},
26
+ requestCloseProject = () => {},
24
27
  renderDashboard = () => {},
25
28
  renderAgentDashboard = () => {},
26
29
  renderScreen = () => {},
@@ -374,6 +377,73 @@ function createDashboardKeyController(options = {}) {
374
377
  return true;
375
378
  }
376
379
 
380
+ function handleProjectsKey(key) {
381
+ const projects = Array.isArray(state.projects) ? state.projects : [];
382
+ if (projects.length === 0) {
383
+ if (key.name === "up" || key.name === "enter" || key.name === "return" || key.name === "escape") {
384
+ exitDashboardMode(false);
385
+ }
386
+ return true;
387
+ }
388
+
389
+ if (key.name === "x" && key.ctrl) {
390
+ const current = Number.isFinite(state.selectedProjectIndex) ? state.selectedProjectIndex : 0;
391
+ if (current >= 0 && current < projects.length) {
392
+ requestCloseProject(current);
393
+ }
394
+ return true;
395
+ }
396
+
397
+ if (key.name === "down") {
398
+ state.dashboardView = "agents";
399
+ if (!Array.isArray(state.activeAgents) || state.activeAgents.length === 0) {
400
+ state.selectedAgentIndex = -1;
401
+ } else if (!Number.isFinite(state.selectedAgentIndex) || state.selectedAgentIndex < 0) {
402
+ state.selectedAgentIndex = 0;
403
+ } else if (state.selectedAgentIndex >= state.activeAgents.length) {
404
+ state.selectedAgentIndex = state.activeAgents.length - 1;
405
+ }
406
+ clampAgentWindow();
407
+ syncTargetFromSelection();
408
+ renderDashboardAndScreen();
409
+ return true;
410
+ }
411
+
412
+ if (key.name === "left") {
413
+ const current = Number.isFinite(state.selectedProjectIndex) ? state.selectedProjectIndex : 0;
414
+ if (current > 0) {
415
+ const next = current - 1;
416
+ state.selectedProjectIndex = next;
417
+ renderDashboardAndScreen();
418
+ requestProjectSwitch(next);
419
+ }
420
+ return true;
421
+ }
422
+
423
+ if (key.name === "right") {
424
+ const current = Number.isFinite(state.selectedProjectIndex) ? state.selectedProjectIndex : 0;
425
+ if (current < projects.length - 1) {
426
+ const next = current + 1;
427
+ state.selectedProjectIndex = next;
428
+ renderDashboardAndScreen();
429
+ requestProjectSwitch(next);
430
+ }
431
+ return true;
432
+ }
433
+
434
+ if (key.name === "up" || key.name === "escape") {
435
+ exitDashboardMode(false);
436
+ return true;
437
+ }
438
+
439
+ if (key.name === "enter" || key.name === "return") {
440
+ exitDashboardMode(false);
441
+ return true;
442
+ }
443
+
444
+ return true;
445
+ }
446
+
377
447
  function handleAgentsKey(key) {
378
448
  if (key.name === "left") {
379
449
  if (state.activeAgents.length > 0 && state.selectedAgentIndex > 0) {
@@ -403,7 +473,18 @@ function createDashboardKeyController(options = {}) {
403
473
  return true;
404
474
  }
405
475
 
406
- if (key.name === "up" || key.name === "escape") {
476
+ if (key.name === "up") {
477
+ clearTargetAgent();
478
+ if (globalMode) {
479
+ state.dashboardView = "projects";
480
+ renderDashboardAndScreen();
481
+ return true;
482
+ }
483
+ exitDashboardMode(false);
484
+ return true;
485
+ }
486
+
487
+ if (key.name === "escape") {
407
488
  clearTargetAgent();
408
489
  exitDashboardMode(false);
409
490
  return true;
@@ -461,6 +542,13 @@ function createDashboardKeyController(options = {}) {
461
542
  return handleAgentDashboardKey(key);
462
543
  }
463
544
 
545
+ if (globalMode && state.dashboardView === "projects") {
546
+ return handleProjectsKey(key);
547
+ }
548
+ if (!globalMode && state.dashboardView === "projects") {
549
+ return true;
550
+ }
551
+
464
552
  if (state.dashboardView === "mode") return handleModeKey(key);
465
553
  if (state.dashboardView === "provider") return handleProviderKey(key);
466
554
  if (state.dashboardView === "assistant") return handleAssistantKey(key);
@@ -22,18 +22,116 @@ function ensureAtPrefix(value) {
22
22
  return text.startsWith("@") ? text : `@${text}`;
23
23
  }
24
24
 
25
- function computeDashboardContent(options = {}) {
25
+ function activityMarker(state = "") {
26
+ const normalized = String(state || "").trim().toLowerCase();
27
+ if (normalized === "working") return "*";
28
+ if (normalized === "waiting_input") return "?";
29
+ if (normalized === "blocked") return "!";
30
+ return "";
31
+ }
32
+
33
+ function withActivityMarker(label = "", state = "") {
34
+ const marker = activityMarker(state);
35
+ if (!marker) return label;
36
+ return `${marker}${label}`;
37
+ }
38
+
39
+ function buildSummaryLine(options = {}) {
26
40
  const {
27
- focusMode = "input",
41
+ activeAgents = [],
42
+ getAgentLabel = (id) => id,
43
+ getAgentState = () => "",
44
+ launchMode = "terminal",
45
+ agentProvider = "codex-cli",
46
+ assistantEngine = "auto",
47
+ cronTasks = [],
48
+ } = options;
49
+ const agents = activeAgents.length > 0
50
+ ? activeAgents.slice(0, 3)
51
+ .map((id) => withActivityMarker(ensureAtPrefix(getAgentLabel(id)), getAgentState(id)))
52
+ .join(", ")
53
+ + (activeAgents.length > 3 ? ` +${activeAgents.length - 3}` : "")
54
+ : "none";
55
+ return `{gray-fg}Agents:{/gray-fg} {cyan-fg}${agents}{/cyan-fg}`
56
+ + ` {gray-fg}Mode:{/gray-fg} {cyan-fg}${launchMode}{/cyan-fg}`
57
+ + ` {gray-fg}Agent:{/gray-fg} {cyan-fg}${providerLabel(agentProvider)}{/cyan-fg}`
58
+ + ` {gray-fg}Assistant:{/gray-fg} {cyan-fg}${assistantLabel(assistantEngine)}{/cyan-fg}`
59
+ + ` {gray-fg}Cron:{/gray-fg} {cyan-fg}${Array.isArray(cronTasks) ? cronTasks.length : 0}{/cyan-fg}`;
60
+ }
61
+
62
+ function buildProjectRailLine(options = {}) {
63
+ const {
64
+ projects = [],
65
+ selectedProjectIndex = -1,
66
+ projectListWindowStart = 0,
67
+ maxProjectWindow = 5,
68
+ activeProjectRoot = "",
69
+ projectsFocused = false,
70
+ } = options;
71
+ const rows = Array.isArray(projects) ? projects : [];
72
+ let windowStart = projectListWindowStart;
73
+ if (rows.length === 0) {
74
+ return {
75
+ hasProjects: false,
76
+ line: " {gray-fg}Projects:{/gray-fg} {cyan-fg}none{/cyan-fg}",
77
+ windowStart,
78
+ };
79
+ }
80
+
81
+ const activeRoot = String(activeProjectRoot || "");
82
+ const fallbackIndex = rows.findIndex((row) => String((row || {}).project_root || "") === activeRoot);
83
+ const normalizedSelectedIndex = Number.isFinite(selectedProjectIndex)
84
+ ? Math.trunc(selectedProjectIndex)
85
+ : -1;
86
+ const safeSelectedIndex = normalizedSelectedIndex >= 0 && normalizedSelectedIndex < rows.length
87
+ ? normalizedSelectedIndex
88
+ : (fallbackIndex >= 0 ? fallbackIndex : 0);
89
+
90
+ windowStart = clampAgentWindowWithSelection({
91
+ activeCount: rows.length,
92
+ maxWindow: Math.max(1, maxProjectWindow),
93
+ windowStart,
94
+ selectionIndex: safeSelectedIndex,
95
+ });
96
+
97
+ const maxItems = Math.max(1, Math.min(Math.max(1, maxProjectWindow), rows.length));
98
+ const start = windowStart;
99
+ const end = start + maxItems;
100
+ const visibleRows = rows.slice(start, end);
101
+ const projectParts = visibleRows.map((row, i) => {
102
+ const absoluteIndex = start + i;
103
+ const name = String((row && row.project_name) || (row && row.project_root) || "-");
104
+ const rowRoot = String((row && row.project_root) || "");
105
+ const isActiveProject = Boolean(activeRoot && rowRoot === activeRoot);
106
+ const isSelected = absoluteIndex === safeSelectedIndex;
107
+ if (projectsFocused && isSelected) {
108
+ return `{inverse}${name}{/inverse}`;
109
+ }
110
+ if (isActiveProject) {
111
+ return `{bold}{cyan-fg}${name}{/cyan-fg}{/bold}`;
112
+ }
113
+ return `{cyan-fg}${name}{/cyan-fg}`;
114
+ });
115
+
116
+ const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
117
+ const rightMore = end < rows.length ? " {gray-fg}>{/gray-fg}" : "";
118
+ return {
119
+ hasProjects: true,
120
+ line: ` {gray-fg}Projects:{/gray-fg} ${leftMore}${projectParts.join(" ")}${rightMore}`,
121
+ windowStart,
122
+ };
123
+ }
124
+
125
+ function buildDashboardDetailLine(options = {}) {
126
+ const {
127
+ globalMode = false,
28
128
  dashboardView = "agents",
29
129
  activeAgents = [],
30
130
  selectedAgentIndex = -1,
31
131
  agentListWindowStart = 0,
32
132
  maxAgentWindow = 4,
33
133
  getAgentLabel = (id) => id,
34
- launchMode = "terminal",
35
- agentProvider = "codex-cli",
36
- assistantEngine = "auto",
134
+ getAgentState = () => "",
37
135
  selectedModeIndex = 0,
38
136
  selectedProviderIndex = 0,
39
137
  selectedAssistantIndex = 0,
@@ -49,105 +147,226 @@ function computeDashboardContent(options = {}) {
49
147
  let content = " ";
50
148
  let windowStart = agentListWindowStart;
51
149
 
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
- }
150
+ if (dashboardView === "mode") {
151
+ const modeParts = modeOptions.map((mode, i) => {
152
+ if (i === selectedModeIndex) {
153
+ return `{inverse}${mode}{/inverse}`;
154
+ }
155
+ return `{cyan-fg}${mode}{/cyan-fg}`;
156
+ });
157
+ content += `{gray-fg}Mode:{/gray-fg} ${modeParts.join(" ")}`;
158
+ content += ` {gray-fg} ${dashHints.mode || ""}{/gray-fg}`;
159
+ return { content, windowStart };
160
+ }
64
161
 
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
- }
162
+ if (dashboardView === "provider") {
163
+ const providerParts = providerOptions.map((opt, i) => {
164
+ if (i === selectedProviderIndex) {
165
+ return `{inverse}${opt.label}{/inverse}`;
166
+ }
167
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
168
+ });
169
+ content += `{gray-fg}Agent:{/gray-fg} ${providerParts.join(" ")}`;
170
+ content += ` {gray-fg}│ ${dashHints.provider || ""}{/gray-fg}`;
171
+ return { content, windowStart };
172
+ }
76
173
 
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
- }
174
+ if (dashboardView === "assistant") {
175
+ const assistantParts = assistantOptions.map((opt, i) => {
176
+ if (i === selectedAssistantIndex) {
177
+ return `{inverse}${opt.label}{/inverse}`;
178
+ }
179
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
180
+ });
181
+ content += `{gray-fg}Assistant:{/gray-fg} ${assistantParts.join(" ")}`;
182
+ content += ` {gray-fg}│ ${dashHints.assistant || ""}{/gray-fg}`;
183
+ return { content, windowStart };
184
+ }
88
185
 
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
- }
186
+ if (dashboardView === "resume") {
187
+ const resumeParts = resumeOptions.map((opt, i) => {
188
+ if (i === selectedResumeIndex) {
189
+ return `{inverse}${opt.label}{/inverse}`;
190
+ }
191
+ return `{cyan-fg}${opt.label}{/cyan-fg}`;
192
+ });
193
+ content += `{gray-fg}Resume:{/gray-fg} ${resumeParts.join(" ")}`;
194
+ content += ` {gray-fg}│ ${dashHints.resume || ""}{/gray-fg}`;
195
+ return { content, windowStart };
196
+ }
197
+
198
+ if (dashboardView === "cron") {
199
+ const items = Array.isArray(cronTasks) ? cronTasks : [];
200
+ const summary = items.length > 0
201
+ ? items.map((item) => item.summary || item.id || "").filter(Boolean).join(", ")
202
+ : "none";
203
+ content += `{gray-fg}Cron:{/gray-fg} {cyan-fg}${summary}{/cyan-fg}`;
204
+ content += ` {gray-fg}│ ${dashHints.cron || ""}{/gray-fg}`;
205
+ return { content, windowStart };
206
+ }
207
+
208
+ if (activeAgents.length > 0) {
209
+ windowStart = clampAgentWindowWithSelection({
210
+ activeCount: activeAgents.length,
211
+ maxWindow: maxAgentWindow,
212
+ windowStart,
213
+ selectionIndex: selectedAgentIndex,
214
+ });
215
+ const maxItems = Math.max(1, Math.min(maxAgentWindow, activeAgents.length));
216
+ const start = windowStart;
217
+ const end = start + maxItems;
218
+ const visibleAgents = activeAgents.slice(start, end);
219
+ const agentParts = visibleAgents.map((agent, i) => {
220
+ const absoluteIndex = start + i;
221
+ const label = withActivityMarker(
222
+ ensureAtPrefix(getAgentLabel(agent)),
223
+ getAgentState(agent)
224
+ );
225
+ if (absoluteIndex === selectedAgentIndex) {
226
+ return `{inverse}${label}{/inverse}`;
227
+ }
228
+ return `{cyan-fg}${label}{/cyan-fg}`;
229
+ });
230
+ const leftMore = start > 0 ? "{gray-fg}<{/gray-fg} " : "";
231
+ const rightMore = end < activeAgents.length ? " {gray-fg}>{/gray-fg}" : "";
232
+ content += `{gray-fg}Agents:{/gray-fg} ${leftMore}${agentParts.join(" ")}${rightMore}`;
233
+ const agentsHint = globalMode
234
+ ? (dashHints.agentsGlobal || dashHints.agents || "")
235
+ : (dashHints.agents || "");
236
+ content += ` {gray-fg}│ ${agentsHint}{/gray-fg}`;
237
+ } else {
238
+ content += "{gray-fg}Agents:{/gray-fg} {cyan-fg}none{/cyan-fg}";
239
+ content += ` {gray-fg}│ ${dashHints.agentsEmpty || ""}{/gray-fg}`;
240
+ }
241
+ return { content, windowStart };
242
+ }
100
243
 
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 };
244
+ function computeDashboardContent(options = {}) {
245
+ const {
246
+ globalMode = false,
247
+ focusMode = "input",
248
+ dashboardView = "agents",
249
+ activeAgents = [],
250
+ projects = [],
251
+ selectedProjectIndex = -1,
252
+ projectListWindowStart = 0,
253
+ maxProjectWindow = 5,
254
+ activeProjectRoot = "",
255
+ selectedAgentIndex = -1,
256
+ agentListWindowStart = 0,
257
+ maxAgentWindow = 4,
258
+ getAgentLabel = (id) => id,
259
+ getAgentState = () => "",
260
+ launchMode = "terminal",
261
+ agentProvider = "codex-cli",
262
+ assistantEngine = "auto",
263
+ selectedModeIndex = 0,
264
+ selectedProviderIndex = 0,
265
+ selectedAssistantIndex = 0,
266
+ selectedResumeIndex = 0,
267
+ cronTasks = [],
268
+ providerOptions = [],
269
+ assistantOptions = [],
270
+ resumeOptions = [],
271
+ dashHints = {},
272
+ modeOptions = DEFAULT_MODE_OPTIONS,
273
+ } = options;
274
+
275
+ if (globalMode) {
276
+ const projectsFocused = focusMode === "dashboard" && dashboardView === "projects";
277
+ const rail = buildProjectRailLine({
278
+ projects,
279
+ selectedProjectIndex,
280
+ projectListWindowStart,
281
+ maxProjectWindow,
282
+ activeProjectRoot,
283
+ projectsFocused,
284
+ });
285
+ if (!rail.hasProjects) {
286
+ const line2 = ` {gray-fg}${dashHints.projectsEmpty || "Run ufoo chat/daemon in projects to populate registry"}{/gray-fg}`;
287
+ return {
288
+ content: `${rail.line}\n${line2}`,
289
+ windowStart: rail.windowStart,
290
+ };
109
291
  }
110
292
 
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}`;
293
+ if (focusMode !== "dashboard" || projectsFocused) {
294
+ const line2 = buildSummaryLine({
295
+ activeAgents,
296
+ getAgentLabel,
297
+ getAgentState,
298
+ launchMode,
299
+ agentProvider,
300
+ assistantEngine,
301
+ cronTasks,
129
302
  });
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}`;
303
+ return {
304
+ content: `${rail.line}\n ${line2}`,
305
+ windowStart: rail.windowStart,
306
+ };
137
307
  }
138
- return { content, windowStart };
308
+
309
+ const detail = buildDashboardDetailLine({
310
+ globalMode,
311
+ dashboardView,
312
+ activeAgents,
313
+ selectedAgentIndex,
314
+ agentListWindowStart,
315
+ maxAgentWindow,
316
+ getAgentLabel,
317
+ getAgentState,
318
+ selectedModeIndex,
319
+ selectedProviderIndex,
320
+ selectedAssistantIndex,
321
+ selectedResumeIndex,
322
+ cronTasks,
323
+ providerOptions,
324
+ assistantOptions,
325
+ resumeOptions,
326
+ dashHints,
327
+ modeOptions,
328
+ });
329
+ return {
330
+ content: `${rail.line}\n${detail.content}`,
331
+ windowStart: detail.windowStart,
332
+ };
139
333
  }
140
334
 
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}`;
335
+ if (focusMode === "dashboard") {
336
+ return buildDashboardDetailLine({
337
+ globalMode,
338
+ dashboardView,
339
+ activeAgents,
340
+ selectedAgentIndex,
341
+ agentListWindowStart,
342
+ maxAgentWindow,
343
+ getAgentLabel,
344
+ getAgentState,
345
+ selectedModeIndex,
346
+ selectedProviderIndex,
347
+ selectedAssistantIndex,
348
+ selectedResumeIndex,
349
+ cronTasks,
350
+ providerOptions,
351
+ assistantOptions,
352
+ resumeOptions,
353
+ dashHints,
354
+ modeOptions,
355
+ });
356
+ }
149
357
 
150
- return { content, windowStart };
358
+ let content = " ";
359
+ content += buildSummaryLine({
360
+ activeAgents,
361
+ getAgentLabel,
362
+ getAgentState,
363
+ launchMode,
364
+ agentProvider,
365
+ assistantEngine,
366
+ cronTasks,
367
+ });
368
+
369
+ return { content, windowStart: agentListWindowStart };
151
370
  }
152
371
 
153
372
  module.exports = {