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
|
@@ -35,6 +35,7 @@ const COMMAND_TREE = {
|
|
|
35
35
|
stop: { desc: "Stop cron task by id or all" },
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
|
+
"/clear": { desc: "Clear chat log on screen" },
|
|
38
39
|
"/group": {
|
|
39
40
|
desc: "Agent group orchestration",
|
|
40
41
|
children: {
|
|
@@ -47,13 +48,15 @@ const COMMAND_TREE = {
|
|
|
47
48
|
},
|
|
48
49
|
},
|
|
49
50
|
"/init": { desc: "Initialize modules" },
|
|
51
|
+
"/multi": { desc: "Toggle multi-window agent view" },
|
|
50
52
|
"/open": { desc: "Open project path in global mode" },
|
|
51
53
|
"/launch": {
|
|
52
54
|
desc: "Launch new agent",
|
|
53
55
|
children: {
|
|
54
|
-
claude: { desc: "Launch Claude agent" },
|
|
55
|
-
codex: { desc: "Launch Codex agent" },
|
|
56
|
-
|
|
56
|
+
claude: { desc: "Launch Claude agent", order: 1 },
|
|
57
|
+
codex: { desc: "Launch Codex agent", order: 2 },
|
|
58
|
+
agy: { desc: "Launch Antigravity (agy) agent", order: 3 },
|
|
59
|
+
ucode: { desc: "Launch ucode core agent", order: 4 },
|
|
57
60
|
},
|
|
58
61
|
},
|
|
59
62
|
"/project": {
|
|
@@ -136,7 +139,7 @@ const COMMAND_TREE = {
|
|
|
136
139
|
},
|
|
137
140
|
},
|
|
138
141
|
"/status": { desc: "Status display" },
|
|
139
|
-
"/ufoo": { desc: "ufoo protocol
|
|
142
|
+
"/ufoo": { desc: "ufoo protocol" },
|
|
140
143
|
};
|
|
141
144
|
|
|
142
145
|
const COMMAND_ORDER = ["/launch", "/group", "/bus", "/ctx"];
|
|
@@ -196,10 +199,120 @@ function parseCommand(text) {
|
|
|
196
199
|
return { command, args };
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
function parseCommandOptions(args = []) {
|
|
203
|
+
const options = {};
|
|
204
|
+
const positional = [];
|
|
205
|
+
for (const arg of args) {
|
|
206
|
+
const raw = String(arg || "").trim();
|
|
207
|
+
if (!raw) continue;
|
|
208
|
+
if (raw.includes("=")) {
|
|
209
|
+
const [key, value] = raw.split("=", 2);
|
|
210
|
+
options[String(key || "").trim().toLowerCase()] = String(value || "").trim();
|
|
211
|
+
} else {
|
|
212
|
+
positional.push(raw);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { options, positional };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeAgentLabel(value = "") {
|
|
219
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
220
|
+
if (raw === "claude" || raw === "uclaude") return "claude";
|
|
221
|
+
if (raw === "codex" || raw === "ucodex") return "codex";
|
|
222
|
+
if (raw === "agy" || raw === "antigravity" || raw === "uagy") return "agy";
|
|
223
|
+
if (raw === "ucode" || raw === "ufoo") return "ufoo";
|
|
224
|
+
return raw || "agent";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function describeCommandForChat(text) {
|
|
228
|
+
const parsed = parseCommand(String(text || "").trim());
|
|
229
|
+
if (!parsed) return "";
|
|
230
|
+
const command = String(parsed.command || "").trim().toLowerCase();
|
|
231
|
+
const args = parsed.args || [];
|
|
232
|
+
const sub = String(args[0] || "").trim().toLowerCase();
|
|
233
|
+
const { options, positional } = parseCommandOptions(args);
|
|
234
|
+
|
|
235
|
+
if (command === "launch") {
|
|
236
|
+
const agent = normalizeAgentLabel(args[0]);
|
|
237
|
+
const nickname = options.nickname || options.nick || options.name || "";
|
|
238
|
+
const profile = options.profile || options.prompt_profile || "";
|
|
239
|
+
const count = parseInt(options.count || "1", 10);
|
|
240
|
+
const scope = options.scope || options.launch_scope || positional.slice(1).find((item) =>
|
|
241
|
+
/^(window|new-window|separate|inplace|same|current|tab|pane)$/i.test(item)
|
|
242
|
+
) || "";
|
|
243
|
+
const base = nickname
|
|
244
|
+
? `Launching a ${agent} named ${nickname}`
|
|
245
|
+
: (Number.isFinite(count) && count > 1 ? `Launching ${count} ${agent} agents` : `Launching a ${agent} agent`);
|
|
246
|
+
return `${base}${profile ? ` with profile ${profile}` : ""}${/^(window|new-window|separate)$/i.test(scope) ? " in a new window" : ""}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (command === "group") {
|
|
250
|
+
if (sub === "run") return `Launching group ${args[1] || "template"}`;
|
|
251
|
+
if (sub === "status") return `Checking group ${args[1] || "status"}`;
|
|
252
|
+
if (sub === "stop") return `Stopping group ${args[1] || "run"}`;
|
|
253
|
+
if (sub === "diagram") return `Showing group diagram${args[1] ? ` for ${args[1]}` : ""}`;
|
|
254
|
+
if (sub === "template" || sub === "templates") return "Browsing group templates";
|
|
255
|
+
return "Managing agent groups";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (command === "bus") {
|
|
259
|
+
if (sub === "send") return `Sending a message to ${args[1] || "an agent"}`;
|
|
260
|
+
if (sub === "rename") return `Renaming ${args[1] || "an agent"}${args[2] ? ` to ${args[2]}` : ""}`;
|
|
261
|
+
if (sub === "activate") return `Activating ${args[1] || "an agent"}`;
|
|
262
|
+
if (sub === "list") return "Listing active agents";
|
|
263
|
+
if (sub === "status") return "Checking bus status";
|
|
264
|
+
return "Using the event bus";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (command === "daemon") {
|
|
268
|
+
if (sub === "start") return "Starting the ufoo daemon";
|
|
269
|
+
if (sub === "stop") return "Stopping the ufoo daemon";
|
|
270
|
+
if (sub === "restart") return "Restarting the ufoo daemon";
|
|
271
|
+
if (sub === "status") return "Checking daemon status";
|
|
272
|
+
return "Managing the ufoo daemon";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (command === "project") {
|
|
276
|
+
if (sub === "switch") return `Switching to project ${args[1] || ""}`.trim();
|
|
277
|
+
if (sub === "current") return "Showing current project";
|
|
278
|
+
if (sub === "list") return "Listing running projects";
|
|
279
|
+
return "Managing projects";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (command === "solo") {
|
|
283
|
+
if (sub === "run") return `Launching solo role ${args[1] || "agent"}`;
|
|
284
|
+
if (sub === "list") return "Listing solo roles";
|
|
285
|
+
return "Managing solo agents";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (command === "role") {
|
|
289
|
+
if (sub === "assign") return `Assigning role ${args[2] || ""}${args[1] ? ` to ${args[1]}` : ""}`.trim();
|
|
290
|
+
if (sub === "list") return "Listing roles";
|
|
291
|
+
return "Managing roles";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (command === "cron") {
|
|
295
|
+
if (sub === "start") return "Creating a cron task";
|
|
296
|
+
if (sub === "stop") return `Stopping cron task ${args[1] || ""}`.trim();
|
|
297
|
+
if (sub === "list") return "Listing cron tasks";
|
|
298
|
+
return "Managing cron tasks";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (command === "settings") return !sub || sub === "show" ? "Showing settings" : "Updating settings";
|
|
302
|
+
if (command === "ctx") return `Checking context${sub ? ` ${sub}` : ""}`;
|
|
303
|
+
if (command === "doctor") return "Running ufoo diagnostics";
|
|
304
|
+
if (command === "clear") return "Clearing the chat log";
|
|
305
|
+
if (command === "multi") return "Toggling multi-pane view";
|
|
306
|
+
if (command === "open") return `Opening project ${args[0] || ""}`.trim();
|
|
307
|
+
if (command === "resume") return args[0] === "list" ? "Listing recoverable agents" : `Resuming ${args[0] || "agents"}`;
|
|
308
|
+
if (command === "init") return "Initializing ufoo modules";
|
|
309
|
+
|
|
310
|
+
return `Running /${command}`;
|
|
311
|
+
}
|
|
312
|
+
|
|
199
313
|
function shouldEchoCommandInChat(text) {
|
|
200
314
|
const parsed = parseCommand(String(text || "").trim());
|
|
201
315
|
if (!parsed) return true;
|
|
202
|
-
if (parsed.command === "group" && parsed.args[0] === "run") return false;
|
|
203
316
|
return true;
|
|
204
317
|
}
|
|
205
318
|
|
|
@@ -222,6 +335,7 @@ module.exports = {
|
|
|
222
335
|
sortCommands,
|
|
223
336
|
buildCommandRegistry,
|
|
224
337
|
parseCommand,
|
|
338
|
+
describeCommandForChat,
|
|
225
339
|
shouldEchoCommandInChat,
|
|
226
340
|
parseAtTarget,
|
|
227
341
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const { IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("
|
|
2
|
-
const { renderMarkdownLines } = require("
|
|
1
|
+
const { IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("../../runtime/contracts/eventContract");
|
|
2
|
+
const { renderMarkdownLines } = require("../../ui/format/markdownRenderer");
|
|
3
3
|
const { decodeEscapedNewlines } = require("./text");
|
|
4
4
|
|
|
5
5
|
function createDaemonMessageRouter(options = {}) {
|
|
@@ -47,6 +47,45 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
47
47
|
return `{cyan-fg}${escapedLabel}{/cyan-fg} {gray-fg}·{/gray-fg} `;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function launchTargetLabel(result = {}, renameResult = null) {
|
|
51
|
+
const nickname = (renameResult && renameResult.ok && renameResult.nickname)
|
|
52
|
+
|| result.nickname
|
|
53
|
+
|| "";
|
|
54
|
+
if (nickname) return nickname;
|
|
55
|
+
const ids = Array.isArray(result.subscriber_ids) ? result.subscriber_ids : [];
|
|
56
|
+
return ids[0] || result.agent_id || "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildLifecycleSummary(payload = {}, msg = {}) {
|
|
60
|
+
const ops = Array.isArray(payload.ops) ? payload.ops : [];
|
|
61
|
+
const results = Array.isArray(msg.opsResults) ? msg.opsResults : [];
|
|
62
|
+
if (ops.length === 0) return "";
|
|
63
|
+
const launchOp = ops.find((op) => op && op.action === "launch");
|
|
64
|
+
if (launchOp) {
|
|
65
|
+
const launchResult = results.find((item) => item && item.action === "launch") || {};
|
|
66
|
+
const renameResult = results.find((item) => item && item.action === "rename" && item.ok);
|
|
67
|
+
if (launchResult.ok === false) {
|
|
68
|
+
return `Launch failed: ${launchResult.error || "unknown error"}`;
|
|
69
|
+
}
|
|
70
|
+
const agent = String(launchResult.agent || launchOp.agent || "agent").trim();
|
|
71
|
+
const count = Number(launchResult.count || launchOp.count || 1);
|
|
72
|
+
const ids = Array.isArray(launchResult.subscriber_ids) ? launchResult.subscriber_ids.filter(Boolean) : [];
|
|
73
|
+
const target = launchTargetLabel(launchResult, renameResult);
|
|
74
|
+
if (launchResult.skipped && target) return `Reused ${agent}:${target}`;
|
|
75
|
+
if (ids.length > 1) return `Launched ${ids.length} ${agent} agents: ${ids.join(", ")}`;
|
|
76
|
+
if (target) return `Launched ${agent}:${target}`;
|
|
77
|
+
if (count > 1) return `Launched ${count} ${agent} agents`;
|
|
78
|
+
return `Launched a ${agent} agent`;
|
|
79
|
+
}
|
|
80
|
+
const closeOp = ops.find((op) => op && op.action === "close");
|
|
81
|
+
if (closeOp) {
|
|
82
|
+
const closeResult = results.find((item) => item && item.action === "close") || {};
|
|
83
|
+
if (closeResult.ok === false) return `Close failed: ${closeResult.error || "unknown error"}`;
|
|
84
|
+
return `Closed ${closeResult.agent_id || closeOp.agent_id || closeOp.target || "agent"}`;
|
|
85
|
+
}
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
|
|
50
89
|
function normalizeDisplayMessage(raw) {
|
|
51
90
|
let displayMessage = raw || "";
|
|
52
91
|
let streamPayload = null;
|
|
@@ -223,7 +262,10 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
223
262
|
/^Group started\b/i.test(replyText)
|
|
224
263
|
);
|
|
225
264
|
// Suppress lifecycle confirmations from chat history — status line plus structured payload is enough.
|
|
226
|
-
if (
|
|
265
|
+
if (isLifecycleStatusOnly) {
|
|
266
|
+
const summary = buildLifecycleSummary(payload, msg);
|
|
267
|
+
if (summary) logMessage("reply", `${speakerPrefix("ufoo", "white")}${escapeBlessed(summary)}`);
|
|
268
|
+
} else if (!isGroupStartedConfirmation) {
|
|
227
269
|
logMessage("reply", `${speakerPrefix("ufoo", "white")}${escapeBlessed(replyText)}`);
|
|
228
270
|
}
|
|
229
271
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const { clampAgentWindowWithSelection } = require("./agentDirectory");
|
|
2
2
|
|
|
3
|
-
const DEFAULT_MODE_OPTIONS = ["auto", "host", "terminal", "tmux", "internal
|
|
3
|
+
const DEFAULT_MODE_OPTIONS = ["auto", "host", "terminal", "tmux", "internal"];
|
|
4
4
|
|
|
5
5
|
function providerLabel(value) {
|
|
6
6
|
if (value === "claude-cli") return "claude";
|
|
7
|
+
if (value === "agy-cli" || value === "agy" || value === "antigravity") return "agy";
|
|
7
8
|
if (value === "ucode" || value === "ufoo" || value === "ufoo-code") return "ucode";
|
|
8
9
|
return "codex";
|
|
9
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const { IPC_REQUEST_TYPES } = require("
|
|
1
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
2
2
|
const { decodeEscapedNewlines } = require("./text");
|
|
3
|
-
const { shouldEchoCommandInChat } = require("./commands");
|
|
3
|
+
const { describeCommandForChat, shouldEchoCommandInChat } = require("./commands");
|
|
4
4
|
const { parseShellCommand, runShellCommand: defaultRunShellCommand } = require("./shellCommand");
|
|
5
5
|
|
|
6
6
|
function createInputSubmitHandler(options = {}) {
|
|
@@ -20,8 +20,6 @@ function createInputSubmitHandler(options = {}) {
|
|
|
20
20
|
enterAgentView = () => {},
|
|
21
21
|
getAgentAdapter = () => null,
|
|
22
22
|
activateAgent = async () => {},
|
|
23
|
-
getInjectSockPath = () => "",
|
|
24
|
-
existsSync = () => false,
|
|
25
23
|
commitInputHistory = () => {},
|
|
26
24
|
focusInput = () => {},
|
|
27
25
|
renderScreen = () => {}, // Add renderScreen callback
|
|
@@ -42,17 +40,9 @@ function createInputSubmitHandler(options = {}) {
|
|
|
42
40
|
async function tryActivateTargetAgent(agentId) {
|
|
43
41
|
const adapter = getAgentAdapter(agentId);
|
|
44
42
|
const capabilities = adapter && adapter.capabilities ? adapter.capabilities : null;
|
|
45
|
-
const sockPath = getInjectSockPath(agentId);
|
|
46
|
-
const supportsSocket = Boolean(capabilities && capabilities.supportsSocketProtocol);
|
|
47
43
|
const supportsActivate = Boolean(capabilities && capabilities.supportsActivate);
|
|
48
44
|
const supportsInternalQueue = Boolean(capabilities && capabilities.supportsInternalQueueLoop);
|
|
49
45
|
|
|
50
|
-
if (existsSync(sockPath) && supportsSocket) {
|
|
51
|
-
clearTargetAgent();
|
|
52
|
-
enterAgentView(agentId);
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
46
|
if (supportsActivate) {
|
|
57
47
|
clearTargetAgent();
|
|
58
48
|
try {
|
|
@@ -179,7 +169,8 @@ function createInputSubmitHandler(options = {}) {
|
|
|
179
169
|
|
|
180
170
|
if (text.startsWith("/")) {
|
|
181
171
|
if (shouldEchoCommandInChat(text)) {
|
|
182
|
-
|
|
172
|
+
const commandSummary = describeCommandForChat(text);
|
|
173
|
+
logMessage("user", userEcho(commandSummary || text));
|
|
183
174
|
renderScreen(); // Render slash command immediately
|
|
184
175
|
}
|
|
185
176
|
try {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const { getUfooPaths } = require("
|
|
5
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
6
6
|
|
|
7
7
|
function stripAnsi(text = "") {
|
|
8
8
|
return String(text || "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
const { calculatePaneLayout } = require("./paneLayout");
|
|
2
|
+
const { createPaneManager } = require("./paneManager");
|
|
3
|
+
const { createRenderer } = require("./renderer");
|
|
4
|
+
|
|
5
|
+
function createMultiWindowController(options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
processStdout = process.stdout,
|
|
8
|
+
getRows = () => process.stdout.rows || 24,
|
|
9
|
+
getCols = () => process.stdout.columns || 80,
|
|
10
|
+
getInjectSockPath = () => "",
|
|
11
|
+
getActiveAgents = () => [],
|
|
12
|
+
getAgentPaneOptions = () => ({}),
|
|
13
|
+
getChatLogLines = () => [],
|
|
14
|
+
getStatusText = () => "",
|
|
15
|
+
getPromptPrefix = () => "› ",
|
|
16
|
+
getCurrentDraft = () => "",
|
|
17
|
+
getCursorPos = () => 0,
|
|
18
|
+
getCompletions = () => ({ items: [], index: -1, windowStart: 0, pageSize: 8 }),
|
|
19
|
+
getAgentLabel = (id) => id,
|
|
20
|
+
getInternalPaneInfo = () => ({}),
|
|
21
|
+
getDashboardLines = () => [],
|
|
22
|
+
getTerminalFocused = () => false,
|
|
23
|
+
freezeScreen = () => {},
|
|
24
|
+
restoreTerminal = () => {},
|
|
25
|
+
onExit = () => {},
|
|
26
|
+
onFocusAgent = () => {},
|
|
27
|
+
onInternalSubmit = () => {},
|
|
28
|
+
} = options;
|
|
29
|
+
|
|
30
|
+
let active = false;
|
|
31
|
+
let renderThrottleTimer = null;
|
|
32
|
+
let dirtyPanes = new Set();
|
|
33
|
+
let lastCompletionPopup = null;
|
|
34
|
+
const renderer = createRenderer({ write: (d) => processStdout.write(d) });
|
|
35
|
+
const paneManager = createPaneManager({
|
|
36
|
+
getInjectSockPath,
|
|
37
|
+
onInternalSubmit,
|
|
38
|
+
onPaneOutput: (agentId) => {
|
|
39
|
+
if (!active) return;
|
|
40
|
+
if (getTerminalFocused()) {
|
|
41
|
+
renderSinglePane(agentId);
|
|
42
|
+
} else {
|
|
43
|
+
dirtyPanes.add(agentId);
|
|
44
|
+
if (!renderThrottleTimer) {
|
|
45
|
+
renderThrottleTimer = setTimeout(() => {
|
|
46
|
+
renderThrottleTimer = null;
|
|
47
|
+
const panes = [...dirtyPanes];
|
|
48
|
+
dirtyPanes.clear();
|
|
49
|
+
for (const id of panes) renderSinglePane(id);
|
|
50
|
+
}, 200);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function enter() {
|
|
57
|
+
const agents = getActiveAgents();
|
|
58
|
+
if (agents.length === 0) return false;
|
|
59
|
+
if (active) return false;
|
|
60
|
+
active = true;
|
|
61
|
+
freezeScreen(true);
|
|
62
|
+
renderer.hideCursor();
|
|
63
|
+
renderer.clear();
|
|
64
|
+
syncAgents();
|
|
65
|
+
renderAll();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function exit() {
|
|
70
|
+
if (!active) return;
|
|
71
|
+
active = false;
|
|
72
|
+
if (renderThrottleTimer) {
|
|
73
|
+
clearTimeout(renderThrottleTimer);
|
|
74
|
+
renderThrottleTimer = null;
|
|
75
|
+
dirtyPanes.clear();
|
|
76
|
+
}
|
|
77
|
+
paneManager.disconnectAll();
|
|
78
|
+
renderer.showCursor();
|
|
79
|
+
restoreTerminal();
|
|
80
|
+
freezeScreen(false);
|
|
81
|
+
onExit();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function syncAgents() {
|
|
85
|
+
const agents = getActiveAgents();
|
|
86
|
+
const current = new Set(paneManager.getAgentIds());
|
|
87
|
+
const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < agents.length; i++) {
|
|
90
|
+
const id = agents[i];
|
|
91
|
+
const pane = layout.agentPanes[i];
|
|
92
|
+
if (!pane) continue;
|
|
93
|
+
const innerW = Math.max(1, pane.width - 2);
|
|
94
|
+
const innerH = Math.max(1, pane.height - 2);
|
|
95
|
+
if (!current.has(id)) {
|
|
96
|
+
paneManager.addAgent(id, innerW, innerH, getAgentPaneOptions(id) || {});
|
|
97
|
+
} else {
|
|
98
|
+
paneManager.sendResize(id, innerW, innerH);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const id of current) {
|
|
102
|
+
if (!agents.includes(id)) {
|
|
103
|
+
paneManager.removeAgent(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderSinglePane(agentId) {
|
|
109
|
+
if (!active) return;
|
|
110
|
+
try {
|
|
111
|
+
const agents = paneManager.getAgentIds();
|
|
112
|
+
const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
|
|
113
|
+
const idx = agents.indexOf(agentId);
|
|
114
|
+
if (idx < 0 || !layout.agentPanes[idx]) return;
|
|
115
|
+
const pane = paneManager.getPane(agentId);
|
|
116
|
+
if (!pane) return;
|
|
117
|
+
const isFocused = getTerminalFocused() && agentId === paneManager.getFocused();
|
|
118
|
+
if (pane.mode === "internal" && typeof renderer.renderInternalPane === "function") {
|
|
119
|
+
renderer.renderInternalPane(pane.vt, layout.agentPanes[idx], isFocused, {
|
|
120
|
+
label: getAgentLabel(agentId),
|
|
121
|
+
...(getInternalPaneInfo(agentId) || {}),
|
|
122
|
+
input: pane.internalInput || "",
|
|
123
|
+
cursor: pane.internalCursor || 0,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
renderer.renderPane(pane.vt, layout.agentPanes[idx], isFocused, getAgentLabel(agentId));
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// swallow render errors to prevent crash
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderAll() {
|
|
134
|
+
if (!active) return;
|
|
135
|
+
try {
|
|
136
|
+
const agents = paneManager.getAgentIds();
|
|
137
|
+
const layout = calculatePaneLayout(getCols(), getRows(), agents.length);
|
|
138
|
+
const cols = getCols();
|
|
139
|
+
|
|
140
|
+
renderer.renderChatLog(layout.chatPane, getChatLogLines());
|
|
141
|
+
|
|
142
|
+
const focused = getTerminalFocused() ? paneManager.getFocused() : null;
|
|
143
|
+
for (let i = 0; i < agents.length; i++) {
|
|
144
|
+
const pane = paneManager.getPane(agents[i]);
|
|
145
|
+
if (!pane || !layout.agentPanes[i]) continue;
|
|
146
|
+
if (pane.mode === "internal" && typeof renderer.renderInternalPane === "function") {
|
|
147
|
+
renderer.renderInternalPane(pane.vt, layout.agentPanes[i], agents[i] === focused, {
|
|
148
|
+
label: getAgentLabel(agents[i]),
|
|
149
|
+
...(getInternalPaneInfo(agents[i]) || {}),
|
|
150
|
+
input: pane.internalInput || "",
|
|
151
|
+
cursor: pane.internalCursor || 0,
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
renderer.renderPane(pane.vt, layout.agentPanes[i], agents[i] === focused, getAgentLabel(agents[i]));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const chatFocused = !getTerminalFocused();
|
|
159
|
+
if (layout.separatorPane) {
|
|
160
|
+
renderer.renderSeparator(layout.separatorPane, chatFocused);
|
|
161
|
+
}
|
|
162
|
+
if (layout.statusPane) {
|
|
163
|
+
renderer.renderStatusLine(layout.statusPane, getStatusText());
|
|
164
|
+
}
|
|
165
|
+
if (layout.inputPane) {
|
|
166
|
+
renderer.renderInputPrompt(layout.inputPane, getPromptPrefix(), getCurrentDraft(), getCursorPos());
|
|
167
|
+
}
|
|
168
|
+
if (layout.inputSepPane) {
|
|
169
|
+
renderer.renderSeparator(layout.inputSepPane, chatFocused);
|
|
170
|
+
}
|
|
171
|
+
if (layout.dashboardPane) {
|
|
172
|
+
const lines = getDashboardLines();
|
|
173
|
+
renderer.renderDashboard(layout.dashboardPane, lines);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const cmp = getCompletions();
|
|
177
|
+
let nextCompletionPopup = null;
|
|
178
|
+
if (lastCompletionPopup && typeof renderer.clearRows === "function") {
|
|
179
|
+
renderer.clearRows(
|
|
180
|
+
lastCompletionPopup.top,
|
|
181
|
+
lastCompletionPopup.height,
|
|
182
|
+
lastCompletionPopup.width,
|
|
183
|
+
lastCompletionPopup.left || 0
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (cmp && Array.isArray(cmp.items) && cmp.items.length > 0 && layout.inputPane) {
|
|
187
|
+
const start = Math.min(cmp.windowStart || 0, Math.max(0, cmp.items.length - (cmp.pageSize || 8)));
|
|
188
|
+
const end = Math.min(cmp.items.length, start + (cmp.pageSize || 8));
|
|
189
|
+
const visible = cmp.items.slice(start, end);
|
|
190
|
+
const popupTop = layout.inputPane.top - visible.length - 1;
|
|
191
|
+
if (popupTop >= 0) {
|
|
192
|
+
nextCompletionPopup = { top: popupTop, left: 0, width: cols, height: visible.length + 1 };
|
|
193
|
+
renderer.renderSeparator({ top: popupTop, left: 0, width: cols });
|
|
194
|
+
for (let i = 0; i < visible.length; i++) {
|
|
195
|
+
const idx = start + i;
|
|
196
|
+
const selected = idx === cmp.index;
|
|
197
|
+
const label = visible[i].label || "";
|
|
198
|
+
const desc = visible[i].description || "";
|
|
199
|
+
const line = selected
|
|
200
|
+
? `\x1b[7;36m${label}\x1b[0m \x1b[90m${desc}\x1b[0m`
|
|
201
|
+
: `\x1b[90m${label} ${desc}\x1b[0m`;
|
|
202
|
+
const pad = Math.max(0, cols - renderer.visibleLength(line));
|
|
203
|
+
renderer.write(renderer.moveTo(popupTop + 1 + i, 0) + line + " ".repeat(pad) + "\x1b[0m");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
lastCompletionPopup = nextCompletionPopup;
|
|
208
|
+
} catch {
|
|
209
|
+
// swallow render errors to prevent crash
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function handleKey(key) {
|
|
214
|
+
if (!active) return false;
|
|
215
|
+
|
|
216
|
+
if (key.name === "c" && key.ctrl) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (key.name === "w" && key.ctrl) {
|
|
221
|
+
paneManager.cycleFocus();
|
|
222
|
+
renderAll();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (key.name === "q" && key.ctrl) {
|
|
227
|
+
exit();
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function focusAgent(agentId) {
|
|
235
|
+
if (!active) return;
|
|
236
|
+
const agents = paneManager.getAgentIds();
|
|
237
|
+
if (!agents.includes(agentId)) return;
|
|
238
|
+
paneManager.setFocused(agentId);
|
|
239
|
+
onFocusAgent(agentId);
|
|
240
|
+
renderAll();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleResize() {
|
|
244
|
+
if (!active) return;
|
|
245
|
+
syncAgents();
|
|
246
|
+
renderer.clear();
|
|
247
|
+
renderAll();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isActive() { return active; }
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
enter,
|
|
254
|
+
exit,
|
|
255
|
+
isActive,
|
|
256
|
+
handleKey,
|
|
257
|
+
handleResize,
|
|
258
|
+
syncAgents,
|
|
259
|
+
renderAll,
|
|
260
|
+
focusAgent,
|
|
261
|
+
sendInput: (data) => paneManager.sendInput(data),
|
|
262
|
+
writeToPane: (agentId, data) => paneManager.writeToPane(agentId, data),
|
|
263
|
+
getFocused: () => paneManager.getFocused(),
|
|
264
|
+
getAgentIds: () => paneManager.getAgentIds(),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = { createMultiWindowController };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
function calculatePaneLayout(termCols, termRows, agentCount) {
|
|
2
|
+
const bottomRows = 5;
|
|
3
|
+
const safeRows = termRows - 1;
|
|
4
|
+
const contentHeight = Math.max(1, safeRows - bottomRows);
|
|
5
|
+
const bottomTop = contentHeight;
|
|
6
|
+
|
|
7
|
+
const statusPane = { top: bottomTop, left: 0, width: termCols };
|
|
8
|
+
const separatorPane = { top: bottomTop + 1, left: 0, width: termCols };
|
|
9
|
+
const inputPane = { top: bottomTop + 2, left: 0, width: termCols };
|
|
10
|
+
const inputSepPane = { top: bottomTop + 3, left: 0, width: termCols };
|
|
11
|
+
const dashboardPane = { top: bottomTop + 4, left: 0, width: termCols };
|
|
12
|
+
|
|
13
|
+
if (agentCount <= 0) {
|
|
14
|
+
return {
|
|
15
|
+
separatorPane,
|
|
16
|
+
statusPane,
|
|
17
|
+
inputPane,
|
|
18
|
+
inputSepPane,
|
|
19
|
+
dashboardPane,
|
|
20
|
+
chatPane: { top: 0, left: 0, width: termCols, height: contentHeight },
|
|
21
|
+
agentPanes: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const chatWidth = Math.floor(termCols / 3);
|
|
26
|
+
const rightLeft = chatWidth + 1;
|
|
27
|
+
const rightWidth = termCols - chatWidth - 1;
|
|
28
|
+
|
|
29
|
+
const chatPane = { top: 0, left: 0, width: chatWidth, height: contentHeight };
|
|
30
|
+
|
|
31
|
+
if (rightWidth < 4 || contentHeight < 3) {
|
|
32
|
+
return {
|
|
33
|
+
separatorPane,
|
|
34
|
+
statusPane,
|
|
35
|
+
inputPane,
|
|
36
|
+
inputSepPane,
|
|
37
|
+
dashboardPane,
|
|
38
|
+
chatPane: { top: 0, left: 0, width: termCols, height: contentHeight },
|
|
39
|
+
agentPanes: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const agentPanes = layoutAgentPanes(rightLeft, rightWidth, contentHeight, agentCount, 0);
|
|
44
|
+
return { separatorPane, statusPane, inputPane, inputSepPane, dashboardPane, chatPane, agentPanes };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function layoutAgentPanes(left, width, height, count, topOffset = 0) {
|
|
48
|
+
if (count === 1) {
|
|
49
|
+
return [{ top: topOffset, left, width, height }];
|
|
50
|
+
}
|
|
51
|
+
if (count === 2) {
|
|
52
|
+
const h1 = Math.floor(height / 2);
|
|
53
|
+
return [
|
|
54
|
+
{ top: topOffset, left, width, height: h1 },
|
|
55
|
+
{ top: topOffset + h1, left, width, height: height - h1 },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rowCount = Math.ceil(count / 2);
|
|
60
|
+
const rowHeight = Math.floor(height / rowCount);
|
|
61
|
+
const panes = [];
|
|
62
|
+
let placed = 0;
|
|
63
|
+
|
|
64
|
+
for (let row = 0; row < rowCount; row++) {
|
|
65
|
+
const rowTop = topOffset + row * rowHeight;
|
|
66
|
+
const actualHeight = row === rowCount - 1 ? height - row * rowHeight : rowHeight;
|
|
67
|
+
const remaining = count - placed;
|
|
68
|
+
const isOddRow = remaining % 2 === 1 && row === 0 && count % 2 === 1;
|
|
69
|
+
|
|
70
|
+
if (isOddRow) {
|
|
71
|
+
panes.push({ top: rowTop, left, width, height: actualHeight });
|
|
72
|
+
placed++;
|
|
73
|
+
} else {
|
|
74
|
+
const halfWidth = Math.floor(width / 2);
|
|
75
|
+
panes.push({ top: rowTop, left, width: halfWidth, height: actualHeight });
|
|
76
|
+
panes.push({ top: rowTop, left: left + halfWidth + 1, width: width - halfWidth - 1, height: actualHeight });
|
|
77
|
+
placed += 2;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return panes;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { calculatePaneLayout };
|