@pheem49/mint 1.3.0 → 1.4.1
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/.codex +0 -0
- package/README.md +174 -126
- package/main.js +21 -1
- package/mint-cli-logic.js +21 -1
- package/mint-cli.js +287 -45
- package/package.json +13 -2
- package/src/AI_Brain/Gemini_API.js +331 -64
- package/src/AI_Brain/agent_orchestrator.js +73 -0
- package/src/AI_Brain/autonomous_brain.js +2 -0
- package/src/AI_Brain/memory_store.js +318 -0
- package/src/AI_Brain/proactive_engine.js +2 -8
- package/src/Automation_Layer/file_operations.js +123 -4
- package/src/Automation_Layer/open_app.js +72 -43
- package/src/Automation_Layer/open_website.js +3 -3
- package/src/CLI/chat_router.js +57 -9
- package/src/CLI/chat_ui.js +117 -11
- package/src/CLI/code_agent.js +249 -36
- package/src/CLI/onboarding.js +53 -6
- package/src/CLI/workspace_manager.js +90 -0
- package/src/Plugins/docker.js +12 -10
- package/src/Plugins/spotify.js +168 -40
- package/src/Plugins/system_monitor.js +72 -0
- package/src/System/config_manager.js +35 -2
- package/src/System/custom_workflows.js +9 -2
- package/src/System/notifications.js +23 -0
- package/src/UI/settings.html +143 -65
- package/src/UI/settings.js +155 -41
- package/tests/agent_orchestrator.test.js +41 -0
- package/tests/chat_router.test.js +42 -0
- package/tests/code_agent.test.js +69 -0
- package/tests/config_manager.test.js +141 -0
- package/tests/docker.test.js +46 -0
- package/tests/file_operations.test.js +57 -0
- package/tests/memory_store.test.js +185 -0
- package/tests/provider_routing.test.js +67 -0
- package/tests/spotify.test.js +201 -0
- package/tests/system_monitor.test.js +37 -0
- package/tests/workspace_manager.test.js +56 -0
package/src/CLI/chat_router.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { GoogleGenAI } = require('@google/genai');
|
|
4
|
-
const { executeCodeTask } = require('./code_agent');
|
|
5
|
-
const { readConfig } = require('../System/config_manager');
|
|
4
|
+
const { executeCodeTask, _helpers: codeAgentHelpers } = require('./code_agent');
|
|
5
|
+
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
6
6
|
|
|
7
7
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
8
8
|
|
|
@@ -26,7 +26,19 @@ Return JSON only:
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
Choose "code" when the user is asking to inspect, edit, review, debug, explain, refactor, verify, or otherwise operate on the current project/workspace/codebase/files.
|
|
29
|
-
Choose "chat" for general conversation, factual Q&A,
|
|
29
|
+
Choose "chat" for general conversation, factual Q&A, small/simple requests, non-code assistant tasks, or direct file-system actions like finding/opening a folder or file by name.
|
|
30
|
+
Only choose "code" for substantial coding work that likely needs multiple steps, workspace inspection, edits, verification, or project-wide reasoning.`;
|
|
31
|
+
|
|
32
|
+
function isDirectFilesystemActionRequest(text) {
|
|
33
|
+
const input = (text || '').trim().toLowerCase();
|
|
34
|
+
if (!input) return false;
|
|
35
|
+
|
|
36
|
+
const filesystemActionPattern = /(open|find|locate|search for|look for|หา|ค้นหา|เปิด)/;
|
|
37
|
+
const filesystemTargetPattern = /(folder|directory|dir|file|โฟลเดอร์|ไฟล์|ไดเรกทอรี)/;
|
|
38
|
+
const codeOperationPattern = /(inspect|review|refactor|debug|implement|edit|change|fix|explain|analyze|สำรวจ|รีวิว|รีแฟกเตอร์|แก้|อธิบาย|วิเคราะห์)/;
|
|
39
|
+
|
|
40
|
+
return filesystemActionPattern.test(input) && filesystemTargetPattern.test(input) && !codeOperationPattern.test(input);
|
|
41
|
+
}
|
|
30
42
|
|
|
31
43
|
function workspaceLooksLikeCodebase(workspaceRoot) {
|
|
32
44
|
const markers = [
|
|
@@ -45,13 +57,27 @@ function detectCodeIntentHeuristic(text, workspaceRoot = process.cwd()) {
|
|
|
45
57
|
const input = (text || '').trim().toLowerCase();
|
|
46
58
|
if (!input) return false;
|
|
47
59
|
if (input.startsWith('/code ')) return true;
|
|
60
|
+
if (isDirectFilesystemActionRequest(input)) return false;
|
|
61
|
+
|
|
62
|
+
return isLargeCodeTaskRequest(input, workspaceRoot);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isLargeCodeTaskRequest(text, workspaceRoot = process.cwd()) {
|
|
66
|
+
const input = (text || '').trim().toLowerCase();
|
|
67
|
+
if (!input) return false;
|
|
68
|
+
if (!workspaceLooksLikeCodebase(workspaceRoot)) return false;
|
|
48
69
|
|
|
49
70
|
const hasCodeKeyword = CODE_KEYWORDS.some(keyword => input.includes(keyword));
|
|
50
71
|
const hasThaiCodeKeyword = THAI_CODE_KEYWORDS.some(keyword => input.includes(keyword));
|
|
51
72
|
const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace/.test(input);
|
|
52
73
|
const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|implement|inspect|explore|fix|update|change|refactor|review|explain|debug/.test(input);
|
|
74
|
+
const strongTaskSignal = /failing tests?|run tests?|verify|verification|bug|issue|error|refactor|implement|feature|patch|edit|modify|analyze the project|แก้บั๊ก|รันเทสต์|ทดสอบ|ตรวจสอบ|ยืนยันผล|รีแฟกเตอร์|เพิ่มฟีเจอร์|แก้โค้ด|วิเคราะห์โปรเจค/.test(input);
|
|
75
|
+
const multiStepSignal = /and|then|พร้อม|แล้ว|จากนั้น|ทั้ง|ทั่วทั้ง|ทั้งโปรเจค|project-wide|entire project|whole project/.test(input);
|
|
53
76
|
|
|
54
|
-
|
|
77
|
+
if (referencesProject && strongTaskSignal) return true;
|
|
78
|
+
if ((hasCodeKeyword || hasThaiCodeKeyword) && asksForAction && strongTaskSignal) return true;
|
|
79
|
+
if ((hasCodeKeyword || hasThaiCodeKeyword) && multiStepSignal && asksForAction) return true;
|
|
80
|
+
return false;
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
function getRouterClient() {
|
|
@@ -71,7 +97,7 @@ function summarizeWorkspace(workspaceRoot) {
|
|
|
71
97
|
.join(', ') || '(no obvious code markers)';
|
|
72
98
|
}
|
|
73
99
|
|
|
74
|
-
async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
100
|
+
async function detectCodeIntent(text, workspaceRoot = process.cwd(), history = []) {
|
|
75
101
|
const input = (text || '').trim();
|
|
76
102
|
if (!input) {
|
|
77
103
|
return { route: 'chat', reason: 'Empty input.' };
|
|
@@ -81,6 +107,10 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
|
81
107
|
return { route: 'code', reason: 'Explicit /code command.' };
|
|
82
108
|
}
|
|
83
109
|
|
|
110
|
+
if (isDirectFilesystemActionRequest(input)) {
|
|
111
|
+
return { route: 'chat', reason: 'Direct file-system action request.' };
|
|
112
|
+
}
|
|
113
|
+
|
|
84
114
|
const heuristicRoute = detectCodeIntentHeuristic(input, workspaceRoot);
|
|
85
115
|
const routerClient = getRouterClient();
|
|
86
116
|
if (!routerClient) {
|
|
@@ -103,7 +133,8 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
|
103
133
|
text: [
|
|
104
134
|
`Workspace: ${workspaceRoot}`,
|
|
105
135
|
`Workspace markers: ${summarizeWorkspace(workspaceRoot)}`,
|
|
106
|
-
`
|
|
136
|
+
`Context (Last 5 turns): ${history.slice(-10).map(m => `${m.sender}: ${m.text}`).join('\n')}`,
|
|
137
|
+
`Current Message: ${input}`
|
|
107
138
|
].join('\n')
|
|
108
139
|
}]
|
|
109
140
|
}]
|
|
@@ -112,6 +143,12 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
|
112
143
|
const textOutput = typeof response.text === 'function' ? response.text() : response.text;
|
|
113
144
|
const parsed = JSON.parse(textOutput);
|
|
114
145
|
const route = parsed.route === 'code' ? 'code' : 'chat';
|
|
146
|
+
if (route === 'code' && !isLargeCodeTaskRequest(input, workspaceRoot)) {
|
|
147
|
+
return {
|
|
148
|
+
route: 'chat',
|
|
149
|
+
reason: 'Request looks small enough for normal chat.'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
115
152
|
return {
|
|
116
153
|
route,
|
|
117
154
|
reason: parsed.reason || (route === 'code' ? 'Model classified as code.' : 'Model classified as chat.')
|
|
@@ -126,9 +163,13 @@ async function detectCodeIntent(text, workspaceRoot = process.cwd()) {
|
|
|
126
163
|
|
|
127
164
|
async function runChatRoutedTask(input, context) {
|
|
128
165
|
const text = input.startsWith('/code ') ? input.slice('/code '.length).trim() : input;
|
|
129
|
-
const { appendMessage, setThinking, requestApproval, setMode } = context;
|
|
166
|
+
const { appendMessage, setThinking, requestApproval, setMode, history } = context;
|
|
167
|
+
|
|
168
|
+
const config = readConfig();
|
|
169
|
+
const availableProviders = getAvailableProviders(config);
|
|
170
|
+
const preferredProvider = codeAgentHelpers.selectSupportedCodeProvider(config, availableProviders);
|
|
130
171
|
|
|
131
|
-
appendMessage('system', `Routing this request to Code Mode for workspace: ${process.cwd()}`);
|
|
172
|
+
appendMessage('system', `Routing this request to Code Mode for workspace: ${process.cwd()} using [${preferredProvider}]`);
|
|
132
173
|
if (setMode) setMode('Code');
|
|
133
174
|
|
|
134
175
|
let seconds = 0;
|
|
@@ -142,6 +183,8 @@ async function runChatRoutedTask(input, context) {
|
|
|
142
183
|
const result = await executeCodeTask(text, {
|
|
143
184
|
cwd: process.cwd(),
|
|
144
185
|
requestApproval,
|
|
186
|
+
provider: preferredProvider,
|
|
187
|
+
history: history,
|
|
145
188
|
onProgress: (message) => appendMessage('system', `[Code] ${message}`)
|
|
146
189
|
});
|
|
147
190
|
clearInterval(timer);
|
|
@@ -162,5 +205,10 @@ async function runChatRoutedTask(input, context) {
|
|
|
162
205
|
|
|
163
206
|
module.exports = {
|
|
164
207
|
detectCodeIntent,
|
|
165
|
-
runChatRoutedTask
|
|
208
|
+
runChatRoutedTask,
|
|
209
|
+
_helpers: {
|
|
210
|
+
detectCodeIntentHeuristic,
|
|
211
|
+
isDirectFilesystemActionRequest,
|
|
212
|
+
isLargeCodeTaskRequest
|
|
213
|
+
}
|
|
166
214
|
};
|
package/src/CLI/chat_ui.js
CHANGED
|
@@ -14,6 +14,10 @@ const SLASH_COMMANDS = [
|
|
|
14
14
|
{ name: '/copy', desc: 'Copy last response to clipboard' },
|
|
15
15
|
{ name: '/clear', desc: 'Clear conversation history' },
|
|
16
16
|
{ name: '/reset', desc: 'Reset conversation history' },
|
|
17
|
+
{ name: '/agent', desc: 'Switch AI personas (coder, researcher, etc)' },
|
|
18
|
+
{ name: '/workspace', desc: 'Manage project-specific contexts' },
|
|
19
|
+
{ name: '/review', desc: 'Request a second-pass review of the last response' },
|
|
20
|
+
{ name: '/stats', desc: 'Show system health stats (CPU/RAM/Disk)' },
|
|
17
21
|
{ name: '/help', desc: 'Show help information' },
|
|
18
22
|
{ name: '/exit', desc: 'Exit Mint' }
|
|
19
23
|
];
|
|
@@ -86,11 +90,10 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
86
90
|
style: { bg: 'default' }
|
|
87
91
|
});
|
|
88
92
|
|
|
89
|
-
// ─── Input area ───────────────────────────────────────────────────────────
|
|
90
93
|
const inputBox = blessed.textbox({
|
|
91
94
|
bottom: 3, left: 1, width: '100%-2', height: 3,
|
|
92
95
|
tags: false,
|
|
93
|
-
inputOnFocus:
|
|
96
|
+
inputOnFocus: false, // We'll manage this manually for stability
|
|
94
97
|
keys: true,
|
|
95
98
|
style: {
|
|
96
99
|
bg: INPUT_BG,
|
|
@@ -107,6 +110,16 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
107
110
|
label: ' Message '
|
|
108
111
|
});
|
|
109
112
|
|
|
113
|
+
// --- SAFETY PATCH ---
|
|
114
|
+
// Prevent "TypeError: done is not a function" if a listener survives a blur/focus cycle.
|
|
115
|
+
const originalListener = inputBox._listener;
|
|
116
|
+
inputBox._listener = function(ch, key) {
|
|
117
|
+
if (typeof this._done !== 'function') return;
|
|
118
|
+
return originalListener.call(this, ch, key);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
110
123
|
// ─── Placeholder (SIBLING widget floating over input content area) ─────────
|
|
111
124
|
// inputBox: bottom=3, height=3, border=1 → content row at bottom=4, left=2
|
|
112
125
|
const placeholderWidget = blessed.text({
|
|
@@ -232,6 +245,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
232
245
|
|
|
233
246
|
/** Update model name in status bar (called after /models switch) */
|
|
234
247
|
function updateStatusModel(newModel) {
|
|
248
|
+
if (!newModel) return;
|
|
235
249
|
statusRight.setContent(`{#88e0b0-fg}${newModel}{/}`);
|
|
236
250
|
screen.render();
|
|
237
251
|
}
|
|
@@ -280,7 +294,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
280
294
|
border: { fg: '#88e0b0' }
|
|
281
295
|
},
|
|
282
296
|
width: '80%',
|
|
283
|
-
height: 'shrink'
|
|
297
|
+
height: 12, // Fixed height to avoid 'shrink' miscalculation with buttons
|
|
284
298
|
top: 'center',
|
|
285
299
|
left: 'center',
|
|
286
300
|
label: ' Approval ',
|
|
@@ -377,26 +391,28 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
377
391
|
|
|
378
392
|
|
|
379
393
|
// Submit or Select Suggestion on Enter
|
|
380
|
-
inputBox.
|
|
394
|
+
inputBox.on('submit', (value) => {
|
|
381
395
|
if (!commandList.hidden) {
|
|
382
396
|
const selected = activeSuggestions[commandList.selected];
|
|
383
397
|
if (selected) {
|
|
384
398
|
inputBox.setValue(selected.name + ' ');
|
|
385
399
|
commandList.hide();
|
|
386
400
|
hidePlaceholder();
|
|
387
|
-
inputBox.focus();
|
|
401
|
+
inputBox.focus();
|
|
402
|
+
inputBox.readInput(); // Re-focus to continue typing
|
|
388
403
|
refreshInputStyles();
|
|
389
404
|
screen.render();
|
|
390
405
|
return; // Don't submit yet, let user add args or press enter again
|
|
391
406
|
}
|
|
392
407
|
}
|
|
393
408
|
|
|
394
|
-
const raw =
|
|
409
|
+
const raw = value || '';
|
|
395
410
|
const text = raw.trim();
|
|
396
411
|
if (!text) {
|
|
397
412
|
inputBox.clearValue();
|
|
398
413
|
showPlaceholder();
|
|
399
|
-
inputBox.focus();
|
|
414
|
+
inputBox.focus();
|
|
415
|
+
inputBox.readInput(); // Re-focus to continue typing
|
|
400
416
|
refreshInputStyles();
|
|
401
417
|
screen.render();
|
|
402
418
|
return;
|
|
@@ -405,7 +421,8 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
405
421
|
// Clear input and restore placeholder
|
|
406
422
|
inputBox.clearValue();
|
|
407
423
|
showPlaceholder();
|
|
408
|
-
inputBox.focus();
|
|
424
|
+
inputBox.focus();
|
|
425
|
+
inputBox.readInput(); // Explicitly restart reading
|
|
409
426
|
refreshInputStyles();
|
|
410
427
|
screen.render();
|
|
411
428
|
|
|
@@ -482,6 +499,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
482
499
|
|
|
483
500
|
// ─── Initial render ───────────────────────────────────────────────────────
|
|
484
501
|
inputBox.focus();
|
|
502
|
+
inputBox.readInput(); // Initial start
|
|
485
503
|
refreshInputStyles();
|
|
486
504
|
screen.render();
|
|
487
505
|
|
|
@@ -588,6 +606,84 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
588
606
|
screen.render();
|
|
589
607
|
}
|
|
590
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Opens a streaming message bubble for the assistant.
|
|
611
|
+
* Returns { appendChunk(text), finalize(timestamp) } for typewriter rendering.
|
|
612
|
+
* Usage:
|
|
613
|
+
* const stream = streamMessage('assistant');
|
|
614
|
+
* stream.appendChunk('Hello'); stream.appendChunk(' World');
|
|
615
|
+
* stream.finalize(timestamp);
|
|
616
|
+
*/
|
|
617
|
+
function streamMessage(role = 'assistant') {
|
|
618
|
+
const now = new Date();
|
|
619
|
+
const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
620
|
+
const maxLineWidth = Math.max(screen.width - 20, 36);
|
|
621
|
+
|
|
622
|
+
// Print the header bubble once
|
|
623
|
+
chatBox.log('');
|
|
624
|
+
if (role === 'assistant') {
|
|
625
|
+
chatBox.log(` {bold}{#d4a8ff-fg}Mint{/} {gray-fg}${timeStr}{/}`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let buffer = ''; // accumulates the full response text
|
|
629
|
+
let lineBuffer = ''; // current partial line being built
|
|
630
|
+
let lineRendered = false; // whether we already pushed the first line prefix
|
|
631
|
+
|
|
632
|
+
function flushLine(force = false) {
|
|
633
|
+
// Flush content that fits on one line-width or when forced
|
|
634
|
+
if (!lineBuffer && !force) return;
|
|
635
|
+
if (!lineRendered) {
|
|
636
|
+
chatBox.log(` {#5a456d-fg}▏{/} {#ffffff-fg}${lineBuffer}{/}`);
|
|
637
|
+
lineRendered = true;
|
|
638
|
+
} else {
|
|
639
|
+
// Overwrite the last line by popping + re-pushing (blessed.log limitation)
|
|
640
|
+
// We can't truly overwrite, so we just keep appending new lines for each chunk.
|
|
641
|
+
// For large chunks, split on newline and emit per-line.
|
|
642
|
+
chatBox.log(` {#5a456d-fg}▏{/} {#ffffff-fg}${lineBuffer}{/}`);
|
|
643
|
+
}
|
|
644
|
+
screen.render();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function appendChunk(text) {
|
|
648
|
+
if (!text) return;
|
|
649
|
+
buffer += text;
|
|
650
|
+
const segments = text.split('\n');
|
|
651
|
+
for (let i = 0; i < segments.length; i++) {
|
|
652
|
+
lineBuffer += segments[i];
|
|
653
|
+
if (i < segments.length - 1) {
|
|
654
|
+
// Newline boundary — emit current line
|
|
655
|
+
const lines = wrapLineSmart(lineBuffer, maxLineWidth);
|
|
656
|
+
lines.forEach(l => chatBox.log(` {#5a456d-fg}▏{/} {#ffffff-fg}${l}{/}`));
|
|
657
|
+
lineBuffer = '';
|
|
658
|
+
lineRendered = true;
|
|
659
|
+
screen.render();
|
|
660
|
+
} else if (lineBuffer.length >= maxLineWidth) {
|
|
661
|
+
// Line overflow — auto-wrap
|
|
662
|
+
const lines = wrapLineSmart(lineBuffer, maxLineWidth);
|
|
663
|
+
lines.slice(0, -1).forEach(l => chatBox.log(` {#5a456d-fg}▏{/} {#ffffff-fg}${l}{/}`));
|
|
664
|
+
lineBuffer = lines[lines.length - 1] || '';
|
|
665
|
+
lineRendered = true;
|
|
666
|
+
screen.render();
|
|
667
|
+
}
|
|
668
|
+
// Otherwise keep buffering the partial line
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function finalize(timestamp = null) {
|
|
673
|
+
// Flush remaining buffer
|
|
674
|
+
if (lineBuffer) {
|
|
675
|
+
const lines = wrapLineSmart(lineBuffer, maxLineWidth);
|
|
676
|
+
lines.forEach(l => chatBox.log(` {#5a456d-fg}▏{/} {#ffffff-fg}${l}{/}`));
|
|
677
|
+
lineBuffer = '';
|
|
678
|
+
}
|
|
679
|
+
// Track last response for clipboard
|
|
680
|
+
lastAssistantResponse = buffer;
|
|
681
|
+
screen.render();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return { appendChunk, finalize };
|
|
685
|
+
}
|
|
686
|
+
|
|
591
687
|
/** Show/hide thinking indicator in status bar */
|
|
592
688
|
function setThinking(active, secondsElapsed = 0) {
|
|
593
689
|
if (active) {
|
|
@@ -609,18 +705,28 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
609
705
|
? 'Shell Command'
|
|
610
706
|
: request.type === 'patch'
|
|
611
707
|
? 'Patch Edit'
|
|
612
|
-
:
|
|
708
|
+
: request.type === 'code_mode'
|
|
709
|
+
? 'Enter Code Mode'
|
|
710
|
+
: 'File Write';
|
|
613
711
|
const preview = request.preview || request.label || '';
|
|
614
712
|
const message = [
|
|
615
713
|
`{bold}${typeLabel}{/bold}`,
|
|
616
714
|
'',
|
|
617
715
|
preview,
|
|
618
716
|
'',
|
|
619
|
-
'Approve this action?'
|
|
717
|
+
'Approve this action?',
|
|
718
|
+
'', // Extra lines to push buttons down and avoid overlapping
|
|
719
|
+
''
|
|
620
720
|
].join('\n');
|
|
621
721
|
|
|
722
|
+
// Temporarily stop reading input so the dialog can receive keys
|
|
723
|
+
if (inputBox._reading) {
|
|
724
|
+
inputBox.cancel();
|
|
725
|
+
}
|
|
726
|
+
|
|
622
727
|
approvalDialog.ask(message, (approved) => {
|
|
623
728
|
inputBox.focus();
|
|
729
|
+
inputBox.readInput(); // Ensure we resume reading after dialog
|
|
624
730
|
refreshInputStyles();
|
|
625
731
|
screen.render();
|
|
626
732
|
resolve(Boolean(approved));
|
|
@@ -628,7 +734,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
628
734
|
});
|
|
629
735
|
}
|
|
630
736
|
|
|
631
|
-
return { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode };
|
|
737
|
+
return { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode };
|
|
632
738
|
}
|
|
633
739
|
|
|
634
740
|
module.exports = { createChatUI };
|