u-foo 1.0.6 → 1.1.9
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 +44 -4
- package/SKILLS/ufoo/SKILL.md +17 -2
- package/SKILLS/uinit/SKILL.md +8 -3
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +4 -0
- package/modules/AGENTS.template.md +14 -4
- package/modules/bus/README.md +8 -5
- package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
- package/modules/context/SKILLS/uctx/SKILL.md +3 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +12 -3
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +20 -49
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +524 -31
- package/src/agent/internalRunner.js +76 -9
- package/src/agent/launcher.js +97 -45
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +144 -4
- package/src/agent/ptyRunner.js +480 -10
- package/src/agent/ptyWrapper.js +28 -3
- package/src/agent/readyDetector.js +16 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +11 -2
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +27 -11
- package/src/bus/daemon.js +133 -5
- package/src/bus/index.js +137 -80
- package/src/bus/inject.js +47 -17
- package/src/bus/message.js +145 -17
- package/src/bus/nickname.js +3 -1
- package/src/bus/queue.js +6 -1
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +20 -4
- package/src/bus/utils.js +9 -3
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +935 -2909
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +741 -238
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +47 -1
- package/src/context/decisions.js +12 -2
- package/src/context/index.js +18 -1
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +661 -488
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +417 -179
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +32 -17
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +2 -5
- package/src/daemon/status.js +24 -1
- package/src/init/index.js +68 -14
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/status/index.js +50 -17
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/ufoo/agentsStore.js +69 -3
- package/src/utils/banner.js +5 -2
- package/scripts/.archived/bash-to-js-migration/README.md +0 -46
- package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
- package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
- package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
- package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
- package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
- package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
- package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
- package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
- package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
- package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
- package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
- package/scripts/banner.sh +0 -2
- package/src/bus/API_DESIGN.md +0 -204
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const net = require("net");
|
|
3
|
+
const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../shared/ptySocketContract");
|
|
4
|
+
|
|
5
|
+
function createAgentSockets(options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
onTermWrite = () => {},
|
|
8
|
+
onPlaceCursor = () => {},
|
|
9
|
+
isAgentView = () => false,
|
|
10
|
+
isBusMode = () => false,
|
|
11
|
+
getViewingAgent = () => "",
|
|
12
|
+
sendBusRaw = () => {},
|
|
13
|
+
} = options;
|
|
14
|
+
|
|
15
|
+
let outputClient = null;
|
|
16
|
+
let outputBuffer = "";
|
|
17
|
+
let inputClient = null;
|
|
18
|
+
let pendingResize = null;
|
|
19
|
+
|
|
20
|
+
function requestSnapshot(mode = PTY_SOCKET_SUBSCRIBE_MODES.SCREEN) {
|
|
21
|
+
if (!outputClient || outputClient.destroyed) return false;
|
|
22
|
+
const safeMode = mode === PTY_SOCKET_SUBSCRIBE_MODES.FULL
|
|
23
|
+
? PTY_SOCKET_SUBSCRIBE_MODES.FULL
|
|
24
|
+
: PTY_SOCKET_SUBSCRIBE_MODES.SCREEN;
|
|
25
|
+
try {
|
|
26
|
+
outputClient.write(JSON.stringify({
|
|
27
|
+
type: PTY_SOCKET_MESSAGE_TYPES.SUBSCRIBE,
|
|
28
|
+
mode: safeMode,
|
|
29
|
+
}) + "\n");
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requestScreenSnapshot() {
|
|
37
|
+
return requestSnapshot(PTY_SOCKET_SUBSCRIBE_MODES.SCREEN);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function connectOutput(sockPath) {
|
|
41
|
+
if (outputClient) {
|
|
42
|
+
disconnectOutput();
|
|
43
|
+
}
|
|
44
|
+
outputBuffer = "";
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(sockPath)) {
|
|
47
|
+
onTermWrite("\x1b[1;31m[Error]\x1b[0m inject.sock not found\r\n");
|
|
48
|
+
onTermWrite("\x1b[33m[Hint]\x1b[0m Agent may not be running in terminal mode\r\n");
|
|
49
|
+
onTermWrite("Press Esc to return\r\n");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
outputClient = net.createConnection(sockPath, () => {
|
|
55
|
+
outputClient.write(JSON.stringify({
|
|
56
|
+
type: PTY_SOCKET_MESSAGE_TYPES.SUBSCRIBE,
|
|
57
|
+
mode: PTY_SOCKET_SUBSCRIBE_MODES.FULL,
|
|
58
|
+
}) + "\n");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const connectTimeout = setTimeout(() => {
|
|
62
|
+
if (outputClient && !outputClient.connecting) return;
|
|
63
|
+
onTermWrite("\x1b[1;31m[Timeout]\x1b[0m Could not connect\r\nPress Esc to return\r\n");
|
|
64
|
+
disconnectOutput();
|
|
65
|
+
}, 5000);
|
|
66
|
+
|
|
67
|
+
outputClient.on("connect", () => {
|
|
68
|
+
clearTimeout(connectTimeout);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
outputClient.on("data", (data) => {
|
|
72
|
+
outputBuffer += data.toString("utf8");
|
|
73
|
+
const lines = outputBuffer.split("\n");
|
|
74
|
+
outputBuffer = lines.pop() || "";
|
|
75
|
+
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (!line.trim()) continue;
|
|
78
|
+
try {
|
|
79
|
+
const msg = JSON.parse(line);
|
|
80
|
+
if (msg.type === PTY_SOCKET_MESSAGE_TYPES.OUTPUT) {
|
|
81
|
+
if (msg.data) onTermWrite(msg.data);
|
|
82
|
+
} else if (msg.type === PTY_SOCKET_MESSAGE_TYPES.REPLAY ||
|
|
83
|
+
msg.type === PTY_SOCKET_MESSAGE_TYPES.SNAPSHOT) {
|
|
84
|
+
if (msg.data) onTermWrite(msg.data);
|
|
85
|
+
if (msg.type === PTY_SOCKET_MESSAGE_TYPES.SNAPSHOT && msg.cursor) {
|
|
86
|
+
onPlaceCursor(msg.cursor);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// ignore malformed messages
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
outputClient.on("error", (err) => {
|
|
96
|
+
if (isAgentView()) {
|
|
97
|
+
onTermWrite(`\r\n\x1b[1;31m[Connection error]\x1b[0m ${err.message}\r\nPress Esc to return\r\n`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
outputClient.on("close", () => {
|
|
102
|
+
outputClient = null;
|
|
103
|
+
if (isAgentView()) {
|
|
104
|
+
onTermWrite("\r\n\x1b[1;33m[Agent disconnected]\x1b[0m\r\nPress Esc to return\r\n");
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
onTermWrite(`\x1b[1;31m[Error]\x1b[0m ${err.message}\r\nPress Esc to return\r\n`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function disconnectOutput() {
|
|
113
|
+
if (outputClient) {
|
|
114
|
+
try {
|
|
115
|
+
outputClient.removeAllListeners();
|
|
116
|
+
outputClient.destroy();
|
|
117
|
+
} catch {
|
|
118
|
+
// ignore
|
|
119
|
+
}
|
|
120
|
+
outputClient = null;
|
|
121
|
+
}
|
|
122
|
+
outputBuffer = "";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function connectInput(sockPath) {
|
|
126
|
+
if (inputClient) {
|
|
127
|
+
disconnectInput();
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
inputClient = net.createConnection(sockPath);
|
|
131
|
+
inputClient.on("connect", () => {
|
|
132
|
+
if (pendingResize) {
|
|
133
|
+
const { cols, rows } = pendingResize;
|
|
134
|
+
pendingResize = null;
|
|
135
|
+
try {
|
|
136
|
+
inputClient.write(JSON.stringify({
|
|
137
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
|
|
138
|
+
cols,
|
|
139
|
+
rows,
|
|
140
|
+
}) + "\n");
|
|
141
|
+
} catch {
|
|
142
|
+
// ignore write errors
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
inputClient.on("error", () => {
|
|
147
|
+
inputClient = null;
|
|
148
|
+
});
|
|
149
|
+
inputClient.on("close", () => {
|
|
150
|
+
inputClient = null;
|
|
151
|
+
});
|
|
152
|
+
} catch {
|
|
153
|
+
inputClient = null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function disconnectInput() {
|
|
158
|
+
if (inputClient) {
|
|
159
|
+
try {
|
|
160
|
+
inputClient.removeAllListeners();
|
|
161
|
+
inputClient.destroy();
|
|
162
|
+
} catch {
|
|
163
|
+
// ignore
|
|
164
|
+
}
|
|
165
|
+
inputClient = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function sendRaw(data) {
|
|
170
|
+
if (inputClient && !inputClient.destroyed) {
|
|
171
|
+
try {
|
|
172
|
+
inputClient.write(JSON.stringify({
|
|
173
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RAW,
|
|
174
|
+
data,
|
|
175
|
+
}) + "\n");
|
|
176
|
+
return;
|
|
177
|
+
} catch {
|
|
178
|
+
// ignore write errors
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (isBusMode()) {
|
|
182
|
+
const viewingAgent = getViewingAgent();
|
|
183
|
+
if (viewingAgent) {
|
|
184
|
+
sendBusRaw(viewingAgent, data);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function sendResize(cols, rows) {
|
|
190
|
+
if (!inputClient || inputClient.destroyed) {
|
|
191
|
+
pendingResize = { cols, rows };
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
inputClient.write(JSON.stringify({
|
|
196
|
+
type: PTY_SOCKET_MESSAGE_TYPES.RESIZE,
|
|
197
|
+
cols,
|
|
198
|
+
rows,
|
|
199
|
+
}) + "\n");
|
|
200
|
+
} catch {
|
|
201
|
+
// ignore write errors
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function disconnectAll() {
|
|
206
|
+
disconnectOutput();
|
|
207
|
+
disconnectInput();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
connectOutput,
|
|
212
|
+
disconnectOutput,
|
|
213
|
+
connectInput,
|
|
214
|
+
disconnectInput,
|
|
215
|
+
disconnectAll,
|
|
216
|
+
sendRaw,
|
|
217
|
+
sendResize,
|
|
218
|
+
requestSnapshot,
|
|
219
|
+
requestScreenSnapshot,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
createAgentSockets,
|
|
225
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
function createAgentViewController(options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
screen,
|
|
4
|
+
input,
|
|
5
|
+
processStdout = process.stdout,
|
|
6
|
+
now = () => Date.now(),
|
|
7
|
+
setTimeoutFn = setTimeout,
|
|
8
|
+
computeAgentBar = () => ({ bar: "", windowStart: 0 }),
|
|
9
|
+
agentBarHints = { normal: "", dashboard: "" },
|
|
10
|
+
maxAgentWindow = 4,
|
|
11
|
+
getFocusMode = () => "input",
|
|
12
|
+
setFocusMode = () => {},
|
|
13
|
+
getSelectedAgentIndex = () => -1,
|
|
14
|
+
setSelectedAgentIndex = () => {},
|
|
15
|
+
getActiveAgents = () => [],
|
|
16
|
+
getAgentListWindowStart = () => 0,
|
|
17
|
+
setAgentListWindowStart = () => {},
|
|
18
|
+
getAgentLabel = (id) => id,
|
|
19
|
+
setDashboardView = () => {},
|
|
20
|
+
setScreenGrabKeys = (value) => {
|
|
21
|
+
if (screen) screen.grabKeys = Boolean(value);
|
|
22
|
+
},
|
|
23
|
+
clearTargetAgent = () => {},
|
|
24
|
+
renderDashboard = () => {},
|
|
25
|
+
focusInput = () => {},
|
|
26
|
+
resizeInput = () => {},
|
|
27
|
+
renderScreen = () => {},
|
|
28
|
+
getInjectSockPath = () => "",
|
|
29
|
+
connectAgentOutput = () => {},
|
|
30
|
+
disconnectAgentOutput = () => {},
|
|
31
|
+
connectAgentInput = () => {},
|
|
32
|
+
disconnectAgentInput = () => {},
|
|
33
|
+
sendRaw = () => {},
|
|
34
|
+
sendResize = () => {},
|
|
35
|
+
requestScreenSnapshot = () => {},
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
if (!screen || typeof screen.render !== "function") {
|
|
39
|
+
throw new Error("createAgentViewController requires screen.render");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let currentView = "main";
|
|
43
|
+
let viewingAgent = null;
|
|
44
|
+
let agentViewUsesBus = false;
|
|
45
|
+
let agentOutputSuppressed = false;
|
|
46
|
+
let agentBarVisible = false;
|
|
47
|
+
let detachedChildren = null;
|
|
48
|
+
let agentInputSuppressUntil = 0;
|
|
49
|
+
const originalRender = screen.render.bind(screen);
|
|
50
|
+
let renderFrozen = false;
|
|
51
|
+
|
|
52
|
+
screen.render = function wrappedRender() {
|
|
53
|
+
if (renderFrozen) return;
|
|
54
|
+
return originalRender();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function getRows() {
|
|
58
|
+
return processStdout.rows || 24;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getCols() {
|
|
62
|
+
return processStdout.columns || 80;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderAgentDashboard() {
|
|
66
|
+
if (!agentBarVisible && getFocusMode() !== "dashboard") return;
|
|
67
|
+
const rows = getRows();
|
|
68
|
+
const cols = getCols();
|
|
69
|
+
const hintText = getFocusMode() === "dashboard"
|
|
70
|
+
? agentBarHints.dashboard
|
|
71
|
+
: agentBarHints.normal;
|
|
72
|
+
const computed = computeAgentBar({
|
|
73
|
+
cols,
|
|
74
|
+
hintText,
|
|
75
|
+
focusMode: getFocusMode(),
|
|
76
|
+
selectedAgentIndex: getSelectedAgentIndex(),
|
|
77
|
+
activeAgents: getActiveAgents(),
|
|
78
|
+
viewingAgent,
|
|
79
|
+
agentListWindowStart: getAgentListWindowStart(),
|
|
80
|
+
maxAgentWindow,
|
|
81
|
+
getAgentLabel,
|
|
82
|
+
});
|
|
83
|
+
setAgentListWindowStart(computed.windowStart);
|
|
84
|
+
processStdout.write(`\x1b7\x1b[${rows};1H${computed.bar}\x1b8`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setAgentBarVisible(visible) {
|
|
88
|
+
const next = Boolean(visible);
|
|
89
|
+
if (agentBarVisible === next) return;
|
|
90
|
+
agentBarVisible = next;
|
|
91
|
+
const rows = getRows();
|
|
92
|
+
if (agentBarVisible) {
|
|
93
|
+
processStdout.write(`\x1b[1;${rows - 1}r`);
|
|
94
|
+
renderAgentDashboard();
|
|
95
|
+
} else {
|
|
96
|
+
processStdout.write(`\x1b[1;${rows}r`);
|
|
97
|
+
processStdout.write(`\x1b7\x1b[${rows};1H\x1b[2K\x1b8`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function enterAgentView(agentId, options = {}) {
|
|
102
|
+
if (currentView === "agent" && viewingAgent === agentId) return;
|
|
103
|
+
if (currentView === "agent") {
|
|
104
|
+
disconnectAgentOutput();
|
|
105
|
+
disconnectAgentInput();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
currentView = "agent";
|
|
109
|
+
viewingAgent = agentId;
|
|
110
|
+
setFocusMode("input");
|
|
111
|
+
|
|
112
|
+
detachedChildren = [...screen.children];
|
|
113
|
+
for (const child of detachedChildren) screen.remove(child);
|
|
114
|
+
|
|
115
|
+
renderFrozen = true;
|
|
116
|
+
|
|
117
|
+
const rows = getRows();
|
|
118
|
+
const cols = getCols();
|
|
119
|
+
processStdout.write("\x1b[2J\x1b[H");
|
|
120
|
+
processStdout.write(`\x1b[1;${rows - 1}r`);
|
|
121
|
+
processStdout.write("\x1b[H");
|
|
122
|
+
processStdout.write("\x1b[?25h");
|
|
123
|
+
setAgentBarVisible(true);
|
|
124
|
+
|
|
125
|
+
agentInputSuppressUntil = now() + 300;
|
|
126
|
+
agentViewUsesBus = Boolean(options.useBus);
|
|
127
|
+
if (!agentViewUsesBus) {
|
|
128
|
+
const sockPath = getInjectSockPath(agentId);
|
|
129
|
+
connectAgentOutput(sockPath);
|
|
130
|
+
connectAgentInput(sockPath);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setTimeoutFn(() => {
|
|
134
|
+
sendResize(cols, Math.max(1, rows - 1));
|
|
135
|
+
requestScreenSnapshot();
|
|
136
|
+
}, 120);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function exitAgentView() {
|
|
140
|
+
if (currentView !== "agent") return;
|
|
141
|
+
|
|
142
|
+
const rows = getRows();
|
|
143
|
+
const cols = getCols();
|
|
144
|
+
sendResize(cols, rows);
|
|
145
|
+
|
|
146
|
+
disconnectAgentOutput();
|
|
147
|
+
disconnectAgentInput();
|
|
148
|
+
agentViewUsesBus = false;
|
|
149
|
+
agentOutputSuppressed = false;
|
|
150
|
+
agentBarVisible = false;
|
|
151
|
+
|
|
152
|
+
currentView = "main";
|
|
153
|
+
viewingAgent = null;
|
|
154
|
+
|
|
155
|
+
processStdout.write(`\x1b[1;${rows}r`);
|
|
156
|
+
processStdout.write("\x1b[2J\x1b[H");
|
|
157
|
+
|
|
158
|
+
if (detachedChildren) {
|
|
159
|
+
for (const child of detachedChildren) screen.append(child);
|
|
160
|
+
detachedChildren = null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
renderFrozen = false;
|
|
164
|
+
setFocusMode("input");
|
|
165
|
+
setDashboardView("agents");
|
|
166
|
+
setSelectedAgentIndex(-1);
|
|
167
|
+
setScreenGrabKeys(false);
|
|
168
|
+
if (typeof screen.alloc === "function") {
|
|
169
|
+
screen.alloc();
|
|
170
|
+
}
|
|
171
|
+
clearTargetAgent();
|
|
172
|
+
renderDashboard();
|
|
173
|
+
focusInput();
|
|
174
|
+
resizeInput();
|
|
175
|
+
try {
|
|
176
|
+
if (screen.program && typeof screen.program.showCursor === "function") {
|
|
177
|
+
screen.program.showCursor();
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Ignore cursor restore errors.
|
|
181
|
+
}
|
|
182
|
+
if (input && typeof input._updateCursor === "function") {
|
|
183
|
+
input._updateCursor();
|
|
184
|
+
}
|
|
185
|
+
renderScreen();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function enterAgentDashboardMode() {
|
|
189
|
+
setFocusMode("dashboard");
|
|
190
|
+
setDashboardView("agents");
|
|
191
|
+
setSelectedAgentIndex(0);
|
|
192
|
+
setAgentBarVisible(true);
|
|
193
|
+
renderAgentDashboard();
|
|
194
|
+
agentOutputSuppressed = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function sendRawToAgent(data) {
|
|
198
|
+
sendRaw(data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function sendResizeToAgent(cols, rows) {
|
|
202
|
+
sendResize(cols, rows);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function requestAgentSnapshot() {
|
|
206
|
+
requestScreenSnapshot();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function writeToAgentTerm(text) {
|
|
210
|
+
if (!text) return;
|
|
211
|
+
if (currentView !== "agent") return;
|
|
212
|
+
if (agentOutputSuppressed) return;
|
|
213
|
+
|
|
214
|
+
const cleaned = text
|
|
215
|
+
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
216
|
+
.replace(/\x1b\[(?:[?>=]?[0-9]*c|[?]?6n|5n)/g, "");
|
|
217
|
+
if (cleaned) processStdout.write(cleaned);
|
|
218
|
+
if (agentBarVisible) {
|
|
219
|
+
const rows = getRows();
|
|
220
|
+
processStdout.write("\x1b7");
|
|
221
|
+
processStdout.write(`\x1b[1;${rows - 1}r`);
|
|
222
|
+
processStdout.write("\x1b8");
|
|
223
|
+
renderAgentDashboard();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function placeAgentCursor(cursor) {
|
|
228
|
+
if (!cursor || currentView !== "agent") return;
|
|
229
|
+
const rows = getRows();
|
|
230
|
+
const cols = getCols();
|
|
231
|
+
const row = Math.max(1, Math.min(rows - 1, (cursor.y || 0) + 1));
|
|
232
|
+
const col = Math.max(1, Math.min(cols, (cursor.x || 0) + 1));
|
|
233
|
+
processStdout.write(`\x1b[${row};${col}H\x1b[?25h`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function handleResizeInAgentView() {
|
|
237
|
+
if (currentView !== "agent") return false;
|
|
238
|
+
const rows = getRows();
|
|
239
|
+
const cols = getCols();
|
|
240
|
+
processStdout.write(`\x1b[1;${rows - 1}r`);
|
|
241
|
+
sendResize(cols, Math.max(1, rows - 1));
|
|
242
|
+
renderAgentDashboard();
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getCurrentView() {
|
|
247
|
+
return currentView;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getViewingAgent() {
|
|
251
|
+
return viewingAgent || "";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isAgentViewUsesBus() {
|
|
255
|
+
return agentViewUsesBus;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getAgentInputSuppressUntil() {
|
|
259
|
+
return agentInputSuppressUntil;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function getAgentOutputSuppressed() {
|
|
263
|
+
return agentOutputSuppressed;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function setAgentOutputSuppressed(value) {
|
|
267
|
+
agentOutputSuppressed = Boolean(value);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isAgentBarVisible() {
|
|
271
|
+
return agentBarVisible;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
getCurrentView,
|
|
276
|
+
getViewingAgent,
|
|
277
|
+
isAgentViewUsesBus,
|
|
278
|
+
getAgentInputSuppressUntil,
|
|
279
|
+
getAgentOutputSuppressed,
|
|
280
|
+
setAgentOutputSuppressed,
|
|
281
|
+
isAgentBarVisible,
|
|
282
|
+
renderAgentDashboard,
|
|
283
|
+
setAgentBarVisible,
|
|
284
|
+
enterAgentView,
|
|
285
|
+
exitAgentView,
|
|
286
|
+
enterAgentDashboardMode,
|
|
287
|
+
sendRawToAgent,
|
|
288
|
+
sendResizeToAgent,
|
|
289
|
+
requestAgentSnapshot,
|
|
290
|
+
writeToAgentTerm,
|
|
291
|
+
placeAgentCursor,
|
|
292
|
+
handleResizeInAgentView,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
createAgentViewController,
|
|
298
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
function createChatLogController(options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
logBox,
|
|
4
|
+
fsModule,
|
|
5
|
+
historyDir,
|
|
6
|
+
historyFile,
|
|
7
|
+
now = () => new Date().toISOString(),
|
|
8
|
+
} = options;
|
|
9
|
+
|
|
10
|
+
if (!logBox || typeof logBox.log !== "function") {
|
|
11
|
+
throw new Error("createChatLogController requires logBox.log");
|
|
12
|
+
}
|
|
13
|
+
if (!fsModule) {
|
|
14
|
+
throw new Error("createChatLogController requires fsModule");
|
|
15
|
+
}
|
|
16
|
+
if (!historyDir || !historyFile) {
|
|
17
|
+
throw new Error("createChatLogController requires historyDir/historyFile");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SPACED_TYPES = new Set(["user", "reply", "bus", "dispatch", "error"]);
|
|
21
|
+
let lastLogWasSpacer = false;
|
|
22
|
+
let hasLoggedAny = false;
|
|
23
|
+
|
|
24
|
+
function appendHistory(entry) {
|
|
25
|
+
fsModule.mkdirSync(historyDir, { recursive: true });
|
|
26
|
+
fsModule.appendFileSync(historyFile, `${JSON.stringify(entry)}\n`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function shouldSpace(type, text) {
|
|
30
|
+
if (SPACED_TYPES.has(type)) return true;
|
|
31
|
+
if (typeof text === "string" && /daemon/i.test(text)) return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeSpacer(writeHistory = true) {
|
|
36
|
+
if (lastLogWasSpacer || !hasLoggedAny) return;
|
|
37
|
+
logBox.log(" ");
|
|
38
|
+
if (writeHistory) {
|
|
39
|
+
appendHistory({
|
|
40
|
+
ts: now(),
|
|
41
|
+
type: "spacer",
|
|
42
|
+
text: "",
|
|
43
|
+
meta: {},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
lastLogWasSpacer = true;
|
|
47
|
+
hasLoggedAny = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function recordLog(type, text, meta = {}, writeHistory = true) {
|
|
51
|
+
if (type !== "spacer" && shouldSpace(type, text)) {
|
|
52
|
+
writeSpacer(writeHistory);
|
|
53
|
+
}
|
|
54
|
+
logBox.log(text);
|
|
55
|
+
if (writeHistory) {
|
|
56
|
+
appendHistory({
|
|
57
|
+
ts: now(),
|
|
58
|
+
type,
|
|
59
|
+
text,
|
|
60
|
+
meta,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
lastLogWasSpacer = false;
|
|
64
|
+
hasLoggedAny = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function logMessage(type, text, meta = {}) {
|
|
68
|
+
recordLog(type, text, meta, true);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function markStreamStart() {
|
|
72
|
+
lastLogWasSpacer = false;
|
|
73
|
+
hasLoggedAny = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadHistory(limit = 2000) {
|
|
77
|
+
try {
|
|
78
|
+
const raw = fsModule.readFileSync(historyFile, "utf8").trim();
|
|
79
|
+
if (!raw) return;
|
|
80
|
+
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
81
|
+
const items = lines.slice(-limit).map((line) => JSON.parse(line));
|
|
82
|
+
const hasSpacer = items.some((item) => item && item.type === "spacer");
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
if (!item) continue;
|
|
85
|
+
if (item.type === "spacer") {
|
|
86
|
+
writeSpacer(false);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!item.text) continue;
|
|
90
|
+
if (hasSpacer) {
|
|
91
|
+
logBox.log(item.text);
|
|
92
|
+
lastLogWasSpacer = false;
|
|
93
|
+
hasLoggedAny = true;
|
|
94
|
+
} else {
|
|
95
|
+
recordLog(item.type || "unknown", item.text, item.meta || {}, false);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Ignore missing/invalid history.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
appendHistory,
|
|
105
|
+
writeSpacer,
|
|
106
|
+
recordLog,
|
|
107
|
+
logMessage,
|
|
108
|
+
markStreamStart,
|
|
109
|
+
loadHistory,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
createChatLogController,
|
|
115
|
+
};
|