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.
Files changed (149) hide show
  1. package/README.md +44 -4
  2. package/SKILLS/ufoo/SKILL.md +17 -2
  3. package/SKILLS/uinit/SKILL.md +8 -3
  4. package/bin/ucode-core.js +15 -0
  5. package/bin/ucode.js +125 -0
  6. package/bin/ufoo-assistant-agent.js +5 -0
  7. package/bin/ufoo-engine.js +25 -0
  8. package/bin/ufoo.js +4 -0
  9. package/modules/AGENTS.template.md +14 -4
  10. package/modules/bus/README.md +8 -5
  11. package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
  12. package/modules/context/SKILLS/uctx/SKILL.md +3 -1
  13. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  14. package/package.json +12 -3
  15. package/scripts/import-pi-mono.js +124 -0
  16. package/scripts/postinstall.js +20 -49
  17. package/scripts/sync-claude-skills.sh +21 -0
  18. package/src/agent/cliRunner.js +524 -31
  19. package/src/agent/internalRunner.js +76 -9
  20. package/src/agent/launcher.js +97 -45
  21. package/src/agent/normalizeOutput.js +1 -1
  22. package/src/agent/notifier.js +144 -4
  23. package/src/agent/ptyRunner.js +480 -10
  24. package/src/agent/ptyWrapper.js +28 -3
  25. package/src/agent/readyDetector.js +16 -0
  26. package/src/agent/ucode.js +443 -0
  27. package/src/agent/ucodeBootstrap.js +113 -0
  28. package/src/agent/ucodeBuild.js +67 -0
  29. package/src/agent/ucodeDoctor.js +184 -0
  30. package/src/agent/ucodeRuntimeConfig.js +129 -0
  31. package/src/agent/ufooAgent.js +11 -2
  32. package/src/assistant/agent.js +260 -0
  33. package/src/assistant/bridge.js +172 -0
  34. package/src/assistant/engine.js +252 -0
  35. package/src/assistant/stdio.js +58 -0
  36. package/src/assistant/ufooEngineCli.js +306 -0
  37. package/src/bus/activate.js +27 -11
  38. package/src/bus/daemon.js +133 -5
  39. package/src/bus/index.js +137 -80
  40. package/src/bus/inject.js +47 -17
  41. package/src/bus/message.js +145 -17
  42. package/src/bus/nickname.js +3 -1
  43. package/src/bus/queue.js +6 -1
  44. package/src/bus/store.js +189 -0
  45. package/src/bus/subscriber.js +20 -4
  46. package/src/bus/utils.js +9 -3
  47. package/src/chat/agentBar.js +117 -0
  48. package/src/chat/agentDirectory.js +88 -0
  49. package/src/chat/agentSockets.js +225 -0
  50. package/src/chat/agentViewController.js +298 -0
  51. package/src/chat/chatLogController.js +115 -0
  52. package/src/chat/commandExecutor.js +700 -0
  53. package/src/chat/commands.js +132 -0
  54. package/src/chat/completionController.js +414 -0
  55. package/src/chat/cronScheduler.js +160 -0
  56. package/src/chat/daemonConnection.js +166 -0
  57. package/src/chat/daemonCoordinator.js +64 -0
  58. package/src/chat/daemonMessageRouter.js +257 -0
  59. package/src/chat/daemonReconnect.js +41 -0
  60. package/src/chat/daemonTransport.js +36 -0
  61. package/src/chat/daemonTransportDefaults.js +10 -0
  62. package/src/chat/dashboardKeyController.js +480 -0
  63. package/src/chat/dashboardView.js +154 -0
  64. package/src/chat/index.js +935 -2909
  65. package/src/chat/inputHistoryController.js +105 -0
  66. package/src/chat/inputListenerController.js +304 -0
  67. package/src/chat/inputMath.js +104 -0
  68. package/src/chat/inputSubmitHandler.js +171 -0
  69. package/src/chat/layout.js +165 -0
  70. package/src/chat/pasteController.js +81 -0
  71. package/src/chat/rawKeyMap.js +42 -0
  72. package/src/chat/settingsController.js +132 -0
  73. package/src/chat/statusLineController.js +177 -0
  74. package/src/chat/streamTracker.js +138 -0
  75. package/src/chat/text.js +70 -0
  76. package/src/chat/transport.js +61 -0
  77. package/src/cli/busCoreCommands.js +59 -0
  78. package/src/cli/ctxCoreCommands.js +199 -0
  79. package/src/cli/onlineCoreCommands.js +379 -0
  80. package/src/cli.js +741 -238
  81. package/src/code/README.md +29 -0
  82. package/src/code/UCODE_PROMPT.md +32 -0
  83. package/src/code/agent.js +1651 -0
  84. package/src/code/cli.js +158 -0
  85. package/src/code/config +0 -0
  86. package/src/code/dispatch.js +42 -0
  87. package/src/code/index.js +70 -0
  88. package/src/code/nativeRunner.js +1213 -0
  89. package/src/code/runtime.js +154 -0
  90. package/src/code/sessionStore.js +162 -0
  91. package/src/code/taskDecomposer.js +269 -0
  92. package/src/code/tools/bash.js +53 -0
  93. package/src/code/tools/common.js +42 -0
  94. package/src/code/tools/edit.js +70 -0
  95. package/src/code/tools/read.js +44 -0
  96. package/src/code/tools/write.js +35 -0
  97. package/src/code/tui.js +1580 -0
  98. package/src/config.js +47 -1
  99. package/src/context/decisions.js +12 -2
  100. package/src/context/index.js +18 -1
  101. package/src/context/sync.js +127 -0
  102. package/src/daemon/agentProcessManager.js +74 -0
  103. package/src/daemon/cronOps.js +241 -0
  104. package/src/daemon/index.js +661 -488
  105. package/src/daemon/ipcServer.js +99 -0
  106. package/src/daemon/ops.js +417 -179
  107. package/src/daemon/promptLoop.js +319 -0
  108. package/src/daemon/promptRequest.js +101 -0
  109. package/src/daemon/providerSessions.js +32 -17
  110. package/src/daemon/reporting.js +90 -0
  111. package/src/daemon/run.js +2 -5
  112. package/src/daemon/status.js +24 -1
  113. package/src/init/index.js +68 -14
  114. package/src/online/bridge.js +663 -0
  115. package/src/online/client.js +245 -0
  116. package/src/online/runner.js +253 -0
  117. package/src/online/server.js +992 -0
  118. package/src/online/tokens.js +103 -0
  119. package/src/report/store.js +331 -0
  120. package/src/shared/eventContract.js +35 -0
  121. package/src/shared/ptySocketContract.js +21 -0
  122. package/src/status/index.js +50 -17
  123. package/src/terminal/adapterContract.js +87 -0
  124. package/src/terminal/adapterRouter.js +84 -0
  125. package/src/terminal/adapters/externalAdapter.js +14 -0
  126. package/src/terminal/adapters/internalAdapter.js +13 -0
  127. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  128. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  129. package/src/terminal/adapters/terminalAdapter.js +31 -0
  130. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  131. package/src/ufoo/agentsStore.js +69 -3
  132. package/src/utils/banner.js +5 -2
  133. package/scripts/.archived/bash-to-js-migration/README.md +0 -46
  134. package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
  135. package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
  136. package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
  137. package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
  138. package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
  139. package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
  140. package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
  141. package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
  142. package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
  143. package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
  144. package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
  145. package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
  146. package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
  147. package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
  148. package/scripts/banner.sh +0 -2
  149. 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
+ };