u-foo 2.3.32 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -213
- package/README.zh-CN.md +151 -197
- package/SKILLS/ufoo/SKILL.md +8 -8
- package/bin/uagy.js +69 -0
- package/bin/uclaude.js +2 -2
- package/bin/ucode.js +4 -4
- package/bin/ucodex.js +2 -2
- package/bin/ufoo.js +5 -23
- package/modules/AGENTS.template.md +1 -1
- package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
- package/package.json +5 -5
- package/scripts/chat-app-smoke.js +1 -1
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +1 -1
- package/scripts/ink-smoke.js +1 -1
- package/scripts/ucode-app-smoke.js +1 -1
- package/src/{agent → agents/activity}/activityDetector.js +39 -2
- package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
- package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
- package/src/{agent → agents/activity}/activityTracker.js +1 -1
- package/src/agents/activity/index.js +8 -0
- package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
- package/src/agents/controller/index.js +8 -0
- package/src/{agent → agents/controller}/loopObservability.js +2 -2
- package/src/{agent → agents/controller}/loopRuntime.js +1 -1
- package/src/{agent → agents/controller}/ufooAgent.js +9 -9
- package/src/agents/index.js +10 -0
- package/src/agents/internal/index.js +3 -0
- package/src/{agent → agents/internal}/internalRunner.js +45 -22
- package/src/agents/launch/agyConversation.js +159 -0
- package/src/agents/launch/index.js +12 -0
- package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
- package/src/{agent → agents/launch}/launcher.js +64 -21
- package/src/{agent → agents/launch}/notifier.js +23 -12
- package/src/{agent → agents/launch}/ptyRunner.js +44 -12
- package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
- package/src/{agent → agents/launch}/publisherRouting.js +1 -1
- package/src/{agent → agents/launch}/readyDetector.js +23 -0
- package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
- package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
- package/src/agents/prompts/index.js +8 -0
- package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
- package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
- package/src/agents/providers/index.js +13 -0
- package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
- package/src/{chat → app/chat}/agentSockets.js +1 -1
- package/src/{chat → app/chat}/commandExecutor.js +50 -26
- package/src/{chat → app/chat}/commands.js +119 -5
- package/src/{chat → app/chat}/daemonConnection.js +1 -1
- package/src/{chat → app/chat}/daemonMessageRouter.js +45 -3
- package/src/{chat → app/chat}/dashboardView.js +2 -1
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +4 -13
- package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
- package/src/app/chat/multiWindow/index.js +268 -0
- package/src/app/chat/multiWindow/paneLayout.js +84 -0
- package/src/app/chat/multiWindow/paneManager.js +299 -0
- package/src/app/chat/multiWindow/renderer.js +384 -0
- package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
- package/src/{chat → app/chat}/transport.js +1 -1
- package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
- package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
- package/src/{init/index.js → app/cli/features/init.js} +14 -32
- package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
- package/src/app/cli/index.js +9 -0
- package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
- package/src/{cli.js → app/cli/run.js} +59 -57
- package/src/app/index.js +6 -0
- package/src/code/agent.js +10 -9
- package/src/code/index.js +2 -0
- package/src/code/launcher/index.js +9 -0
- package/src/{agent → code/launcher}/ucode.js +7 -8
- package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
- package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
- package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
- package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
- package/src/code/nativeRunner.js +4 -4
- package/src/code/tui.js +3 -1454
- package/src/config.js +15 -2
- package/src/{bus → coordination/bus}/activate.js +2 -2
- package/src/{bus → coordination/bus}/daemon.js +15 -5
- package/src/coordination/bus/envelope.js +173 -0
- package/src/{bus → coordination/bus}/index.js +7 -3
- package/src/{bus → coordination/bus}/inject.js +11 -3
- package/src/{bus → coordination/bus}/message.js +1 -1
- package/src/coordination/bus/messageMeta.js +130 -0
- package/src/coordination/bus/promptEnvelope.js +65 -0
- package/src/{bus → coordination/bus}/shake.js +1 -1
- package/src/{bus → coordination/bus}/store.js +3 -3
- package/src/{bus → coordination/bus}/subscriber.js +2 -2
- package/src/{bus → coordination/bus}/utils.js +2 -2
- package/src/{history → coordination/history}/inputTimeline.js +5 -5
- package/src/coordination/index.js +10 -0
- package/src/{memory → coordination/memory}/historySearch.js +1 -1
- package/src/{memory → coordination/memory}/index.js +3 -3
- package/src/{report → coordination/report}/store.js +2 -2
- package/src/{status → coordination/status}/index.js +3 -3
- package/src/online/bridge.js +2 -2
- package/src/{controller → orchestration/controller}/flags.js +1 -1
- package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
- package/src/orchestration/controller/index.js +10 -0
- package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
- package/src/orchestration/groups/bootstrap.js +3 -0
- package/src/orchestration/groups/index.js +10 -0
- package/src/orchestration/groups/promptProfiles.js +3 -0
- package/src/{group → orchestration/groups}/templates.js +1 -1
- package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
- package/src/orchestration/index.js +7 -0
- package/src/orchestration/solo/index.js +3 -0
- package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
- package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
- package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
- package/src/{daemon → runtime/daemon}/index.js +105 -53
- package/src/{daemon → runtime/daemon}/ipcServer.js +1 -1
- package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
- package/src/{daemon → runtime/daemon}/ops.js +48 -61
- package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
- package/src/{daemon → runtime/daemon}/promptRequest.js +7 -7
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +4 -4
- package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
- package/src/{daemon → runtime/daemon}/status.js +5 -5
- package/src/runtime/index.js +10 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
- package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
- package/src/tools/handlers/common.js +1 -1
- package/src/tools/handlers/listAgents.js +1 -1
- package/src/tools/handlers/memory.js +3 -3
- package/src/tools/handlers/readBusSummary.js +1 -1
- package/src/tools/handlers/readOpenDecisions.js +1 -1
- package/src/tools/handlers/readProjectRegistry.js +1 -1
- package/src/tools/handlers/readPromptHistory.js +2 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/MIGRATION.md +42 -88
- package/src/ui/format/index.js +5 -28
- package/src/ui/index.js +1 -1
- package/src/ui/{components → ink}/ChatApp.js +812 -88
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/{components → ink}/MultilineInput.js +230 -5
- package/src/ui/{components → ink}/UcodeApp.js +16 -7
- package/src/ui/{components → ink}/agentMirror.js +24 -19
- package/src/ui/{components → ink}/chatReducer.js +29 -7
- package/src/bus/messageMeta.js +0 -52
- package/src/chat/agentViewController.js +0 -1072
- package/src/chat/chatLogController.js +0 -138
- package/src/chat/completionController.js +0 -533
- package/src/chat/dashboardKeyController.js +0 -533
- package/src/chat/index.js +0 -2222
- package/src/chat/inputHistoryController.js +0 -135
- package/src/chat/inputListenerController.js +0 -470
- package/src/chat/layout.js +0 -186
- package/src/chat/pasteController.js +0 -81
- package/src/chat/statusLineController.js +0 -223
- package/src/chat/streamTracker.js +0 -156
- package/src/code/config +0 -0
- package/src/daemon/providerSessions.js +0 -488
- package/src/terminal/adapters/internalPtyAdapter.js +0 -42
- package/src/ui/components/DashboardBar.js +0 -417
- /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
- /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
- /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
- /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
- /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
- /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
- /package/src/{agent → agents/providers}/credentials/index.js +0 -0
- /package/src/{chat → app/chat}/agentBar.js +0 -0
- /package/src/{chat → app/chat}/agentDirectory.js +0 -0
- /package/src/{chat → app/chat}/cronScheduler.js +0 -0
- /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
- /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
- /package/src/{chat → app/chat}/daemonTransport.js +0 -0
- /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.js +0 -0
- /package/src/{chat → app/chat}/projectCloseController.js +0 -0
- /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
- /package/src/{chat → app/chat}/settingsController.js +0 -0
- /package/src/{chat → app/chat}/shellCommand.js +0 -0
- /package/src/{chat → app/chat}/text.js +0 -0
- /package/src/{chat → app/chat}/transientAgentState.js +0 -0
- /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
- /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
- /package/src/{bus → coordination/bus}/nickname.js +0 -0
- /package/src/{bus → coordination/bus}/queue.js +0 -0
- /package/src/{context → coordination/context}/decisions.js +0 -0
- /package/src/{context → coordination/context}/doctor.js +0 -0
- /package/src/{context → coordination/context}/index.js +0 -0
- /package/src/{context → coordination/context}/sync.js +0 -0
- /package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +0 -0
- /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
- /package/src/{ufoo → coordination/state}/paths.js +0 -0
- /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
- /package/src/{group → orchestration/groups}/diagram.js +0 -0
- /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
- /package/src/{solo → orchestration/solo}/commands.js +0 -0
- /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
- /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
- /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
- /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
- /package/src/{utils → runtime/process}/nodeExecutable.js +0 -0
- /package/src/{projects → runtime/projects}/identity.js +0 -0
- /package/src/{projects → runtime/projects}/index.js +0 -0
- /package/src/{projects → runtime/projects}/projectId.js +0 -0
- /package/src/{projects → runtime/projects}/runtimes.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/detect.js +0 -0
- /package/src/{terminal → runtime/terminal}/index.js +0 -0
- /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
- /package/src/{utils → ui/format}/banner.js +0 -0
- /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
- /package/src/ui/{components → ink}/InkDemo.js +0 -0
package/src/code/tui.js
CHANGED
|
@@ -16,7 +16,6 @@ const {
|
|
|
16
16
|
filterSelectableAgents,
|
|
17
17
|
findLogicalLineEnd,
|
|
18
18
|
findLogicalLineStart,
|
|
19
|
-
formatHighlightedUserInput,
|
|
20
19
|
formatPendingElapsed,
|
|
21
20
|
loadActiveAgents,
|
|
22
21
|
moveCursorByWord,
|
|
@@ -24,7 +23,6 @@ const {
|
|
|
24
23
|
moveCursorToVisualLineBoundary,
|
|
25
24
|
moveCursorVertically,
|
|
26
25
|
normalizeBashToolCommand,
|
|
27
|
-
normalizeModelLabel,
|
|
28
26
|
normalizeToolMergeEntry,
|
|
29
27
|
parseActiveAgentsFromBusStatus,
|
|
30
28
|
renderLogLinesWithMarkdown,
|
|
@@ -36,1468 +34,18 @@ const {
|
|
|
36
34
|
stripLeakedEscapeTags,
|
|
37
35
|
} = fmt;
|
|
38
36
|
|
|
39
|
-
function safeRead(getter, fallback = undefined) {
|
|
40
|
-
try {
|
|
41
|
-
return getter();
|
|
42
|
-
} catch {
|
|
43
|
-
return fallback;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function resolveLogContentWidth({ logBox = null, screen = null, fallback = 80 } = {}) {
|
|
48
|
-
const coords = safeRead(() => logBox && typeof logBox._getCoords === "function" ? logBox._getCoords() : null, null);
|
|
49
|
-
if (coords && Number.isFinite(coords.xl) && Number.isFinite(coords.xi)) {
|
|
50
|
-
return Math.max(1, coords.xl - coords.xi);
|
|
51
|
-
}
|
|
52
|
-
const width = safeRead(() => logBox && logBox.width, null);
|
|
53
|
-
if (typeof width === "number") return Math.max(1, width);
|
|
54
|
-
const screenWidth = safeRead(() => screen && screen.width, null);
|
|
55
|
-
if (typeof screenWidth === "number") return Math.max(1, screenWidth);
|
|
56
|
-
const screenCols = safeRead(() => screen && screen.cols, null);
|
|
57
|
-
if (typeof screenCols === "number") return Math.max(1, screenCols);
|
|
58
|
-
return Math.max(1, fallback);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function escapeBlessedLiteral(text) {
|
|
62
|
-
const raw = String(text == null ? "" : text);
|
|
63
|
-
const safe = raw.replace(/\{\/escape\}/g, "{open}/escape{close}");
|
|
64
|
-
return `{escape}${safe}{/escape}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function buildUcodeBannerBlessedLines({
|
|
68
|
-
model = "",
|
|
69
|
-
engine = "ufoo-core",
|
|
70
|
-
nickname = "",
|
|
71
|
-
agentId = "",
|
|
72
|
-
workspaceRoot = "",
|
|
73
|
-
sessionId = "",
|
|
74
|
-
width = 0,
|
|
75
|
-
} = {}) {
|
|
76
|
-
const modelLabel = normalizeModelLabel(model);
|
|
77
|
-
void width;
|
|
78
|
-
void engine;
|
|
79
|
-
void nickname;
|
|
80
|
-
void agentId;
|
|
81
|
-
|
|
82
|
-
const path = require("path");
|
|
83
|
-
const os = require("os");
|
|
84
|
-
const currentDir = workspaceRoot || process.cwd();
|
|
85
|
-
const homeDir = os.homedir();
|
|
86
|
-
|
|
87
|
-
let shortPath = currentDir;
|
|
88
|
-
if (currentDir.startsWith(homeDir)) {
|
|
89
|
-
shortPath = currentDir.replace(homeDir, "~");
|
|
90
|
-
}
|
|
91
|
-
shortPath = path.normalize(shortPath);
|
|
92
|
-
|
|
93
|
-
const logoLines = UCODE_BANNER_LINES.map(
|
|
94
|
-
(line) => `{cyan-fg}${escapeBlessedLiteral(line)}{/cyan-fg}`
|
|
95
|
-
);
|
|
96
|
-
const infoLines = [
|
|
97
|
-
`{gray-fg}Version:{/gray-fg} {cyan-fg}{bold}${escapeBlessedLiteral(UCODE_VERSION)}{/bold}{/cyan-fg}`,
|
|
98
|
-
`{gray-fg}Model:{/gray-fg} {yellow-fg}${escapeBlessedLiteral(modelLabel)}{/yellow-fg}`,
|
|
99
|
-
`{gray-fg}Dictionary:{/gray-fg} {gray-fg}${escapeBlessedLiteral(shortPath)}{/gray-fg}`,
|
|
100
|
-
];
|
|
101
|
-
const normalizedSessionId = String(sessionId || "").trim();
|
|
102
|
-
if (normalizedSessionId) {
|
|
103
|
-
infoLines.push(`{gray-fg}Session:{/gray-fg} {gray-fg}${escapeBlessedLiteral(normalizedSessionId)}{/gray-fg}`);
|
|
104
|
-
}
|
|
105
|
-
const logoPadding = " ".repeat(
|
|
106
|
-
UCODE_BANNER_LINES.reduce((max, line) => Math.max(max, String(line || "").length), 0)
|
|
107
|
-
);
|
|
108
|
-
const rows = Math.max(logoLines.length, infoLines.length);
|
|
109
|
-
|
|
110
|
-
return Array.from({ length: rows }, (_, index) => {
|
|
111
|
-
const logoLine = logoLines[index] || logoPadding;
|
|
112
|
-
const info = infoLines[index] || "";
|
|
113
|
-
return ` ${logoLine} ${info}`;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
37
|
function runUcodeTui(props = {}) {
|
|
118
|
-
|
|
119
|
-
return runUcodeBlessedTui(props);
|
|
120
|
-
}
|
|
121
|
-
const { runUcodeInkTui } = require("../ui/components/UcodeApp");
|
|
38
|
+
const { runUcodeInkTui } = require("../ui/ink/UcodeApp");
|
|
122
39
|
return runUcodeInkTui(props);
|
|
123
40
|
}
|
|
124
41
|
|
|
125
|
-
function runUcodeBlessedTui({
|
|
126
|
-
stdin = process.stdin,
|
|
127
|
-
stdout = process.stdout,
|
|
128
|
-
runSingleCommand = () => ({ kind: "empty" }),
|
|
129
|
-
runNaturalLanguageTask = async () => ({ ok: true, summary: "ok" }),
|
|
130
|
-
runUbusCommand = async () => ({ ok: false, error: "ubus unsupported", summary: "" }),
|
|
131
|
-
formatNlResult = () => "ok",
|
|
132
|
-
workspaceRoot = process.cwd(),
|
|
133
|
-
state = {},
|
|
134
|
-
resumeSessionState = () => ({ ok: false, error: "resume unsupported", sessionId: "", restoredMessages: 0 }),
|
|
135
|
-
persistSessionState = () => ({ ok: true }),
|
|
136
|
-
autoBus = {},
|
|
137
|
-
} = {}) {
|
|
138
|
-
return new Promise((resolve) => {
|
|
139
|
-
const blessed = require("blessed");
|
|
140
|
-
const { execFileSync } = require("child_process");
|
|
141
|
-
const { createChatLayout } = require("../chat/layout");
|
|
142
|
-
const { computeDashboardContent } = require("../chat/dashboardView");
|
|
143
|
-
const { escapeBlessed, stripBlessedTags } = require("../chat/text");
|
|
144
|
-
const currentSubscriberId = String(process.env.UFOO_SUBSCRIBER_ID || "").trim();
|
|
145
|
-
const autoBusEnabled = Boolean(autoBus && autoBus.enabled);
|
|
146
|
-
const autoBusSubscriberId = String((autoBus && autoBus.subscriberId) || currentSubscriberId || "").trim();
|
|
147
|
-
const getAutoBusPendingCount = typeof (autoBus && autoBus.getPendingCount) === "function"
|
|
148
|
-
? autoBus.getPendingCount
|
|
149
|
-
: () => 0;
|
|
150
|
-
|
|
151
|
-
let closing = false;
|
|
152
|
-
let chain = Promise.resolve();
|
|
153
|
-
let statusInterval = null;
|
|
154
|
-
let statusIndex = 0;
|
|
155
|
-
let activeAgents = [];
|
|
156
|
-
let activeAgentMetaMap = new Map();
|
|
157
|
-
let targetAgent = null;
|
|
158
|
-
let selectedAgentIndex = -1;
|
|
159
|
-
let agentListWindowStart = 0;
|
|
160
|
-
let agentSelectionMode = false;
|
|
161
|
-
let pendingTask = null;
|
|
162
|
-
const backgroundTasks = new Map();
|
|
163
|
-
let backgroundSeq = 0;
|
|
164
|
-
const logRenderState = { inCodeBlock: false };
|
|
165
|
-
const inputHistory = [];
|
|
166
|
-
let historyIndex = -1;
|
|
167
|
-
let activeToolMerge = null;
|
|
168
|
-
let lastMergedToolGroup = null;
|
|
169
|
-
let toolMergeId = 0;
|
|
170
|
-
let cursorPos = 0;
|
|
171
|
-
let preferredCol = null;
|
|
172
|
-
let currentInputHeight = 4;
|
|
173
|
-
const MIN_INPUT_CONTENT_HEIGHT = 1;
|
|
174
|
-
const MAX_INPUT_CONTENT_HEIGHT = 8;
|
|
175
|
-
const DASHBOARD_HEIGHT = 1;
|
|
176
|
-
let autoBusTimer = null;
|
|
177
|
-
let autoBusQueued = false;
|
|
178
|
-
let autoBusError = "";
|
|
179
|
-
const inputMath = require("../chat/inputMath");
|
|
180
|
-
|
|
181
|
-
const {
|
|
182
|
-
screen,
|
|
183
|
-
logBox,
|
|
184
|
-
statusLine,
|
|
185
|
-
completionPanel,
|
|
186
|
-
dashboard,
|
|
187
|
-
inputTopLine,
|
|
188
|
-
promptBox,
|
|
189
|
-
input,
|
|
190
|
-
} = createChatLayout({
|
|
191
|
-
blessed,
|
|
192
|
-
currentInputHeight: 4,
|
|
193
|
-
version: UCODE_VERSION,
|
|
194
|
-
logBorder: false,
|
|
195
|
-
logScrollbar: false,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (completionPanel && typeof completionPanel.hide === "function") {
|
|
199
|
-
completionPanel.hide();
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const getAgentTag = (agent) => {
|
|
203
|
-
if (!agent) return "";
|
|
204
|
-
if (agent.id) return `${agent.type}:${agent.id.slice(0, 6)}`;
|
|
205
|
-
return agent.type;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const getAgentLabel = (id) => {
|
|
209
|
-
const meta = activeAgentMetaMap.get(id);
|
|
210
|
-
if (!meta) return id;
|
|
211
|
-
if (meta.nickname) return meta.nickname;
|
|
212
|
-
return getAgentTag(meta);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const refreshAgents = () => {
|
|
216
|
-
const list = filterSelectableAgents(
|
|
217
|
-
loadActiveAgents(workspaceRoot),
|
|
218
|
-
currentSubscriberId
|
|
219
|
-
);
|
|
220
|
-
activeAgents = list.map((agent) => agent.fullId);
|
|
221
|
-
activeAgentMetaMap = new Map(list.map((agent) => [agent.fullId, agent]));
|
|
222
|
-
if (targetAgent && !activeAgentMetaMap.has(targetAgent)) {
|
|
223
|
-
targetAgent = null;
|
|
224
|
-
}
|
|
225
|
-
selectedAgentIndex = targetAgent ? activeAgents.indexOf(targetAgent) : -1;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const setPrompt = () => {
|
|
229
|
-
const content = targetAgent ? `>@${getAgentLabel(targetAgent)}` : ">";
|
|
230
|
-
promptBox.setContent(content);
|
|
231
|
-
const plain = stripBlessedTags(content);
|
|
232
|
-
promptBox.width = Math.max(2, plain.length + 1);
|
|
233
|
-
input.left = promptBox.width;
|
|
234
|
-
input.width = `100%-${promptBox.width}`;
|
|
235
|
-
resizeInput();
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// --- Cursor position helpers (mirrors chat inputListenerController) ---
|
|
239
|
-
const getInnerWidth = () => {
|
|
240
|
-
const promptWidth = typeof promptBox.width === "number" ? promptBox.width : 2;
|
|
241
|
-
return inputMath.getInnerWidth({ input, screen, promptWidth });
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const getWrapWidth = () => inputMath.getWrapWidth(input, getInnerWidth());
|
|
245
|
-
|
|
246
|
-
const resetPreferredCol = () => {
|
|
247
|
-
preferredCol = null;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const ensureInputCursorVisible = () => {
|
|
251
|
-
const innerWidth = getWrapWidth();
|
|
252
|
-
if (innerWidth <= 0) return;
|
|
253
|
-
const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
|
|
254
|
-
const visibleRows = Math.max(1, input.height || 1);
|
|
255
|
-
const { row } = inputMath.getCursorRowCol(input.value || "", cursorPos, innerWidth, (v) => input.strWidth(v));
|
|
256
|
-
let base = input.childBase || 0;
|
|
257
|
-
const maxBase = Math.max(0, totalRows - visibleRows);
|
|
258
|
-
if (row < base) base = row;
|
|
259
|
-
else if (row >= base + visibleRows) base = row - visibleRows + 1;
|
|
260
|
-
if (base > maxBase) base = maxBase;
|
|
261
|
-
if (base < 0) base = 0;
|
|
262
|
-
if (base !== input.childBase) {
|
|
263
|
-
input.childBase = base;
|
|
264
|
-
if (typeof input.scrollTo === "function") input.scrollTo(base);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const resizeInput = () => {
|
|
269
|
-
const innerWidth = getWrapWidth();
|
|
270
|
-
if (innerWidth <= 0) return;
|
|
271
|
-
const totalRows = inputMath.countLines(input.value || "", innerWidth, (v) => input.strWidth(v));
|
|
272
|
-
const contentHeight = Math.min(
|
|
273
|
-
MAX_INPUT_CONTENT_HEIGHT,
|
|
274
|
-
Math.max(MIN_INPUT_CONTENT_HEIGHT, totalRows)
|
|
275
|
-
);
|
|
276
|
-
const targetHeight = contentHeight + DASHBOARD_HEIGHT + 2;
|
|
277
|
-
if (targetHeight !== currentInputHeight) {
|
|
278
|
-
currentInputHeight = targetHeight;
|
|
279
|
-
input.height = contentHeight;
|
|
280
|
-
promptBox.height = contentHeight;
|
|
281
|
-
if (inputTopLine) inputTopLine.bottom = currentInputHeight - 1;
|
|
282
|
-
}
|
|
283
|
-
statusLine.bottom = currentInputHeight;
|
|
284
|
-
logBox.height = Math.max(1, screen.height - currentInputHeight - 1);
|
|
285
|
-
ensureInputCursorVisible();
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const renderInput = () => {
|
|
289
|
-
resizeInput();
|
|
290
|
-
ensureInputCursorVisible();
|
|
291
|
-
input._updateCursor();
|
|
292
|
-
screen.render();
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const setCursor = (nextPos) => {
|
|
296
|
-
cursorPos = clampCursorPos(nextPos, input.value || "");
|
|
297
|
-
ensureInputCursorVisible();
|
|
298
|
-
input._updateCursor();
|
|
299
|
-
screen.render();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const setInputValue = (value) => {
|
|
303
|
-
input.setValue(value || "");
|
|
304
|
-
cursorPos = (value || "").length;
|
|
305
|
-
resetPreferredCol();
|
|
306
|
-
renderInput();
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const replaceInputRange = (start, end, replacement = "") => {
|
|
310
|
-
const value = input.value || "";
|
|
311
|
-
const safeStart = clampCursorPos(start, value);
|
|
312
|
-
const safeEnd = clampCursorPos(end, value);
|
|
313
|
-
const from = Math.min(safeStart, safeEnd);
|
|
314
|
-
const to = Math.max(safeStart, safeEnd);
|
|
315
|
-
input.value = value.slice(0, from) + String(replacement || "") + value.slice(to);
|
|
316
|
-
cursorPos = from + String(replacement || "").length;
|
|
317
|
-
resetPreferredCol();
|
|
318
|
-
renderInput();
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const insertTextAtCursor = (text = "") => {
|
|
322
|
-
const normalized = inputMath.normalizePaste(text);
|
|
323
|
-
if (!normalized) return;
|
|
324
|
-
replaceInputRange(cursorPos, cursorPos, normalized);
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const deleteBeforeCursor = () => {
|
|
328
|
-
if (cursorPos <= 0) return;
|
|
329
|
-
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
const deleteAtCursor = () => {
|
|
333
|
-
const value = input.value || "";
|
|
334
|
-
if (cursorPos >= value.length) return;
|
|
335
|
-
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
const deleteToBoundary = (boundary) => {
|
|
339
|
-
const value = input.value || "";
|
|
340
|
-
const innerWidth = getWrapWidth();
|
|
341
|
-
const target = boundary === "end"
|
|
342
|
-
? moveCursorToVisualLineBoundary({
|
|
343
|
-
cursorPos,
|
|
344
|
-
inputValue: value,
|
|
345
|
-
width: innerWidth,
|
|
346
|
-
boundary: "end",
|
|
347
|
-
strWidth: (v) => input.strWidth(v),
|
|
348
|
-
})
|
|
349
|
-
: moveCursorToVisualLineBoundary({
|
|
350
|
-
cursorPos,
|
|
351
|
-
inputValue: value,
|
|
352
|
-
width: innerWidth,
|
|
353
|
-
boundary: "start",
|
|
354
|
-
strWidth: (v) => input.strWidth(v),
|
|
355
|
-
});
|
|
356
|
-
if (target === cursorPos && boundary === "end" && value[cursorPos] === "\n") {
|
|
357
|
-
replaceInputRange(cursorPos, cursorPos + 1, "");
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (target === cursorPos && boundary === "start" && value[cursorPos - 1] === "\n") {
|
|
361
|
-
replaceInputRange(cursorPos - 1, cursorPos, "");
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
replaceInputRange(Math.min(cursorPos, target), Math.max(cursorPos, target), "");
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// Override _updateCursor to use our tracked cursorPos
|
|
368
|
-
input._updateCursor = function () {
|
|
369
|
-
if (this.screen.focused !== this) return;
|
|
370
|
-
let lpos;
|
|
371
|
-
try { lpos = this._getCoords(); } catch { return; }
|
|
372
|
-
if (!lpos) return;
|
|
373
|
-
const innerWidth = getWrapWidth();
|
|
374
|
-
if (innerWidth <= 0) return;
|
|
375
|
-
ensureInputCursorVisible();
|
|
376
|
-
const { row, col } = inputMath.getCursorRowCol(this.value || "", cursorPos, innerWidth, (v) => this.strWidth(v));
|
|
377
|
-
const scrollOffset = this.childBase || 0;
|
|
378
|
-
const displayRow = row - scrollOffset;
|
|
379
|
-
const safeCol = Math.min(Math.max(0, col), innerWidth - 1);
|
|
380
|
-
const cy = lpos.yi + displayRow;
|
|
381
|
-
const cx = lpos.xi + safeCol;
|
|
382
|
-
this.screen.program.cup(cy, cx);
|
|
383
|
-
this.screen.program.showCursor();
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
// Override _listener to support cursor-aware editing
|
|
387
|
-
let lastKeyRef = null;
|
|
388
|
-
let skipSubmitKeyRef = null;
|
|
389
|
-
input._listener = function (ch, key) {
|
|
390
|
-
const keyName = key && key.name;
|
|
391
|
-
|
|
392
|
-
// Dedup: blessed delivers the same key object via element 'keypress' event
|
|
393
|
-
// from both readInput's __listener binding and screen's focused.emit('keypress').
|
|
394
|
-
// Use object identity to skip the duplicate delivery.
|
|
395
|
-
if (key && key === lastKeyRef) return;
|
|
396
|
-
lastKeyRef = key || null;
|
|
397
|
-
|
|
398
|
-
if (keyName === "escape") return;
|
|
399
|
-
|
|
400
|
-
if (keyName === "return" || keyName === "enter") {
|
|
401
|
-
const value = this.value || "";
|
|
402
|
-
if (key && (key.shift || key.meta)) {
|
|
403
|
-
insertTextAtCursor("\n");
|
|
404
|
-
skipSubmitKeyRef = key || true;
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
|
|
408
|
-
replaceInputRange(cursorPos - 1, cursorPos, "\n");
|
|
409
|
-
skipSubmitKeyRef = key || true;
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Arrow keys handled by input.key() handlers below
|
|
416
|
-
if (keyName === "left" || keyName === "right" || keyName === "up" || keyName === "down") return;
|
|
417
|
-
|
|
418
|
-
if (key && key.ctrl) {
|
|
419
|
-
if (keyName === "a") {
|
|
420
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
421
|
-
cursorPos,
|
|
422
|
-
inputValue: this.value || "",
|
|
423
|
-
width: getWrapWidth(),
|
|
424
|
-
boundary: "start",
|
|
425
|
-
strWidth: (v) => this.strWidth(v),
|
|
426
|
-
}));
|
|
427
|
-
resetPreferredCol();
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (keyName === "e") {
|
|
431
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
432
|
-
cursorPos,
|
|
433
|
-
inputValue: this.value || "",
|
|
434
|
-
width: getWrapWidth(),
|
|
435
|
-
boundary: "end",
|
|
436
|
-
strWidth: (v) => this.strWidth(v),
|
|
437
|
-
}));
|
|
438
|
-
resetPreferredCol();
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (keyName === "b") {
|
|
442
|
-
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "left"));
|
|
443
|
-
resetPreferredCol();
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (keyName === "f") {
|
|
447
|
-
setCursor(moveCursorHorizontally(cursorPos, this.value || "", "right"));
|
|
448
|
-
resetPreferredCol();
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
if (keyName === "d") {
|
|
452
|
-
deleteAtCursor();
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (keyName === "h") {
|
|
456
|
-
deleteBeforeCursor();
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
if (keyName === "k") {
|
|
460
|
-
deleteToBoundary("end");
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (keyName === "u") {
|
|
464
|
-
deleteToBoundary("start");
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (keyName === "w") {
|
|
468
|
-
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
469
|
-
this.value = next.value;
|
|
470
|
-
cursorPos = next.cursorPos;
|
|
471
|
-
resetPreferredCol();
|
|
472
|
-
renderInput();
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (key && key.meta) {
|
|
478
|
-
if (keyName === "b") {
|
|
479
|
-
setCursor(moveCursorByWord(this.value || "", cursorPos, "backward"));
|
|
480
|
-
resetPreferredCol();
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (keyName === "f") {
|
|
484
|
-
setCursor(moveCursorByWord(this.value || "", cursorPos, "forward"));
|
|
485
|
-
resetPreferredCol();
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (keyName === "d") {
|
|
489
|
-
const end = moveCursorByWord(this.value || "", cursorPos, "forward");
|
|
490
|
-
replaceInputRange(cursorPos, end, "");
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
if (keyName === "backspace") {
|
|
496
|
-
if (key && (key.meta || key.ctrl)) {
|
|
497
|
-
const next = deleteWordBeforeCursor(this.value || "", cursorPos);
|
|
498
|
-
this.value = next.value;
|
|
499
|
-
cursorPos = next.cursorPos;
|
|
500
|
-
resetPreferredCol();
|
|
501
|
-
renderInput();
|
|
502
|
-
} else {
|
|
503
|
-
deleteBeforeCursor();
|
|
504
|
-
}
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (keyName === "delete") {
|
|
509
|
-
if (key && key.meta) {
|
|
510
|
-
deleteToBoundary("end");
|
|
511
|
-
} else {
|
|
512
|
-
deleteAtCursor();
|
|
513
|
-
}
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (keyName === "home") {
|
|
518
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
519
|
-
cursorPos,
|
|
520
|
-
inputValue: this.value || "",
|
|
521
|
-
width: getWrapWidth(),
|
|
522
|
-
boundary: "start",
|
|
523
|
-
strWidth: (v) => this.strWidth(v),
|
|
524
|
-
}));
|
|
525
|
-
resetPreferredCol();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (keyName === "end") {
|
|
530
|
-
setCursor(moveCursorToVisualLineBoundary({
|
|
531
|
-
cursorPos,
|
|
532
|
-
inputValue: this.value || "",
|
|
533
|
-
width: getWrapWidth(),
|
|
534
|
-
boundary: "end",
|
|
535
|
-
strWidth: (v) => this.strWidth(v),
|
|
536
|
-
}));
|
|
537
|
-
resetPreferredCol();
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (ch && ch.length > 1 && (!keyName || keyName.length !== 1)) {
|
|
542
|
-
insertTextAtCursor(ch);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Normal character insertion at cursor position
|
|
547
|
-
const insertChar = (ch && ch.length === 1) ? ch : (keyName && keyName.length === 1 ? keyName : null);
|
|
548
|
-
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar)) {
|
|
549
|
-
insertTextAtCursor(insertChar);
|
|
550
|
-
}
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
const renderDashboard = () => {
|
|
554
|
-
let hint = "No target agents";
|
|
555
|
-
if (activeAgents.length > 0) {
|
|
556
|
-
if (targetAgent) {
|
|
557
|
-
hint = `↓ select ${getAgentLabel(targetAgent)} · ←/→ switch · ↑ clear`;
|
|
558
|
-
} else {
|
|
559
|
-
hint = "↓ select target · ←/→ switch";
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
const computed = computeDashboardContent({
|
|
563
|
-
focusMode: "dashboard",
|
|
564
|
-
dashboardView: "agents",
|
|
565
|
-
activeAgents,
|
|
566
|
-
selectedAgentIndex,
|
|
567
|
-
agentListWindowStart,
|
|
568
|
-
maxAgentWindow: 4,
|
|
569
|
-
getAgentLabel,
|
|
570
|
-
dashHints: { agents: hint, agentsEmpty: hint },
|
|
571
|
-
});
|
|
572
|
-
agentListWindowStart = computed.windowStart;
|
|
573
|
-
dashboard.setContent(computed.content);
|
|
574
|
-
screen.render();
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const logText = (text = "") => {
|
|
578
|
-
activeToolMerge = null;
|
|
579
|
-
firstToolInGroup = true; // Reset tool group flag when switching back to text
|
|
580
|
-
const sanitized = stripLeakedEscapeTags(text);
|
|
581
|
-
const lines = renderLogLinesWithMarkdown(
|
|
582
|
-
sanitized,
|
|
583
|
-
logRenderState,
|
|
584
|
-
escapeBlessed
|
|
585
|
-
);
|
|
586
|
-
for (const line of lines) {
|
|
587
|
-
logBox.log(line);
|
|
588
|
-
}
|
|
589
|
-
screen.render();
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
const logUserInput = (text = "") => {
|
|
593
|
-
activeToolMerge = null;
|
|
594
|
-
const line = formatHighlightedUserInput(text, {
|
|
595
|
-
width: resolveLogContentWidth({ logBox, screen, fallback: (stdout && stdout.columns) || 80 }),
|
|
596
|
-
escapeText: escapeBlessed,
|
|
597
|
-
});
|
|
598
|
-
if (!line) return;
|
|
599
|
-
logBox.log(line);
|
|
600
|
-
logBox.log(""); // Add line break after user input
|
|
601
|
-
screen.render();
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
const logControlAction = (text = "") => {
|
|
605
|
-
activeToolMerge = null;
|
|
606
|
-
const plain = String(text || "").trim();
|
|
607
|
-
if (!plain) return;
|
|
608
|
-
logBox.log(`{gray-fg}⚙{/gray-fg} ${escapeBlessed(plain)}`);
|
|
609
|
-
screen.render();
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
const summarizeToolDetail = (tool = "", args = {}, payload = {}) => {
|
|
613
|
-
const toolName = String(tool || "").trim().toLowerCase();
|
|
614
|
-
const argObj = args && typeof args === "object" ? args : {};
|
|
615
|
-
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
616
|
-
|
|
617
|
-
if (toolName === "read") {
|
|
618
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
619
|
-
const lineInfo = Number.isFinite(resObj.totalLines) ? `${resObj.totalLines} lines` : "";
|
|
620
|
-
return [target, lineInfo].filter(Boolean).join(" · ");
|
|
621
|
-
}
|
|
622
|
-
if (toolName === "write") {
|
|
623
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
624
|
-
const mode = String(resObj.mode || argObj.mode || (argObj.append ? "append" : "overwrite")).trim();
|
|
625
|
-
const bytes = Number.isFinite(resObj.bytes) ? `${resObj.bytes} bytes` : "";
|
|
626
|
-
return [target, mode, bytes].filter(Boolean).join(" · ");
|
|
627
|
-
}
|
|
628
|
-
if (toolName === "edit") {
|
|
629
|
-
const target = String(resObj.path || argObj.path || argObj.file || "").trim();
|
|
630
|
-
const replacements = Number.isFinite(resObj.replacements) ? `${resObj.replacements} replacements` : "";
|
|
631
|
-
return [target, replacements].filter(Boolean).join(" · ");
|
|
632
|
-
}
|
|
633
|
-
if (toolName === "bash") {
|
|
634
|
-
return normalizeBashToolCommand(argObj, resObj);
|
|
635
|
-
}
|
|
636
|
-
return "";
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const truncateText = (text = "", maxLength = 80) => {
|
|
640
|
-
const str = String(text || "");
|
|
641
|
-
if (str.length <= maxLength) return str;
|
|
642
|
-
return str.slice(0, maxLength - 3) + "...";
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
const renderSingleToolEntryLine = (entry = {}) => {
|
|
646
|
-
const item = normalizeToolMergeEntry(entry);
|
|
647
|
-
const marker = item.isError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
|
|
648
|
-
const summary = buildMergedToolSummaryText([item]);
|
|
649
|
-
const truncated = truncateText(summary, 100);
|
|
650
|
-
return `${marker} ${escapeBlessed(truncated)}`;
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
const renderCollapsedToolMergeLine = (entries = []) => {
|
|
654
|
-
const summary = buildMergedToolSummaryText(entries);
|
|
655
|
-
const hasError = entries.some((item) => normalizeToolMergeEntry(item).isError);
|
|
656
|
-
const marker = hasError ? "{red-fg}•{/red-fg}" : "{cyan-fg}•{/cyan-fg}";
|
|
657
|
-
return `${marker} ${escapeBlessed(summary)} {gray-fg}(Ctrl+O expand){/gray-fg}`;
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
let firstToolInGroup = true;
|
|
661
|
-
|
|
662
|
-
const logToolHint = (entry = {}, payload = {}) => {
|
|
663
|
-
const tool = String(entry.tool || "").trim().toLowerCase();
|
|
664
|
-
if (!tool) return;
|
|
665
|
-
const resObj = payload && typeof payload === "object" ? payload : {};
|
|
666
|
-
const isError = String(entry.phase || "").trim().toLowerCase() === "error" || resObj.ok === false;
|
|
667
|
-
const detail = summarizeToolDetail(tool, entry.args, resObj);
|
|
668
|
-
const errorText = String(entry.error || resObj.error || "").trim();
|
|
669
|
-
|
|
670
|
-
const toolEntry = normalizeToolMergeEntry({
|
|
671
|
-
tool,
|
|
672
|
-
detail,
|
|
673
|
-
isError,
|
|
674
|
-
errorText,
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
if (activeToolMerge) {
|
|
678
|
-
activeToolMerge.entries.push(toolEntry);
|
|
679
|
-
// Only show collapsed format for 2+ tool calls
|
|
680
|
-
if (activeToolMerge.entries.length === 2) {
|
|
681
|
-
// Convert first single line to collapsed format
|
|
682
|
-
logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
|
|
683
|
-
} else if (activeToolMerge.entries.length > 2) {
|
|
684
|
-
logBox.setLine(activeToolMerge.lineIndex, renderCollapsedToolMergeLine(activeToolMerge.entries));
|
|
685
|
-
}
|
|
686
|
-
if (activeToolMerge.entries.length > 1) {
|
|
687
|
-
lastMergedToolGroup = activeToolMerge;
|
|
688
|
-
}
|
|
689
|
-
} else {
|
|
690
|
-
// Add line break before first tool call
|
|
691
|
-
if (firstToolInGroup) {
|
|
692
|
-
logBox.log("");
|
|
693
|
-
firstToolInGroup = false;
|
|
694
|
-
}
|
|
695
|
-
logBox.log(renderSingleToolEntryLine(toolEntry));
|
|
696
|
-
activeToolMerge = {
|
|
697
|
-
id: ++toolMergeId,
|
|
698
|
-
lineIndex: logBox.getLines().length - 1,
|
|
699
|
-
entries: [toolEntry],
|
|
700
|
-
expanded: false,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
screen.render();
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
const renderSingleMarkdownLine = (rawLine = "", options = {}) => {
|
|
707
|
-
const preview = Boolean(options.preview);
|
|
708
|
-
const renderState = preview
|
|
709
|
-
? { inCodeBlock: Boolean(logRenderState.inCodeBlock) }
|
|
710
|
-
: logRenderState;
|
|
711
|
-
const rendered = renderLogLinesWithMarkdown(rawLine, renderState, escapeBlessed);
|
|
712
|
-
return rendered[0] || "";
|
|
713
|
-
};
|
|
714
|
-
|
|
715
|
-
const createNlStreamState = () => {
|
|
716
|
-
activeToolMerge = null;
|
|
717
|
-
firstToolInGroup = true; // Reset flag for new response
|
|
718
|
-
logBox.log(""); // Add empty line to start the response
|
|
719
|
-
return {
|
|
720
|
-
lineIndex: logBox.getLines().length - 1,
|
|
721
|
-
buffer: "",
|
|
722
|
-
full: "",
|
|
723
|
-
seenVisibleContent: false,
|
|
724
|
-
};
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
const appendNlStreamDelta = (streamState, delta) => {
|
|
728
|
-
if (!streamState) return;
|
|
729
|
-
const chunk = stripLeakedEscapeTags(String(delta || ""));
|
|
730
|
-
if (!chunk) return;
|
|
731
|
-
|
|
732
|
-
streamState.full += chunk;
|
|
733
|
-
streamState.buffer += chunk;
|
|
734
|
-
|
|
735
|
-
const parts = streamState.buffer.split("\n");
|
|
736
|
-
if (parts.length > 1) {
|
|
737
|
-
const completed = parts.slice(0, -1);
|
|
738
|
-
for (const line of completed) {
|
|
739
|
-
const hasVisible = /[^\s]/.test(line);
|
|
740
|
-
if (!streamState.seenVisibleContent && !hasVisible) {
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
if (hasVisible) {
|
|
744
|
-
streamState.seenVisibleContent = true;
|
|
745
|
-
}
|
|
746
|
-
const rendered = renderSingleMarkdownLine(line);
|
|
747
|
-
logBox.setLine(streamState.lineIndex, rendered);
|
|
748
|
-
logBox.pushLine("");
|
|
749
|
-
streamState.lineIndex = logBox.getLines().length - 1;
|
|
750
|
-
}
|
|
751
|
-
streamState.buffer = parts[parts.length - 1];
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const previewHasVisible = /[^\s]/.test(streamState.buffer);
|
|
755
|
-
if (!streamState.seenVisibleContent && !previewHasVisible) {
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
if (previewHasVisible) {
|
|
759
|
-
streamState.seenVisibleContent = true;
|
|
760
|
-
}
|
|
761
|
-
const previewLine = renderSingleMarkdownLine(streamState.buffer, { preview: true });
|
|
762
|
-
logBox.setLine(streamState.lineIndex, previewLine);
|
|
763
|
-
screen.render();
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
const finalizeNlStream = (streamState) => {
|
|
767
|
-
if (!streamState) return { lastChar: "" };
|
|
768
|
-
streamState.buffer = stripLeakedEscapeTags(streamState.buffer);
|
|
769
|
-
const rendered = renderSingleMarkdownLine(streamState.buffer);
|
|
770
|
-
logBox.setLine(streamState.lineIndex, rendered);
|
|
771
|
-
screen.render();
|
|
772
|
-
const full = String(streamState.full || "");
|
|
773
|
-
return { lastChar: full ? full.charAt(full.length - 1) : "" };
|
|
774
|
-
};
|
|
775
|
-
|
|
776
|
-
const updateStatus = (message = "", type = "thinking", options = {}) => {
|
|
777
|
-
const getBackgroundSuffix = () => {
|
|
778
|
-
if (!backgroundTasks || backgroundTasks.size === 0) return "";
|
|
779
|
-
let running = 0;
|
|
780
|
-
let done = 0;
|
|
781
|
-
let failed = 0;
|
|
782
|
-
for (const task of backgroundTasks.values()) {
|
|
783
|
-
const status = String(task && task.status || "").trim().toLowerCase();
|
|
784
|
-
if (status === "running") running += 1;
|
|
785
|
-
else if (status === "done") done += 1;
|
|
786
|
-
else if (status === "failed") failed += 1;
|
|
787
|
-
}
|
|
788
|
-
const total = running + done + failed;
|
|
789
|
-
if (total <= 0) return "";
|
|
790
|
-
return ` · BG ${running}/${done}/${failed}`;
|
|
791
|
-
};
|
|
792
|
-
if (statusInterval) {
|
|
793
|
-
clearInterval(statusInterval);
|
|
794
|
-
statusInterval = null;
|
|
795
|
-
}
|
|
796
|
-
if (!message) {
|
|
797
|
-
statusLine.setContent(escapeBlessed(`UCODE · Ready · Enter send · Shift/Alt+Enter newline · PgUp/PgDn log · Ctrl+O tools${getBackgroundSuffix()}`));
|
|
798
|
-
screen.render();
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
const showTimer = Boolean(options.showTimer);
|
|
802
|
-
const startedAt = Number.isFinite(options.startedAt) ? options.startedAt : Date.now();
|
|
803
|
-
const indicators = STATUS_INDICATORS[type] || STATUS_INDICATORS.thinking;
|
|
804
|
-
statusIndex = 0;
|
|
805
|
-
const draw = () => {
|
|
806
|
-
const indicator = indicators[statusIndex % indicators.length];
|
|
807
|
-
const timerText = showTimer
|
|
808
|
-
? ` (${formatPendingElapsed(Date.now() - startedAt)},esc cancel)`
|
|
809
|
-
: "";
|
|
810
|
-
statusLine.setContent(escapeBlessed(`${indicator} ${message}${timerText}${getBackgroundSuffix()}`));
|
|
811
|
-
statusIndex += 1;
|
|
812
|
-
screen.render();
|
|
813
|
-
};
|
|
814
|
-
draw();
|
|
815
|
-
if (type !== "none") {
|
|
816
|
-
statusInterval = setInterval(draw, 100);
|
|
817
|
-
}
|
|
818
|
-
};
|
|
819
|
-
|
|
820
|
-
const closeWithCode = (code = 0) => {
|
|
821
|
-
if (closing) return;
|
|
822
|
-
closing = true;
|
|
823
|
-
if (autoBusTimer) {
|
|
824
|
-
clearInterval(autoBusTimer);
|
|
825
|
-
autoBusTimer = null;
|
|
826
|
-
}
|
|
827
|
-
if (statusInterval) {
|
|
828
|
-
clearInterval(statusInterval);
|
|
829
|
-
statusInterval = null;
|
|
830
|
-
}
|
|
831
|
-
if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
|
|
832
|
-
try {
|
|
833
|
-
pendingTask.abortController.abort();
|
|
834
|
-
} catch {
|
|
835
|
-
// ignore
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
try {
|
|
839
|
-
screen.destroy();
|
|
840
|
-
} catch {
|
|
841
|
-
// ignore
|
|
842
|
-
}
|
|
843
|
-
resolve({ code });
|
|
844
|
-
};
|
|
845
|
-
|
|
846
|
-
const runAutoBusOnce = async () => {
|
|
847
|
-
if (!autoBusEnabled || closing || pendingTask) return;
|
|
848
|
-
if (Number(getAutoBusPendingCount()) <= 0) {
|
|
849
|
-
autoBusError = "";
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// Set pending state for autoBus tasks
|
|
854
|
-
const abortController = new AbortController();
|
|
855
|
-
pendingTask = {
|
|
856
|
-
abortController,
|
|
857
|
-
startedAt: Date.now(),
|
|
858
|
-
};
|
|
859
|
-
updateStatus("Processing bus messages...", "thinking", {
|
|
860
|
-
showTimer: true,
|
|
861
|
-
startedAt: pendingTask.startedAt,
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
try {
|
|
865
|
-
const ubusResult = await runUbusCommand(state, {
|
|
866
|
-
workspaceRoot,
|
|
867
|
-
subscriberId: autoBusSubscriberId,
|
|
868
|
-
signal: abortController.signal,
|
|
869
|
-
onMessageReceived: (msg) => {
|
|
870
|
-
// Display the incoming message immediately
|
|
871
|
-
const { extractAgentNickname } = require("./agent");
|
|
872
|
-
const nickname = extractAgentNickname(msg.from) || msg.from;
|
|
873
|
-
logText(`${nickname}: ${msg.task}`);
|
|
874
|
-
// Update status to show we're working on this specific task
|
|
875
|
-
updateStatus("Working on task...", "thinking", {
|
|
876
|
-
showTimer: true,
|
|
877
|
-
startedAt: pendingTask.startedAt,
|
|
878
|
-
});
|
|
879
|
-
},
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
if (!ubusResult.ok) {
|
|
883
|
-
const nextError = String(ubusResult.error || "ubus failed");
|
|
884
|
-
if (nextError !== autoBusError) {
|
|
885
|
-
autoBusError = nextError;
|
|
886
|
-
logText(`Error: ${nextError}`);
|
|
887
|
-
}
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
autoBusError = "";
|
|
891
|
-
if (ubusResult.handled > 0) {
|
|
892
|
-
// Display only the replies (tasks were already shown via onMessageReceived)
|
|
893
|
-
if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
|
|
894
|
-
const { extractAgentNickname } = require("./agent");
|
|
895
|
-
for (const exchange of ubusResult.messageExchanges) {
|
|
896
|
-
const nickname = extractAgentNickname(exchange.from) || exchange.from;
|
|
897
|
-
// Only show the reply since task was already displayed
|
|
898
|
-
logText(`@${nickname} ${exchange.reply}`);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const persisted = persistSessionState(state);
|
|
902
|
-
if (!persisted || persisted.ok === false) {
|
|
903
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
} finally {
|
|
907
|
-
// Clear pending state
|
|
908
|
-
pendingTask = null;
|
|
909
|
-
updateStatus("", "none");
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
const scheduleAutoBus = () => {
|
|
914
|
-
if (!autoBusEnabled || closing || autoBusQueued || pendingTask) return;
|
|
915
|
-
if (Number(getAutoBusPendingCount()) <= 0) return;
|
|
916
|
-
autoBusQueued = true;
|
|
917
|
-
chain = chain
|
|
918
|
-
.then(() => runAutoBusOnce())
|
|
919
|
-
.catch(() => {})
|
|
920
|
-
.finally(() => {
|
|
921
|
-
autoBusQueued = false;
|
|
922
|
-
});
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
const resolveTargetToken = (token = "") => {
|
|
926
|
-
const text = String(token || "").trim();
|
|
927
|
-
if (!text) return "";
|
|
928
|
-
|
|
929
|
-
if (text.includes(":")) {
|
|
930
|
-
const match = activeAgents.find((id) => id === text || id.startsWith(text));
|
|
931
|
-
if (match) return match;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const normalized = text.toLowerCase();
|
|
935
|
-
for (const id of activeAgents) {
|
|
936
|
-
const meta = activeAgentMetaMap.get(id);
|
|
937
|
-
if (!meta) continue;
|
|
938
|
-
const nick = String(meta.nickname || "").toLowerCase();
|
|
939
|
-
if (nick && (nick === normalized || nick.startsWith(normalized))) return id;
|
|
940
|
-
}
|
|
941
|
-
return "";
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
const executeLine = async (line) => {
|
|
945
|
-
const normalizedLine = String(line || "").replace(/\r?\n/g, " ").trim();
|
|
946
|
-
if (!normalizedLine) return;
|
|
947
|
-
logUserInput(normalizedLine);
|
|
948
|
-
|
|
949
|
-
refreshAgents();
|
|
950
|
-
|
|
951
|
-
let actualLine = normalizedLine;
|
|
952
|
-
let isBusMessage = false;
|
|
953
|
-
|
|
954
|
-
if (targetAgent) {
|
|
955
|
-
isBusMessage = true;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
const mentionMatch = normalizedLine.match(/^@(\S+)\s+(.+)$/);
|
|
959
|
-
if (mentionMatch) {
|
|
960
|
-
const [, token, message] = mentionMatch;
|
|
961
|
-
const resolved = resolveTargetToken(token);
|
|
962
|
-
if (resolved) {
|
|
963
|
-
isBusMessage = true;
|
|
964
|
-
actualLine = message;
|
|
965
|
-
targetAgent = resolved;
|
|
966
|
-
selectedAgentIndex = activeAgents.indexOf(resolved);
|
|
967
|
-
setPrompt();
|
|
968
|
-
renderDashboard();
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
if (isBusMessage && targetAgent) {
|
|
973
|
-
updateStatus("Sending message...", "typing");
|
|
974
|
-
try {
|
|
975
|
-
execFileSync("ufoo", ["bus", "send", targetAgent, actualLine], {
|
|
976
|
-
cwd: workspaceRoot,
|
|
977
|
-
encoding: "utf8",
|
|
978
|
-
});
|
|
979
|
-
updateStatus("", "none");
|
|
980
|
-
logText(`✓ Message sent to ${getAgentLabel(targetAgent)}`);
|
|
981
|
-
} catch (err) {
|
|
982
|
-
updateStatus("", "none");
|
|
983
|
-
const msg = err && err.message ? err.message : "unknown error";
|
|
984
|
-
logText(`Failed to send message: ${msg}`);
|
|
985
|
-
}
|
|
986
|
-
targetAgent = null;
|
|
987
|
-
selectedAgentIndex = -1;
|
|
988
|
-
agentSelectionMode = false;
|
|
989
|
-
setPrompt();
|
|
990
|
-
renderDashboard();
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
const runtimeWorkspace = String((state && state.workspaceRoot) || workspaceRoot || process.cwd());
|
|
995
|
-
const result = runSingleCommand(actualLine, runtimeWorkspace);
|
|
996
|
-
if (result.kind === "empty") return;
|
|
997
|
-
if (result.kind === "exit") {
|
|
998
|
-
closeWithCode(0);
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
if (result.kind === "tool") {
|
|
1002
|
-
const payload = result.result && typeof result.result === "object" ? result.result : {};
|
|
1003
|
-
logToolHint({
|
|
1004
|
-
tool: result.tool,
|
|
1005
|
-
args: result.args,
|
|
1006
|
-
phase: payload.ok === false ? "error" : "end",
|
|
1007
|
-
error: payload.error || "",
|
|
1008
|
-
}, payload);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
if (result.kind === "probe") {
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
if (result.kind === "help" || result.kind === "error") {
|
|
1015
|
-
logText(result.output || "");
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
if (result.kind === "ubus") {
|
|
1019
|
-
updateStatus("Checking bus messages...", "typing");
|
|
1020
|
-
const ubusResult = await runUbusCommand(state, {
|
|
1021
|
-
workspaceRoot,
|
|
1022
|
-
onMessageReceived: (msg) => {
|
|
1023
|
-
// Display the incoming message immediately
|
|
1024
|
-
const { extractAgentNickname } = require("./agent");
|
|
1025
|
-
const nickname = extractAgentNickname(msg.from) || msg.from;
|
|
1026
|
-
logText(`${nickname}: ${msg.task}`);
|
|
1027
|
-
},
|
|
1028
|
-
});
|
|
1029
|
-
updateStatus("", "none");
|
|
1030
|
-
if (!ubusResult.ok) {
|
|
1031
|
-
logText(`Error: ${ubusResult.error}`);
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// Display only the replies (tasks were already shown via onMessageReceived)
|
|
1036
|
-
if (ubusResult.messageExchanges && ubusResult.messageExchanges.length > 0) {
|
|
1037
|
-
const { extractAgentNickname } = require("./agent");
|
|
1038
|
-
for (const exchange of ubusResult.messageExchanges) {
|
|
1039
|
-
const nickname = extractAgentNickname(exchange.from) || exchange.from;
|
|
1040
|
-
// Only show the reply since task was already displayed
|
|
1041
|
-
logText(`@${nickname} ${exchange.reply}`);
|
|
1042
|
-
}
|
|
1043
|
-
} else if (ubusResult.handled === 0) {
|
|
1044
|
-
logText("ubus: no pending messages.");
|
|
1045
|
-
}
|
|
1046
|
-
const persisted = persistSessionState(state);
|
|
1047
|
-
if (!persisted || persisted.ok === false) {
|
|
1048
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1049
|
-
}
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (result.kind === "resume") {
|
|
1053
|
-
const resumed = resumeSessionState(state, result.sessionId, workspaceRoot);
|
|
1054
|
-
if (!resumed.ok) {
|
|
1055
|
-
logText(`Error: ${resumed.error}`);
|
|
1056
|
-
return;
|
|
1057
|
-
}
|
|
1058
|
-
logText(`Resumed session ${resumed.sessionId} (${resumed.restoredMessages} messages).`);
|
|
1059
|
-
return;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
if (result.kind === "nl_bg") {
|
|
1063
|
-
backgroundSeq += 1;
|
|
1064
|
-
const jobId = `bg-${Date.now().toString(36)}-${backgroundSeq.toString(36)}`;
|
|
1065
|
-
const taskRecord = {
|
|
1066
|
-
id: jobId,
|
|
1067
|
-
task: result.task,
|
|
1068
|
-
status: "running",
|
|
1069
|
-
startedAt: Date.now(),
|
|
1070
|
-
summary: "",
|
|
1071
|
-
};
|
|
1072
|
-
backgroundTasks.set(jobId, taskRecord);
|
|
1073
|
-
updateStatus("", "none");
|
|
1074
|
-
logText(`[${jobId}] started in background.`);
|
|
1075
|
-
|
|
1076
|
-
const bgState = {
|
|
1077
|
-
workspaceRoot: state.workspaceRoot,
|
|
1078
|
-
provider: state.provider,
|
|
1079
|
-
model: state.model,
|
|
1080
|
-
engine: state.engine,
|
|
1081
|
-
context: state.context,
|
|
1082
|
-
nlMessages: Array.isArray(state.nlMessages) ? state.nlMessages.slice() : [],
|
|
1083
|
-
sessionId: "",
|
|
1084
|
-
timeoutMs: state.timeoutMs,
|
|
1085
|
-
jsonOutput: false,
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
|
-
Promise.resolve()
|
|
1089
|
-
.then(() => runNaturalLanguageTask(result.task, bgState))
|
|
1090
|
-
.then((nlResult) => {
|
|
1091
|
-
taskRecord.status = nlResult && nlResult.ok ? "done" : "failed";
|
|
1092
|
-
taskRecord.finishedAt = Date.now();
|
|
1093
|
-
taskRecord.summary = String(formatNlResult(nlResult, false) || "").trim();
|
|
1094
|
-
const title = taskRecord.status === "done" ? "done" : "failed";
|
|
1095
|
-
logText(`[${jobId}] ${title}: ${taskRecord.summary || "no summary"}`);
|
|
1096
|
-
})
|
|
1097
|
-
.catch((err) => {
|
|
1098
|
-
taskRecord.status = "failed";
|
|
1099
|
-
taskRecord.finishedAt = Date.now();
|
|
1100
|
-
taskRecord.summary = err && err.message ? String(err.message) : "background task failed";
|
|
1101
|
-
logText(`[${jobId}] failed: ${taskRecord.summary}`);
|
|
1102
|
-
})
|
|
1103
|
-
.finally(() => {
|
|
1104
|
-
updateStatus("", "none");
|
|
1105
|
-
screen.render();
|
|
1106
|
-
});
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (result.kind === "nl") {
|
|
1111
|
-
const abortController = new AbortController();
|
|
1112
|
-
const escapeStripper = createEscapeTagStripper();
|
|
1113
|
-
pendingTask = {
|
|
1114
|
-
abortController,
|
|
1115
|
-
startedAt: Date.now(),
|
|
1116
|
-
};
|
|
1117
|
-
const TOOL_LABELS = {
|
|
1118
|
-
read: "Reading file",
|
|
1119
|
-
write: "Writing file",
|
|
1120
|
-
edit: "Editing file",
|
|
1121
|
-
bash: "Running command",
|
|
1122
|
-
};
|
|
1123
|
-
const setNlStatus = (msg) => {
|
|
1124
|
-
updateStatus(msg, "thinking", {
|
|
1125
|
-
showTimer: true,
|
|
1126
|
-
startedAt: pendingTask.startedAt,
|
|
1127
|
-
});
|
|
1128
|
-
};
|
|
1129
|
-
setNlStatus("Waiting for model...");
|
|
1130
|
-
let streamState = null;
|
|
1131
|
-
let renderedToolLogCount = 0;
|
|
1132
|
-
let nlResult = null;
|
|
1133
|
-
try {
|
|
1134
|
-
nlResult = await runNaturalLanguageTask(result.task, state, {
|
|
1135
|
-
signal: abortController.signal,
|
|
1136
|
-
onPhase: (event) => {
|
|
1137
|
-
if (!event || typeof event !== "object") return;
|
|
1138
|
-
if (event.type === "request_start") {
|
|
1139
|
-
setNlStatus("Waiting for model...");
|
|
1140
|
-
} else if (event.type === "thinking_delta") {
|
|
1141
|
-
setNlStatus("Thinking...");
|
|
1142
|
-
} else if (event.type === "text_delta") {
|
|
1143
|
-
setNlStatus("Generating response...");
|
|
1144
|
-
} else if (event.type === "tool_request") {
|
|
1145
|
-
const label = TOOL_LABELS[String(event.name || "").toLowerCase()] || `Calling ${event.name}`;
|
|
1146
|
-
setNlStatus(`${label}...`);
|
|
1147
|
-
}
|
|
1148
|
-
},
|
|
1149
|
-
onDelta: (delta) => {
|
|
1150
|
-
const text = escapeStripper.write(String(delta || ""));
|
|
1151
|
-
if (!text) return;
|
|
1152
|
-
if (!streamState) {
|
|
1153
|
-
streamState = createNlStreamState();
|
|
1154
|
-
}
|
|
1155
|
-
appendNlStreamDelta(streamState, text);
|
|
1156
|
-
},
|
|
1157
|
-
onToolLog: (entry) => {
|
|
1158
|
-
renderedToolLogCount += 1;
|
|
1159
|
-
if (entry && entry.tool && entry.phase === "start") {
|
|
1160
|
-
const label = TOOL_LABELS[String(entry.tool || "").toLowerCase()] || `Calling ${entry.tool}`;
|
|
1161
|
-
setNlStatus(`${label}...`);
|
|
1162
|
-
}
|
|
1163
|
-
logToolHint(entry);
|
|
1164
|
-
},
|
|
1165
|
-
});
|
|
1166
|
-
const tail = escapeStripper.flush();
|
|
1167
|
-
if (tail) {
|
|
1168
|
-
if (!streamState) {
|
|
1169
|
-
streamState = createNlStreamState();
|
|
1170
|
-
}
|
|
1171
|
-
appendNlStreamDelta(streamState, tail);
|
|
1172
|
-
}
|
|
1173
|
-
let finalStreamInfo = { lastChar: "" };
|
|
1174
|
-
if (streamState) {
|
|
1175
|
-
finalStreamInfo = finalizeNlStream(streamState);
|
|
1176
|
-
}
|
|
1177
|
-
if (Array.isArray(nlResult && nlResult.logs) && nlResult.logs.length > renderedToolLogCount) {
|
|
1178
|
-
for (const entry of nlResult.logs.slice(renderedToolLogCount)) {
|
|
1179
|
-
logToolHint(entry);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
const streamed = Boolean(nlResult && nlResult.streamed);
|
|
1183
|
-
const hasVisibleStreamText = Boolean(
|
|
1184
|
-
streamState
|
|
1185
|
-
&& typeof streamState.full === "string"
|
|
1186
|
-
&& /[^\s]/.test(streamState.full)
|
|
1187
|
-
);
|
|
1188
|
-
const streamLastChar = nlResult && typeof nlResult.streamLastChar === "string"
|
|
1189
|
-
? nlResult.streamLastChar.slice(-1)
|
|
1190
|
-
: finalStreamInfo.lastChar;
|
|
1191
|
-
if (streamed && hasVisibleStreamText && streamLastChar !== "\n") {
|
|
1192
|
-
logBox.log("");
|
|
1193
|
-
screen.render();
|
|
1194
|
-
}
|
|
1195
|
-
const shouldSkipSummary = Boolean(streamed && nlResult && nlResult.ok && hasVisibleStreamText);
|
|
1196
|
-
if (!shouldSkipSummary) {
|
|
1197
|
-
logText(formatNlResult(nlResult, false));
|
|
1198
|
-
}
|
|
1199
|
-
const persisted = persistSessionState(state);
|
|
1200
|
-
if (!persisted || persisted.ok === false) {
|
|
1201
|
-
logText(`Error: failed to persist session ${state.sessionId}: ${(persisted && persisted.error) || "unknown error"}`);
|
|
1202
|
-
}
|
|
1203
|
-
} finally {
|
|
1204
|
-
pendingTask = null;
|
|
1205
|
-
updateStatus("", "none");
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
|
|
1210
|
-
const submitInput = (value = "") => {
|
|
1211
|
-
const raw = String(value || "");
|
|
1212
|
-
const trimmed = raw.trim();
|
|
1213
|
-
input.setValue("");
|
|
1214
|
-
cursorPos = 0;
|
|
1215
|
-
resetPreferredCol();
|
|
1216
|
-
resizeInput();
|
|
1217
|
-
screen.render();
|
|
1218
|
-
agentSelectionMode = false;
|
|
1219
|
-
|
|
1220
|
-
if (trimmed) {
|
|
1221
|
-
inputHistory.push(trimmed);
|
|
1222
|
-
}
|
|
1223
|
-
historyIndex = inputHistory.length;
|
|
1224
|
-
|
|
1225
|
-
chain = chain
|
|
1226
|
-
.then(() => executeLine(raw))
|
|
1227
|
-
.catch((err) => {
|
|
1228
|
-
updateStatus("", "none");
|
|
1229
|
-
logText(`Error: ${err && err.message ? err.message : "agent loop failed"}`);
|
|
1230
|
-
})
|
|
1231
|
-
.finally(() => {
|
|
1232
|
-
if (closing) return;
|
|
1233
|
-
refreshAgents();
|
|
1234
|
-
setPrompt();
|
|
1235
|
-
renderDashboard();
|
|
1236
|
-
input.focus();
|
|
1237
|
-
screen.render();
|
|
1238
|
-
});
|
|
1239
|
-
};
|
|
1240
|
-
|
|
1241
|
-
input.key(["enter"], (ch, key) => {
|
|
1242
|
-
if (skipSubmitKeyRef && (!key || skipSubmitKeyRef === key || skipSubmitKeyRef === true)) {
|
|
1243
|
-
skipSubmitKeyRef = null;
|
|
1244
|
-
return false;
|
|
1245
|
-
}
|
|
1246
|
-
submitInput(input.getValue());
|
|
1247
|
-
return false;
|
|
1248
|
-
});
|
|
1249
|
-
input.key(["up"], () => {
|
|
1250
|
-
const currentValue = input.getValue();
|
|
1251
|
-
if (shouldClearAgentSelectionOnUp({
|
|
1252
|
-
agentSelectionMode,
|
|
1253
|
-
inputValue: currentValue,
|
|
1254
|
-
})) {
|
|
1255
|
-
targetAgent = null;
|
|
1256
|
-
selectedAgentIndex = -1;
|
|
1257
|
-
agentSelectionMode = false;
|
|
1258
|
-
setPrompt();
|
|
1259
|
-
renderDashboard();
|
|
1260
|
-
// Target selection cleared - removed redundant log
|
|
1261
|
-
input.focus();
|
|
1262
|
-
return false;
|
|
1263
|
-
}
|
|
1264
|
-
if (currentValue) {
|
|
1265
|
-
const move = moveCursorVertically({
|
|
1266
|
-
cursorPos,
|
|
1267
|
-
inputValue: currentValue,
|
|
1268
|
-
width: getWrapWidth(),
|
|
1269
|
-
direction: "up",
|
|
1270
|
-
preferredCol,
|
|
1271
|
-
strWidth: (v) => input.strWidth(v),
|
|
1272
|
-
});
|
|
1273
|
-
preferredCol = move.preferredCol;
|
|
1274
|
-
if (move.moved) {
|
|
1275
|
-
setCursor(move.nextCursorPos);
|
|
1276
|
-
return false;
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
if (inputHistory.length === 0) return false;
|
|
1280
|
-
historyIndex = Math.max(0, historyIndex - 1);
|
|
1281
|
-
setInputValue(inputHistory[historyIndex] || "");
|
|
1282
|
-
return false;
|
|
1283
|
-
});
|
|
1284
|
-
input.key(["down"], () => {
|
|
1285
|
-
const currentValue = input.getValue();
|
|
1286
|
-
if (currentValue) {
|
|
1287
|
-
const move = moveCursorVertically({
|
|
1288
|
-
cursorPos,
|
|
1289
|
-
inputValue: currentValue,
|
|
1290
|
-
width: getWrapWidth(),
|
|
1291
|
-
direction: "down",
|
|
1292
|
-
preferredCol,
|
|
1293
|
-
strWidth: (v) => input.strWidth(v),
|
|
1294
|
-
});
|
|
1295
|
-
preferredCol = move.preferredCol;
|
|
1296
|
-
if (move.moved) {
|
|
1297
|
-
setCursor(move.nextCursorPos);
|
|
1298
|
-
return false;
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
const historyTransition = resolveHistoryDownTransition({
|
|
1302
|
-
inputHistory,
|
|
1303
|
-
historyIndex,
|
|
1304
|
-
currentValue,
|
|
1305
|
-
});
|
|
1306
|
-
if (historyTransition.moved) {
|
|
1307
|
-
historyIndex = historyTransition.nextHistoryIndex;
|
|
1308
|
-
setInputValue(historyTransition.nextValue);
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
if (shouldEnterAgentSelection(currentValue)) {
|
|
1313
|
-
const cachedAgents = Array.isArray(activeAgents) ? activeAgents.slice() : [];
|
|
1314
|
-
const cachedMeta = activeAgentMetaMap instanceof Map ? new Map(activeAgentMetaMap) : new Map();
|
|
1315
|
-
if (!agentSelectionMode) {
|
|
1316
|
-
refreshAgents();
|
|
1317
|
-
}
|
|
1318
|
-
if (!agentSelectionMode && activeAgents.length === 0 && cachedAgents.length > 0) {
|
|
1319
|
-
activeAgents = cachedAgents;
|
|
1320
|
-
activeAgentMetaMap = cachedMeta;
|
|
1321
|
-
}
|
|
1322
|
-
const decision = resolveAgentSelectionOnDown({
|
|
1323
|
-
agentSelectionMode,
|
|
1324
|
-
selectedAgentIndex,
|
|
1325
|
-
totalAgents: activeAgents.length,
|
|
1326
|
-
});
|
|
1327
|
-
if (decision.action === "enter") {
|
|
1328
|
-
selectedAgentIndex = decision.index;
|
|
1329
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1330
|
-
agentSelectionMode = true;
|
|
1331
|
-
setPrompt();
|
|
1332
|
-
renderDashboard();
|
|
1333
|
-
// Removed redundant target selection log
|
|
1334
|
-
input.focus();
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
|
-
if (decision.action === "hold") {
|
|
1338
|
-
return false;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
return false;
|
|
1342
|
-
});
|
|
1343
|
-
input.key(["left"], () => {
|
|
1344
|
-
const currentValue = input.getValue();
|
|
1345
|
-
if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
|
|
1346
|
-
if (activeAgents.length === 0) refreshAgents();
|
|
1347
|
-
if (activeAgents.length === 0) return false;
|
|
1348
|
-
selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "left");
|
|
1349
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1350
|
-
setPrompt();
|
|
1351
|
-
renderDashboard();
|
|
1352
|
-
// Removed redundant target switch log
|
|
1353
|
-
input.focus();
|
|
1354
|
-
return false;
|
|
1355
|
-
}
|
|
1356
|
-
const next = moveCursorHorizontally(cursorPos, currentValue, "left");
|
|
1357
|
-
if (next !== cursorPos) {
|
|
1358
|
-
setCursor(next);
|
|
1359
|
-
resetPreferredCol();
|
|
1360
|
-
}
|
|
1361
|
-
return false;
|
|
1362
|
-
});
|
|
1363
|
-
input.key(["right"], () => {
|
|
1364
|
-
const currentValue = input.getValue();
|
|
1365
|
-
if (agentSelectionMode && shouldEnterAgentSelection(currentValue)) {
|
|
1366
|
-
if (activeAgents.length === 0) refreshAgents();
|
|
1367
|
-
if (activeAgents.length === 0) return false;
|
|
1368
|
-
selectedAgentIndex = cycleAgentSelectionIndex(selectedAgentIndex, activeAgents.length, "right");
|
|
1369
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1370
|
-
setPrompt();
|
|
1371
|
-
renderDashboard();
|
|
1372
|
-
// Removed redundant target switch log
|
|
1373
|
-
input.focus();
|
|
1374
|
-
return false;
|
|
1375
|
-
}
|
|
1376
|
-
const next = moveCursorHorizontally(cursorPos, currentValue, "right");
|
|
1377
|
-
if (next !== cursorPos) {
|
|
1378
|
-
setCursor(next);
|
|
1379
|
-
resetPreferredCol();
|
|
1380
|
-
}
|
|
1381
|
-
return false;
|
|
1382
|
-
});
|
|
1383
|
-
|
|
1384
|
-
screen.key(["tab"], () => {
|
|
1385
|
-
refreshAgents();
|
|
1386
|
-
if (activeAgents.length === 0) return;
|
|
1387
|
-
if (selectedAgentIndex < 0) selectedAgentIndex = 0;
|
|
1388
|
-
else selectedAgentIndex = (selectedAgentIndex + 1) % activeAgents.length;
|
|
1389
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1390
|
-
agentSelectionMode = true;
|
|
1391
|
-
setPrompt();
|
|
1392
|
-
renderDashboard();
|
|
1393
|
-
// Removed redundant target switch log
|
|
1394
|
-
input.focus();
|
|
1395
|
-
});
|
|
1396
|
-
screen.key(["S-tab"], () => {
|
|
1397
|
-
refreshAgents();
|
|
1398
|
-
if (activeAgents.length === 0) return;
|
|
1399
|
-
if (selectedAgentIndex < 0) selectedAgentIndex = 0;
|
|
1400
|
-
else selectedAgentIndex = (selectedAgentIndex - 1 + activeAgents.length) % activeAgents.length;
|
|
1401
|
-
targetAgent = activeAgents[selectedAgentIndex];
|
|
1402
|
-
agentSelectionMode = true;
|
|
1403
|
-
setPrompt();
|
|
1404
|
-
renderDashboard();
|
|
1405
|
-
// Removed redundant target switch log
|
|
1406
|
-
input.focus();
|
|
1407
|
-
});
|
|
1408
|
-
screen.key(["C-o"], () => {
|
|
1409
|
-
if (!lastMergedToolGroup || lastMergedToolGroup.expanded) return;
|
|
1410
|
-
if (!Array.isArray(lastMergedToolGroup.entries) || lastMergedToolGroup.entries.length < 2) return;
|
|
1411
|
-
const lines = buildMergedToolExpandedLines(lastMergedToolGroup.entries);
|
|
1412
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
1413
|
-
const branch = i === lines.length - 1 ? "└" : "│";
|
|
1414
|
-
logBox.log(`{gray-fg}${branch}{/gray-fg} ${escapeBlessed(lines[i])}`);
|
|
1415
|
-
}
|
|
1416
|
-
lastMergedToolGroup.expanded = true;
|
|
1417
|
-
if (activeToolMerge && activeToolMerge.id === lastMergedToolGroup.id) {
|
|
1418
|
-
activeToolMerge = null;
|
|
1419
|
-
}
|
|
1420
|
-
screen.render();
|
|
1421
|
-
});
|
|
1422
|
-
screen.key(["pageup"], () => {
|
|
1423
|
-
logBox.scroll(-Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1424
|
-
screen.render();
|
|
1425
|
-
});
|
|
1426
|
-
screen.key(["pagedown"], () => {
|
|
1427
|
-
logBox.scroll(Math.max(1, Math.floor((logBox.height || 10) / 2)));
|
|
1428
|
-
screen.render();
|
|
1429
|
-
});
|
|
1430
|
-
input.key(["escape"], () => {
|
|
1431
|
-
if (pendingTask && pendingTask.abortController && !pendingTask.abortController.signal.aborted) {
|
|
1432
|
-
try {
|
|
1433
|
-
pendingTask.abortController.abort();
|
|
1434
|
-
} catch {
|
|
1435
|
-
// ignore
|
|
1436
|
-
}
|
|
1437
|
-
logControlAction("Cancellation requested. Stopping the current task...");
|
|
1438
|
-
updateStatus("Cancelling...", "waiting", {
|
|
1439
|
-
showTimer: true,
|
|
1440
|
-
startedAt: pendingTask.startedAt,
|
|
1441
|
-
});
|
|
1442
|
-
return false;
|
|
1443
|
-
}
|
|
1444
|
-
targetAgent = null;
|
|
1445
|
-
selectedAgentIndex = -1;
|
|
1446
|
-
agentSelectionMode = false;
|
|
1447
|
-
setInputValue("");
|
|
1448
|
-
setPrompt();
|
|
1449
|
-
renderDashboard();
|
|
1450
|
-
// Target selection cleared - removed redundant log
|
|
1451
|
-
input.focus();
|
|
1452
|
-
return false;
|
|
1453
|
-
});
|
|
1454
|
-
screen.key(["C-c"], () => closeWithCode(0));
|
|
1455
|
-
screen.on("resize", () => {
|
|
1456
|
-
renderDashboard();
|
|
1457
|
-
screen.render();
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
const nickname = process.env.UFOO_NICKNAME || "";
|
|
1461
|
-
const subscriberId = currentSubscriberId;
|
|
1462
|
-
const agentId = subscriberId.includes(":") ? subscriberId.split(":")[1] : "";
|
|
1463
|
-
const bannerLines = buildUcodeBannerBlessedLines({
|
|
1464
|
-
model: state.model || process.env.UFOO_UCODE_MODEL || "",
|
|
1465
|
-
engine: state.engine || "ufoo-core",
|
|
1466
|
-
nickname,
|
|
1467
|
-
agentId,
|
|
1468
|
-
workspaceRoot,
|
|
1469
|
-
sessionId: state.sessionId || "",
|
|
1470
|
-
width: (stdout && stdout.columns) || 80,
|
|
1471
|
-
});
|
|
1472
|
-
for (const line of bannerLines) {
|
|
1473
|
-
logBox.log(String(line || ""));
|
|
1474
|
-
}
|
|
1475
|
-
logBox.log("");
|
|
1476
|
-
|
|
1477
|
-
refreshAgents();
|
|
1478
|
-
setPrompt();
|
|
1479
|
-
updateStatus("", "none");
|
|
1480
|
-
renderDashboard();
|
|
1481
|
-
if (autoBusEnabled) {
|
|
1482
|
-
autoBusTimer = setInterval(() => {
|
|
1483
|
-
scheduleAutoBus();
|
|
1484
|
-
}, 800);
|
|
1485
|
-
scheduleAutoBus();
|
|
1486
|
-
}
|
|
1487
|
-
input.focus();
|
|
1488
|
-
screen.render();
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
42
|
module.exports = {
|
|
43
|
+
STATUS_INDICATORS,
|
|
1493
44
|
UCODE_BANNER_LINES,
|
|
1494
45
|
UCODE_VERSION,
|
|
1495
46
|
StreamBuffer,
|
|
1496
47
|
displayCellWidth,
|
|
1497
|
-
resolveLogContentWidth,
|
|
1498
|
-
formatHighlightedUserInput,
|
|
1499
48
|
buildUcodeBannerLines,
|
|
1500
|
-
buildUcodeBannerBlessedLines,
|
|
1501
49
|
parseActiveAgentsFromBusStatus,
|
|
1502
50
|
shouldUseUcodeTui,
|
|
1503
51
|
renderLogLinesWithMarkdown,
|
|
@@ -1522,5 +70,6 @@ module.exports = {
|
|
|
1522
70
|
normalizeToolMergeEntry,
|
|
1523
71
|
buildMergedToolSummaryText,
|
|
1524
72
|
buildMergedToolExpandedLines,
|
|
73
|
+
loadActiveAgents,
|
|
1525
74
|
runUcodeTui,
|
|
1526
75
|
};
|