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.
- package/README.md +157 -213
- package/README.zh-CN.md +151 -197
- package/SKILLS/ufoo/SKILL.md +8 -8
- package/bin/uagy.js +69 -0
- package/bin/uclaude.js +2 -2
- package/bin/ucode.js +4 -4
- package/bin/ucodex.js +2 -2
- package/bin/ufoo.js +5 -23
- package/modules/AGENTS.template.md +1 -1
- package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
- package/package.json +5 -5
- package/scripts/chat-app-smoke.js +1 -1
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +1 -1
- package/scripts/ink-smoke.js +1 -1
- package/scripts/ucode-app-smoke.js +1 -1
- package/src/{agent → agents/activity}/activityDetector.js +39 -2
- package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
- package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
- package/src/{agent → agents/activity}/activityTracker.js +1 -1
- package/src/agents/activity/index.js +8 -0
- package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
- package/src/agents/controller/index.js +8 -0
- package/src/{agent → agents/controller}/loopObservability.js +2 -2
- package/src/{agent → agents/controller}/loopRuntime.js +1 -1
- package/src/{agent → agents/controller}/ufooAgent.js +9 -9
- package/src/agents/index.js +10 -0
- package/src/agents/internal/index.js +3 -0
- package/src/{agent → agents/internal}/internalRunner.js +45 -22
- package/src/agents/launch/agyConversation.js +159 -0
- package/src/agents/launch/index.js +12 -0
- package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
- package/src/{agent → agents/launch}/launcher.js +64 -21
- package/src/{agent → agents/launch}/notifier.js +23 -12
- package/src/{agent → agents/launch}/ptyRunner.js +44 -12
- package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
- package/src/{agent → agents/launch}/publisherRouting.js +1 -1
- package/src/{agent → agents/launch}/readyDetector.js +23 -0
- package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
- package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
- package/src/agents/prompts/index.js +8 -0
- package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
- package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
- package/src/agents/providers/index.js +13 -0
- package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
- package/src/{chat → app/chat}/agentSockets.js +1 -1
- package/src/{chat → app/chat}/commandExecutor.js +50 -26
- package/src/{chat → app/chat}/commands.js +119 -5
- package/src/{chat → app/chat}/daemonConnection.js +1 -1
- package/src/{chat → app/chat}/daemonMessageRouter.js +45 -3
- package/src/{chat → app/chat}/dashboardView.js +2 -1
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +4 -13
- package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
- package/src/app/chat/multiWindow/index.js +268 -0
- package/src/app/chat/multiWindow/paneLayout.js +84 -0
- package/src/app/chat/multiWindow/paneManager.js +299 -0
- package/src/app/chat/multiWindow/renderer.js +384 -0
- package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
- package/src/{chat → app/chat}/transport.js +1 -1
- package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
- package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
- package/src/{init/index.js → app/cli/features/init.js} +14 -32
- package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
- package/src/app/cli/index.js +9 -0
- package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
- package/src/{cli.js → app/cli/run.js} +59 -57
- package/src/app/index.js +6 -0
- package/src/code/agent.js +10 -9
- package/src/code/index.js +2 -0
- package/src/code/launcher/index.js +9 -0
- package/src/{agent → code/launcher}/ucode.js +7 -8
- package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
- package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
- package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
- package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
- package/src/code/nativeRunner.js +4 -4
- package/src/code/tui.js +3 -1454
- package/src/config.js +15 -2
- package/src/{bus → coordination/bus}/activate.js +2 -2
- package/src/{bus → coordination/bus}/daemon.js +15 -5
- package/src/coordination/bus/envelope.js +173 -0
- package/src/{bus → coordination/bus}/index.js +7 -3
- package/src/{bus → coordination/bus}/inject.js +11 -3
- package/src/{bus → coordination/bus}/message.js +1 -1
- package/src/coordination/bus/messageMeta.js +130 -0
- package/src/coordination/bus/promptEnvelope.js +65 -0
- package/src/{bus → coordination/bus}/shake.js +1 -1
- package/src/{bus → coordination/bus}/store.js +3 -3
- package/src/{bus → coordination/bus}/subscriber.js +2 -2
- package/src/{bus → coordination/bus}/utils.js +2 -2
- package/src/{history → coordination/history}/inputTimeline.js +5 -5
- package/src/coordination/index.js +10 -0
- package/src/{memory → coordination/memory}/historySearch.js +1 -1
- package/src/{memory → coordination/memory}/index.js +3 -3
- package/src/{report → coordination/report}/store.js +2 -2
- package/src/{status → coordination/status}/index.js +3 -3
- package/src/online/bridge.js +2 -2
- package/src/{controller → orchestration/controller}/flags.js +1 -1
- package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
- package/src/orchestration/controller/index.js +10 -0
- package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
- package/src/orchestration/groups/bootstrap.js +3 -0
- package/src/orchestration/groups/index.js +10 -0
- package/src/orchestration/groups/promptProfiles.js +3 -0
- package/src/{group → orchestration/groups}/templates.js +1 -1
- package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
- package/src/orchestration/index.js +7 -0
- package/src/orchestration/solo/index.js +3 -0
- package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
- package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
- package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
- package/src/{daemon → runtime/daemon}/index.js +105 -53
- package/src/{daemon → runtime/daemon}/ipcServer.js +1 -1
- package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
- package/src/{daemon → runtime/daemon}/ops.js +48 -61
- package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
- package/src/{daemon → runtime/daemon}/promptRequest.js +7 -7
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +4 -4
- package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
- package/src/{daemon → runtime/daemon}/status.js +5 -5
- package/src/runtime/index.js +10 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
- package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
- package/src/tools/handlers/common.js +1 -1
- package/src/tools/handlers/listAgents.js +1 -1
- package/src/tools/handlers/memory.js +3 -3
- package/src/tools/handlers/readBusSummary.js +1 -1
- package/src/tools/handlers/readOpenDecisions.js +1 -1
- package/src/tools/handlers/readProjectRegistry.js +1 -1
- package/src/tools/handlers/readPromptHistory.js +2 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/MIGRATION.md +42 -88
- package/src/ui/format/index.js +5 -28
- package/src/ui/index.js +1 -1
- package/src/ui/{components → ink}/ChatApp.js +812 -88
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/{components → ink}/MultilineInput.js +230 -5
- package/src/ui/{components → ink}/UcodeApp.js +16 -7
- package/src/ui/{components → ink}/agentMirror.js +24 -19
- package/src/ui/{components → ink}/chatReducer.js +29 -7
- package/src/bus/messageMeta.js +0 -52
- package/src/chat/agentViewController.js +0 -1072
- package/src/chat/chatLogController.js +0 -138
- package/src/chat/completionController.js +0 -533
- package/src/chat/dashboardKeyController.js +0 -533
- package/src/chat/index.js +0 -2222
- package/src/chat/inputHistoryController.js +0 -135
- package/src/chat/inputListenerController.js +0 -470
- package/src/chat/layout.js +0 -186
- package/src/chat/pasteController.js +0 -81
- package/src/chat/statusLineController.js +0 -223
- package/src/chat/streamTracker.js +0 -156
- package/src/code/config +0 -0
- package/src/daemon/providerSessions.js +0 -488
- package/src/terminal/adapters/internalPtyAdapter.js +0 -42
- package/src/ui/components/DashboardBar.js +0 -417
- /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
- /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
- /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
- /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
- /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
- /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
- /package/src/{agent → agents/providers}/credentials/index.js +0 -0
- /package/src/{chat → app/chat}/agentBar.js +0 -0
- /package/src/{chat → app/chat}/agentDirectory.js +0 -0
- /package/src/{chat → app/chat}/cronScheduler.js +0 -0
- /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
- /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
- /package/src/{chat → app/chat}/daemonTransport.js +0 -0
- /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.js +0 -0
- /package/src/{chat → app/chat}/projectCloseController.js +0 -0
- /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
- /package/src/{chat → app/chat}/settingsController.js +0 -0
- /package/src/{chat → app/chat}/shellCommand.js +0 -0
- /package/src/{chat → app/chat}/text.js +0 -0
- /package/src/{chat → app/chat}/transientAgentState.js +0 -0
- /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
- /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
- /package/src/{bus → coordination/bus}/nickname.js +0 -0
- /package/src/{bus → coordination/bus}/queue.js +0 -0
- /package/src/{context → coordination/context}/decisions.js +0 -0
- /package/src/{context → coordination/context}/doctor.js +0 -0
- /package/src/{context → coordination/context}/index.js +0 -0
- /package/src/{context → coordination/context}/sync.js +0 -0
- /package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +0 -0
- /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
- /package/src/{ufoo → coordination/state}/paths.js +0 -0
- /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
- /package/src/{group → orchestration/groups}/diagram.js +0 -0
- /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
- /package/src/{solo → orchestration/solo}/commands.js +0 -0
- /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
- /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
- /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
- /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
- /package/src/{utils → runtime/process}/nodeExecutable.js +0 -0
- /package/src/{projects → runtime/projects}/identity.js +0 -0
- /package/src/{projects → runtime/projects}/index.js +0 -0
- /package/src/{projects → runtime/projects}/projectId.js +0 -0
- /package/src/{projects → runtime/projects}/runtimes.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/detect.js +0 -0
- /package/src/{terminal → runtime/terminal}/index.js +0 -0
- /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
- /package/src/{utils → ui/format}/banner.js +0 -0
- /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
- /package/src/ui/{components → ink}/InkDemo.js +0 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DashboardBar — the bottom 1-2 rows in chat showing the current dashboard
|
|
5
|
+
* mode (projects rail in global mode + one of agents/mode/provider/cron).
|
|
6
|
+
* Each row is laid out inside a hard cell budget so narrow terminals never
|
|
7
|
+
* spill content onto the next line: chips are dropped into "<…>" overflow
|
|
8
|
+
* markers, the trailing hint is sacrificed first, and Loop summary fields
|
|
9
|
+
* are progressively trimmed. The final row is folded into a single
|
|
10
|
+
* <Text wrap="truncate"> so ink truncates predictably regardless of the
|
|
11
|
+
* inline color spans.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const chalk = require("chalk");
|
|
15
|
+
|
|
16
|
+
const { clampAgentWindowWithSelection } = require("../../app/chat/agentDirectory");
|
|
17
|
+
const { providerLabel } = require("../../app/chat/dashboardView");
|
|
18
|
+
const { displayCellWidth, planProjectsRail } = require("../format");
|
|
19
|
+
|
|
20
|
+
const CHIP_SEP = " ";
|
|
21
|
+
const CHIP_SEP_WIDTH = displayCellWidth(CHIP_SEP);
|
|
22
|
+
const SUMMARY_GAP = " ";
|
|
23
|
+
const SUMMARY_GAP_WIDTH = displayCellWidth(SUMMARY_GAP);
|
|
24
|
+
const HINT_PREFIX = " · ";
|
|
25
|
+
const HINT_PREFIX_WIDTH = displayCellWidth(HINT_PREFIX);
|
|
26
|
+
|
|
27
|
+
function truncateToCells(text = "", cells = 0) {
|
|
28
|
+
const limit = Math.max(0, Math.floor(Number(cells) || 0));
|
|
29
|
+
const value = String(text || "");
|
|
30
|
+
if (limit <= 0) return "";
|
|
31
|
+
if (displayCellWidth(value) <= limit) return value;
|
|
32
|
+
if (limit <= 1) return "…";
|
|
33
|
+
let out = "";
|
|
34
|
+
let used = 0;
|
|
35
|
+
const body = limit - 1;
|
|
36
|
+
for (const ch of value) {
|
|
37
|
+
const w = displayCellWidth(ch);
|
|
38
|
+
if (used + w > body) break;
|
|
39
|
+
out += ch;
|
|
40
|
+
used += w;
|
|
41
|
+
}
|
|
42
|
+
return `${out || value.slice(0, 1)}…`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureAtPrefix(value) {
|
|
46
|
+
const text = String(value || "").trim();
|
|
47
|
+
if (!text) return text;
|
|
48
|
+
return text.startsWith("@") ? text : `@${text}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function activityMarker(state = "") {
|
|
52
|
+
const normalized = String(state || "").trim().toLowerCase();
|
|
53
|
+
if (normalized === "working") return "*";
|
|
54
|
+
if (normalized === "waiting_input") return "?";
|
|
55
|
+
if (normalized === "blocked") return "!";
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function withActivityMarker(label = "", state = "") {
|
|
60
|
+
const marker = activityMarker(state);
|
|
61
|
+
return marker ? `${marker}${label}` : label;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatToolDistribution(items = []) {
|
|
65
|
+
const tools = Array.isArray(items) ? items : [];
|
|
66
|
+
if (tools.length === 0) return "";
|
|
67
|
+
const visible = tools.slice(0, 2).map((item) => `${item.name}x${item.count}`);
|
|
68
|
+
if (tools.length > 2) visible.push(`+${tools.length - 2}`);
|
|
69
|
+
return visible.join(",");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatLoopSummary(loopSummary) {
|
|
73
|
+
if (!loopSummary || typeof loopSummary !== "object") return "";
|
|
74
|
+
const rounds = Number(loopSummary.rounds) || 0;
|
|
75
|
+
const toolCalls = Number(loopSummary.tool_calls) || 0;
|
|
76
|
+
const totalTokens = Number(loopSummary.total_tokens) || 0;
|
|
77
|
+
const cacheReadTokens = Number(loopSummary.cache_read_tokens) || 0;
|
|
78
|
+
const cacheCreationTokens = Number(loopSummary.cache_creation_tokens) || 0;
|
|
79
|
+
const terminalReason = String(loopSummary.terminal_reason || "").trim();
|
|
80
|
+
const toolDistribution = formatToolDistribution(loopSummary.tools);
|
|
81
|
+
if (rounds <= 0 && toolCalls <= 0 && totalTokens <= 0 && !terminalReason && !toolDistribution) return "";
|
|
82
|
+
const parts = [`r${rounds}`, `tc${toolCalls}`, `tok${totalTokens}`];
|
|
83
|
+
if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
|
|
84
|
+
parts.push(`cache${cacheReadTokens}/${cacheCreationTokens}`);
|
|
85
|
+
}
|
|
86
|
+
if (toolDistribution) parts.push(toolDistribution);
|
|
87
|
+
if (terminalReason) parts.push(terminalReason);
|
|
88
|
+
return parts.join(" ");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function projectName(row) {
|
|
92
|
+
return String(
|
|
93
|
+
(row && (row.project_name || row.label || row.id || row.project_root || row.root)) || "-"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function projectRoot(row) {
|
|
98
|
+
return String((row && (row.project_root || row.root)) || "");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generic chip row planner. Returns the visible slice that fits inside
|
|
103
|
+
* `maxWidth` (caption + chips + optional `< / >` overflow markers + optional
|
|
104
|
+
* trailing hint). Hint is dropped first when budget is tight; chips are
|
|
105
|
+
* windowed around `selectedIndex`. Empty rail (no items) is handled by the
|
|
106
|
+
* caller via `emptyLabel`.
|
|
107
|
+
*/
|
|
108
|
+
function planChipsRow({
|
|
109
|
+
caption,
|
|
110
|
+
labels,
|
|
111
|
+
selectedIndex = -1,
|
|
112
|
+
windowStart = 0,
|
|
113
|
+
hint = "",
|
|
114
|
+
maxWidth = 80,
|
|
115
|
+
reserveHintWhenFocused = false,
|
|
116
|
+
} = {}) {
|
|
117
|
+
const items = Array.isArray(labels) ? labels.map(String) : [];
|
|
118
|
+
const totalBudget = Math.max(1, Math.floor(Number(maxWidth) || 80));
|
|
119
|
+
const captionText = `${caption}: `;
|
|
120
|
+
const captionWidth = displayCellWidth(captionText);
|
|
121
|
+
const railBudget = Math.max(1, totalBudget - captionWidth);
|
|
122
|
+
const hintText = String(hint || "");
|
|
123
|
+
const hintWidth = hintText ? HINT_PREFIX_WIDTH + displayCellWidth(hintText) : 0;
|
|
124
|
+
const minChipCells = reserveHintWhenFocused ? 4 : 1;
|
|
125
|
+
const canShowHint = hintText && railBudget - hintWidth >= minChipCells;
|
|
126
|
+
const finalHint = canShowHint ? hintText : "";
|
|
127
|
+
const railOnlyBudget = Math.max(1, railBudget - (finalHint ? hintWidth : 0));
|
|
128
|
+
const planned = planProjectsRail({
|
|
129
|
+
labels: items,
|
|
130
|
+
selectedIndex,
|
|
131
|
+
windowStart: windowStart || 0,
|
|
132
|
+
maxCells: railOnlyBudget,
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
captionText,
|
|
136
|
+
visible: planned.items,
|
|
137
|
+
leftMore: planned.leftMore,
|
|
138
|
+
rightMore: planned.rightMore,
|
|
139
|
+
windowStart: planned.windowStart,
|
|
140
|
+
hint: finalHint,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildSummaryRow(options = {}) {
|
|
145
|
+
const {
|
|
146
|
+
activeAgents = [],
|
|
147
|
+
activeAgentId = "",
|
|
148
|
+
getAgentLabel = (id) => id,
|
|
149
|
+
getAgentState = () => "",
|
|
150
|
+
launchMode = "terminal",
|
|
151
|
+
agentProvider = "codex-cli",
|
|
152
|
+
cronTasks = [],
|
|
153
|
+
loopSummary = null,
|
|
154
|
+
} = options;
|
|
155
|
+
// agentItems carries the full active list so the renderer can greedy-fit
|
|
156
|
+
// chips into whatever cell budget the terminal gives us. The legacy
|
|
157
|
+
// 3-chip + "+N more" form is preserved on `parts[0].value` for callers
|
|
158
|
+
// that consume the plain text (chat history, banner, tests).
|
|
159
|
+
const allItems = activeAgents.map((id) => {
|
|
160
|
+
const active = Boolean(activeAgentId && id === activeAgentId);
|
|
161
|
+
return {
|
|
162
|
+
label: withActivityMarker(ensureAtPrefix(getAgentLabel(id)), getAgentState(id)),
|
|
163
|
+
selected: false,
|
|
164
|
+
active,
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
const agentItems = allItems;
|
|
168
|
+
const visibleForText = allItems.slice(0, 3);
|
|
169
|
+
const agents = activeAgents.length > 0
|
|
170
|
+
? visibleForText.map((item) => item.label).join(", ")
|
|
171
|
+
+ (activeAgents.length > 3 ? ` +${activeAgents.length - 3}` : "")
|
|
172
|
+
: "none";
|
|
173
|
+
const parts = [
|
|
174
|
+
{ label: "Agents", value: agents },
|
|
175
|
+
{ label: "Mode", value: launchMode },
|
|
176
|
+
{ label: "Agent", value: providerLabel(agentProvider) },
|
|
177
|
+
{ label: "Cron", value: String(Array.isArray(cronTasks) ? cronTasks.length : 0) },
|
|
178
|
+
];
|
|
179
|
+
const loopPart = formatLoopSummary(loopSummary);
|
|
180
|
+
if (loopPart) parts.push({ label: "Loop", value: loopPart });
|
|
181
|
+
return {
|
|
182
|
+
kind: "summary",
|
|
183
|
+
agentItems,
|
|
184
|
+
agentExtraCount: 0,
|
|
185
|
+
parts,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function buildProjectRow(options = {}) {
|
|
190
|
+
const {
|
|
191
|
+
projects = [],
|
|
192
|
+
selectedProjectIndex = -1,
|
|
193
|
+
projectListWindowStart = 0,
|
|
194
|
+
maxWidth = 80,
|
|
195
|
+
activeProjectRoot = "",
|
|
196
|
+
focused = false,
|
|
197
|
+
globalScope = "controller",
|
|
198
|
+
} = options;
|
|
199
|
+
const rows = Array.isArray(projects) ? projects : [];
|
|
200
|
+
if (rows.length === 0) {
|
|
201
|
+
return {
|
|
202
|
+
kind: "chips",
|
|
203
|
+
caption: "Projects",
|
|
204
|
+
items: [],
|
|
205
|
+
emptyLabel: "none",
|
|
206
|
+
hint: options.dashHints && options.dashHints.projectsEmpty
|
|
207
|
+
? options.dashHints.projectsEmpty
|
|
208
|
+
: "Run ufoo chat or ufoo daemon start in project directories",
|
|
209
|
+
windowStart: projectListWindowStart,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const fallbackIndex = rows.findIndex((row) => projectRoot(row) === String(activeProjectRoot || ""));
|
|
213
|
+
const selected = selectedProjectIndex >= 0 && selectedProjectIndex < rows.length
|
|
214
|
+
? selectedProjectIndex
|
|
215
|
+
: (fallbackIndex >= 0 ? fallbackIndex : 0);
|
|
216
|
+
const requestedHint = focused ? (globalScope === "controller" ? "Enter→project" : "Esc→global") : "";
|
|
217
|
+
const planned = planChipsRow({
|
|
218
|
+
caption: "Projects",
|
|
219
|
+
labels: rows.map(projectName),
|
|
220
|
+
selectedIndex: focused ? selected : -1,
|
|
221
|
+
windowStart: projectListWindowStart || 0,
|
|
222
|
+
hint: requestedHint,
|
|
223
|
+
maxWidth,
|
|
224
|
+
reserveHintWhenFocused: focused,
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
kind: "chips",
|
|
228
|
+
caption: "Projects",
|
|
229
|
+
leftMore: planned.leftMore,
|
|
230
|
+
rightMore: planned.rightMore,
|
|
231
|
+
windowStart: planned.windowStart,
|
|
232
|
+
hint: planned.hint,
|
|
233
|
+
items: planned.visible.map((item) => {
|
|
234
|
+
const idx = item.absoluteIndex;
|
|
235
|
+
const row = rows[idx];
|
|
236
|
+
const root = projectRoot(row);
|
|
237
|
+
return {
|
|
238
|
+
label: item.label,
|
|
239
|
+
selected: focused && idx === selected,
|
|
240
|
+
active: Boolean(activeProjectRoot && root === activeProjectRoot),
|
|
241
|
+
};
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function buildDetailRow(options = {}) {
|
|
247
|
+
const {
|
|
248
|
+
dashboardView = "agents",
|
|
249
|
+
globalMode = false,
|
|
250
|
+
activeAgents = [],
|
|
251
|
+
activeAgentId = "",
|
|
252
|
+
selectedAgentIndex = -1,
|
|
253
|
+
agentListWindowStart = 0,
|
|
254
|
+
maxAgentWindow = 4,
|
|
255
|
+
maxWidth = 80,
|
|
256
|
+
getAgentLabel = (id) => id,
|
|
257
|
+
getAgentState = () => "",
|
|
258
|
+
selectedModeIndex = 0,
|
|
259
|
+
selectedProviderIndex = 0,
|
|
260
|
+
selectedCronIndex = -1,
|
|
261
|
+
modeOptions = [],
|
|
262
|
+
providerOptions = [],
|
|
263
|
+
cronTasks = [],
|
|
264
|
+
dashHints = {},
|
|
265
|
+
focused = false,
|
|
266
|
+
} = options;
|
|
267
|
+
|
|
268
|
+
if (dashboardView === "mode") {
|
|
269
|
+
const labels = (modeOptions || []).map((label) => String(label));
|
|
270
|
+
const planned = planChipsRow({
|
|
271
|
+
caption: "Mode",
|
|
272
|
+
labels,
|
|
273
|
+
selectedIndex: focused ? selectedModeIndex : -1,
|
|
274
|
+
hint: dashHints.mode || "",
|
|
275
|
+
maxWidth,
|
|
276
|
+
reserveHintWhenFocused: focused,
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
kind: "chips",
|
|
280
|
+
caption: "Mode",
|
|
281
|
+
hint: planned.hint,
|
|
282
|
+
leftMore: planned.leftMore,
|
|
283
|
+
rightMore: planned.rightMore,
|
|
284
|
+
items: planned.visible.map((item) => ({
|
|
285
|
+
label: item.label,
|
|
286
|
+
selected: focused && item.absoluteIndex === selectedModeIndex,
|
|
287
|
+
})),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (dashboardView === "provider") {
|
|
291
|
+
const opts = Array.isArray(providerOptions) ? providerOptions : [];
|
|
292
|
+
const labels = opts.map((opt) => String(opt && opt.label != null ? opt.label : opt));
|
|
293
|
+
const planned = planChipsRow({
|
|
294
|
+
caption: "Agent",
|
|
295
|
+
labels,
|
|
296
|
+
selectedIndex: focused ? selectedProviderIndex : -1,
|
|
297
|
+
hint: dashHints.provider || "",
|
|
298
|
+
maxWidth,
|
|
299
|
+
reserveHintWhenFocused: focused,
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
kind: "chips",
|
|
303
|
+
caption: "Agent",
|
|
304
|
+
hint: planned.hint,
|
|
305
|
+
leftMore: planned.leftMore,
|
|
306
|
+
rightMore: planned.rightMore,
|
|
307
|
+
items: planned.visible.map((item) => ({
|
|
308
|
+
label: item.label,
|
|
309
|
+
selected: focused && item.absoluteIndex === selectedProviderIndex,
|
|
310
|
+
})),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (dashboardView === "cron") {
|
|
314
|
+
const items = Array.isArray(cronTasks) ? cronTasks : [];
|
|
315
|
+
const labels = items
|
|
316
|
+
.map((item) => String(item.label || item.summary || item.id || ""))
|
|
317
|
+
.filter(Boolean);
|
|
318
|
+
if (labels.length === 0) {
|
|
319
|
+
return {
|
|
320
|
+
kind: "chips",
|
|
321
|
+
caption: "Cron",
|
|
322
|
+
hint: dashHints.cron || "",
|
|
323
|
+
emptyLabel: "none",
|
|
324
|
+
items: [],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const planned = planChipsRow({
|
|
328
|
+
caption: "Cron",
|
|
329
|
+
labels,
|
|
330
|
+
selectedIndex: focused ? selectedCronIndex : -1,
|
|
331
|
+
hint: dashHints.cron || "",
|
|
332
|
+
maxWidth,
|
|
333
|
+
reserveHintWhenFocused: focused,
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
kind: "chips",
|
|
337
|
+
caption: "Cron",
|
|
338
|
+
hint: planned.hint,
|
|
339
|
+
leftMore: planned.leftMore,
|
|
340
|
+
rightMore: planned.rightMore,
|
|
341
|
+
items: planned.visible.map((item) => ({
|
|
342
|
+
label: item.label,
|
|
343
|
+
selected: focused && item.absoluteIndex === selectedCronIndex,
|
|
344
|
+
})),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!activeAgents.length) {
|
|
349
|
+
return {
|
|
350
|
+
kind: "chips",
|
|
351
|
+
caption: "Agents",
|
|
352
|
+
emptyLabel: "none",
|
|
353
|
+
hint: dashHints.agentsEmpty || "",
|
|
354
|
+
items: [],
|
|
355
|
+
windowStart: agentListWindowStart,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const labels = activeAgents.map((agentId) => withActivityMarker(
|
|
359
|
+
ensureAtPrefix(getAgentLabel(agentId)),
|
|
360
|
+
getAgentState(agentId)
|
|
361
|
+
));
|
|
362
|
+
// Keep the legacy `maxAgentWindow` as an upper bound on visible chips even
|
|
363
|
+
// when there's plenty of horizontal room, so the agents row scrolls for
|
|
364
|
+
// long lists in the same way the legacy blessed view did.
|
|
365
|
+
const cap = Math.max(1, Math.min(maxAgentWindow || labels.length, labels.length));
|
|
366
|
+
let cappedStart = clampAgentWindowWithSelection({
|
|
367
|
+
activeCount: labels.length,
|
|
368
|
+
maxWindow: cap,
|
|
369
|
+
windowStart: agentListWindowStart || 0,
|
|
370
|
+
selectionIndex: selectedAgentIndex,
|
|
371
|
+
});
|
|
372
|
+
const cappedLabels = labels.slice(cappedStart, cappedStart + cap);
|
|
373
|
+
const cappedSelected = focused && selectedAgentIndex >= cappedStart
|
|
374
|
+
? selectedAgentIndex - cappedStart
|
|
375
|
+
: -1;
|
|
376
|
+
const planned = planChipsRow({
|
|
377
|
+
caption: "Agents",
|
|
378
|
+
labels: cappedLabels,
|
|
379
|
+
selectedIndex: cappedSelected,
|
|
380
|
+
windowStart: 0,
|
|
381
|
+
hint: globalMode ? (dashHints.agentsGlobal || dashHints.agents || "") : (dashHints.agents || ""),
|
|
382
|
+
maxWidth,
|
|
383
|
+
reserveHintWhenFocused: focused,
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
kind: "chips",
|
|
387
|
+
caption: "Agents",
|
|
388
|
+
leftMore: cappedStart > 0 || planned.leftMore,
|
|
389
|
+
rightMore: (cappedStart + cap < labels.length) || planned.rightMore,
|
|
390
|
+
windowStart: cappedStart,
|
|
391
|
+
hint: planned.hint,
|
|
392
|
+
items: planned.visible.map((item) => {
|
|
393
|
+
const cappedIdx = item.absoluteIndex;
|
|
394
|
+
const absolute = cappedStart + cappedIdx;
|
|
395
|
+
const agentId = activeAgents[absolute];
|
|
396
|
+
const active = Boolean(activeAgentId && agentId === activeAgentId);
|
|
397
|
+
return {
|
|
398
|
+
label: item.label,
|
|
399
|
+
selected: focused && absolute === selectedAgentIndex,
|
|
400
|
+
...(active ? { active: true } : {}),
|
|
401
|
+
};
|
|
402
|
+
}),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildDashboardRows(options = {}) {
|
|
407
|
+
const {
|
|
408
|
+
globalMode = false,
|
|
409
|
+
globalScope = "controller",
|
|
410
|
+
focusMode = "input",
|
|
411
|
+
dashboardView = "agents",
|
|
412
|
+
projects = [],
|
|
413
|
+
dashHints = {},
|
|
414
|
+
} = options;
|
|
415
|
+
const focused = focusMode === "dashboard";
|
|
416
|
+
if (globalMode) {
|
|
417
|
+
const projectsFocused = focused && dashboardView === "projects";
|
|
418
|
+
const projectRow = buildProjectRow({
|
|
419
|
+
...options,
|
|
420
|
+
focused: projectsFocused,
|
|
421
|
+
globalScope,
|
|
422
|
+
});
|
|
423
|
+
if (!focused || projectsFocused) {
|
|
424
|
+
return [projectRow, buildSummaryRow(options)];
|
|
425
|
+
}
|
|
426
|
+
return [projectRow, buildDetailRow({ ...options, focused })];
|
|
427
|
+
}
|
|
428
|
+
if (focused) return [buildDetailRow({ ...options, focused })];
|
|
429
|
+
return [buildSummaryRow(options)];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Render a chip row to plain text + ANSI color spans, sized to fit
|
|
434
|
+
* `maxWidth`. Includes the caption, optional `< / >` markers, optional
|
|
435
|
+
* `emptyLabel`, and the trailing hint when the planner kept it. Used as the
|
|
436
|
+
* sole text payload of a row's <Text wrap="truncate">.
|
|
437
|
+
*/
|
|
438
|
+
function renderChipRowText(row, maxWidth = 80) {
|
|
439
|
+
const budget = Math.max(1, Math.floor(Number(maxWidth) || 80));
|
|
440
|
+
const { caption = "", items = [], hint = "", leftMore, rightMore, emptyLabel } = row || {};
|
|
441
|
+
const captionText = `${caption}: `;
|
|
442
|
+
let out = chalk.gray(captionText);
|
|
443
|
+
let used = displayCellWidth(captionText);
|
|
444
|
+
|
|
445
|
+
if (leftMore) {
|
|
446
|
+
out += chalk.gray("< ");
|
|
447
|
+
used += 2;
|
|
448
|
+
}
|
|
449
|
+
if (items.length === 0 && emptyLabel) {
|
|
450
|
+
const remaining = Math.max(0, budget - used - (hint ? HINT_PREFIX_WIDTH + displayCellWidth(hint) : 0));
|
|
451
|
+
const trimmedEmpty = truncateToCells(emptyLabel, remaining);
|
|
452
|
+
out += chalk.cyan(trimmedEmpty);
|
|
453
|
+
used += displayCellWidth(trimmedEmpty);
|
|
454
|
+
}
|
|
455
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
456
|
+
if (i > 0) {
|
|
457
|
+
out += chalk.gray(CHIP_SEP);
|
|
458
|
+
used += CHIP_SEP_WIDTH;
|
|
459
|
+
}
|
|
460
|
+
const item = items[i];
|
|
461
|
+
const label = String(item.label || "");
|
|
462
|
+
if (item.selected) {
|
|
463
|
+
out += chalk.inverse(label);
|
|
464
|
+
} else if (item.active) {
|
|
465
|
+
out += chalk.bold.cyan(label);
|
|
466
|
+
} else {
|
|
467
|
+
out += chalk.cyan(label);
|
|
468
|
+
}
|
|
469
|
+
used += displayCellWidth(label);
|
|
470
|
+
}
|
|
471
|
+
if (rightMore) {
|
|
472
|
+
out += chalk.gray(" >");
|
|
473
|
+
used += 2;
|
|
474
|
+
}
|
|
475
|
+
if (hint) {
|
|
476
|
+
const remaining = Math.max(0, budget - used);
|
|
477
|
+
if (remaining > HINT_PREFIX_WIDTH + 1) {
|
|
478
|
+
const hintBody = truncateToCells(hint, remaining - HINT_PREFIX_WIDTH);
|
|
479
|
+
out += chalk.gray(`${HINT_PREFIX}${hintBody}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function renderSummaryRowText(row, maxWidth = 80) {
|
|
486
|
+
const budget = Math.max(1, Math.floor(Number(maxWidth) || 80));
|
|
487
|
+
const { parts = [], agentItems = [] } = row || {};
|
|
488
|
+
|
|
489
|
+
// Pre-render the non-Agents parts so we know how many cells they will
|
|
490
|
+
// claim. Agents is special: it carries the full active list and we want
|
|
491
|
+
// to fit as many chips as the remaining budget allows.
|
|
492
|
+
const tailParts = parts.slice(1).map((part) => {
|
|
493
|
+
const labelText = `${part.label}: `;
|
|
494
|
+
const labelWidth = displayCellWidth(labelText);
|
|
495
|
+
const value = String(part.value || "");
|
|
496
|
+
return {
|
|
497
|
+
label: part.label,
|
|
498
|
+
labelText,
|
|
499
|
+
labelColored: chalk.gray(labelText),
|
|
500
|
+
labelWidth,
|
|
501
|
+
value,
|
|
502
|
+
width: labelWidth + displayCellWidth(value),
|
|
503
|
+
colored: chalk.gray(labelText) + chalk.cyan(value),
|
|
504
|
+
truncatable: part.label === "Loop",
|
|
505
|
+
};
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// How many cells would tail parts ideally claim (with their leading gap)?
|
|
509
|
+
// We use this to reserve room for them when packing Agents chips, but we
|
|
510
|
+
// never reserve so much that Agents can't fit at least one short chip
|
|
511
|
+
// (otherwise narrow terminals show "Agents: +N" with zero names).
|
|
512
|
+
let tailIdealWidth = 0;
|
|
513
|
+
for (const tp of tailParts) {
|
|
514
|
+
tailIdealWidth += SUMMARY_GAP_WIDTH + tp.width;
|
|
515
|
+
}
|
|
516
|
+
// Cap reservation so Agents always gets at least ~12 cells to play with
|
|
517
|
+
// when there's any agent to show — enough for "@a +N" on the narrowest
|
|
518
|
+
// displays. The remaining tail parts will simply be dropped one by one.
|
|
519
|
+
const minAgentRoom = 12;
|
|
520
|
+
const captionWidth = displayCellWidth("Agents: ");
|
|
521
|
+
const tailReserve = Math.min(
|
|
522
|
+
tailIdealWidth,
|
|
523
|
+
Math.max(0, budget - captionWidth - minAgentRoom)
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
let out = "";
|
|
527
|
+
let used = 0;
|
|
528
|
+
|
|
529
|
+
const agentsPart = parts[0];
|
|
530
|
+
if (agentsPart && agentsPart.label === "Agents") {
|
|
531
|
+
const labelText = "Agents: ";
|
|
532
|
+
const labelWidth = displayCellWidth(labelText);
|
|
533
|
+
out += chalk.gray(labelText);
|
|
534
|
+
used += labelWidth;
|
|
535
|
+
|
|
536
|
+
if (agentItems.length === 0) {
|
|
537
|
+
const noneText = "none";
|
|
538
|
+
out += chalk.cyan(noneText);
|
|
539
|
+
used += displayCellWidth(noneText);
|
|
540
|
+
} else {
|
|
541
|
+
// Reserve room for the worst-case " +N" overflow tail so we never have
|
|
542
|
+
// to backtrack and pop a chip after committing to it.
|
|
543
|
+
const worstOverflow = ` +${agentItems.length}`;
|
|
544
|
+
const worstOverflowWidth = displayCellWidth(worstOverflow);
|
|
545
|
+
const agentBudget = Math.max(0, budget - used - tailReserve);
|
|
546
|
+
let fittedCount = 0;
|
|
547
|
+
let agentsUsed = 0;
|
|
548
|
+
for (let i = 0; i < agentItems.length; i += 1) {
|
|
549
|
+
const item = agentItems[i];
|
|
550
|
+
const label = String(item.label || "");
|
|
551
|
+
const sepWidth = i === 0 ? 0 : displayCellWidth(", ");
|
|
552
|
+
const remainingItems = agentItems.length - i - 1;
|
|
553
|
+
const reserveOverflow = remainingItems > 0 ? worstOverflowWidth : 0;
|
|
554
|
+
const labelWidthInner = displayCellWidth(label);
|
|
555
|
+
if (agentsUsed + sepWidth + labelWidthInner + reserveOverflow > agentBudget) break;
|
|
556
|
+
if (i > 0) {
|
|
557
|
+
out += chalk.gray(", ");
|
|
558
|
+
agentsUsed += sepWidth;
|
|
559
|
+
}
|
|
560
|
+
if (item.active) out += chalk.bold.cyan(label);
|
|
561
|
+
else out += chalk.cyan(label);
|
|
562
|
+
agentsUsed += labelWidthInner;
|
|
563
|
+
fittedCount += 1;
|
|
564
|
+
}
|
|
565
|
+
const overflow = agentItems.length - fittedCount;
|
|
566
|
+
if (overflow > 0) {
|
|
567
|
+
const tail = ` +${overflow}`;
|
|
568
|
+
out += chalk.cyan(tail);
|
|
569
|
+
agentsUsed += displayCellWidth(tail);
|
|
570
|
+
}
|
|
571
|
+
used += agentsUsed;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
for (let i = 0; i < tailParts.length; i += 1) {
|
|
576
|
+
const part = tailParts[i];
|
|
577
|
+
const remaining = budget - used - SUMMARY_GAP_WIDTH;
|
|
578
|
+
if (remaining <= 0) break;
|
|
579
|
+
if (part.width <= remaining) {
|
|
580
|
+
out += chalk.gray(SUMMARY_GAP) + part.colored;
|
|
581
|
+
used += SUMMARY_GAP_WIDTH + part.width;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (part.truncatable && remaining >= 6) {
|
|
585
|
+
const valueRoom = Math.max(1, remaining - part.labelWidth);
|
|
586
|
+
const trimmedValue = truncateToCells(part.value, valueRoom);
|
|
587
|
+
out += chalk.gray(SUMMARY_GAP) + part.labelColored + chalk.cyan(trimmedValue);
|
|
588
|
+
used += SUMMARY_GAP_WIDTH + part.labelWidth + displayCellWidth(trimmedValue);
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
return out;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function createDashboardBar({ React, ink }) {
|
|
596
|
+
const { Box, Text } = ink;
|
|
597
|
+
const h = React.createElement;
|
|
598
|
+
|
|
599
|
+
return function DashboardBar({
|
|
600
|
+
dashboardView = "agents",
|
|
601
|
+
focusMode = "input",
|
|
602
|
+
globalMode = false,
|
|
603
|
+
globalScope = "controller",
|
|
604
|
+
activeAgents = [],
|
|
605
|
+
activeAgentId = "",
|
|
606
|
+
selectedAgentIndex = -1,
|
|
607
|
+
agentListWindowStart = 0,
|
|
608
|
+
maxAgentWindow = 4,
|
|
609
|
+
projectListWindowStart = 0,
|
|
610
|
+
maxProjectWindow = 5,
|
|
611
|
+
maxWidth = 80,
|
|
612
|
+
getAgentLabel = (id) => id,
|
|
613
|
+
getAgentState = () => "",
|
|
614
|
+
launchMode = "terminal",
|
|
615
|
+
agentProvider = "codex-cli",
|
|
616
|
+
modeOptions = [],
|
|
617
|
+
selectedModeIndex = 0,
|
|
618
|
+
providerOptions = [],
|
|
619
|
+
selectedProviderIndex = 0,
|
|
620
|
+
cronTasks = [],
|
|
621
|
+
loopSummary = null,
|
|
622
|
+
selectedCronIndex = -1,
|
|
623
|
+
projects = [],
|
|
624
|
+
selectedProjectIndex = -1,
|
|
625
|
+
activeProjectRoot = "",
|
|
626
|
+
dashHints = {},
|
|
627
|
+
}) {
|
|
628
|
+
const rows = buildDashboardRows({
|
|
629
|
+
dashboardView,
|
|
630
|
+
focusMode,
|
|
631
|
+
globalMode,
|
|
632
|
+
globalScope,
|
|
633
|
+
activeAgents,
|
|
634
|
+
activeAgentId,
|
|
635
|
+
selectedAgentIndex,
|
|
636
|
+
agentListWindowStart,
|
|
637
|
+
maxAgentWindow,
|
|
638
|
+
projectListWindowStart,
|
|
639
|
+
maxProjectWindow,
|
|
640
|
+
maxWidth,
|
|
641
|
+
getAgentLabel,
|
|
642
|
+
getAgentState,
|
|
643
|
+
launchMode,
|
|
644
|
+
agentProvider,
|
|
645
|
+
modeOptions,
|
|
646
|
+
selectedModeIndex,
|
|
647
|
+
providerOptions,
|
|
648
|
+
selectedProviderIndex,
|
|
649
|
+
cronTasks,
|
|
650
|
+
loopSummary,
|
|
651
|
+
selectedCronIndex,
|
|
652
|
+
projects,
|
|
653
|
+
selectedProjectIndex,
|
|
654
|
+
activeProjectRoot,
|
|
655
|
+
dashHints,
|
|
656
|
+
}).map((row, idx) => {
|
|
657
|
+
let text;
|
|
658
|
+
if (row.kind === "summary") {
|
|
659
|
+
text = renderSummaryRowText(row, maxWidth);
|
|
660
|
+
} else if (row.kind === "message") {
|
|
661
|
+
const value = String(row.text || "");
|
|
662
|
+
text = chalk.gray(truncateToCells(value, maxWidth));
|
|
663
|
+
} else {
|
|
664
|
+
text = renderChipRowText(row, maxWidth);
|
|
665
|
+
}
|
|
666
|
+
return h(Box, { key: `dr-${idx}`, width: "100%" },
|
|
667
|
+
h(Text, { wrap: "truncate" }, text || " "));
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
if (rows.length === 1) return h(Box, { width: "100%" }, rows[0]);
|
|
671
|
+
return h(Box, { flexDirection: "column", width: "100%" }, ...rows);
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function renderDashboardLines(params) {
|
|
676
|
+
const maxWidth = params.maxWidth || 80;
|
|
677
|
+
const rows = buildDashboardRows(params);
|
|
678
|
+
return rows.map((row) => {
|
|
679
|
+
if (row.kind === "summary") return renderSummaryRowText(row, maxWidth);
|
|
680
|
+
if (row.kind === "message") return chalk.gray(truncateToCells(String(row.text || ""), maxWidth));
|
|
681
|
+
return renderChipRowText(row, maxWidth);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
module.exports = { createDashboardBar, buildDashboardRows, renderDashboardLines, formatLoopSummary };
|