u-foo 2.3.31 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -213
- package/README.zh-CN.md +151 -197
- package/SKILLS/ufoo/SKILL.md +8 -8
- package/bin/uagy.js +69 -0
- package/bin/uclaude.js +2 -2
- package/bin/ucode.js +4 -4
- package/bin/ucodex.js +2 -2
- package/bin/ufoo.js +5 -23
- package/modules/AGENTS.template.md +1 -1
- package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
- package/package.json +9 -5
- package/scripts/chat-app-smoke.js +30 -0
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +23 -0
- package/scripts/ink-smoke.js +30 -0
- package/scripts/ucode-app-smoke.js +36 -0
- package/src/{agent → agents/activity}/activityDetector.js +39 -2
- package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
- package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
- package/src/{agent → agents/activity}/activityTracker.js +1 -1
- package/src/agents/activity/index.js +8 -0
- package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
- package/src/agents/controller/index.js +8 -0
- package/src/{agent → agents/controller}/loopObservability.js +2 -2
- package/src/{agent → agents/controller}/loopRuntime.js +1 -1
- package/src/{agent → agents/controller}/ufooAgent.js +9 -9
- package/src/agents/index.js +10 -0
- package/src/agents/internal/index.js +3 -0
- package/src/{agent → agents/internal}/internalRunner.js +45 -22
- package/src/agents/launch/agyConversation.js +159 -0
- package/src/agents/launch/index.js +12 -0
- package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
- package/src/{agent → agents/launch}/launcher.js +64 -21
- package/src/{agent → agents/launch}/notifier.js +23 -12
- package/src/{agent → agents/launch}/ptyRunner.js +44 -12
- package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
- package/src/{agent → agents/launch}/publisherRouting.js +1 -1
- package/src/{agent → agents/launch}/readyDetector.js +23 -0
- package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
- package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
- package/src/agents/prompts/index.js +8 -0
- package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
- package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
- package/src/agents/providers/index.js +13 -0
- package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
- package/src/{chat → app/chat}/agentSockets.js +1 -1
- package/src/{chat → app/chat}/commandExecutor.js +56 -28
- package/src/{chat → app/chat}/commands.js +119 -5
- package/src/{chat → app/chat}/daemonConnection.js +1 -1
- package/src/{chat → app/chat}/daemonMessageRouter.js +54 -4
- package/src/{chat → app/chat}/daemonTransport.js +2 -1
- package/src/{chat → app/chat}/dashboardView.js +2 -21
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +38 -13
- package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
- package/src/app/chat/multiWindow/index.js +268 -0
- package/src/app/chat/multiWindow/paneLayout.js +84 -0
- package/src/app/chat/multiWindow/paneManager.js +299 -0
- package/src/app/chat/multiWindow/renderer.js +384 -0
- package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
- package/src/{chat → app/chat}/projectCloseController.js +1 -1
- package/src/app/chat/shellCommand.js +42 -0
- package/src/{chat → app/chat}/transport.js +16 -3
- package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
- package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
- package/src/{init/index.js → app/cli/features/init.js} +14 -32
- package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
- package/src/app/cli/index.js +9 -0
- package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
- package/src/{cli.js → app/cli/run.js} +62 -59
- package/src/app/index.js +6 -0
- package/src/code/agent.js +10 -9
- package/src/code/index.js +2 -0
- package/src/code/launcher/index.js +9 -0
- package/src/{agent → code/launcher}/ucode.js +7 -8
- package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
- package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
- package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
- package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
- package/src/code/nativeRunner.js +4 -4
- package/src/code/taskDecomposer.js +5 -4
- package/src/code/tui.js +39 -1997
- package/src/config.js +15 -2
- package/src/{bus → coordination/bus}/activate.js +2 -2
- package/src/{bus → coordination/bus}/daemon.js +15 -5
- package/src/coordination/bus/envelope.js +173 -0
- package/src/{bus → coordination/bus}/index.js +7 -3
- package/src/{bus → coordination/bus}/inject.js +11 -3
- package/src/{bus → coordination/bus}/message.js +1 -1
- package/src/coordination/bus/messageMeta.js +130 -0
- package/src/coordination/bus/promptEnvelope.js +65 -0
- package/src/{bus → coordination/bus}/shake.js +1 -1
- package/src/{bus → coordination/bus}/store.js +3 -3
- package/src/{bus → coordination/bus}/subscriber.js +2 -2
- package/src/{bus → coordination/bus}/utils.js +2 -2
- package/src/{history → coordination/history}/inputTimeline.js +5 -5
- package/src/coordination/index.js +10 -0
- package/src/{memory → coordination/memory}/historySearch.js +1 -1
- package/src/{memory → coordination/memory}/index.js +3 -3
- package/src/{report → coordination/report}/store.js +2 -2
- package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +43 -0
- package/src/{status → coordination/status}/index.js +3 -3
- package/src/online/bridge.js +2 -2
- package/src/{controller → orchestration/controller}/flags.js +1 -1
- package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
- package/src/orchestration/controller/index.js +10 -0
- package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
- package/src/orchestration/groups/bootstrap.js +3 -0
- package/src/orchestration/groups/index.js +10 -0
- package/src/orchestration/groups/promptProfiles.js +3 -0
- package/src/{group → orchestration/groups}/templates.js +1 -1
- package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
- package/src/orchestration/index.js +7 -0
- package/src/orchestration/solo/index.js +3 -0
- package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
- package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
- package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
- package/src/{daemon → runtime/daemon}/index.js +273 -79
- package/src/{daemon → runtime/daemon}/ipcServer.js +24 -2
- package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
- package/src/{daemon → runtime/daemon}/ops.js +48 -61
- package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
- package/src/{daemon → runtime/daemon}/promptRequest.js +13 -8
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +12 -5
- package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
- package/src/{daemon → runtime/daemon}/status.js +5 -5
- package/src/runtime/index.js +10 -0
- package/src/runtime/process/nodeExecutable.js +26 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{projects → runtime/projects}/runtimes.js +1 -1
- package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
- package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
- package/src/tools/handlers/common.js +1 -1
- package/src/tools/handlers/listAgents.js +1 -1
- package/src/tools/handlers/memory.js +3 -3
- package/src/tools/handlers/readBusSummary.js +1 -1
- package/src/tools/handlers/readOpenDecisions.js +1 -1
- package/src/tools/handlers/readProjectRegistry.js +1 -1
- package/src/tools/handlers/readPromptHistory.js +2 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/MIGRATION.md +336 -0
- package/src/ui/format/index.js +974 -0
- package/src/ui/index.js +9 -0
- package/src/ui/ink/ChatApp.js +3674 -0
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/ink/InkDemo.js +96 -0
- package/src/ui/ink/MultilineInput.js +612 -0
- package/src/ui/ink/UcodeApp.js +822 -0
- package/src/ui/ink/agentMirror.js +730 -0
- package/src/ui/ink/chatReducer.js +359 -0
- package/src/ui/runInk.js +57 -0
- package/src/bus/messageMeta.js +0 -52
- package/src/chat/agentViewController.js +0 -1072
- package/src/chat/chatLogController.js +0 -138
- package/src/chat/completionController.js +0 -533
- package/src/chat/dashboardKeyController.js +0 -573
- package/src/chat/index.js +0 -2214
- package/src/chat/inputHistoryController.js +0 -135
- package/src/chat/inputListenerController.js +0 -470
- package/src/chat/layout.js +0 -186
- package/src/chat/pasteController.js +0 -81
- package/src/chat/statusLineController.js +0 -223
- package/src/chat/streamTracker.js +0 -156
- package/src/code/config +0 -0
- package/src/daemon/providerSessions.js +0 -488
- package/src/terminal/adapters/internalPtyAdapter.js +0 -42
- /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
- /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
- /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
- /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
- /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
- /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
- /package/src/{agent → agents/providers}/credentials/index.js +0 -0
- /package/src/{chat → app/chat}/agentBar.js +0 -0
- /package/src/{chat → app/chat}/agentDirectory.js +0 -0
- /package/src/{chat → app/chat}/cronScheduler.js +0 -0
- /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
- /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
- /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.js +0 -0
- /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
- /package/src/{chat → app/chat}/settingsController.js +0 -0
- /package/src/{chat → app/chat}/text.js +0 -0
- /package/src/{chat → app/chat}/transientAgentState.js +0 -0
- /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
- /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
- /package/src/{bus → coordination/bus}/nickname.js +0 -0
- /package/src/{bus → coordination/bus}/queue.js +0 -0
- /package/src/{context → coordination/context}/decisions.js +0 -0
- /package/src/{context → coordination/context}/doctor.js +0 -0
- /package/src/{context → coordination/context}/index.js +0 -0
- /package/src/{context → coordination/context}/sync.js +0 -0
- /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
- /package/src/{ufoo → coordination/state}/paths.js +0 -0
- /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
- /package/src/{group → orchestration/groups}/diagram.js +0 -0
- /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
- /package/src/{solo → orchestration/solo}/commands.js +0 -0
- /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
- /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
- /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
- /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
- /package/src/{projects → runtime/projects}/identity.js +0 -0
- /package/src/{projects → runtime/projects}/index.js +0 -0
- /package/src/{projects → runtime/projects}/projectId.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/detect.js +0 -0
- /package/src/{terminal → runtime/terminal}/index.js +0 -0
- /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
- /package/src/{utils → ui/format}/banner.js +0 -0
- /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
|
@@ -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 };
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const net = require("net");
|
|
3
|
+
const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../../../runtime/contracts/ptySocketContract");
|
|
4
|
+
const { createVirtualTerminal } = require("./virtualTerminal");
|
|
5
|
+
|
|
6
|
+
function createPaneManager(options = {}) {
|
|
7
|
+
const {
|
|
8
|
+
getInjectSockPath = () => "",
|
|
9
|
+
onPaneOutput = () => {},
|
|
10
|
+
onInternalSubmit = () => {},
|
|
11
|
+
} = options;
|
|
12
|
+
|
|
13
|
+
const panes = new Map();
|
|
14
|
+
let focusedAgent = null;
|
|
15
|
+
|
|
16
|
+
function addAgent(agentId, cols, rows, options = {}) {
|
|
17
|
+
if (panes.has(agentId)) return;
|
|
18
|
+
const vt = createVirtualTerminal(cols, rows);
|
|
19
|
+
const mode = options.mode === "internal" ? "internal" : "socket";
|
|
20
|
+
const pane = {
|
|
21
|
+
agentId,
|
|
22
|
+
mode,
|
|
23
|
+
vt,
|
|
24
|
+
outputClient: null,
|
|
25
|
+
inputClient: null,
|
|
26
|
+
buffer: "",
|
|
27
|
+
internalInput: "",
|
|
28
|
+
internalCursor: 0,
|
|
29
|
+
};
|
|
30
|
+
panes.set(agentId, pane);
|
|
31
|
+
if (mode === "internal") {
|
|
32
|
+
const initialOutput = Array.isArray(options.initialLines)
|
|
33
|
+
? options.initialLines.join("\r\n")
|
|
34
|
+
: String(options.initialOutput || "");
|
|
35
|
+
if (initialOutput) pane.vt.write(`${initialOutput}\r\n`);
|
|
36
|
+
onPaneOutput(pane.agentId);
|
|
37
|
+
} else {
|
|
38
|
+
connectOutput(pane);
|
|
39
|
+
}
|
|
40
|
+
if (!focusedAgent) focusedAgent = agentId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function removeAgent(agentId) {
|
|
44
|
+
const pane = panes.get(agentId);
|
|
45
|
+
if (!pane) return;
|
|
46
|
+
disconnect(pane);
|
|
47
|
+
panes.delete(agentId);
|
|
48
|
+
if (focusedAgent === agentId) {
|
|
49
|
+
const keys = [...panes.keys()];
|
|
50
|
+
focusedAgent = keys.length > 0 ? keys[0] : null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function connectOutput(pane) {
|
|
55
|
+
const sockPath = getInjectSockPath(pane.agentId);
|
|
56
|
+
if (!sockPath || !fs.existsSync(sockPath)) {
|
|
57
|
+
pane.vt.write("\x1b[33m[waiting]\x1b[0m inject.sock not found\r\n");
|
|
58
|
+
onPaneOutput(pane.agentId);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const client = net.createConnection(sockPath, () => {
|
|
64
|
+
const { cols, rows } = pane.vt.getScreen();
|
|
65
|
+
client.write(JSON.stringify({
|
|
66
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
|
|
67
|
+
cols,
|
|
68
|
+
rows,
|
|
69
|
+
}) + "\n");
|
|
70
|
+
client.write(JSON.stringify({
|
|
71
|
+
type: PTY_SOCKET_MESSAGE_TYPES.SUBSCRIBE,
|
|
72
|
+
mode: PTY_SOCKET_SUBSCRIBE_MODES.FULL,
|
|
73
|
+
}) + "\n");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
client.on("data", (data) => {
|
|
77
|
+
pane.buffer += data.toString("utf8");
|
|
78
|
+
const lines = pane.buffer.split("\n");
|
|
79
|
+
pane.buffer = lines.pop() || "";
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
82
|
+
try {
|
|
83
|
+
const msg = JSON.parse(line);
|
|
84
|
+
if (msg.type === PTY_SOCKET_MESSAGE_TYPES.OUTPUT) {
|
|
85
|
+
if (msg.data) {
|
|
86
|
+
pane.vt.write(msg.data);
|
|
87
|
+
onPaneOutput(pane.agentId);
|
|
88
|
+
}
|
|
89
|
+
} else if (msg.type === PTY_SOCKET_MESSAGE_TYPES.REPLAY) {
|
|
90
|
+
if (msg.data) {
|
|
91
|
+
pane.vt.write(msg.data);
|
|
92
|
+
onPaneOutput(pane.agentId);
|
|
93
|
+
}
|
|
94
|
+
} else if (msg.type === PTY_SOCKET_MESSAGE_TYPES.SNAPSHOT) {
|
|
95
|
+
if (msg.data) {
|
|
96
|
+
pane.vt.write(msg.data);
|
|
97
|
+
onPaneOutput(pane.agentId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore malformed messages or render errors
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
client.on("error", (err) => {
|
|
107
|
+
pane.outputClient = null;
|
|
108
|
+
pane.vt.write(`\r\n\x1b[31m[connection error]\x1b[0m ${err && err.message ? err.message : "socket error"}\r\n`);
|
|
109
|
+
onPaneOutput(pane.agentId);
|
|
110
|
+
});
|
|
111
|
+
client.on("close", () => {
|
|
112
|
+
pane.outputClient = null;
|
|
113
|
+
pane.vt.write("\r\n\x1b[33m[disconnected]\x1b[0m\r\n");
|
|
114
|
+
onPaneOutput(pane.agentId);
|
|
115
|
+
});
|
|
116
|
+
pane.outputClient = client;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
pane.vt.write(`\x1b[31m[connection error]\x1b[0m ${err && err.message ? err.message : "connection failed"}\r\n`);
|
|
119
|
+
onPaneOutput(pane.agentId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function disconnect(pane) {
|
|
124
|
+
if (pane.outputClient) {
|
|
125
|
+
try {
|
|
126
|
+
pane.outputClient.removeAllListeners();
|
|
127
|
+
pane.outputClient.destroy();
|
|
128
|
+
} catch {}
|
|
129
|
+
pane.outputClient = null;
|
|
130
|
+
}
|
|
131
|
+
if (pane.inputClient) {
|
|
132
|
+
try {
|
|
133
|
+
pane.inputClient.removeAllListeners();
|
|
134
|
+
pane.inputClient.destroy();
|
|
135
|
+
} catch {}
|
|
136
|
+
pane.inputClient = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sendInput(data) {
|
|
141
|
+
if (!focusedAgent) return;
|
|
142
|
+
const pane = panes.get(focusedAgent);
|
|
143
|
+
if (!pane) return;
|
|
144
|
+
if (pane.mode === "internal") {
|
|
145
|
+
handleInternalInput(pane, data);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const sockPath = getInjectSockPath(pane.agentId);
|
|
149
|
+
if (!sockPath) return;
|
|
150
|
+
|
|
151
|
+
if (!pane.inputClient || pane.inputClient.destroyed) {
|
|
152
|
+
try {
|
|
153
|
+
const client = net.createConnection(sockPath);
|
|
154
|
+
pane.inputClient = client;
|
|
155
|
+
client.on("error", () => { pane.inputClient = null; });
|
|
156
|
+
client.on("close", () => { pane.inputClient = null; });
|
|
157
|
+
client.once("connect", () => {
|
|
158
|
+
try {
|
|
159
|
+
client.write(JSON.stringify({
|
|
160
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RAW,
|
|
161
|
+
data,
|
|
162
|
+
}) + "\n");
|
|
163
|
+
} catch {}
|
|
164
|
+
});
|
|
165
|
+
} catch { return; }
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
pane.inputClient.write(JSON.stringify({
|
|
171
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RAW,
|
|
172
|
+
data,
|
|
173
|
+
}) + "\n");
|
|
174
|
+
} catch {
|
|
175
|
+
pane.inputClient = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function previousInputBoundary(text = "", cursor = 0) {
|
|
180
|
+
const source = String(text || "");
|
|
181
|
+
const target = Math.max(0, Math.min(source.length, cursor));
|
|
182
|
+
let previous = 0;
|
|
183
|
+
for (const char of Array.from(source)) {
|
|
184
|
+
const next = previous + char.length;
|
|
185
|
+
if (next >= target) break;
|
|
186
|
+
previous = next;
|
|
187
|
+
}
|
|
188
|
+
return previous;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function handleInternalInput(pane, data) {
|
|
192
|
+
const raw = String(data || "");
|
|
193
|
+
if (!raw) return;
|
|
194
|
+
if (raw === "\r" || raw === "\n") {
|
|
195
|
+
const message = String(pane.internalInput || "").trim();
|
|
196
|
+
pane.internalInput = "";
|
|
197
|
+
pane.internalCursor = 0;
|
|
198
|
+
if (message) {
|
|
199
|
+
pane.vt.write(`\r\n> ${message.replace(/\r?\n/g, "\r\n ")}\r\n`);
|
|
200
|
+
try { onInternalSubmit(pane.agentId, message); } catch {}
|
|
201
|
+
}
|
|
202
|
+
onPaneOutput(pane.agentId);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (raw === "\x7f" || raw === "\b" || raw === "\x08") {
|
|
206
|
+
if (pane.internalCursor > 0) {
|
|
207
|
+
const start = previousInputBoundary(pane.internalInput, pane.internalCursor);
|
|
208
|
+
pane.internalInput = pane.internalInput.slice(0, start) + pane.internalInput.slice(pane.internalCursor);
|
|
209
|
+
pane.internalCursor = start;
|
|
210
|
+
onPaneOutput(pane.agentId);
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (raw === "\x1b[D") {
|
|
215
|
+
pane.internalCursor = previousInputBoundary(pane.internalInput, pane.internalCursor);
|
|
216
|
+
onPaneOutput(pane.agentId);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (raw === "\x1b[C") {
|
|
220
|
+
const tail = pane.internalInput.slice(pane.internalCursor);
|
|
221
|
+
const nextChar = Array.from(tail)[0] || "";
|
|
222
|
+
pane.internalCursor = Math.min(pane.internalInput.length, pane.internalCursor + nextChar.length);
|
|
223
|
+
onPaneOutput(pane.agentId);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (raw === "\x1b[A" || raw === "\x1b[B" || raw === "\t" || raw === "\x1b") return;
|
|
227
|
+
|
|
228
|
+
const clean = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/g, "");
|
|
229
|
+
if (!clean) return;
|
|
230
|
+
pane.internalInput = pane.internalInput.slice(0, pane.internalCursor) + clean + pane.internalInput.slice(pane.internalCursor);
|
|
231
|
+
pane.internalCursor += clean.length;
|
|
232
|
+
onPaneOutput(pane.agentId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function sendResize(agentId, cols, rows) {
|
|
236
|
+
const pane = panes.get(agentId);
|
|
237
|
+
if (!pane) return;
|
|
238
|
+
pane.vt.resize(cols, rows);
|
|
239
|
+
if (pane.mode === "internal") return;
|
|
240
|
+
const sockPath = getInjectSockPath(pane.agentId);
|
|
241
|
+
if ((!pane.outputClient || pane.outputClient.destroyed) && sockPath && fs.existsSync(sockPath)) {
|
|
242
|
+
connectOutput(pane);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (pane.outputClient && !pane.outputClient.destroyed) {
|
|
246
|
+
try {
|
|
247
|
+
pane.outputClient.write(JSON.stringify({
|
|
248
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
|
|
249
|
+
cols,
|
|
250
|
+
rows,
|
|
251
|
+
}) + "\n");
|
|
252
|
+
} catch {}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function cycleFocus() {
|
|
257
|
+
const keys = [...panes.keys()];
|
|
258
|
+
if (keys.length === 0) return;
|
|
259
|
+
const idx = keys.indexOf(focusedAgent);
|
|
260
|
+
focusedAgent = keys[(idx + 1) % keys.length];
|
|
261
|
+
return focusedAgent;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function getFocused() { return focusedAgent; }
|
|
265
|
+
function setFocused(agentId) { if (panes.has(agentId)) focusedAgent = agentId; }
|
|
266
|
+
function getPane(agentId) { return panes.get(agentId) || null; }
|
|
267
|
+
function getAllPanes() { return [...panes.values()]; }
|
|
268
|
+
function getAgentIds() { return [...panes.keys()]; }
|
|
269
|
+
function writeToPane(agentId, data) {
|
|
270
|
+
const pane = panes.get(agentId);
|
|
271
|
+
if (!pane) return false;
|
|
272
|
+
pane.vt.write(data);
|
|
273
|
+
onPaneOutput(pane.agentId);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function disconnectAll() {
|
|
278
|
+
for (const pane of panes.values()) disconnect(pane);
|
|
279
|
+
panes.clear();
|
|
280
|
+
focusedAgent = null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
addAgent,
|
|
285
|
+
removeAgent,
|
|
286
|
+
sendInput,
|
|
287
|
+
sendResize,
|
|
288
|
+
cycleFocus,
|
|
289
|
+
getFocused,
|
|
290
|
+
setFocused,
|
|
291
|
+
getPane,
|
|
292
|
+
getAllPanes,
|
|
293
|
+
getAgentIds,
|
|
294
|
+
writeToPane,
|
|
295
|
+
disconnectAll,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = { createPaneManager };
|