u-foo 2.3.31 → 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 +9 -5
- package/scripts/chat-app-smoke.js +30 -0
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +23 -0
- package/scripts/ink-smoke.js +30 -0
- package/scripts/ucode-app-smoke.js +36 -0
- 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 +56 -28
- 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 +54 -4
- package/src/{chat → app/chat}/daemonTransport.js +2 -1
- package/src/{chat → app/chat}/dashboardView.js +2 -21
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +38 -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}/projectCloseController.js +1 -1
- package/src/app/chat/shellCommand.js +42 -0
- package/src/{chat → app/chat}/transport.js +16 -3
- 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} +62 -59
- 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/taskDecomposer.js +5 -4
- package/src/code/tui.js +39 -1997
- 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/{ufoo → coordination/state}/agentRegistryDiagnostics.js +43 -0
- 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 +273 -79
- package/src/{daemon → runtime/daemon}/ipcServer.js +24 -2
- 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 +13 -8
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +12 -5
- 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/runtime/process/nodeExecutable.js +26 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{projects → runtime/projects}/runtimes.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 +336 -0
- package/src/ui/format/index.js +974 -0
- package/src/ui/index.js +9 -0
- package/src/ui/ink/ChatApp.js +3674 -0
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/ink/InkDemo.js +96 -0
- package/src/ui/ink/MultilineInput.js +612 -0
- package/src/ui/ink/UcodeApp.js +822 -0
- package/src/ui/ink/agentMirror.js +730 -0
- package/src/ui/ink/chatReducer.js +359 -0
- package/src/ui/runInk.js +57 -0
- 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 -573
- package/src/chat/index.js +0 -2214
- 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/{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}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.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}/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}/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/{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/{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/code/tui.js
CHANGED
|
@@ -1,2010 +1,51 @@
|
|
|
1
|
-
const
|
|
2
|
-
const pkg = require("../../package.json");
|
|
1
|
+
const fmt = require("../ui/format");
|
|
3
2
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
38
|
-
(code >= 0xf900 && code <= 0xfaff) ||
|
|
39
|
-
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
40
|
-
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
41
|
-
(code >= 0xff00 && code <= 0xff60) ||
|
|
42
|
-
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
43
|
-
(code >= 0x1f300 && code <= 0x1faff)) {
|
|
44
|
-
return 2;
|
|
45
|
-
}
|
|
46
|
-
return 1;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function displayCellWidth(text = "") {
|
|
50
|
-
return Array.from(String(text || "").replace(ANSI_PATTERN, "")).reduce(
|
|
51
|
-
(sum, char) => sum + charDisplayWidth(char),
|
|
52
|
-
0
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function safeRead(getter, fallback = undefined) {
|
|
57
|
-
try {
|
|
58
|
-
return getter();
|
|
59
|
-
} catch {
|
|
60
|
-
return fallback;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function resolveLogContentWidth({ logBox = null, screen = null, fallback = 80 } = {}) {
|
|
65
|
-
const coords = safeRead(() => logBox && typeof logBox._getCoords === "function" ? logBox._getCoords() : null, null);
|
|
66
|
-
if (coords && Number.isFinite(coords.xl) && Number.isFinite(coords.xi)) {
|
|
67
|
-
return Math.max(1, coords.xl - coords.xi);
|
|
68
|
-
}
|
|
69
|
-
const width = safeRead(() => logBox && logBox.width, null);
|
|
70
|
-
if (typeof width === "number") return Math.max(1, width);
|
|
71
|
-
const screenWidth = safeRead(() => screen && screen.width, null);
|
|
72
|
-
if (typeof screenWidth === "number") return Math.max(1, screenWidth);
|
|
73
|
-
const screenCols = safeRead(() => screen && screen.cols, null);
|
|
74
|
-
if (typeof screenCols === "number") return Math.max(1, screenCols);
|
|
75
|
-
return Math.max(1, fallback);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function formatHighlightedUserInput(text = "", {
|
|
79
|
-
width = 80,
|
|
80
|
-
escapeText = (value) => String(value || ""),
|
|
81
|
-
} = {}) {
|
|
82
|
-
const plain = String(text || "").trim();
|
|
83
|
-
if (!plain) return "";
|
|
84
|
-
const targetWidth = Math.max(1, Math.floor(Number(width) || 80) - 1);
|
|
85
|
-
const prefix = " → ";
|
|
86
|
-
const suffix = " ";
|
|
87
|
-
const contentWidth = displayCellWidth(`${prefix}${plain}${suffix}`);
|
|
88
|
-
const pad = " ".repeat(Math.max(0, targetWidth - contentWidth));
|
|
89
|
-
return `{cyan-bg}{white-fg}${prefix}${escapeText(plain)}${suffix}${pad}{/white-fg}{/cyan-bg}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Stream buffer for smooth output
|
|
93
|
-
class StreamBuffer {
|
|
94
|
-
constructor(writer, options = {}) {
|
|
95
|
-
this.writer = writer;
|
|
96
|
-
this.buffer = "";
|
|
97
|
-
this.delay = options.delay || 8; // ms between chunks
|
|
98
|
-
this.chunkSize = options.chunkSize || 3; // chars per chunk
|
|
99
|
-
this.isStreaming = false;
|
|
100
|
-
this.streamPromise = null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async write(text) {
|
|
104
|
-
this.buffer += text;
|
|
105
|
-
if (!this.isStreaming) {
|
|
106
|
-
this.isStreaming = true;
|
|
107
|
-
this.streamPromise = this.flush();
|
|
108
|
-
}
|
|
109
|
-
return this.streamPromise;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async flush() {
|
|
113
|
-
while (this.buffer.length > 0) {
|
|
114
|
-
const chunk = this.buffer.slice(0, this.chunkSize);
|
|
115
|
-
this.buffer = this.buffer.slice(this.chunkSize);
|
|
116
|
-
this.writer(chunk);
|
|
117
|
-
if (this.buffer.length > 0) {
|
|
118
|
-
await new Promise(resolve => setTimeout(resolve, this.delay));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
this.isStreaming = false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async finish() {
|
|
125
|
-
if (this.isStreaming) {
|
|
126
|
-
await this.streamPromise;
|
|
127
|
-
}
|
|
128
|
-
if (this.buffer.length > 0) {
|
|
129
|
-
this.writer(this.buffer);
|
|
130
|
-
this.buffer = "";
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function normalizeModelLabel(model = "") {
|
|
136
|
-
const text = String(model || "").trim();
|
|
137
|
-
if (text) return text;
|
|
138
|
-
return "default";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function buildUcodeBannerLines({ model = "", engine = "ufoo-core", nickname = "", agentId = "", workspaceRoot = "", sessionId = "", width = 0 } = {}) {
|
|
142
|
-
const modelLabel = normalizeModelLabel(model);
|
|
143
|
-
void width;
|
|
144
|
-
void engine; // Not using engine anymore
|
|
145
|
-
void nickname;
|
|
146
|
-
void agentId;
|
|
147
|
-
|
|
148
|
-
// Get current working directory with ~ for home
|
|
149
|
-
const path = require("path");
|
|
150
|
-
const os = require("os");
|
|
151
|
-
const currentDir = workspaceRoot || process.cwd();
|
|
152
|
-
const homeDir = os.homedir();
|
|
153
|
-
|
|
154
|
-
// Replace home directory with ~
|
|
155
|
-
let shortPath = currentDir;
|
|
156
|
-
if (currentDir.startsWith(homeDir)) {
|
|
157
|
-
shortPath = currentDir.replace(homeDir, "~");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const logoLines = UCODE_BANNER_LINES.map((line) => chalk.cyan(line));
|
|
161
|
-
const infoLines = [];
|
|
162
|
-
infoLines.push(`${chalk.dim("Version:")} ${chalk.cyan.bold(UCODE_VERSION)}`);
|
|
163
|
-
infoLines.push(`${chalk.dim("Model:")} ${chalk.yellow(modelLabel)}`);
|
|
164
|
-
infoLines.push(`${chalk.dim("Dictionary:")} ${chalk.gray(shortPath)}`);
|
|
165
|
-
const normalizedSessionId = String(sessionId || "").trim();
|
|
166
|
-
if (normalizedSessionId) {
|
|
167
|
-
infoLines.push(`${chalk.dim("Session:")} ${chalk.gray(normalizedSessionId)}`);
|
|
168
|
-
}
|
|
169
|
-
const logoPadding = " ".repeat(
|
|
170
|
-
UCODE_BANNER_LINES.reduce((max, line) => Math.max(max, String(line || "").length), 0)
|
|
171
|
-
);
|
|
172
|
-
const rows = Math.max(logoLines.length, infoLines.length);
|
|
173
|
-
|
|
174
|
-
return Array.from({ length: rows }, (_, index) => {
|
|
175
|
-
const logoLine = logoLines[index] || logoPadding;
|
|
176
|
-
const info = infoLines[index] || "";
|
|
177
|
-
return ` ${logoLine} ${info}`;
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function escapeBlessedLiteral(text) {
|
|
182
|
-
const raw = String(text == null ? "" : text);
|
|
183
|
-
const safe = raw.replace(/\{\/escape\}/g, "{open}/escape{close}");
|
|
184
|
-
return `{escape}${safe}{/escape}`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function buildUcodeBannerBlessedLines({
|
|
188
|
-
model = "",
|
|
189
|
-
engine = "ufoo-core",
|
|
190
|
-
nickname = "",
|
|
191
|
-
agentId = "",
|
|
192
|
-
workspaceRoot = "",
|
|
193
|
-
sessionId = "",
|
|
194
|
-
width = 0,
|
|
195
|
-
} = {}) {
|
|
196
|
-
const modelLabel = normalizeModelLabel(model);
|
|
197
|
-
void width;
|
|
198
|
-
void engine; // Not using engine anymore
|
|
199
|
-
void nickname;
|
|
200
|
-
void agentId;
|
|
201
|
-
|
|
202
|
-
const path = require("path");
|
|
203
|
-
const os = require("os");
|
|
204
|
-
const currentDir = workspaceRoot || process.cwd();
|
|
205
|
-
const homeDir = os.homedir();
|
|
206
|
-
|
|
207
|
-
let shortPath = currentDir;
|
|
208
|
-
if (currentDir.startsWith(homeDir)) {
|
|
209
|
-
shortPath = currentDir.replace(homeDir, "~");
|
|
210
|
-
}
|
|
211
|
-
shortPath = path.normalize(shortPath);
|
|
212
|
-
|
|
213
|
-
const logoLines = UCODE_BANNER_LINES.map(
|
|
214
|
-
(line) => `{cyan-fg}${escapeBlessedLiteral(line)}{/cyan-fg}`
|
|
215
|
-
);
|
|
216
|
-
const infoLines = [
|
|
217
|
-
`{gray-fg}Version:{/gray-fg} {cyan-fg}{bold}${escapeBlessedLiteral(UCODE_VERSION)}{/bold}{/cyan-fg}`,
|
|
218
|
-
`{gray-fg}Model:{/gray-fg} {yellow-fg}${escapeBlessedLiteral(modelLabel)}{/yellow-fg}`,
|
|
219
|
-
`{gray-fg}Dictionary:{/gray-fg} {gray-fg}${escapeBlessedLiteral(shortPath)}{/gray-fg}`,
|
|
220
|
-
];
|
|
221
|
-
const normalizedSessionId = String(sessionId || "").trim();
|
|
222
|
-
if (normalizedSessionId) {
|
|
223
|
-
infoLines.push(`{gray-fg}Session:{/gray-fg} {gray-fg}${escapeBlessedLiteral(normalizedSessionId)}{/gray-fg}`);
|
|
224
|
-
}
|
|
225
|
-
const logoPadding = " ".repeat(
|
|
226
|
-
UCODE_BANNER_LINES.reduce((max, line) => Math.max(max, String(line || "").length), 0)
|
|
227
|
-
);
|
|
228
|
-
const rows = Math.max(logoLines.length, infoLines.length);
|
|
229
|
-
|
|
230
|
-
return Array.from({ length: rows }, (_, index) => {
|
|
231
|
-
const logoLine = logoLines[index] || logoPadding;
|
|
232
|
-
const info = infoLines[index] || "";
|
|
233
|
-
return ` ${logoLine} ${info}`;
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function shouldUseUcodeTui({ stdin, stdout, jsonOutput, forceTui = false, disableTui = false } = {}) {
|
|
238
|
-
if (disableTui) return false;
|
|
239
|
-
if (jsonOutput) return false;
|
|
240
|
-
if (forceTui) return true;
|
|
241
|
-
return Boolean(stdin && stdin.isTTY && stdout && stdout.isTTY);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Helper function to load agents from bus
|
|
245
|
-
function parseActiveAgentsFromBusStatus(busStatus = "") {
|
|
246
|
-
const lines = String(busStatus || "").replace(ANSI_PATTERN, "").split(/\r?\n/);
|
|
247
|
-
const agents = [];
|
|
248
|
-
let inOnlineSection = false;
|
|
249
|
-
|
|
250
|
-
for (const line of lines) {
|
|
251
|
-
const trimmed = String(line || "").trim();
|
|
252
|
-
if (!trimmed) continue;
|
|
253
|
-
|
|
254
|
-
if (/^Online agents:\s*$/i.test(trimmed)) {
|
|
255
|
-
inOnlineSection = true;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (!inOnlineSection) continue;
|
|
259
|
-
|
|
260
|
-
if (/^\(none\)$/i.test(trimmed)) {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Next heading means we have left the online agents section
|
|
265
|
-
if (/^[A-Za-z][A-Za-z ]+:\s*$/.test(trimmed)) {
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const rawId = trimmed.replace(/\s+\([^)]+\)\s*$/, "");
|
|
270
|
-
if (!rawId) continue;
|
|
271
|
-
const [type, ...idParts] = rawId.split(":");
|
|
272
|
-
const id = idParts.join(":");
|
|
273
|
-
if (!type) continue;
|
|
274
|
-
|
|
275
|
-
agents.push({
|
|
276
|
-
type,
|
|
277
|
-
id,
|
|
278
|
-
status: "active",
|
|
279
|
-
fullId: rawId,
|
|
280
|
-
nickname: (trimmed.match(/\(([^)]+)\)\s*$/) || [])[1] || "",
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Fallback for legacy output: "type:id (active|idle)"
|
|
285
|
-
if (agents.length === 0) {
|
|
286
|
-
for (const line of lines) {
|
|
287
|
-
const trimmed = String(line || "").trim();
|
|
288
|
-
const match = trimmed.match(/^([a-z-]+):([a-f0-9]+)\s+\((active|idle)\)$/);
|
|
289
|
-
if (!match) continue;
|
|
290
|
-
agents.push({
|
|
291
|
-
type: match[1],
|
|
292
|
-
id: match[2],
|
|
293
|
-
status: match[3],
|
|
294
|
-
fullId: `${match[1]}:${match[2]}`,
|
|
295
|
-
nickname: "",
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return agents;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function loadActiveAgents(workspaceRoot) {
|
|
304
|
-
try {
|
|
305
|
-
const { execSync } = require("child_process");
|
|
306
|
-
const busStatus = execSync("ufoo bus status", {
|
|
307
|
-
cwd: workspaceRoot,
|
|
308
|
-
encoding: "utf8",
|
|
309
|
-
});
|
|
310
|
-
return parseActiveAgentsFromBusStatus(busStatus);
|
|
311
|
-
} catch {
|
|
312
|
-
return [];
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function renderLogLinesWithMarkdown(text = "", state = {}, escapeFn = (value) => String(value || "")) {
|
|
317
|
-
const { renderMarkdownLines } = require("../shared/markdownRenderer");
|
|
318
|
-
return renderMarkdownLines(text, state, escapeFn);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function shouldEnterAgentSelection(inputValue = "") {
|
|
322
|
-
const text = String(inputValue || "");
|
|
323
|
-
const trimmed = text.trim();
|
|
324
|
-
return !trimmed;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function resolveAgentSelectionOnDown({
|
|
328
|
-
agentSelectionMode = false,
|
|
329
|
-
selectedAgentIndex = -1,
|
|
330
|
-
totalAgents = 0,
|
|
331
|
-
} = {}) {
|
|
332
|
-
const total = Number.isFinite(totalAgents) ? Math.max(0, Math.floor(totalAgents)) : 0;
|
|
333
|
-
if (total <= 0) return { action: "none", index: -1 };
|
|
334
|
-
if (agentSelectionMode) {
|
|
335
|
-
const keep = selectedAgentIndex >= 0 && selectedAgentIndex < total ? selectedAgentIndex : 0;
|
|
336
|
-
return { action: "hold", index: keep };
|
|
337
|
-
}
|
|
338
|
-
const enter = selectedAgentIndex >= 0 && selectedAgentIndex < total ? selectedAgentIndex : 0;
|
|
339
|
-
return { action: "enter", index: enter };
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function cycleAgentSelectionIndex(selectedAgentIndex = -1, totalAgents = 0, direction = "right") {
|
|
343
|
-
const total = Number.isFinite(totalAgents) ? Math.max(0, Math.floor(totalAgents)) : 0;
|
|
344
|
-
if (total <= 0) return -1;
|
|
345
|
-
const current = selectedAgentIndex >= 0 && selectedAgentIndex < total ? selectedAgentIndex : 0;
|
|
346
|
-
if (direction === "left") {
|
|
347
|
-
return (current - 1 + total) % total;
|
|
348
|
-
}
|
|
349
|
-
return (current + 1) % total;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function shouldClearAgentSelectionOnUp({
|
|
353
|
-
agentSelectionMode = false,
|
|
354
|
-
inputValue = "",
|
|
355
|
-
} = {}) {
|
|
356
|
-
return Boolean(agentSelectionMode && shouldEnterAgentSelection(inputValue));
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function moveCursorHorizontally(cursorPos = 0, inputValue = "", direction = "right") {
|
|
360
|
-
const text = String(inputValue || "");
|
|
361
|
-
const max = text.length;
|
|
362
|
-
const pos = Number.isFinite(cursorPos) ? Math.max(0, Math.floor(cursorPos)) : 0;
|
|
363
|
-
if (direction === "left") return Math.max(0, pos - 1);
|
|
364
|
-
return Math.min(max, pos + 1);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function clampCursorPos(cursorPos = 0, inputValue = "") {
|
|
368
|
-
const text = String(inputValue || "");
|
|
369
|
-
const pos = Number.isFinite(cursorPos) ? Math.floor(cursorPos) : 0;
|
|
370
|
-
return Math.max(0, Math.min(text.length, pos));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function findLogicalLineStart(inputValue = "", cursorPos = 0) {
|
|
374
|
-
const text = String(inputValue || "");
|
|
375
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
376
|
-
const prevNewline = text.lastIndexOf("\n", Math.max(0, pos - 1));
|
|
377
|
-
return prevNewline === -1 ? 0 : prevNewline + 1;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function findLogicalLineEnd(inputValue = "", cursorPos = 0) {
|
|
381
|
-
const text = String(inputValue || "");
|
|
382
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
383
|
-
const nextNewline = text.indexOf("\n", pos);
|
|
384
|
-
return nextNewline === -1 ? text.length : nextNewline;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function moveCursorToVisualLineBoundary({
|
|
388
|
-
cursorPos = 0,
|
|
389
|
-
inputValue = "",
|
|
390
|
-
width = 80,
|
|
391
|
-
boundary = "start",
|
|
392
|
-
strWidth,
|
|
393
|
-
} = {}) {
|
|
394
|
-
const inputMath = require("../chat/inputMath");
|
|
395
|
-
const text = String(inputValue || "");
|
|
396
|
-
const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
397
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
398
|
-
const { row } = inputMath.getCursorRowCol(text, pos, normalizedWidth, strWidth);
|
|
399
|
-
if (boundary === "end") {
|
|
400
|
-
return inputMath.getCursorPosForRowCol(text, row, normalizedWidth, normalizedWidth, strWidth);
|
|
401
|
-
}
|
|
402
|
-
return inputMath.getCursorPosForRowCol(text, row, 0, normalizedWidth, strWidth);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function moveCursorVertically({
|
|
406
|
-
cursorPos = 0,
|
|
407
|
-
inputValue = "",
|
|
408
|
-
width = 80,
|
|
409
|
-
direction = "down",
|
|
410
|
-
preferredCol = null,
|
|
411
|
-
strWidth,
|
|
412
|
-
} = {}) {
|
|
413
|
-
const inputMath = require("../chat/inputMath");
|
|
414
|
-
const text = String(inputValue || "");
|
|
415
|
-
const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
|
|
416
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
417
|
-
const { row, col } = inputMath.getCursorRowCol(text, pos, normalizedWidth, strWidth);
|
|
418
|
-
const totalRows = inputMath.countLines(text, normalizedWidth, strWidth);
|
|
419
|
-
const targetCol = Number.isFinite(preferredCol) ? preferredCol : col;
|
|
420
|
-
|
|
421
|
-
if (direction === "up") {
|
|
422
|
-
if (row <= 0) {
|
|
423
|
-
return { moved: false, nextCursorPos: pos, preferredCol: targetCol, boundary: "top" };
|
|
424
|
-
}
|
|
425
|
-
return {
|
|
426
|
-
moved: true,
|
|
427
|
-
nextCursorPos: inputMath.getCursorPosForRowCol(text, row - 1, targetCol, normalizedWidth, strWidth),
|
|
428
|
-
preferredCol: targetCol,
|
|
429
|
-
boundary: "",
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (row >= totalRows - 1) {
|
|
434
|
-
return { moved: false, nextCursorPos: pos, preferredCol: targetCol, boundary: "bottom" };
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
moved: true,
|
|
438
|
-
nextCursorPos: inputMath.getCursorPosForRowCol(text, row + 1, targetCol, normalizedWidth, strWidth),
|
|
439
|
-
preferredCol: targetCol,
|
|
440
|
-
boundary: "",
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function deleteWordBeforeCursor(inputValue = "", cursorPos = 0) {
|
|
445
|
-
const text = String(inputValue || "");
|
|
446
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
447
|
-
if (pos <= 0) return { value: text, cursorPos: pos };
|
|
448
|
-
const before = text.slice(0, pos);
|
|
449
|
-
const after = text.slice(pos);
|
|
450
|
-
const match = before.match(/\s*\S+\s*$/);
|
|
451
|
-
const start = match ? pos - match[0].length : Math.max(0, pos - 1);
|
|
452
|
-
return {
|
|
453
|
-
value: before.slice(0, start) + after,
|
|
454
|
-
cursorPos: start,
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function moveCursorByWord(inputValue = "", cursorPos = 0, direction = "forward") {
|
|
459
|
-
const text = String(inputValue || "");
|
|
460
|
-
const pos = clampCursorPos(cursorPos, text);
|
|
461
|
-
if (direction === "backward") {
|
|
462
|
-
const before = text.slice(0, pos);
|
|
463
|
-
const trimmedEnd = before.search(/\S\s*$/) >= 0 ? before.replace(/\s+$/, "") : before;
|
|
464
|
-
const match = trimmedEnd.match(/\S+$/);
|
|
465
|
-
return match ? trimmedEnd.length - match[0].length : 0;
|
|
466
|
-
}
|
|
467
|
-
const after = text.slice(pos);
|
|
468
|
-
const match = after.match(/^\s*\S+/);
|
|
469
|
-
return match ? Math.min(text.length, pos + match[0].length) : text.length;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function resolveHistoryDownTransition({
|
|
473
|
-
inputHistory = [],
|
|
474
|
-
historyIndex = 0,
|
|
475
|
-
currentValue = "",
|
|
476
|
-
} = {}) {
|
|
477
|
-
const history = Array.isArray(inputHistory) ? inputHistory : [];
|
|
478
|
-
if (history.length <= 0) {
|
|
479
|
-
return {
|
|
480
|
-
moved: false,
|
|
481
|
-
nextHistoryIndex: Number.isFinite(historyIndex) ? Math.max(0, Math.floor(historyIndex)) : 0,
|
|
482
|
-
nextValue: String(currentValue || ""),
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
const currentIndex = Number.isFinite(historyIndex) ? Math.max(0, Math.floor(historyIndex)) : 0;
|
|
486
|
-
const nextHistoryIndex = Math.min(history.length, currentIndex + 1);
|
|
487
|
-
const nextValue = nextHistoryIndex >= history.length ? "" : String(history[nextHistoryIndex] || "");
|
|
488
|
-
const moved = nextHistoryIndex !== currentIndex || nextValue !== String(currentValue || "");
|
|
489
|
-
return {
|
|
490
|
-
moved,
|
|
491
|
-
nextHistoryIndex,
|
|
492
|
-
nextValue,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function filterSelectableAgents(agents = [], selfSubscriberId = "") {
|
|
497
|
-
const selfId = String(selfSubscriberId || "").trim();
|
|
498
|
-
const list = Array.isArray(agents) ? agents : [];
|
|
499
|
-
if (!selfId) {
|
|
500
|
-
return list.filter((agent) => {
|
|
501
|
-
const fullId = String(agent && agent.fullId ? agent.fullId : "").trim();
|
|
502
|
-
const type = String(agent && agent.type ? agent.type : "").trim();
|
|
503
|
-
if (fullId === "ufoo-agent") return false;
|
|
504
|
-
if (type === "ufoo-agent") return false;
|
|
505
|
-
return true;
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
return list.filter((agent) => {
|
|
509
|
-
const fullId = String(agent && agent.fullId ? agent.fullId : "").trim();
|
|
510
|
-
const type = String(agent && agent.type ? agent.type : "").trim();
|
|
511
|
-
if (!fullId) return true;
|
|
512
|
-
if (fullId === "ufoo-agent") return false;
|
|
513
|
-
if (type === "ufoo-agent") return false;
|
|
514
|
-
return fullId !== selfId;
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function stripLeakedEscapeTags(text = "") {
|
|
519
|
-
const source = String(text == null ? "" : text);
|
|
520
|
-
const withoutClosedTags = source.replace(/\{[^{}\n]*escape[^{}\n]*\}/gi, "");
|
|
521
|
-
const withoutDanglingEscape = withoutClosedTags.replace(/\{\s*\/?\s*escape[\s\S]*$/gi, "");
|
|
522
|
-
return withoutDanglingEscape.replace(/\{\s*\/?\s*e?s?c?a?p?e?[^{}\n]*$/gi, "");
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function findTrailingEscapeTagPrefix(text = "") {
|
|
526
|
-
const raw = String(text == null ? "" : text);
|
|
527
|
-
if (!raw) return "";
|
|
528
|
-
const windowSize = 40;
|
|
529
|
-
const tail = raw.slice(Math.max(0, raw.length - windowSize));
|
|
530
|
-
const braceIndex = tail.lastIndexOf("{");
|
|
531
|
-
if (braceIndex < 0) return "";
|
|
532
|
-
const suffix = tail.slice(braceIndex);
|
|
533
|
-
if (suffix.includes("}")) return "";
|
|
534
|
-
|
|
535
|
-
const compact = suffix.toLowerCase().replace(/\s+/g, "");
|
|
536
|
-
if (!compact.startsWith("{")) return "";
|
|
537
|
-
if (/^\{\/?e?s?c?a?p?e?[^}]*$/.test(compact)) {
|
|
538
|
-
return suffix;
|
|
539
|
-
}
|
|
540
|
-
return "";
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function createEscapeTagStripper() {
|
|
544
|
-
let carry = "";
|
|
545
|
-
|
|
546
|
-
return {
|
|
547
|
-
write(chunk = "") {
|
|
548
|
-
const incoming = String(chunk == null ? "" : chunk);
|
|
549
|
-
if (!incoming && !carry) return "";
|
|
550
|
-
const combined = `${carry}${incoming}`;
|
|
551
|
-
const trailing = findTrailingEscapeTagPrefix(combined);
|
|
552
|
-
const safeText = trailing
|
|
553
|
-
? combined.slice(0, combined.length - trailing.length)
|
|
554
|
-
: combined;
|
|
555
|
-
carry = trailing;
|
|
556
|
-
return stripLeakedEscapeTags(safeText);
|
|
557
|
-
},
|
|
558
|
-
flush() {
|
|
559
|
-
if (!carry) return "";
|
|
560
|
-
// carry only stores trailing prefixes of escape tags; do not emit it
|
|
561
|
-
// to avoid leaking partial markers like "{/escape" at stream end.
|
|
562
|
-
const rest = "";
|
|
563
|
-
carry = "";
|
|
564
|
-
return rest;
|
|
565
|
-
},
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function formatPendingElapsed(ms = 0) {
|
|
570
|
-
const totalSeconds = Math.max(0, Math.floor(Number(ms) / 1000));
|
|
571
|
-
return `${totalSeconds} s`;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function normalizeBashToolCommand(args = {}, payload = {}) {
|
|
575
|
-
const argObj = args && typeof args === "object" ? args : {};
|
|
576
|
-
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
577
|
-
const command = String(argObj.command || argObj.cmd || "").trim();
|
|
578
|
-
const code = Number.isFinite(resObj.code) ? `exit ${resObj.code}` : "";
|
|
579
|
-
return [command, code].filter(Boolean).join(" · ");
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function normalizeToolMergeEntry(entry = {}) {
|
|
583
|
-
const source = entry && typeof entry === "object" ? entry : {};
|
|
584
|
-
const tool = String(source.tool || "").trim().toLowerCase() || "tool";
|
|
585
|
-
const detail = String(source.detail || "").trim();
|
|
586
|
-
const isError = Boolean(source.isError);
|
|
587
|
-
const errorText = String(source.errorText || "").trim();
|
|
588
|
-
const summary = [tool, detail].filter(Boolean).join(" · ") || tool;
|
|
589
|
-
return {
|
|
590
|
-
tool,
|
|
591
|
-
detail,
|
|
592
|
-
isError,
|
|
593
|
-
errorText,
|
|
594
|
-
summary,
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function buildMergedToolSummaryText(entries = []) {
|
|
599
|
-
const list = Array.isArray(entries)
|
|
600
|
-
? entries.map((item) => normalizeToolMergeEntry(item))
|
|
601
|
-
: [];
|
|
602
|
-
const count = list.length;
|
|
603
|
-
if (count <= 0) return "Ran tool";
|
|
604
|
-
const first = list[0];
|
|
605
|
-
if (count === 1) return `Ran ${first.summary}`;
|
|
606
|
-
const errorCount = list.filter((item) => item.isError).length;
|
|
607
|
-
const errorSuffix = errorCount > 0 ? ` · ${errorCount} error${errorCount === 1 ? "" : "s"}` : "";
|
|
608
|
-
return `Ran ${first.summary} · … +${count - 1} calls${errorSuffix}`;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function buildMergedToolExpandedLines(entries = []) {
|
|
612
|
-
const list = Array.isArray(entries)
|
|
613
|
-
? entries.map((item) => normalizeToolMergeEntry(item))
|
|
614
|
-
: [];
|
|
615
|
-
const maxLength = 120; // Max length for expanded lines
|
|
616
|
-
return list.map((item, index) => {
|
|
617
|
-
const base = item.summary;
|
|
618
|
-
let line;
|
|
619
|
-
if (!item.isError) {
|
|
620
|
-
line = base;
|
|
621
|
-
} else {
|
|
622
|
-
line = item.errorText ? `${base} · error: ${item.errorText}` : `${base} · error`;
|
|
623
|
-
}
|
|
624
|
-
// Truncate long lines
|
|
625
|
-
if (line.length > maxLength) {
|
|
626
|
-
return line.slice(0, maxLength - 3) + "...";
|
|
627
|
-
}
|
|
628
|
-
return line;
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function runUcodeTui({
|
|
633
|
-
stdin = process.stdin,
|
|
634
|
-
stdout = process.stdout,
|
|
635
|
-
runSingleCommand = () => ({ kind: "empty" }),
|
|
636
|
-
runNaturalLanguageTask = async () => ({ ok: true, summary: "ok" }),
|
|
637
|
-
runUbusCommand = async () => ({ ok: false, error: "ubus unsupported", summary: "" }),
|
|
638
|
-
formatNlResult = () => "ok",
|
|
639
|
-
workspaceRoot = process.cwd(),
|
|
640
|
-
state = {},
|
|
641
|
-
resumeSessionState = () => ({ ok: false, error: "resume unsupported", sessionId: "", restoredMessages: 0 }),
|
|
642
|
-
persistSessionState = () => ({ ok: true }),
|
|
643
|
-
autoBus = {},
|
|
644
|
-
} = {}) {
|
|
645
|
-
return new Promise((resolve) => {
|
|
646
|
-
const blessed = require("blessed");
|
|
647
|
-
const { execFileSync } = require("child_process");
|
|
648
|
-
const { createChatLayout } = require("../chat/layout");
|
|
649
|
-
const { computeDashboardContent } = require("../chat/dashboardView");
|
|
650
|
-
const { escapeBlessed, stripBlessedTags } = require("../chat/text");
|
|
651
|
-
const currentSubscriberId = String(process.env.UFOO_SUBSCRIBER_ID || "").trim();
|
|
652
|
-
const autoBusEnabled = Boolean(autoBus && autoBus.enabled);
|
|
653
|
-
const autoBusSubscriberId = String((autoBus && autoBus.subscriberId) || currentSubscriberId || "").trim();
|
|
654
|
-
const getAutoBusPendingCount = typeof (autoBus && autoBus.getPendingCount) === "function"
|
|
655
|
-
? autoBus.getPendingCount
|
|
656
|
-
: () => 0;
|
|
657
|
-
|
|
658
|
-
let closing = false;
|
|
659
|
-
let chain = Promise.resolve();
|
|
660
|
-
let statusInterval = null;
|
|
661
|
-
let statusIndex = 0;
|
|
662
|
-
let activeAgents = [];
|
|
663
|
-
let activeAgentMetaMap = new Map();
|
|
664
|
-
let targetAgent = null;
|
|
665
|
-
let selectedAgentIndex = -1;
|
|
666
|
-
let agentListWindowStart = 0;
|
|
667
|
-
let agentSelectionMode = false;
|
|
668
|
-
let pendingTask = null;
|
|
669
|
-
const backgroundTasks = new Map();
|
|
670
|
-
let backgroundSeq = 0;
|
|
671
|
-
const logRenderState = { inCodeBlock: false };
|
|
672
|
-
const inputHistory = [];
|
|
673
|
-
let historyIndex = -1;
|
|
674
|
-
let activeToolMerge = null;
|
|
675
|
-
let lastMergedToolGroup = null;
|
|
676
|
-
let toolMergeId = 0;
|
|
677
|
-
let cursorPos = 0;
|
|
678
|
-
let preferredCol = null;
|
|
679
|
-
let currentInputHeight = 4;
|
|
680
|
-
const MIN_INPUT_CONTENT_HEIGHT = 1;
|
|
681
|
-
const MAX_INPUT_CONTENT_HEIGHT = 8;
|
|
682
|
-
const DASHBOARD_HEIGHT = 1;
|
|
683
|
-
let autoBusTimer = null;
|
|
684
|
-
let autoBusQueued = false;
|
|
685
|
-
let autoBusError = "";
|
|
686
|
-
const inputMath = require("../chat/inputMath");
|
|
687
|
-
|
|
688
|
-
const {
|
|
689
|
-
screen,
|
|
690
|
-
logBox,
|
|
691
|
-
statusLine,
|
|
692
|
-
completionPanel,
|
|
693
|
-
dashboard,
|
|
694
|
-
inputTopLine,
|
|
695
|
-
promptBox,
|
|
696
|
-
input,
|
|
697
|
-
} = createChatLayout({
|
|
698
|
-
blessed,
|
|
699
|
-
currentInputHeight: 4,
|
|
700
|
-
version: UCODE_VERSION,
|
|
701
|
-
logBorder: false,
|
|
702
|
-
logScrollbar: false,
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
if (completionPanel && typeof completionPanel.hide === "function") {
|
|
706
|
-
completionPanel.hide();
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const getAgentTag = (agent) => {
|
|
710
|
-
if (!agent) return "";
|
|
711
|
-
if (agent.id) return `${agent.type}:${agent.id.slice(0, 6)}`;
|
|
712
|
-
return agent.type;
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
const getAgentLabel = (id) => {
|
|
716
|
-
const meta = activeAgentMetaMap.get(id);
|
|
717
|
-
if (!meta) return id;
|
|
718
|
-
if (meta.nickname) return meta.nickname;
|
|
719
|
-
return getAgentTag(meta);
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
const refreshAgents = () => {
|
|
723
|
-
const list = filterSelectableAgents(
|
|
724
|
-
loadActiveAgents(workspaceRoot),
|
|
725
|
-
currentSubscriberId
|
|
726
|
-
);
|
|
727
|
-
activeAgents = list.map((agent) => agent.fullId);
|
|
728
|
-
activeAgentMetaMap = new Map(list.map((agent) => [agent.fullId, agent]));
|
|
729
|
-
if (targetAgent && !activeAgentMetaMap.has(targetAgent)) {
|
|
730
|
-
targetAgent = null;
|
|
731
|
-
}
|
|
732
|
-
selectedAgentIndex = targetAgent ? activeAgents.indexOf(targetAgent) : -1;
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
const setPrompt = () => {
|
|
736
|
-
const content = targetAgent ? `>@${getAgentLabel(targetAgent)}` : ">";
|
|
737
|
-
promptBox.setContent(content);
|
|
738
|
-
const plain = stripBlessedTags(content);
|
|
739
|
-
promptBox.width = Math.max(2, plain.length + 1);
|
|
740
|
-
input.left = promptBox.width;
|
|
741
|
-
input.width = `100%-${promptBox.width}`;
|
|
742
|
-
resizeInput();
|
|
743
|
-
};
|
|
744
|
-
|
|
745
|
-
// --- Cursor position helpers (mirrors chat inputListenerController) ---
|
|
746
|
-
const getInnerWidth = () => {
|
|
747
|
-
const promptWidth = typeof promptBox.width === "number" ? promptBox.width : 2;
|
|
748
|
-
return inputMath.getInnerWidth({ input, screen, promptWidth });
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
const getWrapWidth = () => inputMath.getWrapWidth(input, getInnerWidth());
|
|
752
|
-
|
|
753
|
-
const resetPreferredCol = () => {
|
|
754
|
-
preferredCol = null;
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
const ensureInputCursorVisible = () => {
|
|
758
|
-
const innerWidth = getWrapWidth();
|
|
759
|
-
if (innerWidth <= 0) return;
|
|
760
|
-
const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
|
|
761
|
-
const visibleRows = Math.max(1, input.height || 1);
|
|
762
|
-
const { row } = inputMath.getCursorRowCol(input.value || "", cursorPos, innerWidth, (v) => input.strWidth(v));
|
|
763
|
-
let base = input.childBase || 0;
|
|
764
|
-
const maxBase = Math.max(0, totalRows - visibleRows);
|
|
765
|
-
if (row < base) base = row;
|
|
766
|
-
else if (row >= base + visibleRows) base = row - visibleRows + 1;
|
|
767
|
-
if (base > maxBase) base = maxBase;
|
|
768
|
-
if (base < 0) base = 0;
|
|
769
|
-
if (base !== input.childBase) {
|
|
770
|
-
input.childBase = base;
|
|
771
|
-
if (typeof input.scrollTo === "function") input.scrollTo(base);
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
const resizeInput = () => {
|
|
776
|
-
const innerWidth = getWrapWidth();
|
|
777
|
-
if (innerWidth <= 0) return;
|
|
778
|
-
const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
|
|
779
|
-
const contentHeight = Math.min(
|
|
780
|
-
MAX_INPUT_CONTENT_HEIGHT,
|
|
781
|
-
Math.max(MIN_INPUT_CONTENT_HEIGHT, totalRows)
|
|
782
|
-
);
|
|
783
|
-
const targetHeight = contentHeight + DASHBOARD_HEIGHT + 2;
|
|
784
|
-
if (targetHeight !== currentInputHeight) {
|
|
785
|
-
currentInputHeight = targetHeight;
|
|
786
|
-
input.height = contentHeight;
|
|
787
|
-
promptBox.height = contentHeight;
|
|
788
|
-
if (inputTopLine) inputTopLine.bottom = currentInputHeight - 1;
|
|
789
|
-
}
|
|
790
|
-
statusLine.bottom = currentInputHeight;
|
|
791
|
-
logBox.height = Math.max(1, screen.height - currentInputHeight - 1);
|
|
792
|
-
ensureInputCursorVisible();
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const renderInput = () => {
|
|
796
|
-
resizeInput();
|
|
797
|
-
ensureInputCursorVisible();
|
|
798
|
-
input._updateCursor();
|
|
799
|
-
screen.render();
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const setCursor = (nextPos) => {
|
|
803
|
-
cursorPos = clampCursorPos(nextPos, input.value || "");
|
|
804
|
-
ensureInputCursorVisible();
|
|
805
|
-
input._updateCursor();
|
|
806
|
-
screen.render();
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
const setInputValue = (value) => {
|
|
810
|
-
input.setValue(value || "");
|
|
811
|
-
cursorPos = (value || "").length;
|
|
812
|
-
resetPreferredCol();
|
|
813
|
-
renderInput();
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
const replaceInputRange = (start, end, replacement = "") => {
|
|
817
|
-
const value = input.value || "";
|
|
818
|
-
const safeStart = clampCursorPos(start, value);
|
|
819
|
-
const safeEnd = clampCursorPos(end, value);
|
|
820
|
-
const from = Math.min(safeStart, safeEnd);
|
|
821
|
-
const to = Math.max(safeStart, safeEnd);
|
|
822
|
-
input.value = value.slice(0, from) + String(replacement || "") + value.slice(to);
|
|
823
|
-
cursorPos = from + String(replacement || "").length;
|
|
824
|
-
resetPreferredCol();
|
|
825
|
-
renderInput();
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
const insertTextAtCursor = (text = "") => {
|
|
829
|
-
const normalized = inputMath.normalizePaste(text);
|
|
830
|
-
if (!normalized) return;
|
|
831
|
-
replaceInputRange(cursorPos, cursorPos, normalized);
|
|
832
|
-
};
|
|
833
|
-
|
|
834
|
-
const deleteBeforeCursor = () => {
|
|
835
|
-
if (cursorPos <= 0) return;
|
|
836
|
-
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
const deleteAtCursor = () => {
|
|
840
|
-
const value = input.value || "";
|
|
841
|
-
if (cursorPos >= value.length) return;
|
|
842
|
-
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
const deleteToBoundary = (boundary) => {
|
|
846
|
-
const value = input.value || "";
|
|
847
|
-
const innerWidth = getWrapWidth();
|
|
848
|
-
const target = boundary === "end"
|
|
849
|
-
? moveCursorToVisualLineBoundary({
|
|
850
|
-
cursorPos,
|
|
851
|
-
inputValue: value,
|
|
852
|
-
width: innerWidth,
|
|
853
|
-
boundary: "end",
|
|
854
|
-
strWidth: (v) => input.strWidth(v),
|
|
855
|
-
})
|
|
856
|
-
: moveCursorToVisualLineBoundary({
|
|
857
|
-
cursorPos,
|
|
858
|
-
inputValue: value,
|
|
859
|
-
width: innerWidth,
|
|
860
|
-
boundary: "start",
|
|
861
|
-
strWidth: (v) => input.strWidth(v),
|
|
862
|
-
});
|
|
863
|
-
if (target === cursorPos && boundary === "end" && value[cursorPos] === "\n") {
|
|
864
|
-
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
if (target === cursorPos && boundary === "start" && value[cursorPos - 1] === "\n") {
|
|
868
|
-
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
replaceInputRange(Math.min(cursorPos, target), Math.max(cursorPos, target), "");
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
// Override _updateCursor to use our tracked cursorPos
|
|
875
|
-
input._updateCursor = function () {
|
|
876
|
-
if (this.screen.focused !== this) return;
|
|
877
|
-
let lpos;
|
|
878
|
-
try { lpos = this._getCoords(); } catch { return; }
|
|
879
|
-
if (!lpos) return;
|
|
880
|
-
const innerWidth = getWrapWidth();
|
|
881
|
-
if (innerWidth <= 0) return;
|
|
882
|
-
ensureInputCursorVisible();
|
|
883
|
-
const { row, col } = inputMath.getCursorRowCol(this.value || "", cursorPos, innerWidth, (v) => this.strWidth(v));
|
|
884
|
-
const scrollOffset = this.childBase || 0;
|
|
885
|
-
const displayRow = row - scrollOffset;
|
|
886
|
-
const safeCol = Math.min(Math.max(0, col), innerWidth - 1);
|
|
887
|
-
const cy = lpos.yi + displayRow;
|
|
888
|
-
const cx = lpos.xi + safeCol;
|
|
889
|
-
this.screen.program.cup(cy, cx);
|
|
890
|
-
this.screen.program.showCursor();
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
// Override _listener to support cursor-aware editing
|
|
894
|
-
let lastKeyRef = null;
|
|
895
|
-
let skipSubmitKeyRef = null;
|
|
896
|
-
input._listener = function (ch, key) {
|
|
897
|
-
const keyName = key && key.name;
|
|
898
|
-
|
|
899
|
-
// Dedup: blessed delivers the same key object via element 'keypress' event
|
|
900
|
-
// from both readInput's __listener binding and screen's focused.emit('keypress').
|
|
901
|
-
// Use object identity to skip the duplicate delivery.
|
|
902
|
-
if (key && key === lastKeyRef) return;
|
|
903
|
-
lastKeyRef = key || null;
|
|
904
|
-
|
|
905
|
-
if (keyName === "escape") return;
|
|
906
|
-
|
|
907
|
-
if (keyName === "return" || keyName === "enter") {
|
|
908
|
-
const value = this.value || "";
|
|
909
|
-
if (key && (key.shift || key.meta)) {
|
|
910
|
-
insertTextAtCursor("\n");
|
|
911
|
-
skipSubmitKeyRef = key || true;
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
|
|
915
|
-
replaceInputRange(cursorPos - 1, cursorPos, "\n");
|
|
916
|
-
skipSubmitKeyRef = key || true;
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// Arrow keys handled by input.key() handlers below
|
|
923
|
-
if (keyName === "left" || keyName === "right" || keyName === "up" || keyName === "down") return;
|
|
924
|
-
|
|
925
|
-
if (key && key.ctrl) {
|
|
926
|
-
if (keyName === "a") {
|
|
927
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
928
|
-
cursorPos,
|
|
929
|
-
inputValue: this.value || "",
|
|
930
|
-
width: getWrapWidth(),
|
|
931
|
-
boundary: "start",
|
|
932
|
-
strWidth: (v) => this.strWidth(v),
|
|
933
|
-
}));
|
|
934
|
-
resetPreferredCol();
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
if (keyName === "e") {
|
|
938
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
939
|
-
cursorPos,
|
|
940
|
-
inputValue: this.value || "",
|
|
941
|
-
width: getWrapWidth(),
|
|
942
|
-
boundary: "end",
|
|
943
|
-
strWidth: (v) => this.strWidth(v),
|
|
944
|
-
}));
|
|
945
|
-
resetPreferredCol();
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
if (keyName === "b") {
|
|
949
|
-
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "left"));
|
|
950
|
-
resetPreferredCol();
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
if (keyName === "f") {
|
|
954
|
-
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "right"));
|
|
955
|
-
resetPreferredCol();
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
if (keyName === "d") {
|
|
959
|
-
deleteAtCursor();
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (keyName === "h") {
|
|
963
|
-
deleteBeforeCursor();
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
if (keyName === "k") {
|
|
967
|
-
deleteToBoundary("end");
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
if (keyName === "u") {
|
|
971
|
-
deleteToBoundary("start");
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
if (keyName === "w") {
|
|
975
|
-
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
976
|
-
this.value = next.value;
|
|
977
|
-
cursorPos = next.cursorPos;
|
|
978
|
-
resetPreferredCol();
|
|
979
|
-
renderInput();
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if (key && key.meta) {
|
|
985
|
-
if (keyName === "b") {
|
|
986
|
-
setCursor(moveCursorByWord(this.value || "", cursorPos, "backward"));
|
|
987
|
-
resetPreferredCol();
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
if (keyName === "f") {
|
|
991
|
-
setCursor(moveCursorByWord(this.value || "", cursorPos, "forward"));
|
|
992
|
-
resetPreferredCol();
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
if (keyName === "d") {
|
|
996
|
-
const end = moveCursorByWord(this.value || "", cursorPos, "forward");
|
|
997
|
-
replaceInputRange(cursorPos, end, "");
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
if (keyName === "backspace") {
|
|
1003
|
-
if (key && (key.meta || key.ctrl)) {
|
|
1004
|
-
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
1005
|
-
this.value = next.value;
|
|
1006
|
-
cursorPos = next.cursorPos;
|
|
1007
|
-
resetPreferredCol();
|
|
1008
|
-
renderInput();
|
|
1009
|
-
} else {
|
|
1010
|
-
deleteBeforeCursor();
|
|
1011
|
-
}
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (keyName === "delete") {
|
|
1016
|
-
if (key && key.meta) {
|
|
1017
|
-
deleteToBoundary("end");
|
|
1018
|
-
} else {
|
|
1019
|
-
deleteAtCursor();
|
|
1020
|
-
}
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
if (keyName === "home") {
|
|
1025
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
1026
|
-
cursorPos,
|
|
1027
|
-
inputValue: this.value || "",
|
|
1028
|
-
width: getWrapWidth(),
|
|
1029
|
-
boundary: "start",
|
|
1030
|
-
strWidth: (v) => this.strWidth(v),
|
|
1031
|
-
}));
|
|
1032
|
-
resetPreferredCol();
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
if (keyName === "end") {
|
|
1037
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
1038
|
-
cursorPos,
|
|
1039
|
-
inputValue: this.value || "",
|
|
1040
|
-
width: getWrapWidth(),
|
|
1041
|
-
boundary: "end",
|
|
1042
|
-
strWidth: (v) => this.strWidth(v),
|
|
1043
|
-
}));
|
|
1044
|
-
resetPreferredCol();
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
|
|
1049
|
-
insertTextAtCursor(ch);
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Normal character insertion at cursor position
|
|
1054
|
-
const insertChar = (ch && ch.length === 1) ? ch : (keyName && keyName.length === 1 ? keyName : null);
|
|
1055
|
-
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
|
|
1056
|
-
insertTextAtCursor(insertChar);
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
const renderDashboard = () => {
|
|
1061
|
-
let hint = "No target agents";
|
|
1062
|
-
if (activeAgents.length > 0) {
|
|
1063
|
-
if (targetAgent) {
|
|
1064
|
-
hint = `↓ select ${getAgentLabel(targetAgent)} · ←/→ switch · ↑ clear`;
|
|
1065
|
-
} else {
|
|
1066
|
-
hint = "↓ select target · ←/→ switch";
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
const computed = computeDashboardContent({
|
|
1070
|
-
focusMode: "dashboard",
|
|
1071
|
-
dashboardView: "agents",
|
|
1072
|
-
activeAgents,
|
|
1073
|
-
selectedAgentIndex,
|
|
1074
|
-
agentListWindowStart,
|
|
1075
|
-
maxAgentWindow: 4,
|
|
1076
|
-
getAgentLabel,
|
|
1077
|
-
dashHints: { agents: hint, agentsEmpty: hint },
|
|
1078
|
-
});
|
|
1079
|
-
agentListWindowStart = computed.windowStart;
|
|
1080
|
-
dashboard.setContent(computed.content);
|
|
1081
|
-
screen.render();
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
const logText = (text = "") => {
|
|
1085
|
-
activeToolMerge = null;
|
|
1086
|
-
firstToolInGroup = true; // Reset tool group flag when switching back to text
|
|
1087
|
-
const sanitized = stripLeakedEscapeTags(text);
|
|
1088
|
-
const lines = renderLogLinesWithMarkdown(
|
|
1089
|
-
sanitized,
|
|
1090
|
-
logRenderState,
|
|
1091
|
-
escapeBlessed
|
|
1092
|
-
);
|
|
1093
|
-
for (const line of lines) {
|
|
1094
|
-
logBox.log(line);
|
|
1095
|
-
}
|
|
1096
|
-
screen.render();
|
|
1097
|
-
};
|
|
1098
|
-
|
|
1099
|
-
const logUserInput = (text = "") => {
|
|
1100
|
-
activeToolMerge = null;
|
|
1101
|
-
const line = formatHighlightedUserInput(text, {
|
|
1102
|
-
width: resolveLogContentWidth({ logBox, screen, fallback: (stdout && stdout.columns) || 80 }),
|
|
1103
|
-
escapeText: escapeBlessed,
|
|
1104
|
-
});
|
|
1105
|
-
if (!line) return;
|
|
1106
|
-
logBox.log(line);
|
|
1107
|
-
logBox.log(""); // Add line break after user input
|
|
1108
|
-
screen.render();
|
|
1109
|
-
};
|
|
1110
|
-
|
|
1111
|
-
const logControlAction = (text = "") => {
|
|
1112
|
-
activeToolMerge = null;
|
|
1113
|
-
const plain = String(text || "").trim();
|
|
1114
|
-
if (!plain) return;
|
|
1115
|
-
logBox.log(`{gray-fg}⚙{/gray-fg} ${escapeBlessed(plain)}`);
|
|
1116
|
-
screen.render();
|
|
1117
|
-
};
|
|
1118
|
-
|
|
1119
|
-
const summarizeToolDetail = (tool = "", args = {}, payload = {}) => {
|
|
1120
|
-
const toolName = String(tool || "").trim().toLowerCase();
|
|
1121
|
-
const argObj = args && typeof args === "object" ? args : {};
|
|
1122
|
-
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
1123
|
-
|
|
1124
|
-
if (toolName === "read") {
|
|
1125
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
1126
|
-
const lineInfo = Number.isFinite(resObj.totalLines) ? `${resObj.totalLines} lines` : "";
|
|
1127
|
-
return [target, lineInfo].filter(Boolean).join(" · ");
|
|
1128
|
-
}
|
|
1129
|
-
if (toolName === "write") {
|
|
1130
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
1131
|
-
const mode = String(resObj.mode || argObj.mode || (argObj.append ? "append" : "overwrite")).trim();
|
|
1132
|
-
const bytes = Number.isFinite(resObj.bytes) ? `${resObj.bytes} bytes` : "";
|
|
1133
|
-
return [target, mode, bytes].filter(Boolean).join(" · ");
|
|
1134
|
-
}
|
|
1135
|
-
if (toolName === "edit") {
|
|
1136
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
1137
|
-
const replacements = Number.isFinite(resObj.replacements) ? `${resObj.replacements} replacements` : "";
|
|
1138
|
-
return [target, replacements].filter(Boolean).join(" · ");
|
|
1139
|
-
}
|
|
1140
|
-
if (toolName === "bash") {
|
|
1141
|
-
return normalizeBashToolCommand(argObj, resObj);
|
|
1142
|
-
}
|
|
1143
|
-
return "";
|
|
1144
|
-
};
|
|
1145
|
-
|
|
1146
|
-
const truncateText = (text = "", maxLength = 80) => {
|
|
1147
|
-
const str = String(text || "");
|
|
1148
|
-
if (str.length <= maxLength) return str;
|
|
1149
|
-
return str.slice(0, maxLength - 3) + "...";
|
|
1150
|
-
};
|
|
1151
|
-
|
|
1152
|
-
const renderSingleToolEntryLine = (entry = {}) => {
|
|
1153
|
-
const item = normalizeToolMergeEntry(entry);
|
|
1154
|
-
const marker = item.isError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
|
|
1155
|
-
const summary = buildMergedToolSummaryText([item]);
|
|
1156
|
-
const truncated = truncateText(summary, 100);
|
|
1157
|
-
return `${marker} ${escapeBlessed(truncated)}`;
|
|
1158
|
-
};
|
|
1159
|
-
|
|
1160
|
-
const renderCollapsedToolMergeLine = (entries = []) => {
|
|
1161
|
-
const summary = buildMergedToolSummaryText(entries);
|
|
1162
|
-
const hasError = entries.some((item) => normalizeToolMergeEntry(item).isError);
|
|
1163
|
-
const marker = hasError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
|
|
1164
|
-
return `${marker} ${escapeBlessed(summary)} {gray-fg}(Ctrl+O expand){/gray-fg}`;
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
let firstToolInGroup = true;
|
|
1168
|
-
|
|
1169
|
-
const logToolHint = (entry = {}, payload = {}) => {
|
|
1170
|
-
const tool = String(entry.tool || "").trim().toLowerCase();
|
|
1171
|
-
if (!tool) return;
|
|
1172
|
-
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
1173
|
-
const isError = String(entry.phase || "").trim().toLowerCase() === "error" || resObj.ok === false;
|
|
1174
|
-
const detail = summarizeToolDetail(tool, entry.args, resObj);
|
|
1175
|
-
const errorText = String(entry.error || resObj.error || "").trim();
|
|
1176
|
-
|
|
1177
|
-
const toolEntry = normalizeToolMergeEntry({
|
|
1178
|
-
tool,
|
|
1179
|
-
detail,
|
|
1180
|
-
isError,
|
|
1181
|
-
errorText,
|
|
1182
|
-
});
|
|
1183
|
-
|
|
1184
|
-
if (activeToolMerge) {
|
|
1185
|
-
activeToolMerge.entries.push(toolEntry);
|
|
1186
|
-
// Only show collapsed format for 2+ tool calls
|
|
1187
|
-
if (activeToolMerge.entries.length === 2) {
|
|
1188
|
-
// Convert first single line to collapsed format
|
|
1189
|
-
logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
|
|
1190
|
-
} else if (activeToolMerge.entries.length > 2) {
|
|
1191
|
-
logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
|
|
1192
|
-
}
|
|
1193
|
-
if (activeToolMerge.entries.length > 1) {
|
|
1194
|
-
lastMergedToolGroup = activeToolMerge;
|
|
1195
|
-
}
|
|
1196
|
-
} else {
|
|
1197
|
-
// Add line break before first tool call
|
|
1198
|
-
if (firstToolInGroup) {
|
|
1199
|
-
logBox.log("");
|
|
1200
|
-
firstToolInGroup = false;
|
|
1201
|
-
}
|
|
1202
|
-
logBox.log(renderSingleToolEntryLine(toolEntry));
|
|
1203
|
-
activeToolMerge = {
|
|
1204
|
-
id: ++toolMergeId,
|
|
1205
|
-
lineIndex: logBox.getLines().length - 1,
|
|
1206
|
-
entries: [toolEntry],
|
|
1207
|
-
expanded: false,
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
screen.render();
|
|
1211
|
-
};
|
|
1212
|
-
|
|
1213
|
-
const renderSingleMarkdownLine = (rawLine = "", options = {}) => {
|
|
1214
|
-
const preview = Boolean(options.preview);
|
|
1215
|
-
const renderState = preview
|
|
1216
|
-
? { inCodeBlock: Boolean(logRenderState.inCodeBlock) }
|
|
1217
|
-
: logRenderState;
|
|
1218
|
-
const rendered = renderLogLinesWithMarkdown(rawLine, renderState, escapeBlessed);
|
|
1219
|
-
return rendered[0] || "";
|
|
1220
|
-
};
|
|
1221
|
-
|
|
1222
|
-
const createNlStreamState = () => {
|
|
1223
|
-
activeToolMerge = null;
|
|
1224
|
-
firstToolInGroup = true; // Reset flag for new response
|
|
1225
|
-
logBox.log(""); // Add empty line to start the response
|
|
1226
|
-
return {
|
|
1227
|
-
lineIndex: logBox.getLines().length - 1,
|
|
1228
|
-
buffer: "",
|
|
1229
|
-
full: "",
|
|
1230
|
-
seenVisibleContent: false,
|
|
1231
|
-
};
|
|
1232
|
-
};
|
|
1233
|
-
|
|
1234
|
-
const appendNlStreamDelta = (streamState, delta) => {
|
|
1235
|
-
if (!streamState) return;
|
|
1236
|
-
const chunk = stripLeakedEscapeTags(String(delta || ""));
|
|
1237
|
-
if (!chunk) return;
|
|
1238
|
-
|
|
1239
|
-
streamState.full += chunk;
|
|
1240
|
-
streamState.buffer += chunk;
|
|
1241
|
-
|
|
1242
|
-
const parts = streamState.buffer.split("\n");
|
|
1243
|
-
if (parts.length > 1) {
|
|
1244
|
-
const completed = parts.slice(0, -1);
|
|
1245
|
-
for (const line of completed) {
|
|
1246
|
-
const hasVisible = /[^\s]/.test(line);
|
|
1247
|
-
if (!streamState.seenVisibleContent && !hasVisible) {
|
|
1248
|
-
continue;
|
|
1249
|
-
}
|
|
1250
|
-
if (hasVisible) {
|
|
1251
|
-
streamState.seenVisibleContent = true;
|
|
1252
|
-
}
|
|
1253
|
-
const rendered = renderSingleMarkdownLine(line);
|
|
1254
|
-
logBox.setLine(streamState.lineIndex, rendered);
|
|
1255
|
-
logBox.pushLine("");
|
|
1256
|
-
streamState.lineIndex = logBox.getLines().length - 1;
|
|
1257
|
-
}
|
|
1258
|
-
streamState.buffer = parts[parts.length - 1];
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
const previewHasVisible = /[^\s]/.test(streamState.buffer);
|
|
1262
|
-
if (!streamState.seenVisibleContent && !previewHasVisible) {
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
if (previewHasVisible) {
|
|
1266
|
-
streamState.seenVisibleContent = true;
|
|
1267
|
-
}
|
|
1268
|
-
const previewLine = renderSingleMarkdownLine(streamState.buffer, { preview: true });
|
|
1269
|
-
logBox.setLine(streamState.lineIndex, previewLine);
|
|
1270
|
-
screen.render();
|
|
1271
|
-
};
|
|
1272
|
-
|
|
1273
|
-
const finalizeNlStream = (streamState) => {
|
|
1274
|
-
if (!streamState) return { lastChar: "" };
|
|
1275
|
-
streamState.buffer = stripLeakedEscapeTags(streamState.buffer);
|
|
1276
|
-
const rendered = renderSingleMarkdownLine(streamState.buffer);
|
|
1277
|
-
logBox.setLine(streamState.lineIndex, rendered);
|
|
1278
|
-
screen.render();
|
|
1279
|
-
const full = String(streamState.full || "");
|
|
1280
|
-
return { lastChar: full ? full.charAt(full.length - 1) : "" };
|
|
1281
|
-
};
|
|
1282
|
-
|
|
1283
|
-
const updateStatus = (message = "", type = "thinking", options = {}) => {
|
|
1284
|
-
const getBackgroundSuffix = () => {
|
|
1285
|
-
if (!backgroundTasks || backgroundTasks.size === 0) return "";
|
|
1286
|
-
let running = 0;
|
|
1287
|
-
let done = 0;
|
|
1288
|
-
let failed = 0;
|
|
1289
|
-
for (const task of backgroundTasks.values()) {
|
|
1290
|
-
const status = String(task && task.status || "").trim().toLowerCase();
|
|
1291
|
-
if (status === "running") running += 1;
|
|
1292
|
-
else if (status === "done") done += 1;
|
|
1293
|
-
else if (status === "failed") failed += 1;
|
|
1294
|
-
}
|
|
1295
|
-
const total = running + done + failed;
|
|
1296
|
-
if (total <= 0) return "";
|
|
1297
|
-
return ` · BG ${running}/${done}/${failed}`;
|
|
1298
|
-
};
|
|
1299
|
-
if (statusInterval) {
|
|
1300
|
-
clearInterval(statusInterval);
|
|
1301
|
-
statusInterval = null;
|
|
1302
|
-
}
|
|
1303
|
-
if (!message) {
|
|
1304
|
-
statusLine.setContent(escapeBlessed(`UCODE · Ready · Enter send · Shift/Alt+Enter newline · PgUp/PgDn log · Ctrl+O tools${getBackgroundSuffix()}`));
|
|
1305
|
-
screen.render();
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
const showTimer = Boolean(options.showTimer);
|
|
1309
|
-
const startedAt = Number.isFinite(options.startedAt) ? options.startedAt : Date.now();
|
|
1310
|
-
const indicators = STATUS_INDICATORS[type] || STATUS_INDICATORS.thinking;
|
|
1311
|
-
statusIndex = 0;
|
|
1312
|
-
const draw = () => {
|
|
1313
|
-
const indicator = indicators[statusIndex % indicators.length];
|
|
1314
|
-
const timerText = showTimer
|
|
1315
|
-
? ` (${formatPendingElapsed(Date.now() - startedAt)},esc cancel)`
|
|
1316
|
-
: "";
|
|
1317
|
-
statusLine.setContent(escapeBlessed(`${indicator} ${message}${timerText}${getBackgroundSuffix()}`));
|
|
1318
|
-
statusIndex += 1;
|
|
1319
|
-
screen.render();
|
|
1320
|
-
};
|
|
1321
|
-
draw();
|
|
1322
|
-
if (type !== "none") {
|
|
1323
|
-
statusInterval = setInterval(draw, 100);
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
|
-
|
|
1327
|
-
const closeWithCode = (code = 0) => {
|
|
1328
|
-
if (closing) return;
|
|
1329
|
-
closing = true;
|
|
1330
|
-
if (autoBusTimer) {
|
|
1331
|
-
clearInterval(autoBusTimer);
|
|
1332
|
-
autoBusTimer = null;
|
|
1333
|
-
}
|
|
1334
|
-
if (statusInterval) {
|
|
1335
|
-
clearInterval(statusInterval);
|
|
1336
|
-
statusInterval = null;
|
|
1337
|
-
}
|
|
1338
|
-
if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
|
|
1339
|
-
try {
|
|
1340
|
-
pendingTask.abortController.abort();
|
|
1341
|
-
} catch {
|
|
1342
|
-
// ignore
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
try {
|
|
1346
|
-
screen.destroy();
|
|
1347
|
-
} catch {
|
|
1348
|
-
// ignore
|
|
1349
|
-
}
|
|
1350
|
-
resolve({ code });
|
|
1351
|
-
};
|
|
1352
|
-
|
|
1353
|
-
const runAutoBusOnce = async () => {
|
|
1354
|
-
if (!autoBusEnabled || closing || pendingTask) return;
|
|
1355
|
-
if (Number(getAutoBusPendingCount()) <= 0) {
|
|
1356
|
-
autoBusError = "";
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// Set pending state for autoBus tasks
|
|
1361
|
-
const abortController = new AbortController();
|
|
1362
|
-
pendingTask = {
|
|
1363
|
-
abortController,
|
|
1364
|
-
startedAt: Date.now(),
|
|
1365
|
-
};
|
|
1366
|
-
updateStatus("Processing bus messages...", "thinking", {
|
|
1367
|
-
showTimer: true,
|
|
1368
|
-
startedAt: pendingTask.startedAt,
|
|
1369
|
-
});
|
|
1370
|
-
|
|
1371
|
-
try {
|
|
1372
|
-
const ubusResult = await runUbusCommand(state, {
|
|
1373
|
-
workspaceRoot,
|
|
1374
|
-
subscriberId: autoBusSubscriberId,
|
|
1375
|
-
signal: abortController.signal,
|
|
1376
|
-
onMessageReceived: (msg) => {
|
|
1377
|
-
// Display the incoming message immediately
|
|
1378
|
-
const { extractAgentNickname } = require("./agent");
|
|
1379
|
-
const nickname = extractAgentNickname(msg.from) || msg.from;
|
|
1380
|
-
logText(`${nickname}: ${msg.task}`);
|
|
1381
|
-
// Update status to show we're working on this specific task
|
|
1382
|
-
updateStatus("Working on task...", "thinking", {
|
|
1383
|
-
showTimer: true,
|
|
1384
|
-
startedAt: pendingTask.startedAt,
|
|
1385
|
-
});
|
|
1386
|
-
},
|
|
1387
|
-
});
|
|
1388
|
-
|
|
1389
|
-
if (!ubusResult.ok) {
|
|
1390
|
-
const nextError = String(ubusResult.error || "ubus failed");
|
|
1391
|
-
if (nextError !== autoBusError) {
|
|
1392
|
-
autoBusError = nextError;
|
|
1393
|
-
logText(`Error: ${nextError}`);
|
|
1394
|
-
}
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
autoBusError = "";
|
|
1398
|
-
if (ubusResult.handled > 0) {
|
|
1399
|
-
// Display only the replies (tasks were already shown via onMessageReceived)
|
|
1400
|
-
if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
|
|
1401
|
-
const { extractAgentNickname } = require("./agent");
|
|
1402
|
-
for (const exchange of ubusResult.messageExchanges) {
|
|
1403
|
-
const nickname = extractAgentNickname(exchange.from) || exchange.from;
|
|
1404
|
-
// Only show the reply since task was already displayed
|
|
1405
|
-
logText(`@${nickname} ${exchange.reply}`);
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
const persisted = persistSessionState(state);
|
|
1409
|
-
if (!persisted || persisted.ok === false) {
|
|
1410
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
} finally {
|
|
1414
|
-
// Clear pending state
|
|
1415
|
-
pendingTask = null;
|
|
1416
|
-
updateStatus("", "none");
|
|
1417
|
-
}
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
|
-
const scheduleAutoBus = () => {
|
|
1421
|
-
if (!autoBusEnabled || closing || autoBusQueued || pendingTask) return;
|
|
1422
|
-
if (Number(getAutoBusPendingCount()) <= 0) return;
|
|
1423
|
-
autoBusQueued = true;
|
|
1424
|
-
chain = chain
|
|
1425
|
-
.then(() => runAutoBusOnce())
|
|
1426
|
-
.catch(() => {})
|
|
1427
|
-
.finally(() => {
|
|
1428
|
-
autoBusQueued = false;
|
|
1429
|
-
});
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1432
|
-
const resolveTargetToken = (token = "") => {
|
|
1433
|
-
const text = String(token || "").trim();
|
|
1434
|
-
if (!text) return "";
|
|
1435
|
-
|
|
1436
|
-
if (text.includes(":")) {
|
|
1437
|
-
const match = activeAgents.find((id) => id === text || id.startsWith(text));
|
|
1438
|
-
if (match) return match;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const normalized = text.toLowerCase();
|
|
1442
|
-
for (const id of activeAgents) {
|
|
1443
|
-
const meta = activeAgentMetaMap.get(id);
|
|
1444
|
-
if (!meta) continue;
|
|
1445
|
-
const nick = String(meta.nickname || "").toLowerCase();
|
|
1446
|
-
if (nick && (nick === normalized || nick.startsWith(normalized))) return id;
|
|
1447
|
-
}
|
|
1448
|
-
return "";
|
|
1449
|
-
};
|
|
1450
|
-
|
|
1451
|
-
const executeLine = async (line) => {
|
|
1452
|
-
const normalizedLine = String(line || "").replace(/\r?\n/g, " ").trim();
|
|
1453
|
-
if (!normalizedLine) return;
|
|
1454
|
-
logUserInput(normalizedLine);
|
|
1455
|
-
|
|
1456
|
-
refreshAgents();
|
|
1457
|
-
|
|
1458
|
-
let actualLine = normalizedLine;
|
|
1459
|
-
let isBusMessage = false;
|
|
1460
|
-
|
|
1461
|
-
if (targetAgent) {
|
|
1462
|
-
isBusMessage = true;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
const mentionMatch = normalizedLine.match(/^@(\S+)\s+(.+)$/);
|
|
1466
|
-
if (mentionMatch) {
|
|
1467
|
-
const [, token, message] = mentionMatch;
|
|
1468
|
-
const resolved = resolveTargetToken(token);
|
|
1469
|
-
if (resolved) {
|
|
1470
|
-
isBusMessage = true;
|
|
1471
|
-
actualLine = message;
|
|
1472
|
-
targetAgent = resolved;
|
|
1473
|
-
selectedAgentIndex = activeAgents.indexOf(resolved);
|
|
1474
|
-
setPrompt();
|
|
1475
|
-
renderDashboard();
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
if (isBusMessage && targetAgent) {
|
|
1480
|
-
updateStatus("Sending message...", "typing");
|
|
1481
|
-
try {
|
|
1482
|
-
execFileSync("ufoo", ["bus", "send", targetAgent, actualLine], {
|
|
1483
|
-
cwd: workspaceRoot,
|
|
1484
|
-
encoding: "utf8",
|
|
1485
|
-
});
|
|
1486
|
-
updateStatus("", "none");
|
|
1487
|
-
logText(`✓ Message sent to ${getAgentLabel(targetAgent)}`);
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
updateStatus("", "none");
|
|
1490
|
-
const msg = err && err.message ? err.message : "unknown error";
|
|
1491
|
-
logText(`Failed to send message: ${msg}`);
|
|
1492
|
-
}
|
|
1493
|
-
targetAgent = null;
|
|
1494
|
-
selectedAgentIndex = -1;
|
|
1495
|
-
agentSelectionMode = false;
|
|
1496
|
-
setPrompt();
|
|
1497
|
-
renderDashboard();
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
const runtimeWorkspace = String((state && state.workspaceRoot) || workspaceRoot || process.cwd());
|
|
1502
|
-
const result = runSingleCommand(actualLine, runtimeWorkspace);
|
|
1503
|
-
if (result.kind === "empty") return;
|
|
1504
|
-
if (result.kind === "exit") {
|
|
1505
|
-
closeWithCode(0);
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
if (result.kind === "tool") {
|
|
1509
|
-
const payload = result.result && typeof result.result === "object" ? result.result : {};
|
|
1510
|
-
logToolHint({
|
|
1511
|
-
tool: result.tool,
|
|
1512
|
-
args: result.args,
|
|
1513
|
-
phase: payload.ok === false ? "error" : "end",
|
|
1514
|
-
error: payload.error || "",
|
|
1515
|
-
}, payload);
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
if (result.kind === "probe") {
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
if (result.kind === "help" || result.kind === "error") {
|
|
1522
|
-
logText(result.output || "");
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
if (result.kind === "ubus") {
|
|
1526
|
-
updateStatus("Checking bus messages...", "typing");
|
|
1527
|
-
const ubusResult = await runUbusCommand(state, {
|
|
1528
|
-
workspaceRoot,
|
|
1529
|
-
onMessageReceived: (msg) => {
|
|
1530
|
-
// Display the incoming message immediately
|
|
1531
|
-
const { extractAgentNickname } = require("./agent");
|
|
1532
|
-
const nickname = extractAgentNickname(msg.from) || msg.from;
|
|
1533
|
-
logText(`${nickname}: ${msg.task}`);
|
|
1534
|
-
},
|
|
1535
|
-
});
|
|
1536
|
-
updateStatus("", "none");
|
|
1537
|
-
if (!ubusResult.ok) {
|
|
1538
|
-
logText(`Error: ${ubusResult.error}`);
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
// Display only the replies (tasks were already shown via onMessageReceived)
|
|
1543
|
-
if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
|
|
1544
|
-
const { extractAgentNickname } = require("./agent");
|
|
1545
|
-
for (const exchange of ubusResult.messageExchanges) {
|
|
1546
|
-
const nickname = extractAgentNickname(exchange.from) || exchange.from;
|
|
1547
|
-
// Only show the reply since task was already displayed
|
|
1548
|
-
logText(`@${nickname} ${exchange.reply}`);
|
|
1549
|
-
}
|
|
1550
|
-
} else if (ubusResult.handled === 0) {
|
|
1551
|
-
logText("ubus: no pending messages.");
|
|
1552
|
-
}
|
|
1553
|
-
const persisted = persistSessionState(state);
|
|
1554
|
-
if (!persisted || persisted.ok === false) {
|
|
1555
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1556
|
-
}
|
|
1557
|
-
return;
|
|
1558
|
-
}
|
|
1559
|
-
if (result.kind === "resume") {
|
|
1560
|
-
const resumed = resumeSessionState(state, result.sessionId, workspaceRoot);
|
|
1561
|
-
if (!resumed.ok) {
|
|
1562
|
-
logText(`Error: ${resumed.error}`);
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
logText(`Resumed session ${resumed.sessionId} (${resumed.restoredMessages} messages).`);
|
|
1566
|
-
return;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
if (result.kind === "nl_bg") {
|
|
1570
|
-
backgroundSeq += 1;
|
|
1571
|
-
const jobId = `bg-${Date.now().toString(36)}-${backgroundSeq.toString(36)}`;
|
|
1572
|
-
const taskRecord = {
|
|
1573
|
-
id: jobId,
|
|
1574
|
-
task: result.task,
|
|
1575
|
-
status: "running",
|
|
1576
|
-
startedAt: Date.now(),
|
|
1577
|
-
summary: "",
|
|
1578
|
-
};
|
|
1579
|
-
backgroundTasks.set(jobId, taskRecord);
|
|
1580
|
-
updateStatus("", "none");
|
|
1581
|
-
logText(`[${jobId}] started in background.`);
|
|
1582
|
-
|
|
1583
|
-
const bgState = {
|
|
1584
|
-
workspaceRoot: state.workspaceRoot,
|
|
1585
|
-
provider: state.provider,
|
|
1586
|
-
model: state.model,
|
|
1587
|
-
engine: state.engine,
|
|
1588
|
-
context: state.context,
|
|
1589
|
-
nlMessages: Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [],
|
|
1590
|
-
sessionId: "",
|
|
1591
|
-
timeoutMs: state.timeoutMs,
|
|
1592
|
-
jsonOutput: false,
|
|
1593
|
-
};
|
|
1594
|
-
|
|
1595
|
-
Promise.resolve()
|
|
1596
|
-
.then(() => runNaturalLanguageTask(result.task, bgState))
|
|
1597
|
-
.then((nlResult) => {
|
|
1598
|
-
taskRecord.status = nlResult && nlResult.ok ? "done" : "failed";
|
|
1599
|
-
taskRecord.finishedAt = Date.now();
|
|
1600
|
-
taskRecord.summary = String(formatNlResult(nlResult, false) || "").trim();
|
|
1601
|
-
const title = taskRecord.status === "done" ? "done" : "failed";
|
|
1602
|
-
logText(`[${jobId}] ${title}: ${taskRecord.summary || "no summary"}`);
|
|
1603
|
-
})
|
|
1604
|
-
.catch((err) => {
|
|
1605
|
-
taskRecord.status = "failed";
|
|
1606
|
-
taskRecord.finishedAt = Date.now();
|
|
1607
|
-
taskRecord.summary = err && err.message ? String(err.message) : "background task failed";
|
|
1608
|
-
logText(`[${jobId}] failed: ${taskRecord.summary}`);
|
|
1609
|
-
})
|
|
1610
|
-
.finally(() => {
|
|
1611
|
-
updateStatus("", "none");
|
|
1612
|
-
screen.render();
|
|
1613
|
-
});
|
|
1614
|
-
return;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
if (result.kind === "nl") {
|
|
1618
|
-
const abortController = new AbortController();
|
|
1619
|
-
const escapeStripper = createEscapeTagStripper();
|
|
1620
|
-
pendingTask = {
|
|
1621
|
-
abortController,
|
|
1622
|
-
startedAt: Date.now(),
|
|
1623
|
-
};
|
|
1624
|
-
const TOOL_LABELS = {
|
|
1625
|
-
read: "Reading file",
|
|
1626
|
-
write: "Writing file",
|
|
1627
|
-
edit: "Editing file",
|
|
1628
|
-
bash: "Running command",
|
|
1629
|
-
};
|
|
1630
|
-
const setNlStatus = (msg) => {
|
|
1631
|
-
updateStatus(msg, "thinking", {
|
|
1632
|
-
showTimer: true,
|
|
1633
|
-
startedAt: pendingTask.startedAt,
|
|
1634
|
-
});
|
|
1635
|
-
};
|
|
1636
|
-
setNlStatus("Waiting for model...");
|
|
1637
|
-
let streamState = null;
|
|
1638
|
-
let renderedToolLogCount = 0;
|
|
1639
|
-
let nlResult = null;
|
|
1640
|
-
try {
|
|
1641
|
-
nlResult = await runNaturalLanguageTask(result.task, state, {
|
|
1642
|
-
signal: abortController.signal,
|
|
1643
|
-
onPhase: (event) => {
|
|
1644
|
-
if (!event || typeof event !== "object") return;
|
|
1645
|
-
if (event.type === "request_start") {
|
|
1646
|
-
setNlStatus("Waiting for model...");
|
|
1647
|
-
} else if (event.type === "thinking_delta") {
|
|
1648
|
-
setNlStatus("Thinking...");
|
|
1649
|
-
} else if (event.type === "text_delta") {
|
|
1650
|
-
setNlStatus("Generating response...");
|
|
1651
|
-
} else if (event.type === "tool_request") {
|
|
1652
|
-
const label = TOOL_LABELS[String(event.name || "").toLowerCase()] || `Calling ${event.name}`;
|
|
1653
|
-
setNlStatus(`${label}...`);
|
|
1654
|
-
}
|
|
1655
|
-
},
|
|
1656
|
-
onDelta: (delta) => {
|
|
1657
|
-
const text = escapeStripper.write(String(delta || ""));
|
|
1658
|
-
if (!text) return;
|
|
1659
|
-
if (!streamState) {
|
|
1660
|
-
streamState = createNlStreamState();
|
|
1661
|
-
}
|
|
1662
|
-
appendNlStreamDelta(streamState, text);
|
|
1663
|
-
},
|
|
1664
|
-
onToolLog: (entry) => {
|
|
1665
|
-
renderedToolLogCount += 1;
|
|
1666
|
-
if (entry && entry.tool && entry.phase === "start") {
|
|
1667
|
-
const label = TOOL_LABELS[String(entry.tool || "").toLowerCase()] || `Calling ${entry.tool}`;
|
|
1668
|
-
setNlStatus(`${label}...`);
|
|
1669
|
-
}
|
|
1670
|
-
logToolHint(entry);
|
|
1671
|
-
},
|
|
1672
|
-
});
|
|
1673
|
-
const tail = escapeStripper.flush();
|
|
1674
|
-
if (tail) {
|
|
1675
|
-
if (!streamState) {
|
|
1676
|
-
streamState = createNlStreamState();
|
|
1677
|
-
}
|
|
1678
|
-
appendNlStreamDelta(streamState, tail);
|
|
1679
|
-
}
|
|
1680
|
-
let finalStreamInfo = { lastChar: "" };
|
|
1681
|
-
if (streamState) {
|
|
1682
|
-
finalStreamInfo = finalizeNlStream(streamState);
|
|
1683
|
-
}
|
|
1684
|
-
if (Array.isArray(nlResult && nlResult.logs) && nlResult.logs.length > renderedToolLogCount) {
|
|
1685
|
-
for (const entry of nlResult.logs.slice(renderedToolLogCount)) {
|
|
1686
|
-
logToolHint(entry);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
const streamed = Boolean(nlResult && nlResult.streamed);
|
|
1690
|
-
const hasVisibleStreamText = Boolean(
|
|
1691
|
-
streamState
|
|
1692
|
-
&& typeof streamState.full === "string"
|
|
1693
|
-
&& /[^\s]/.test(streamState.full)
|
|
1694
|
-
);
|
|
1695
|
-
const streamLastChar = nlResult && typeof nlResult.streamLastChar === "string"
|
|
1696
|
-
? nlResult.streamLastChar.slice(-1)
|
|
1697
|
-
: finalStreamInfo.lastChar;
|
|
1698
|
-
if (streamed && hasVisibleStreamText && streamLastChar !== "\n") {
|
|
1699
|
-
logBox.log("");
|
|
1700
|
-
screen.render();
|
|
1701
|
-
}
|
|
1702
|
-
const shouldSkipSummary = Boolean(streamed && nlResult && nlResult.ok && hasVisibleStreamText);
|
|
1703
|
-
if (!shouldSkipSummary) {
|
|
1704
|
-
logText(formatNlResult(nlResult, false));
|
|
1705
|
-
}
|
|
1706
|
-
const persisted = persistSessionState(state);
|
|
1707
|
-
if (!persisted || persisted.ok === false) {
|
|
1708
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1709
|
-
}
|
|
1710
|
-
} finally {
|
|
1711
|
-
pendingTask = null;
|
|
1712
|
-
updateStatus("", "none");
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
};
|
|
1716
|
-
|
|
1717
|
-
const submitInput = (value = "") => {
|
|
1718
|
-
const raw = String(value || "");
|
|
1719
|
-
const trimmed = raw.trim();
|
|
1720
|
-
input.setValue("");
|
|
1721
|
-
cursorPos = 0;
|
|
1722
|
-
resetPreferredCol();
|
|
1723
|
-
resizeInput();
|
|
1724
|
-
screen.render();
|
|
1725
|
-
agentSelectionMode = false;
|
|
1726
|
-
|
|
1727
|
-
if (trimmed) {
|
|
1728
|
-
inputHistory.push(trimmed);
|
|
1729
|
-
}
|
|
1730
|
-
historyIndex = inputHistory.length;
|
|
1731
|
-
|
|
1732
|
-
chain = chain
|
|
1733
|
-
.then(() => executeLine(raw))
|
|
1734
|
-
.catch((err) => {
|
|
1735
|
-
updateStatus("", "none");
|
|
1736
|
-
logText(`Error: ${err && err.message ? err.message : "agent loop failed"}`);
|
|
1737
|
-
})
|
|
1738
|
-
.finally(() => {
|
|
1739
|
-
if (closing) return;
|
|
1740
|
-
refreshAgents();
|
|
1741
|
-
setPrompt();
|
|
1742
|
-
renderDashboard();
|
|
1743
|
-
input.focus();
|
|
1744
|
-
screen.render();
|
|
1745
|
-
});
|
|
1746
|
-
};
|
|
1747
|
-
|
|
1748
|
-
input.key(["enter"], (ch, key) => {
|
|
1749
|
-
if (skipSubmitKeyRef && (!key || skipSubmitKeyRef === key || skipSubmitKeyRef === true)) {
|
|
1750
|
-
skipSubmitKeyRef = null;
|
|
1751
|
-
return false;
|
|
1752
|
-
}
|
|
1753
|
-
submitInput(input.getValue());
|
|
1754
|
-
return false;
|
|
1755
|
-
});
|
|
1756
|
-
input.key(["up"], () => {
|
|
1757
|
-
const currentValue = input.getValue();
|
|
1758
|
-
if (shouldClearAgentSelectionOnUp({
|
|
1759
|
-
agentSelectionMode,
|
|
1760
|
-
inputValue: currentValue,
|
|
1761
|
-
})) {
|
|
1762
|
-
targetAgent = null;
|
|
1763
|
-
selectedAgentIndex = -1;
|
|
1764
|
-
agentSelectionMode = false;
|
|
1765
|
-
setPrompt();
|
|
1766
|
-
renderDashboard();
|
|
1767
|
-
// Target selection cleared - removed redundant log
|
|
1768
|
-
input.focus();
|
|
1769
|
-
return false;
|
|
1770
|
-
}
|
|
1771
|
-
if (currentValue) {
|
|
1772
|
-
const move = moveCursorVertically({
|
|
1773
|
-
cursorPos,
|
|
1774
|
-
inputValue: currentValue,
|
|
1775
|
-
width: getWrapWidth(),
|
|
1776
|
-
direction: "up",
|
|
1777
|
-
preferredCol,
|
|
1778
|
-
strWidth: (v) => input.strWidth(v),
|
|
1779
|
-
});
|
|
1780
|
-
preferredCol = move.preferredCol;
|
|
1781
|
-
if (move.moved) {
|
|
1782
|
-
setCursor(move.nextCursorPos);
|
|
1783
|
-
return false;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
if (inputHistory.length === 0) return false;
|
|
1787
|
-
historyIndex = Math.max(0, historyIndex - 1);
|
|
1788
|
-
setInputValue(inputHistory[historyIndex] || "");
|
|
1789
|
-
return false;
|
|
1790
|
-
});
|
|
1791
|
-
input.key(["down"], () => {
|
|
1792
|
-
const currentValue = input.getValue();
|
|
1793
|
-
if (currentValue) {
|
|
1794
|
-
const move = moveCursorVertically({
|
|
1795
|
-
cursorPos,
|
|
1796
|
-
inputValue: currentValue,
|
|
1797
|
-
width: getWrapWidth(),
|
|
1798
|
-
direction: "down",
|
|
1799
|
-
preferredCol,
|
|
1800
|
-
strWidth: (v) => input.strWidth(v),
|
|
1801
|
-
});
|
|
1802
|
-
preferredCol = move.preferredCol;
|
|
1803
|
-
if (move.moved) {
|
|
1804
|
-
setCursor(move.nextCursorPos);
|
|
1805
|
-
return false;
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
const historyTransition = resolveHistoryDownTransition({
|
|
1809
|
-
inputHistory,
|
|
1810
|
-
historyIndex,
|
|
1811
|
-
currentValue,
|
|
1812
|
-
});
|
|
1813
|
-
if (historyTransition.moved) {
|
|
1814
|
-
historyIndex = historyTransition.nextHistoryIndex;
|
|
1815
|
-
setInputValue(historyTransition.nextValue);
|
|
1816
|
-
return false;
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
if (shouldEnterAgentSelection(currentValue)) {
|
|
1820
|
-
const cachedAgents = Array.isArray(activeAgents) ? activeAgents.slice() : [];
|
|
1821
|
-
const cachedMeta = activeAgentMetaMap instanceof Map ? new Map(activeAgentMetaMap) : new Map();
|
|
1822
|
-
if (!agentSelectionMode) {
|
|
1823
|
-
refreshAgents();
|
|
1824
|
-
}
|
|
1825
|
-
if (!agentSelectionMode && activeAgents.length === 0 && cachedAgents.length > 0) {
|
|
1826
|
-
activeAgents = cachedAgents;
|
|
1827
|
-
activeAgentMetaMap = cachedMeta;
|
|
1828
|
-
}
|
|
1829
|
-
const decision = resolveAgentSelectionOnDown({
|
|
1830
|
-
agentSelectionMode,
|
|
1831
|
-
selectedAgentIndex,
|
|
1832
|
-
totalAgents: activeAgents.length,
|
|
1833
|
-
});
|
|
1834
|
-
if (decision.action === "enter") {
|
|
1835
|
-
selectedAgentIndex = decision.index;
|
|
1836
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1837
|
-
agentSelectionMode = true;
|
|
1838
|
-
setPrompt();
|
|
1839
|
-
renderDashboard();
|
|
1840
|
-
// Removed redundant target selection log
|
|
1841
|
-
input.focus();
|
|
1842
|
-
return false;
|
|
1843
|
-
}
|
|
1844
|
-
if (decision.action === "hold") {
|
|
1845
|
-
return false;
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
return false;
|
|
1849
|
-
});
|
|
1850
|
-
input.key(["left"], () => {
|
|
1851
|
-
const currentValue = input.getValue();
|
|
1852
|
-
if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
|
|
1853
|
-
if (activeAgents.length === 0) refreshAgents();
|
|
1854
|
-
if (activeAgents.length === 0) return false;
|
|
1855
|
-
selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "left");
|
|
1856
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1857
|
-
setPrompt();
|
|
1858
|
-
renderDashboard();
|
|
1859
|
-
// Removed redundant target switch log
|
|
1860
|
-
input.focus();
|
|
1861
|
-
return false;
|
|
1862
|
-
}
|
|
1863
|
-
const next = moveCursorHorizontally(cursorPos, currentValue, "left");
|
|
1864
|
-
if (next !== cursorPos) {
|
|
1865
|
-
setCursor(next);
|
|
1866
|
-
resetPreferredCol();
|
|
1867
|
-
}
|
|
1868
|
-
return false;
|
|
1869
|
-
});
|
|
1870
|
-
input.key(["right"], () => {
|
|
1871
|
-
const currentValue = input.getValue();
|
|
1872
|
-
if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
|
|
1873
|
-
if (activeAgents.length === 0) refreshAgents();
|
|
1874
|
-
if (activeAgents.length === 0) return false;
|
|
1875
|
-
selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "right");
|
|
1876
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1877
|
-
setPrompt();
|
|
1878
|
-
renderDashboard();
|
|
1879
|
-
// Removed redundant target switch log
|
|
1880
|
-
input.focus();
|
|
1881
|
-
return false;
|
|
1882
|
-
}
|
|
1883
|
-
const next = moveCursorHorizontally(cursorPos, currentValue, "right");
|
|
1884
|
-
if (next !== cursorPos) {
|
|
1885
|
-
setCursor(next);
|
|
1886
|
-
resetPreferredCol();
|
|
1887
|
-
}
|
|
1888
|
-
return false;
|
|
1889
|
-
});
|
|
1890
|
-
|
|
1891
|
-
screen.key(["tab"], () => {
|
|
1892
|
-
refreshAgents();
|
|
1893
|
-
if (activeAgents.length === 0) return;
|
|
1894
|
-
if (selectedAgentIndex < 0) selectedAgentIndex = 0;
|
|
1895
|
-
else selectedAgentIndex = (selectedAgentIndex + 1) % activeAgents.length;
|
|
1896
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1897
|
-
agentSelectionMode = true;
|
|
1898
|
-
setPrompt();
|
|
1899
|
-
renderDashboard();
|
|
1900
|
-
// Removed redundant target switch log
|
|
1901
|
-
input.focus();
|
|
1902
|
-
});
|
|
1903
|
-
screen.key(["S-tab"], () => {
|
|
1904
|
-
refreshAgents();
|
|
1905
|
-
if (activeAgents.length === 0) return;
|
|
1906
|
-
if (selectedAgentIndex < 0) selectedAgentIndex = 0;
|
|
1907
|
-
else selectedAgentIndex = (selectedAgentIndex - 1 + activeAgents.length) % activeAgents.length;
|
|
1908
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1909
|
-
agentSelectionMode = true;
|
|
1910
|
-
setPrompt();
|
|
1911
|
-
renderDashboard();
|
|
1912
|
-
// Removed redundant target switch log
|
|
1913
|
-
input.focus();
|
|
1914
|
-
});
|
|
1915
|
-
screen.key(["C-o"], () => {
|
|
1916
|
-
if (!lastMergedToolGroup || lastMergedToolGroup.expanded) return;
|
|
1917
|
-
if (!Array.isArray(lastMergedToolGroup.entries) || lastMergedToolGroup.entries.length < 2) return;
|
|
1918
|
-
const lines = buildMergedToolExpandedLines(lastMergedToolGroup.entries);
|
|
1919
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
1920
|
-
const branch = i === lines.length - 1 ? "└" : "│";
|
|
1921
|
-
logBox.log(`{gray-fg}${branch}{/gray-fg} ${escapeBlessed(lines[i])}`);
|
|
1922
|
-
}
|
|
1923
|
-
lastMergedToolGroup.expanded = true;
|
|
1924
|
-
if (activeToolMerge && activeToolMerge.id === lastMergedToolGroup.id) {
|
|
1925
|
-
activeToolMerge = null;
|
|
1926
|
-
}
|
|
1927
|
-
screen.render();
|
|
1928
|
-
});
|
|
1929
|
-
screen.key(["pageup"], () => {
|
|
1930
|
-
logBox.scroll(-Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1931
|
-
screen.render();
|
|
1932
|
-
});
|
|
1933
|
-
screen.key(["pagedown"], () => {
|
|
1934
|
-
logBox.scroll(Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1935
|
-
screen.render();
|
|
1936
|
-
});
|
|
1937
|
-
input.key(["escape"], () => {
|
|
1938
|
-
if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
|
|
1939
|
-
try {
|
|
1940
|
-
pendingTask.abortController.abort();
|
|
1941
|
-
} catch {
|
|
1942
|
-
// ignore
|
|
1943
|
-
}
|
|
1944
|
-
logControlAction("Cancellation requested. Stopping the current task...");
|
|
1945
|
-
updateStatus("Cancelling...", "waiting", {
|
|
1946
|
-
showTimer: true,
|
|
1947
|
-
startedAt: pendingTask.startedAt,
|
|
1948
|
-
});
|
|
1949
|
-
return false;
|
|
1950
|
-
}
|
|
1951
|
-
targetAgent = null;
|
|
1952
|
-
selectedAgentIndex = -1;
|
|
1953
|
-
agentSelectionMode = false;
|
|
1954
|
-
setInputValue("");
|
|
1955
|
-
setPrompt();
|
|
1956
|
-
renderDashboard();
|
|
1957
|
-
// Target selection cleared - removed redundant log
|
|
1958
|
-
input.focus();
|
|
1959
|
-
return false;
|
|
1960
|
-
});
|
|
1961
|
-
screen.key(["C-c"], () => closeWithCode(0));
|
|
1962
|
-
screen.on("resize", () => {
|
|
1963
|
-
renderDashboard();
|
|
1964
|
-
screen.render();
|
|
1965
|
-
});
|
|
1966
|
-
|
|
1967
|
-
const nickname = process.env.UFOO_NICKNAME || "";
|
|
1968
|
-
const subscriberId = currentSubscriberId;
|
|
1969
|
-
const agentId = subscriberId.includes(":") ? subscriberId.split(":")[1] : "";
|
|
1970
|
-
const bannerLines = buildUcodeBannerBlessedLines({
|
|
1971
|
-
model: state.model || process.env.UFOO_UCODE_MODEL || "",
|
|
1972
|
-
engine: state.engine || "ufoo-core",
|
|
1973
|
-
nickname,
|
|
1974
|
-
agentId,
|
|
1975
|
-
workspaceRoot,
|
|
1976
|
-
sessionId: state.sessionId || "",
|
|
1977
|
-
width: (stdout && stdout.columns) || 80,
|
|
1978
|
-
});
|
|
1979
|
-
for (const line of bannerLines) {
|
|
1980
|
-
logBox.log(String(line || ""));
|
|
1981
|
-
}
|
|
1982
|
-
logBox.log("");
|
|
3
|
+
const {
|
|
4
|
+
STATUS_INDICATORS,
|
|
5
|
+
StreamBuffer,
|
|
6
|
+
UCODE_BANNER_LINES,
|
|
7
|
+
UCODE_VERSION,
|
|
8
|
+
buildMergedToolExpandedLines,
|
|
9
|
+
buildMergedToolSummaryText,
|
|
10
|
+
buildUcodeBannerLines,
|
|
11
|
+
clampCursorPos,
|
|
12
|
+
createEscapeTagStripper,
|
|
13
|
+
cycleAgentSelectionIndex,
|
|
14
|
+
deleteWordBeforeCursor,
|
|
15
|
+
displayCellWidth,
|
|
16
|
+
filterSelectableAgents,
|
|
17
|
+
findLogicalLineEnd,
|
|
18
|
+
findLogicalLineStart,
|
|
19
|
+
formatPendingElapsed,
|
|
20
|
+
loadActiveAgents,
|
|
21
|
+
moveCursorByWord,
|
|
22
|
+
moveCursorHorizontally,
|
|
23
|
+
moveCursorToVisualLineBoundary,
|
|
24
|
+
moveCursorVertically,
|
|
25
|
+
normalizeBashToolCommand,
|
|
26
|
+
normalizeToolMergeEntry,
|
|
27
|
+
parseActiveAgentsFromBusStatus,
|
|
28
|
+
renderLogLinesWithMarkdown,
|
|
29
|
+
resolveAgentSelectionOnDown,
|
|
30
|
+
resolveHistoryDownTransition,
|
|
31
|
+
shouldClearAgentSelectionOnUp,
|
|
32
|
+
shouldEnterAgentSelection,
|
|
33
|
+
shouldUseUcodeTui,
|
|
34
|
+
stripLeakedEscapeTags,
|
|
35
|
+
} = fmt;
|
|
1983
36
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
renderDashboard();
|
|
1988
|
-
if (autoBusEnabled) {
|
|
1989
|
-
autoBusTimer = setInterval(() => {
|
|
1990
|
-
scheduleAutoBus();
|
|
1991
|
-
}, 800);
|
|
1992
|
-
scheduleAutoBus();
|
|
1993
|
-
}
|
|
1994
|
-
input.focus();
|
|
1995
|
-
screen.render();
|
|
1996
|
-
});
|
|
37
|
+
function runUcodeTui(props = {}) {
|
|
38
|
+
const { runUcodeInkTui } = require("../ui/ink/UcodeApp");
|
|
39
|
+
return runUcodeInkTui(props);
|
|
1997
40
|
}
|
|
1998
41
|
|
|
1999
42
|
module.exports = {
|
|
43
|
+
STATUS_INDICATORS,
|
|
2000
44
|
UCODE_BANNER_LINES,
|
|
2001
45
|
UCODE_VERSION,
|
|
2002
46
|
StreamBuffer,
|
|
2003
47
|
displayCellWidth,
|
|
2004
|
-
resolveLogContentWidth,
|
|
2005
|
-
formatHighlightedUserInput,
|
|
2006
48
|
buildUcodeBannerLines,
|
|
2007
|
-
buildUcodeBannerBlessedLines,
|
|
2008
49
|
parseActiveAgentsFromBusStatus,
|
|
2009
50
|
shouldUseUcodeTui,
|
|
2010
51
|
renderLogLinesWithMarkdown,
|
|
@@ -2029,5 +70,6 @@ module.exports = {
|
|
|
2029
70
|
normalizeToolMergeEntry,
|
|
2030
71
|
buildMergedToolSummaryText,
|
|
2031
72
|
buildMergedToolExpandedLines,
|
|
73
|
+
loadActiveAgents,
|
|
2032
74
|
runUcodeTui,
|
|
2033
75
|
};
|