u-foo 2.3.12 → 2.3.14
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/package.json +1 -1
- package/src/agent/defaultBootstrap.js +28 -3
- package/src/agent/internalRunner.js +126 -124
- package/src/agent/ptyRunner.js +70 -8
- package/src/agent/ucodeBootstrap.js +23 -1
- package/src/agent/ufooAgent.js +7 -36
- package/src/chat/daemonMessageRouter.js +3 -1
- package/src/chat/dashboardKeyController.js +1 -1
- package/src/chat/dashboardView.js +1 -1
- package/src/chat/index.js +1 -1
- package/src/chat/inputListenerController.js +196 -54
- package/src/chat/layout.js +18 -2
- package/src/code/tui.js +405 -50
- package/src/config.js +1 -0
- package/src/daemon/ops.js +57 -12
- package/src/agent/cliRunner.js +0 -706
- package/src/agent/normalizeOutput.js +0 -41
package/src/agent/ufooAgent.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const { runCliAgent } = require("./cliRunner");
|
|
4
|
-
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
5
3
|
const { buildStatus } = require("../daemon/status");
|
|
6
4
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
5
|
const { normalizeGateRouterResult } = require("../controller/gateRouter");
|
|
@@ -633,7 +631,6 @@ async function runUfooAgent({
|
|
|
633
631
|
loopRuntime = null,
|
|
634
632
|
controllerMode = null,
|
|
635
633
|
}) {
|
|
636
|
-
const state = loadSessionState(projectRoot);
|
|
637
634
|
const mode = String(routingMode || (routingContext && routingContext.mode) || "").trim().toLowerCase();
|
|
638
635
|
const resolvedControllerMode = String(
|
|
639
636
|
controllerMode
|
|
@@ -660,7 +657,6 @@ async function runUfooAgent({
|
|
|
660
657
|
let res;
|
|
661
658
|
|
|
662
659
|
const useDirectProvider = shouldUseDirectProvider(provider);
|
|
663
|
-
let usedDirectProvider = false;
|
|
664
660
|
|
|
665
661
|
if (useDirectProvider) {
|
|
666
662
|
res = await runNativeRouterCall({
|
|
@@ -671,47 +667,22 @@ async function runUfooAgent({
|
|
|
671
667
|
model,
|
|
672
668
|
});
|
|
673
669
|
if (!res.ok) {
|
|
670
|
+
// eslint-disable-next-line no-console
|
|
671
|
+
console.error(`[ufoo-agent] native provider failed: ${res.error || "unknown error"}`);
|
|
674
672
|
return { ok: false, error: res.error };
|
|
675
673
|
} else {
|
|
676
|
-
usedDirectProvider = true;
|
|
677
674
|
res = { ok: true, output: res.output, sessionId: "", provider: res.provider, model: res.model };
|
|
678
675
|
}
|
|
679
676
|
}
|
|
680
677
|
|
|
681
678
|
if (!useDirectProvider) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
systemPrompt,
|
|
687
|
-
sessionId: state.data?.sessionId,
|
|
688
|
-
disableSession: provider === "claude-cli",
|
|
689
|
-
cwd: projectRoot,
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
if (!res.ok) {
|
|
693
|
-
const msg = (res.error || "").toLowerCase();
|
|
694
|
-
if (msg.includes("session id") || msg.includes("session-id") || msg.includes("already in use")) {
|
|
695
|
-
res = await runCliAgent({
|
|
696
|
-
provider,
|
|
697
|
-
model,
|
|
698
|
-
prompt: fullPrompt,
|
|
699
|
-
systemPrompt,
|
|
700
|
-
sessionId: undefined,
|
|
701
|
-
disableSession: provider === "claude-cli",
|
|
702
|
-
cwd: projectRoot,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (!res.ok) {
|
|
708
|
-
return { ok: false, error: res.error };
|
|
709
|
-
}
|
|
679
|
+
const error = `unsupported ufoo-agent provider "${provider || ""}"; cliRunner fallback has been removed`;
|
|
680
|
+
// eslint-disable-next-line no-console
|
|
681
|
+
console.error(`[ufoo-agent] ${error}`);
|
|
682
|
+
return { ok: false, error };
|
|
710
683
|
}
|
|
711
684
|
|
|
712
|
-
const rawText =
|
|
713
|
-
? String(res.output || "").trim()
|
|
714
|
-
: normalizeCliOutput(res.output);
|
|
685
|
+
const rawText = String(res.output || "").trim();
|
|
715
686
|
const text = stripMarkdownFence(rawText);
|
|
716
687
|
let payload = null;
|
|
717
688
|
try {
|
|
@@ -442,7 +442,9 @@ function createDaemonMessageRouter(options = {}) {
|
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
function handleErrorMessage(msg) {
|
|
445
|
-
|
|
445
|
+
const error = String(msg.error || "unknown error");
|
|
446
|
+
resolveStatusLine(`{gray-fg}✗{/gray-fg} Error: ${error}`);
|
|
447
|
+
logMessage("error", `{white-fg}✗{/white-fg} ${escapeBlessed(error)}`);
|
|
446
448
|
renderScreen();
|
|
447
449
|
return false;
|
|
448
450
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
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-pty", "internal"];
|
|
4
4
|
|
|
5
5
|
function providerLabel(value) {
|
|
6
6
|
if (value === "claude-cli") return "claude";
|
package/src/chat/index.js
CHANGED
|
@@ -65,7 +65,7 @@ const {
|
|
|
65
65
|
pruneTransientAgentStates,
|
|
66
66
|
} = require("./transientAgentState");
|
|
67
67
|
|
|
68
|
-
const MODE_OPTIONS = ["auto", "host", "terminal", "tmux", "internal"];
|
|
68
|
+
const MODE_OPTIONS = ["auto", "host", "terminal", "tmux", "internal-pty", "internal"];
|
|
69
69
|
|
|
70
70
|
async function runChat(projectRoot, options = {}) {
|
|
71
71
|
const globalMode = options && options.globalMode === true;
|
|
@@ -59,6 +59,82 @@ function createInputListenerController(options = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function clampCursorPos(pos = 0, value = "") {
|
|
63
|
+
const text = String(value || "");
|
|
64
|
+
const normalized = Number.isFinite(pos) ? Math.floor(pos) : 0;
|
|
65
|
+
return Math.max(0, Math.min(text.length, normalized));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function refreshAfterEdit(textarea) {
|
|
69
|
+
resizeInput();
|
|
70
|
+
ensureInputCursorVisible();
|
|
71
|
+
updateCursor(textarea);
|
|
72
|
+
updateDraftFromInput();
|
|
73
|
+
|
|
74
|
+
if (textarea && shouldShowCompletion(textarea.value)) {
|
|
75
|
+
completionController.show(textarea.value);
|
|
76
|
+
} else {
|
|
77
|
+
completionController.hide();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(textarea);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function replaceInputRange(textarea, start, end, replacement = "") {
|
|
84
|
+
if (!textarea) return;
|
|
85
|
+
const value = String(textarea.value || "");
|
|
86
|
+
const safeStart = clampCursorPos(start, value);
|
|
87
|
+
const safeEnd = clampCursorPos(end, value);
|
|
88
|
+
const from = Math.min(safeStart, safeEnd);
|
|
89
|
+
const to = Math.max(safeStart, safeEnd);
|
|
90
|
+
const insert = String(replacement || "");
|
|
91
|
+
textarea.value = value.slice(0, from) + insert + value.slice(to);
|
|
92
|
+
setCursorPos(from + insert.length);
|
|
93
|
+
resetPreferredCol();
|
|
94
|
+
refreshAfterEdit(textarea);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function deleteWordBefore(textarea) {
|
|
98
|
+
const value = String((textarea && textarea.value) || "");
|
|
99
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
100
|
+
if (cursorPos <= 0) return;
|
|
101
|
+
const before = value.slice(0, cursorPos);
|
|
102
|
+
const match = before.match(/\s*\S+\s*$/);
|
|
103
|
+
const start = match ? cursorPos - match[0].length : Math.max(0, cursorPos - 1);
|
|
104
|
+
replaceInputRange(textarea, start, cursorPos, "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function moveCursorByWord(textarea, direction = "forward") {
|
|
108
|
+
const value = String((textarea && textarea.value) || "");
|
|
109
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
110
|
+
if (direction === "backward") {
|
|
111
|
+
const before = value.slice(0, cursorPos);
|
|
112
|
+
const trimmedEnd = before.search(/\S\s*$/) >= 0 ? before.replace(/\s+$/, "") : before;
|
|
113
|
+
const match = trimmedEnd.match(/\S+$/);
|
|
114
|
+
return match ? trimmedEnd.length - match[0].length : 0;
|
|
115
|
+
}
|
|
116
|
+
const after = value.slice(cursorPos);
|
|
117
|
+
const match = after.match(/^\s*\S+/);
|
|
118
|
+
return match ? Math.min(value.length, cursorPos + match[0].length) : value.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function moveCursorToVisualBoundary(textarea, boundary = "start") {
|
|
122
|
+
const width = getWrapWidth();
|
|
123
|
+
const value = String((textarea && textarea.value) || "");
|
|
124
|
+
if (width <= 0) return boundary === "end" ? value.length : 0;
|
|
125
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
126
|
+
const { row } = getCursorRowCol(value, cursorPos, width);
|
|
127
|
+
const targetCol = boundary === "end" ? width : 0;
|
|
128
|
+
return getCursorPosForRowCol(value, row, targetCol, width);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function setCursorAndRender(textarea, nextPos) {
|
|
132
|
+
setCursorPos(clampCursorPos(nextPos, (textarea && textarea.value) || ""));
|
|
133
|
+
ensureInputCursorVisible();
|
|
134
|
+
updateCursor(textarea);
|
|
135
|
+
render(textarea);
|
|
136
|
+
}
|
|
137
|
+
|
|
62
138
|
function handleKey(ch, key = {}, textarea) {
|
|
63
139
|
const keyName = key && key.name;
|
|
64
140
|
|
|
@@ -136,8 +212,12 @@ function createInputListenerController(options = {}) {
|
|
|
136
212
|
}
|
|
137
213
|
|
|
138
214
|
if (keyName === "return" || keyName === "enter") {
|
|
139
|
-
|
|
215
|
+
const value = String((textarea && textarea.value) || "");
|
|
216
|
+
const cursorPos = clampCursorPos(getCursorPos(), value);
|
|
217
|
+
if (key && (key.shift || key.meta)) {
|
|
140
218
|
insertTextAtCursor("\n");
|
|
219
|
+
} else if (cursorPos > 0 && value[cursorPos - 1] === "\\") {
|
|
220
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "\n");
|
|
141
221
|
} else {
|
|
142
222
|
resetPreferredCol();
|
|
143
223
|
if (textarea && typeof textarea._done === "function") {
|
|
@@ -147,6 +227,87 @@ function createInputListenerController(options = {}) {
|
|
|
147
227
|
return;
|
|
148
228
|
}
|
|
149
229
|
|
|
230
|
+
if (key && key.ctrl) {
|
|
231
|
+
if (keyName === "a") {
|
|
232
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "start"));
|
|
233
|
+
resetPreferredCol();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (keyName === "e") {
|
|
237
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "end"));
|
|
238
|
+
resetPreferredCol();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (keyName === "b") {
|
|
242
|
+
const cursorPos = getCursorPos();
|
|
243
|
+
if (cursorPos > 0) setCursorAndRender(textarea, cursorPos - 1);
|
|
244
|
+
resetPreferredCol();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (keyName === "f") {
|
|
248
|
+
const cursorPos = getCursorPos();
|
|
249
|
+
const value = String((textarea && textarea.value) || "");
|
|
250
|
+
if (cursorPos < value.length) setCursorAndRender(textarea, cursorPos + 1);
|
|
251
|
+
resetPreferredCol();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (keyName === "d") {
|
|
255
|
+
const cursorPos = getCursorPos();
|
|
256
|
+
const value = String((textarea && textarea.value) || "");
|
|
257
|
+
if (cursorPos < value.length) replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (keyName === "h") {
|
|
261
|
+
const cursorPos = getCursorPos();
|
|
262
|
+
if (cursorPos > 0) replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (keyName === "k") {
|
|
266
|
+
const cursorPos = getCursorPos();
|
|
267
|
+
const value = String((textarea && textarea.value) || "");
|
|
268
|
+
const target = moveCursorToVisualBoundary(textarea, "end");
|
|
269
|
+
if (target === cursorPos && value[cursorPos] === "\n") {
|
|
270
|
+
replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
271
|
+
} else {
|
|
272
|
+
replaceInputRange(textarea, cursorPos, target, "");
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (keyName === "u") {
|
|
277
|
+
const cursorPos = getCursorPos();
|
|
278
|
+
const value = String((textarea && textarea.value) || "");
|
|
279
|
+
const target = moveCursorToVisualBoundary(textarea, "start");
|
|
280
|
+
if (target === cursorPos && value[cursorPos - 1] === "\n") {
|
|
281
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
282
|
+
} else {
|
|
283
|
+
replaceInputRange(textarea, target, cursorPos, "");
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (keyName === "w") {
|
|
288
|
+
deleteWordBefore(textarea);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (key && key.meta) {
|
|
294
|
+
if (keyName === "b") {
|
|
295
|
+
setCursorAndRender(textarea, moveCursorByWord(textarea, "backward"));
|
|
296
|
+
resetPreferredCol();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (keyName === "f") {
|
|
300
|
+
setCursorAndRender(textarea, moveCursorByWord(textarea, "forward"));
|
|
301
|
+
resetPreferredCol();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (keyName === "d") {
|
|
305
|
+
const cursorPos = getCursorPos();
|
|
306
|
+
replaceInputRange(textarea, cursorPos, moveCursorByWord(textarea, "forward"), "");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
150
311
|
if (keyName === "left") {
|
|
151
312
|
const cursorPos = getCursorPos();
|
|
152
313
|
if (cursorPos > 0) setCursorPos(cursorPos - 1);
|
|
@@ -170,20 +331,14 @@ function createInputListenerController(options = {}) {
|
|
|
170
331
|
}
|
|
171
332
|
|
|
172
333
|
if (keyName === "home") {
|
|
173
|
-
|
|
334
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "start"));
|
|
174
335
|
resetPreferredCol();
|
|
175
|
-
ensureInputCursorVisible();
|
|
176
|
-
updateCursor(textarea);
|
|
177
|
-
render(textarea);
|
|
178
336
|
return;
|
|
179
337
|
}
|
|
180
338
|
|
|
181
339
|
if (keyName === "end") {
|
|
182
|
-
|
|
340
|
+
setCursorAndRender(textarea, moveCursorToVisualBoundary(textarea, "end"));
|
|
183
341
|
resetPreferredCol();
|
|
184
|
-
ensureInputCursorVisible();
|
|
185
|
-
updateCursor(textarea);
|
|
186
|
-
render(textarea);
|
|
187
342
|
return;
|
|
188
343
|
}
|
|
189
344
|
|
|
@@ -192,17 +347,6 @@ function createInputListenerController(options = {}) {
|
|
|
192
347
|
completionController.jumpToLast();
|
|
193
348
|
return;
|
|
194
349
|
}
|
|
195
|
-
if (historyUp()) {
|
|
196
|
-
completionController.hide();
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (keyName === "down") {
|
|
202
|
-
if (historyDown()) {
|
|
203
|
-
completionController.hide();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
350
|
}
|
|
207
351
|
|
|
208
352
|
if (keyName === "up" || keyName === "down") {
|
|
@@ -220,11 +364,34 @@ function createInputListenerController(options = {}) {
|
|
|
220
364
|
|
|
221
365
|
const cursorPos = getCursorPos();
|
|
222
366
|
const value = (textarea && textarea.value) || "";
|
|
367
|
+
if (!value) {
|
|
368
|
+
if (keyName === "up") {
|
|
369
|
+
if (historyUp()) completionController.hide();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (historyDown()) {
|
|
373
|
+
completionController.hide();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
enterDashboardMode();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
223
379
|
const { row, col } = getCursorRowCol(value, cursorPos, innerWidth);
|
|
224
380
|
if (getPreferredCol() === null) setPreferredCol(col);
|
|
225
381
|
const totalRows = countLines(value, innerWidth);
|
|
226
382
|
|
|
383
|
+
if (keyName === "up" && row <= 0) {
|
|
384
|
+
if (historyUp()) {
|
|
385
|
+
completionController.hide();
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
227
390
|
if (keyName === "down" && row >= totalRows - 1) {
|
|
391
|
+
if (historyDown()) {
|
|
392
|
+
completionController.hide();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
228
395
|
enterDashboardMode();
|
|
229
396
|
return;
|
|
230
397
|
}
|
|
@@ -261,21 +428,11 @@ function createInputListenerController(options = {}) {
|
|
|
261
428
|
if (keyName === "backspace") {
|
|
262
429
|
const cursorPos = getCursorPos();
|
|
263
430
|
if (cursorPos > 0 && textarea) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
resetPreferredCol();
|
|
267
|
-
resizeInput();
|
|
268
|
-
ensureInputCursorVisible();
|
|
269
|
-
updateCursor(textarea);
|
|
270
|
-
updateDraftFromInput();
|
|
271
|
-
|
|
272
|
-
if (shouldShowCompletion(textarea.value)) {
|
|
273
|
-
completionController.show(textarea.value);
|
|
431
|
+
if (key && (key.ctrl || key.meta)) {
|
|
432
|
+
deleteWordBefore(textarea);
|
|
274
433
|
} else {
|
|
275
|
-
|
|
434
|
+
replaceInputRange(textarea, cursorPos - 1, cursorPos, "");
|
|
276
435
|
}
|
|
277
|
-
|
|
278
|
-
render(textarea);
|
|
279
436
|
}
|
|
280
437
|
return;
|
|
281
438
|
}
|
|
@@ -283,13 +440,11 @@ function createInputListenerController(options = {}) {
|
|
|
283
440
|
if (keyName === "delete") {
|
|
284
441
|
const cursorPos = getCursorPos();
|
|
285
442
|
if (textarea && cursorPos < textarea.value.length) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
render(textarea);
|
|
292
|
-
updateDraftFromInput();
|
|
443
|
+
if (key && key.meta) {
|
|
444
|
+
replaceInputRange(textarea, cursorPos, moveCursorToVisualBoundary(textarea, "end"), "");
|
|
445
|
+
} else {
|
|
446
|
+
replaceInputRange(textarea, cursorPos, cursorPos + 1, "");
|
|
447
|
+
}
|
|
293
448
|
}
|
|
294
449
|
return;
|
|
295
450
|
}
|
|
@@ -300,21 +455,8 @@ function createInputListenerController(options = {}) {
|
|
|
300
455
|
|
|
301
456
|
if (insertChar && !/^[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]$/.test(insertChar) && textarea) {
|
|
302
457
|
const cursorPos = getCursorPos();
|
|
303
|
-
textarea.value = textarea.value.slice(0, cursorPos) + insertChar + textarea.value.slice(cursorPos);
|
|
304
|
-
setCursorPos(cursorPos + 1);
|
|
305
458
|
normalizeCommandPrefix();
|
|
306
|
-
|
|
307
|
-
resizeInput();
|
|
308
|
-
updateCursor(textarea);
|
|
309
|
-
updateDraftFromInput();
|
|
310
|
-
|
|
311
|
-
if (shouldShowCompletion(textarea.value)) {
|
|
312
|
-
completionController.show(textarea.value);
|
|
313
|
-
} else if (completionController.isActive()) {
|
|
314
|
-
completionController.hide();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
render(textarea);
|
|
459
|
+
replaceInputRange(textarea, cursorPos, cursorPos, insertChar);
|
|
318
460
|
}
|
|
319
461
|
}
|
|
320
462
|
|
package/src/chat/layout.js
CHANGED
|
@@ -4,6 +4,8 @@ function createChatLayout(options = {}) {
|
|
|
4
4
|
currentInputHeight = 4,
|
|
5
5
|
dashboardHeight = 1,
|
|
6
6
|
version = "unknown",
|
|
7
|
+
logBorder = false,
|
|
8
|
+
logScrollbar = false,
|
|
7
9
|
} = options;
|
|
8
10
|
const normalizedDashboardHeight = Number.isFinite(dashboardHeight) && dashboardHeight > 0
|
|
9
11
|
? Math.floor(dashboardHeight)
|
|
@@ -32,24 +34,38 @@ function createChatLayout(options = {}) {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Log area
|
|
37
|
+
// Log area
|
|
36
38
|
const logBox = blessed.log({
|
|
37
39
|
parent: screen,
|
|
38
40
|
top: 0,
|
|
39
41
|
left: 0,
|
|
40
42
|
width: "100%",
|
|
41
43
|
height: `100%-${reservedBottomLines}`, // Will be adjusted dynamically
|
|
44
|
+
border: logBorder ? { type: "line" } : null,
|
|
42
45
|
tags: true,
|
|
43
46
|
scrollable: true,
|
|
44
47
|
alwaysScroll: true,
|
|
45
48
|
scrollback: 10000,
|
|
46
|
-
scrollbar:
|
|
49
|
+
scrollbar: logScrollbar
|
|
50
|
+
? {
|
|
51
|
+
ch: " ",
|
|
52
|
+
track: { bg: "black" },
|
|
53
|
+
style: { bg: "gray" },
|
|
54
|
+
}
|
|
55
|
+
: null,
|
|
47
56
|
keys: true,
|
|
48
57
|
vi: true,
|
|
49
58
|
// Mouse handled globally (toggleable) to keep copy working
|
|
50
59
|
mouse: false,
|
|
51
60
|
// Ensure proper wrapping and width calculation
|
|
52
61
|
wrap: true,
|
|
62
|
+
padding: logBorder ? { left: 1, right: 1 } : undefined,
|
|
63
|
+
style: logBorder
|
|
64
|
+
? {
|
|
65
|
+
border: { fg: "gray" },
|
|
66
|
+
scrollbar: { bg: "gray" },
|
|
67
|
+
}
|
|
68
|
+
: undefined,
|
|
53
69
|
});
|
|
54
70
|
|
|
55
71
|
// Status line just above input
|