helixmind 0.1.2 → 0.2.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.
Potentially problematic release.
This version of helixmind might be problematic. Click here for more details.
- package/README.md +346 -207
- package/dist/cli/agent/loop.d.ts +4 -0
- package/dist/cli/agent/loop.d.ts.map +1 -1
- package/dist/cli/agent/loop.js +9 -1
- package/dist/cli/agent/loop.js.map +1 -1
- package/dist/cli/agent/permissions.d.ts.map +1 -1
- package/dist/cli/agent/permissions.js +7 -0
- package/dist/cli/agent/permissions.js.map +1 -1
- package/dist/cli/agent/tools/browser-click.d.ts +2 -0
- package/dist/cli/agent/tools/browser-click.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-click.js +35 -0
- package/dist/cli/agent/tools/browser-click.js.map +1 -0
- package/dist/cli/agent/tools/browser-close.d.ts +2 -0
- package/dist/cli/agent/tools/browser-close.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-close.js +27 -0
- package/dist/cli/agent/tools/browser-close.js.map +1 -0
- package/dist/cli/agent/tools/browser-navigate.d.ts +2 -0
- package/dist/cli/agent/tools/browser-navigate.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-navigate.js +27 -0
- package/dist/cli/agent/tools/browser-navigate.js.map +1 -0
- package/dist/cli/agent/tools/browser-open.d.ts +2 -0
- package/dist/cli/agent/tools/browser-open.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-open.js +38 -0
- package/dist/cli/agent/tools/browser-open.js.map +1 -0
- package/dist/cli/agent/tools/browser-screenshot.d.ts +2 -0
- package/dist/cli/agent/tools/browser-screenshot.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-screenshot.js +68 -0
- package/dist/cli/agent/tools/browser-screenshot.js.map +1 -0
- package/dist/cli/agent/tools/browser-type.d.ts +2 -0
- package/dist/cli/agent/tools/browser-type.d.ts.map +1 -0
- package/dist/cli/agent/tools/browser-type.js +33 -0
- package/dist/cli/agent/tools/browser-type.js.map +1 -0
- package/dist/cli/agent/tools/registry.d.ts +10 -0
- package/dist/cli/agent/tools/registry.d.ts.map +1 -1
- package/dist/cli/agent/tools/registry.js +12 -0
- package/dist/cli/agent/tools/registry.js.map +1 -1
- package/dist/cli/brain/control-protocol.d.ts +83 -2
- package/dist/cli/brain/control-protocol.d.ts.map +1 -1
- package/dist/cli/brain/control-protocol.js.map +1 -1
- package/dist/cli/brain/generator.d.ts +9 -1
- package/dist/cli/brain/generator.d.ts.map +1 -1
- package/dist/cli/brain/generator.js +36 -0
- package/dist/cli/brain/generator.js.map +1 -1
- package/dist/cli/brain/relay-client.d.ts.map +1 -1
- package/dist/cli/brain/relay-client.js +8 -2
- package/dist/cli/brain/relay-client.js.map +1 -1
- package/dist/cli/brain/server.d.ts.map +1 -1
- package/dist/cli/brain/server.js +5 -0
- package/dist/cli/brain/server.js.map +1 -1
- package/dist/cli/brain/web-chat-handler.d.ts +26 -0
- package/dist/cli/brain/web-chat-handler.d.ts.map +1 -0
- package/dist/cli/brain/web-chat-handler.js +130 -0
- package/dist/cli/brain/web-chat-handler.js.map +1 -0
- package/dist/cli/browser/chrome-finder.d.ts +12 -0
- package/dist/cli/browser/chrome-finder.d.ts.map +1 -0
- package/dist/cli/browser/chrome-finder.js +74 -0
- package/dist/cli/browser/chrome-finder.js.map +1 -0
- package/dist/cli/browser/controller.d.ts +51 -0
- package/dist/cli/browser/controller.d.ts.map +1 -0
- package/dist/cli/browser/controller.js +152 -0
- package/dist/cli/browser/controller.js.map +1 -0
- package/dist/cli/browser/vision.d.ts +38 -0
- package/dist/cli/browser/vision.d.ts.map +1 -0
- package/dist/cli/browser/vision.js +123 -0
- package/dist/cli/browser/vision.js.map +1 -0
- package/dist/cli/bugs/journal.d.ts +2 -0
- package/dist/cli/bugs/journal.d.ts.map +1 -1
- package/dist/cli/bugs/journal.js +4 -0
- package/dist/cli/bugs/journal.js.map +1 -1
- package/dist/cli/checkpoints/store.d.ts.map +1 -1
- package/dist/cli/checkpoints/store.js +6 -0
- package/dist/cli/checkpoints/store.js.map +1 -1
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +273 -83
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/context/session-buffer.d.ts.map +1 -1
- package/dist/cli/context/session-buffer.js +6 -0
- package/dist/cli/context/session-buffer.js.map +1 -1
- package/dist/cli/providers/openai.d.ts.map +1 -1
- package/dist/cli/providers/openai.js +36 -7
- package/dist/cli/providers/openai.js.map +1 -1
- package/dist/cli/providers/types.d.ts +10 -1
- package/dist/cli/providers/types.d.ts.map +1 -1
- package/dist/cli/ui/activity.d.ts +9 -32
- package/dist/cli/ui/activity.d.ts.map +1 -1
- package/dist/cli/ui/activity.js +40 -119
- package/dist/cli/ui/activity.js.map +1 -1
- package/dist/cli/ui/bottom-chrome.d.ts +73 -0
- package/dist/cli/ui/bottom-chrome.d.ts.map +1 -0
- package/dist/cli/ui/bottom-chrome.js +213 -0
- package/dist/cli/ui/bottom-chrome.js.map +1 -0
- package/dist/cli/ui/command-suggest.d.ts +5 -3
- package/dist/cli/ui/command-suggest.d.ts.map +1 -1
- package/dist/cli/ui/command-suggest.js +8 -6
- package/dist/cli/ui/command-suggest.js.map +1 -1
- package/dist/spiral/injection.d.ts.map +1 -1
- package/dist/spiral/injection.js +16 -5
- package/dist/spiral/injection.js.map +1 -1
- package/dist/utils/tokens.d.ts +1 -1
- package/dist/utils/tokens.js +1 -1
- package/package.json +4 -1
|
@@ -10,6 +10,7 @@ import { renderError, renderInfo, renderSpiralStatus, renderUserMessage, } from
|
|
|
10
10
|
import { isInsideToolBlock } from '../ui/tool-output.js';
|
|
11
11
|
import { renderFeedProgress, renderFeedSummary } from '../ui/progress.js';
|
|
12
12
|
import { ActivityIndicator } from '../ui/activity.js';
|
|
13
|
+
import { BottomChrome } from '../ui/bottom-chrome.js';
|
|
13
14
|
import { theme } from '../ui/theme.js';
|
|
14
15
|
import { detectFeedIntent } from '../feed/intent.js';
|
|
15
16
|
import { runFeedPipeline } from '../feed/pipeline.js';
|
|
@@ -18,7 +19,7 @@ import { initializeTools } from '../agent/tools/registry.js';
|
|
|
18
19
|
import { runAgentLoop, AgentController, AgentAbortError } from '../agent/loop.js';
|
|
19
20
|
import { PermissionManager } from '../agent/permissions.js';
|
|
20
21
|
import { UndoStack } from '../agent/undo.js';
|
|
21
|
-
import { writeStatusBar, renderStatusBar, getGitInfo, truncateBar } from '../ui/statusbar.js';
|
|
22
|
+
import { writeStatusBar, renderStatusBar, getGitInfo, truncateBar, visibleLength } from '../ui/statusbar.js';
|
|
22
23
|
import { CheckpointStore } from '../checkpoints/store.js';
|
|
23
24
|
import { createKeybindingState, processKeypress } from '../checkpoints/keybinding.js';
|
|
24
25
|
import { runCheckpointBrowser } from '../checkpoints/browser.js';
|
|
@@ -32,6 +33,8 @@ import { getSuggestions, writeSuggestions, clearSuggestions } from '../ui/comman
|
|
|
32
33
|
import { selectMenu } from '../ui/select-menu.js';
|
|
33
34
|
import { BugJournal } from '../bugs/journal.js';
|
|
34
35
|
import { detectBugReport } from '../bugs/detector.js';
|
|
36
|
+
import { BrowserController } from '../browser/controller.js';
|
|
37
|
+
import { VisionProcessor } from '../browser/vision.js';
|
|
35
38
|
import { classifyTask } from '../validation/classifier.js';
|
|
36
39
|
import { generateCriteria } from '../validation/criteria.js';
|
|
37
40
|
import { validationLoop } from '../validation/autofix.js';
|
|
@@ -100,6 +103,13 @@ const HELP_CATEGORIES = [
|
|
|
100
103
|
{ cmd: '/bugfix', label: '/bugfix', description: 'Review & fix all open bugs' },
|
|
101
104
|
],
|
|
102
105
|
},
|
|
106
|
+
{
|
|
107
|
+
category: 'Browser', color: '#ff8800',
|
|
108
|
+
items: [
|
|
109
|
+
{ cmd: '/browser', label: '/browser [url]', description: 'Open browser (optional URL)' },
|
|
110
|
+
{ cmd: '/browser close', label: '/browser close', description: 'Close the browser' },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
103
113
|
{
|
|
104
114
|
category: 'Code & Git', color: '#8a2be2',
|
|
105
115
|
items: [
|
|
@@ -260,14 +270,21 @@ export async function chatCommand(options) {
|
|
|
260
270
|
const sessionBuffer = new SessionBuffer();
|
|
261
271
|
// Bug journal (persistent bug tracking)
|
|
262
272
|
const bugJournal = new BugJournal(process.cwd());
|
|
263
|
-
//
|
|
264
|
-
|
|
273
|
+
// Browser controller (lazy — instantiated on /browser or agent tool use)
|
|
274
|
+
let browserController;
|
|
275
|
+
let visionProcessor;
|
|
276
|
+
// Bottom chrome (3 fixed rows at terminal bottom: separator, hints, statusbar)
|
|
277
|
+
const chrome = new BottomChrome();
|
|
278
|
+
// Activity indicator (renders on chrome row 0 during agent work)
|
|
279
|
+
const activity = new ActivityIndicator(chrome);
|
|
265
280
|
// Agent controller for pause/resume
|
|
266
281
|
const agentController = new AgentController();
|
|
267
282
|
let agentRunning = false;
|
|
268
283
|
let autonomousMode = false;
|
|
269
284
|
// Forward-declared findings handler (reassigned by control protocol if active)
|
|
270
285
|
let pushFindingsToBrainFn = null;
|
|
286
|
+
// Forward-declared browser screenshot handler (reassigned when brain server is active)
|
|
287
|
+
let pushScreenshotToBrainFn = null;
|
|
271
288
|
// Session Manager — manages background sessions (security, auto, etc.)
|
|
272
289
|
const sessionMgr = new SessionManager({
|
|
273
290
|
flags: {
|
|
@@ -340,7 +357,7 @@ export async function chatCommand(options) {
|
|
|
340
357
|
});
|
|
341
358
|
// Single message mode
|
|
342
359
|
if (options.message) {
|
|
343
|
-
await sendAgentMessage(options.message, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, agentController, activity, sessionBuffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; }, undefined, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal);
|
|
360
|
+
await sendAgentMessage(options.message, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, agentController, activity, sessionBuffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; }, undefined, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal, browserController, visionProcessor, pushScreenshotToBrainFn);
|
|
344
361
|
spiralEngine?.close();
|
|
345
362
|
return;
|
|
346
363
|
}
|
|
@@ -380,7 +397,7 @@ export async function chatCommand(options) {
|
|
|
380
397
|
// === Register CLI ↔ Web control protocol ===
|
|
381
398
|
if (brainUrl) {
|
|
382
399
|
try {
|
|
383
|
-
const { registerControlHandlers, setInstanceMeta, getBrainToken, pushSessionCreated, pushSessionUpdate, pushSessionRemoved, pushOutputLine, startRelayClient, } = await import('../brain/generator.js');
|
|
400
|
+
const { registerControlHandlers, setInstanceMeta, getBrainToken, pushSessionCreated, pushSessionUpdate, pushSessionRemoved, pushOutputLine, pushBugCreated, pushBugUpdated, pushBrowserScreenshot, pushControlEvent, startRelayClient, } = await import('../brain/generator.js');
|
|
384
401
|
const { serializeSession, buildInstanceMeta, resetInstanceStartTime } = await import('../brain/control-protocol.js');
|
|
385
402
|
resetInstanceStartTime();
|
|
386
403
|
// Collected findings for getFindings() handler
|
|
@@ -484,12 +501,84 @@ export async function chatCommand(options) {
|
|
|
484
501
|
pushSessionUpdate(serializeSession(session));
|
|
485
502
|
return true;
|
|
486
503
|
},
|
|
487
|
-
sendChat: (text) => {
|
|
488
|
-
|
|
489
|
-
|
|
504
|
+
sendChat: (text, chatId, mode) => {
|
|
505
|
+
const effectiveChatId = chatId || `web-${Date.now()}`;
|
|
506
|
+
// Import web chat handler and run asynchronously
|
|
507
|
+
import('../brain/web-chat-handler.js').then(({ handleWebChat }) => {
|
|
508
|
+
handleWebChat(text, effectiveChatId, {
|
|
509
|
+
provider,
|
|
510
|
+
spiralEngine,
|
|
511
|
+
project,
|
|
512
|
+
config,
|
|
513
|
+
checkpointStore,
|
|
514
|
+
bugJournal,
|
|
515
|
+
}, {
|
|
516
|
+
onStarted: (cid) => {
|
|
517
|
+
pushControlEvent({ type: 'chat_started', chatId: cid, timestamp: Date.now() });
|
|
518
|
+
},
|
|
519
|
+
onTextChunk: (cid, chunk) => {
|
|
520
|
+
pushControlEvent({ type: 'chat_text_chunk', chatId: cid, text: chunk, timestamp: Date.now() });
|
|
521
|
+
},
|
|
522
|
+
onToolStart: (cid, stepNum, toolName, toolInput) => {
|
|
523
|
+
pushControlEvent({ type: 'chat_tool_start', chatId: cid, stepNum, toolName, toolInput, timestamp: Date.now() });
|
|
524
|
+
},
|
|
525
|
+
onToolEnd: (cid, stepNum, toolName, status, result) => {
|
|
526
|
+
pushControlEvent({ type: 'chat_tool_end', chatId: cid, stepNum, toolName, status, result, timestamp: Date.now() });
|
|
527
|
+
},
|
|
528
|
+
onComplete: (cid, fullText, steps, tokensUsed) => {
|
|
529
|
+
pushControlEvent({ type: 'chat_complete', chatId: cid, text: fullText, steps, tokensUsed, timestamp: Date.now() });
|
|
530
|
+
},
|
|
531
|
+
onError: (cid, error) => {
|
|
532
|
+
pushControlEvent({ type: 'chat_error', chatId: cid, error, timestamp: Date.now() });
|
|
533
|
+
},
|
|
534
|
+
}).catch((err) => {
|
|
535
|
+
pushControlEvent({ type: 'chat_error', chatId: effectiveChatId, error: err?.message || 'Unknown error', timestamp: Date.now() });
|
|
536
|
+
});
|
|
537
|
+
}).catch(() => { });
|
|
490
538
|
},
|
|
491
539
|
getFindings: () => [...collectedFindings],
|
|
540
|
+
getBugs: () => bugJournal.getAllBugs().map(b => ({
|
|
541
|
+
id: b.id,
|
|
542
|
+
description: b.description,
|
|
543
|
+
file: b.file,
|
|
544
|
+
line: b.line,
|
|
545
|
+
status: b.status,
|
|
546
|
+
createdAt: b.createdAt,
|
|
547
|
+
updatedAt: b.updatedAt,
|
|
548
|
+
fixedAt: b.fixedAt,
|
|
549
|
+
fixDescription: b.fixDescription,
|
|
550
|
+
})),
|
|
551
|
+
});
|
|
552
|
+
// Wire bug journal change events to brain server
|
|
553
|
+
bugJournal.setOnChange((event, bug) => {
|
|
554
|
+
const bugInfo = {
|
|
555
|
+
id: bug.id,
|
|
556
|
+
description: bug.description,
|
|
557
|
+
file: bug.file,
|
|
558
|
+
line: bug.line,
|
|
559
|
+
status: bug.status,
|
|
560
|
+
createdAt: bug.createdAt,
|
|
561
|
+
updatedAt: bug.updatedAt,
|
|
562
|
+
fixedAt: bug.fixedAt,
|
|
563
|
+
fixDescription: bug.fixDescription,
|
|
564
|
+
};
|
|
565
|
+
if (event === 'bug_created') {
|
|
566
|
+
pushBugCreated(bugInfo);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
pushBugUpdated(bugInfo);
|
|
570
|
+
}
|
|
492
571
|
});
|
|
572
|
+
// Wire browser screenshots to brain server
|
|
573
|
+
pushScreenshotToBrainFn = (info) => {
|
|
574
|
+
pushBrowserScreenshot({
|
|
575
|
+
url: info.url,
|
|
576
|
+
title: info.title,
|
|
577
|
+
timestamp: Date.now(),
|
|
578
|
+
imageBase64: info.imageBase64,
|
|
579
|
+
analysis: info.analysis,
|
|
580
|
+
});
|
|
581
|
+
};
|
|
493
582
|
// Override forward-reference to also collect findings for control protocol
|
|
494
583
|
pushFindingsToBrainFn = (session) => {
|
|
495
584
|
pushFindingsToBrain(session);
|
|
@@ -533,8 +622,29 @@ export async function chatCommand(options) {
|
|
|
533
622
|
startAuto: (goal) => { /* relay delegates to local handlers — already registered */ return ''; },
|
|
534
623
|
startSecurity: () => '',
|
|
535
624
|
abortSession: (id) => { sessionMgr.abort(id); return true; },
|
|
536
|
-
sendChat: (text) => {
|
|
625
|
+
sendChat: (text, chatId, mode) => {
|
|
626
|
+
const effectiveChatId = chatId || `relay-${Date.now()}`;
|
|
627
|
+
import('../brain/web-chat-handler.js').then(({ handleWebChat }) => {
|
|
628
|
+
handleWebChat(text, effectiveChatId, {
|
|
629
|
+
provider, spiralEngine, project, config, checkpointStore, bugJournal,
|
|
630
|
+
}, {
|
|
631
|
+
onStarted: (cid) => { pushControlEvent({ type: 'chat_started', chatId: cid, timestamp: Date.now() }); },
|
|
632
|
+
onTextChunk: (cid, chunk) => { pushControlEvent({ type: 'chat_text_chunk', chatId: cid, text: chunk, timestamp: Date.now() }); },
|
|
633
|
+
onToolStart: (cid, sn, tn, ti) => { pushControlEvent({ type: 'chat_tool_start', chatId: cid, stepNum: sn, toolName: tn, toolInput: ti, timestamp: Date.now() }); },
|
|
634
|
+
onToolEnd: (cid, sn, tn, st, r) => { pushControlEvent({ type: 'chat_tool_end', chatId: cid, stepNum: sn, toolName: tn, status: st, result: r, timestamp: Date.now() }); },
|
|
635
|
+
onComplete: (cid, ft, s, tu) => { pushControlEvent({ type: 'chat_complete', chatId: cid, text: ft, steps: s, tokensUsed: tu, timestamp: Date.now() }); },
|
|
636
|
+
onError: (cid, e) => { pushControlEvent({ type: 'chat_error', chatId: cid, error: e, timestamp: Date.now() }); },
|
|
637
|
+
}).catch((err) => {
|
|
638
|
+
pushControlEvent({ type: 'chat_error', chatId: effectiveChatId, error: err?.message || 'Unknown error', timestamp: Date.now() });
|
|
639
|
+
});
|
|
640
|
+
}).catch(() => { });
|
|
641
|
+
},
|
|
537
642
|
getFindings: () => [...collectedFindings],
|
|
643
|
+
getBugs: () => bugJournal.getAllBugs().map(b => ({
|
|
644
|
+
id: b.id, description: b.description, file: b.file, line: b.line,
|
|
645
|
+
status: b.status, createdAt: b.createdAt, updatedAt: b.updatedAt,
|
|
646
|
+
fixedAt: b.fixedAt, fixDescription: b.fixDescription,
|
|
647
|
+
})),
|
|
538
648
|
}, updateMeta).catch(() => { });
|
|
539
649
|
}
|
|
540
650
|
}
|
|
@@ -566,40 +676,81 @@ export async function chatCommand(options) {
|
|
|
566
676
|
const ansiStart = '\x01'; // RL_PROMPT_START_IGNORE
|
|
567
677
|
const ansiEnd = '\x02'; // RL_PROMPT_END_IGNORE
|
|
568
678
|
// Wrap each ANSI escape sequence so readline ignores it for width calculation
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
|
|
679
|
+
const pipe = chalk.hex('#00d4ff').dim('\u2502');
|
|
680
|
+
const gt = chalk.hex('#00d4ff').bold('\u276F');
|
|
681
|
+
const escapedPipe = pipe.replace(/(\x1b\[[0-9;]*m)/g, `${ansiStart}$1${ansiEnd}`);
|
|
682
|
+
const escapedGt = gt.replace(/(\x1b\[[0-9;]*m)/g, `${ansiStart}$1${ansiEnd}`);
|
|
683
|
+
return `${escapedPipe} ${escapedGt} `;
|
|
572
684
|
}
|
|
573
|
-
/**
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
685
|
+
/** Build top border for input box: ┌──────────────────┐ */
|
|
686
|
+
function buildTopBorder(w) {
|
|
687
|
+
const dim = chalk.hex('#00d4ff').dim;
|
|
688
|
+
return dim('\u250C' + '\u2500'.repeat(w - 2) + '\u2510');
|
|
689
|
+
}
|
|
690
|
+
/** Build bottom border with embedded statusbar: └─ ◉ L1:7020 | ⚡ 636 tok ──┘ */
|
|
691
|
+
function buildBottomBorder(w, statusText) {
|
|
692
|
+
const dim = chalk.hex('#00d4ff').dim;
|
|
693
|
+
const prefix = dim('\u2514\u2500 ');
|
|
694
|
+
const statusVis = visibleLength(statusText);
|
|
695
|
+
const fill = Math.max(0, w - 4 - statusVis - 1); // 4 = "└─ " + space, 1 = "┘"
|
|
696
|
+
return `${prefix}${statusText} ${dim('\u2500'.repeat(fill) + '\u2518')}`;
|
|
697
|
+
}
|
|
698
|
+
/** Build hint line content for chrome row 2 */
|
|
699
|
+
function buildHintLine() {
|
|
586
700
|
const data = getStatusBarData();
|
|
587
|
-
const bar = renderStatusBar(data, w);
|
|
588
|
-
// Build hint line
|
|
589
701
|
const hints = [];
|
|
590
702
|
if (data.permissionMode === 'yolo')
|
|
591
703
|
hints.push(chalk.red('\u25B8\u25B8 yolo mode'));
|
|
592
704
|
else if (data.permissionMode === 'skip')
|
|
593
|
-
hints.push(chalk.yellow('\u25B8\u25B8
|
|
705
|
+
hints.push(chalk.yellow('\u25B8\u25B8 bypass permissions'));
|
|
594
706
|
else
|
|
595
707
|
hints.push(chalk.green('\u25B8\u25B8 safe permissions'));
|
|
596
|
-
hints.push(chalk.dim('
|
|
597
|
-
hints.push(chalk.dim('
|
|
598
|
-
|
|
599
|
-
|
|
708
|
+
hints.push(chalk.dim('shift+tab to cycle'));
|
|
709
|
+
hints.push(chalk.dim('esc to interrupt'));
|
|
710
|
+
return hints.join(chalk.dim(' \u00B7 '));
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Show the full prompt area using sticky bottom chrome with input box:
|
|
714
|
+
* ┌──────────────────────────────────────┐ ← row N-2: top border (chrome row 0)
|
|
715
|
+
* │ ❯ input here_ ← cursor here (bottom of scroll region, row N-3)
|
|
716
|
+
* └─ ◉ L1:7020 | ⚡ 636 tok | safe ──────┘ ← row N-1: bottom border + status (chrome row 1)
|
|
717
|
+
* ▸▸ safe permissions · shift+tab · esc ← row N: hints (chrome row 2)
|
|
718
|
+
*/
|
|
719
|
+
function showPrompt() {
|
|
720
|
+
const w = Math.max(20, (process.stdout.columns || 80) - 2);
|
|
721
|
+
// Build chrome row content
|
|
722
|
+
const topBorder = buildTopBorder(w);
|
|
723
|
+
const data = getStatusBarData();
|
|
724
|
+
const bar = renderStatusBar(data, w - 6); // narrower to fit in border frame
|
|
725
|
+
let statusText = bar;
|
|
726
|
+
if (sessionMgr.all.length > 1) {
|
|
727
|
+
const tabBar = truncateBar(sessionMgr.renderTabs(), Math.floor(w / 2));
|
|
728
|
+
statusText = `${tabBar} ${bar}`;
|
|
729
|
+
}
|
|
730
|
+
const bottomBorder = buildBottomBorder(w, statusText);
|
|
731
|
+
const hintLine = buildHintLine();
|
|
732
|
+
// Set top border content on activity indicator (for restore after agent work)
|
|
733
|
+
activity.setSeparatorContent(topBorder);
|
|
734
|
+
chrome.setRow(0, topBorder);
|
|
735
|
+
chrome.setRow(1, bottomBorder);
|
|
736
|
+
chrome.setRow(2, hintLine);
|
|
737
|
+
// Activate chrome if not already (sets scroll region + hooks stdout)
|
|
738
|
+
if (!chrome.isActive && !chrome.isInlineMode) {
|
|
739
|
+
chrome.activate();
|
|
740
|
+
}
|
|
600
741
|
isAtPrompt = true;
|
|
601
|
-
|
|
602
|
-
|
|
742
|
+
if (chrome.isActive) {
|
|
743
|
+
// Position cursor at the bottom of the scroll region, then prompt
|
|
744
|
+
chrome.positionCursorForPrompt();
|
|
745
|
+
rl.prompt();
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
// Inline fallback for small terminals
|
|
749
|
+
const dim = chalk.hex('#00d4ff').dim;
|
|
750
|
+
process.stdout.write(`\n${dim('\u250C' + '\u2500'.repeat(w - 2) + '\u2510')}\n`);
|
|
751
|
+
process.stdout.write(`${dim('\u2502')} `);
|
|
752
|
+
rl.prompt();
|
|
753
|
+
}
|
|
603
754
|
}
|
|
604
755
|
/** Build current status bar data object */
|
|
605
756
|
function getStatusBarData() {
|
|
@@ -642,18 +793,22 @@ export async function chatCommand(options) {
|
|
|
642
793
|
return [[], line];
|
|
643
794
|
},
|
|
644
795
|
});
|
|
645
|
-
// Update prompt and activity
|
|
796
|
+
// Update prompt, chrome, and activity on terminal resize
|
|
646
797
|
process.stdout.on('resize', () => {
|
|
647
798
|
rl.setPrompt(makePrompt());
|
|
799
|
+
chrome.handleResize();
|
|
648
800
|
activity.handleResize();
|
|
801
|
+
if (isAtPrompt) {
|
|
802
|
+
chrome.positionCursorForPrompt();
|
|
803
|
+
rl.prompt();
|
|
804
|
+
}
|
|
649
805
|
});
|
|
650
806
|
// Track suggestion overlay state
|
|
651
807
|
let lastSuggestionCount = 0;
|
|
652
808
|
permissions.setReadline(rl);
|
|
653
809
|
permissions.setPromptCallback((active) => { isAtPrompt = active; });
|
|
654
|
-
// Activity indicator renders on
|
|
655
|
-
//
|
|
656
|
-
// activity.isAnimating is true, so there's no conflict.
|
|
810
|
+
// Activity indicator renders on chrome row 0 (separator/activity row, terminal row N-2).
|
|
811
|
+
// BottomChrome manages the scroll region and stdout hook for all 3 fixed rows.
|
|
657
812
|
// Ctrl+C behavior:
|
|
658
813
|
// - If there's text on the line → clear the line (like a normal terminal)
|
|
659
814
|
// - If line is empty → count towards exit (double Ctrl+C = exit)
|
|
@@ -783,12 +938,12 @@ export async function chatCommand(options) {
|
|
|
783
938
|
// === Tab switching: Ctrl+PageUp / Ctrl+PageDown ===
|
|
784
939
|
if (key.ctrl && key.name === 'pageup') {
|
|
785
940
|
sessionMgr.switchPrev();
|
|
786
|
-
|
|
941
|
+
updateStatusBar();
|
|
787
942
|
return;
|
|
788
943
|
}
|
|
789
944
|
if (key.ctrl && key.name === 'pagedown') {
|
|
790
945
|
sessionMgr.switchNext();
|
|
791
|
-
|
|
946
|
+
updateStatusBar();
|
|
792
947
|
return;
|
|
793
948
|
}
|
|
794
949
|
// === Command Suggestions ===
|
|
@@ -841,8 +996,9 @@ export async function chatCommand(options) {
|
|
|
841
996
|
// Double-ESC detection (for checkpoint browser when nothing is running)
|
|
842
997
|
const result = processKeypress(key, keyState);
|
|
843
998
|
if (result.action === 'open_browser' && !agentRunning) {
|
|
844
|
-
// Open checkpoint browser
|
|
999
|
+
// Open checkpoint browser — deactivate chrome for fullscreen TUI
|
|
845
1000
|
rl.pause();
|
|
1001
|
+
chrome.deactivate();
|
|
846
1002
|
try {
|
|
847
1003
|
const browserResult = await runCheckpointBrowser({
|
|
848
1004
|
store: checkpointStore,
|
|
@@ -862,56 +1018,34 @@ export async function chatCommand(options) {
|
|
|
862
1018
|
catch {
|
|
863
1019
|
// Browser closed unexpectedly
|
|
864
1020
|
}
|
|
1021
|
+
chrome.activate();
|
|
865
1022
|
rl.resume();
|
|
866
1023
|
showPrompt();
|
|
867
1024
|
}
|
|
868
1025
|
});
|
|
869
1026
|
}
|
|
870
|
-
// Update statusbar
|
|
871
|
-
//
|
|
1027
|
+
// Update statusbar via BottomChrome row 1 (bottom border with embedded status).
|
|
1028
|
+
// Called during agent work by the footer timer to update token counts etc.
|
|
872
1029
|
function updateStatusBar() {
|
|
873
1030
|
if (!process.stdout.isTTY)
|
|
874
1031
|
return;
|
|
875
1032
|
const data = getStatusBarData();
|
|
876
|
-
|
|
877
|
-
|
|
1033
|
+
const w = (process.stdout.columns || 80) - 2;
|
|
1034
|
+
const bar = renderStatusBar(data, w - 6); // narrower to fit in border frame
|
|
1035
|
+
// Combine tab bar into status text when background sessions exist
|
|
1036
|
+
let statusText = bar;
|
|
878
1037
|
if (sessionMgr.all.length > 1) {
|
|
879
|
-
|
|
1038
|
+
const tabBar = truncateBar(sessionMgr.renderTabs(), Math.floor(w / 2));
|
|
1039
|
+
statusText = `${tabBar} ${bar}`;
|
|
1040
|
+
}
|
|
1041
|
+
if (chrome.isActive) {
|
|
1042
|
+
chrome.setRow(1, buildBottomBorder(w, statusText));
|
|
880
1043
|
}
|
|
881
1044
|
else {
|
|
882
|
-
//
|
|
883
|
-
|
|
1045
|
+
// Inline fallback
|
|
1046
|
+
writeStatusBar(data);
|
|
884
1047
|
}
|
|
885
1048
|
}
|
|
886
|
-
/** Clear the tab bar row (row N-1) to remove stale tab bar text */
|
|
887
|
-
function clearTabBarRow() {
|
|
888
|
-
if (!process.stdout.isTTY)
|
|
889
|
-
return;
|
|
890
|
-
const termHeight = process.stdout.rows || 24;
|
|
891
|
-
process.stdout.write(`\x1b7` + // Save cursor
|
|
892
|
-
`\x1b[${termHeight - 1};0H` + // Move to tab bar row
|
|
893
|
-
`\x1b[2K` + // Clear line
|
|
894
|
-
`\x1b8`);
|
|
895
|
-
}
|
|
896
|
-
/** Draw the session tab bar above the statusbar */
|
|
897
|
-
function writeTabBar() {
|
|
898
|
-
if (!process.stdout.isTTY)
|
|
899
|
-
return;
|
|
900
|
-
if (sessionMgr.all.length <= 1)
|
|
901
|
-
return;
|
|
902
|
-
const tabBar = sessionMgr.renderTabs();
|
|
903
|
-
const termHeight = process.stdout.rows || 24;
|
|
904
|
-
const termWidth = (process.stdout.columns || 80) - 2;
|
|
905
|
-
// Truncate tab bar to terminal width to prevent overflow into other rows
|
|
906
|
-
const safeTabBar = truncateBar(tabBar, termWidth);
|
|
907
|
-
// Write tab bar above the statusbar (termHeight - 1)
|
|
908
|
-
// Layout: ..., tabbar(N-1), statusbar(N)
|
|
909
|
-
process.stdout.write(`\x1b7` + // Save cursor
|
|
910
|
-
`\x1b[${termHeight - 1};0H` + // Move to row above statusbar
|
|
911
|
-
`\x1b[2K` + // Clear line
|
|
912
|
-
` ${safeTabBar}` + // Tab bar (truncated to fit)
|
|
913
|
-
`\x1b8`);
|
|
914
|
-
}
|
|
915
1049
|
/** Push session findings to brain visualization */
|
|
916
1050
|
function pushFindingsToBrain(session) {
|
|
917
1051
|
import('../brain/generator.js').then(mod => {
|
|
@@ -958,13 +1092,14 @@ export async function chatCommand(options) {
|
|
|
958
1092
|
const PASTE_THRESHOLD_MS = 100; // Lines arriving faster than this = paste (100ms for Windows compat)
|
|
959
1093
|
// Show full prompt area on startup (separator + status + > prompt)
|
|
960
1094
|
showPrompt();
|
|
961
|
-
// Footer timer — redraws
|
|
1095
|
+
// Footer timer — redraws statusbar on chrome row 2 during agent work.
|
|
962
1096
|
// Skipped when:
|
|
963
1097
|
// - user is at readline prompt (isAtPrompt) — prevents cursor-jumping
|
|
964
|
-
// - activity indicator is animating — prevents flicker collision
|
|
965
1098
|
// - inline progress active (inlineProgressActive) — prevents flicker over feed progress
|
|
1099
|
+
// Note: activity.isAnimating guard no longer needed — activity uses chrome row 0,
|
|
1100
|
+
// statusbar uses chrome row 2, they don't collide.
|
|
966
1101
|
const footerTimer = setInterval(() => {
|
|
967
|
-
if (process.stdout.isTTY && !isAtPrompt && !
|
|
1102
|
+
if (process.stdout.isTTY && !isAtPrompt && !inlineProgressActive)
|
|
968
1103
|
updateStatusBar();
|
|
969
1104
|
}, 500);
|
|
970
1105
|
footerTimer.unref();
|
|
@@ -999,6 +1134,46 @@ export async function chatCommand(options) {
|
|
|
999
1134
|
showPrompt();
|
|
1000
1135
|
return;
|
|
1001
1136
|
}
|
|
1137
|
+
// Handle /browser directly (needs access to browserController closure)
|
|
1138
|
+
if (input.startsWith('/browser')) {
|
|
1139
|
+
const browserParts = input.split(/\s+/);
|
|
1140
|
+
const browserArg = browserParts.slice(1).join(' ').trim();
|
|
1141
|
+
if (browserArg === 'close') {
|
|
1142
|
+
if (browserController?.isOpen()) {
|
|
1143
|
+
await browserController.close();
|
|
1144
|
+
renderInfo('Browser closed.');
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
renderInfo('Browser is not open.');
|
|
1148
|
+
}
|
|
1149
|
+
showPrompt();
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
// Initialize browser controller if needed
|
|
1153
|
+
if (!browserController) {
|
|
1154
|
+
browserController = new BrowserController();
|
|
1155
|
+
}
|
|
1156
|
+
if (browserController.isOpen() && !browserArg) {
|
|
1157
|
+
renderInfo(`Browser already open at: ${browserController.getUrl() || 'about:blank'}`);
|
|
1158
|
+
showPrompt();
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
try {
|
|
1162
|
+
if (!browserController.isOpen()) {
|
|
1163
|
+
await browserController.launch();
|
|
1164
|
+
renderInfo('Browser opened.');
|
|
1165
|
+
}
|
|
1166
|
+
if (browserArg) {
|
|
1167
|
+
const result = await browserController.navigate(browserArg);
|
|
1168
|
+
renderInfo(`Navigated to: ${result.title} (${result.url})`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
catch (err) {
|
|
1172
|
+
renderInfo(`Browser error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1173
|
+
}
|
|
1174
|
+
showPrompt();
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1002
1177
|
// Handle slash commands
|
|
1003
1178
|
if (input.startsWith('/')) {
|
|
1004
1179
|
const handled = await handleSlashCommand(input, messages, agentHistory, config, spiralEngine, store, rl, permissions, undoStack, checkpointStore, sessionBuffer, { input: sessionTokensInput, output: sessionTokensOutput }, sessionToolCalls, (newProvider) => { provider = newProvider; config = store.getAll(); }, async (newScope) => {
|
|
@@ -1213,7 +1388,7 @@ export async function chatCommand(options) {
|
|
|
1213
1388
|
// Activity started — readline stays active for type-ahead buffering
|
|
1214
1389
|
// but we do NOT show a visible prompt (it would collide with tool output)
|
|
1215
1390
|
isAtPrompt = false;
|
|
1216
|
-
}, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal);
|
|
1391
|
+
}, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal, browserController, visionProcessor, pushScreenshotToBrainFn);
|
|
1217
1392
|
agentRunning = false;
|
|
1218
1393
|
// Keep simple message history for state persistence
|
|
1219
1394
|
messages.push({ role: 'user', content: input });
|
|
@@ -1229,7 +1404,7 @@ export async function chatCommand(options) {
|
|
|
1229
1404
|
agentRunning = true;
|
|
1230
1405
|
agentController.reset();
|
|
1231
1406
|
updateStatusBar();
|
|
1232
|
-
await sendAgentMessage(buffered.trim(), agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, agentController, activity, sessionBuffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; roundToolCalls++; }, () => { isAtPrompt = true; rl.prompt(); }, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal);
|
|
1407
|
+
await sendAgentMessage(buffered.trim(), agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, agentController, activity, sessionBuffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; roundToolCalls++; }, () => { isAtPrompt = true; rl.prompt(); }, { enabled: validationEnabled, verbose: validationVerbose, strict: validationStrict }, bugJournal, browserController, visionProcessor, pushScreenshotToBrainFn);
|
|
1233
1408
|
agentRunning = false;
|
|
1234
1409
|
messages.push({ role: 'user', content: buffered.trim() });
|
|
1235
1410
|
}
|
|
@@ -1356,6 +1531,7 @@ export async function chatCommand(options) {
|
|
|
1356
1531
|
}
|
|
1357
1532
|
rl.on('close', async () => {
|
|
1358
1533
|
clearInterval(footerTimer);
|
|
1534
|
+
chrome.deactivate();
|
|
1359
1535
|
if (spiralEngine) {
|
|
1360
1536
|
// Persist session buffer (goals, entities, decisions) into spiral brain
|
|
1361
1537
|
// so next session with the same brain can recall them
|
|
@@ -1377,6 +1553,13 @@ export async function chatCommand(options) {
|
|
|
1377
1553
|
catch { /* best effort */ }
|
|
1378
1554
|
spiralEngine.close();
|
|
1379
1555
|
}
|
|
1556
|
+
// Close browser if open
|
|
1557
|
+
if (browserController?.isOpen()) {
|
|
1558
|
+
try {
|
|
1559
|
+
await browserController.close();
|
|
1560
|
+
}
|
|
1561
|
+
catch { /* best effort */ }
|
|
1562
|
+
}
|
|
1380
1563
|
process.stdout.write('\n');
|
|
1381
1564
|
process.exit(0);
|
|
1382
1565
|
});
|
|
@@ -1404,7 +1587,7 @@ async function runBackgroundSession(session, prompt, provider, project, spiralEn
|
|
|
1404
1587
|
durationMs: session.elapsed,
|
|
1405
1588
|
};
|
|
1406
1589
|
}
|
|
1407
|
-
async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal) {
|
|
1590
|
+
async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal, browserController, visionProcessor, onBrowserScreenshot) {
|
|
1408
1591
|
// User message was rendered by renderUserMessage() in the caller before entering here.
|
|
1409
1592
|
// Intent Detection: Check if user wants to feed the codebase
|
|
1410
1593
|
const feedIntent = detectFeedIntent(input);
|
|
@@ -1471,6 +1654,10 @@ async function sendAgentMessage(input, agentHistory, provider, project, spiralEn
|
|
|
1471
1654
|
activity.start();
|
|
1472
1655
|
// Notify caller so it can show the readline prompt for type-ahead
|
|
1473
1656
|
onAgentStart?.();
|
|
1657
|
+
// Lazy-init vision processor when browser is available
|
|
1658
|
+
if (browserController && !visionProcessor) {
|
|
1659
|
+
visionProcessor = new VisionProcessor(provider);
|
|
1660
|
+
}
|
|
1474
1661
|
try {
|
|
1475
1662
|
const result = await runAgentLoop(input, agentHistory, {
|
|
1476
1663
|
provider,
|
|
@@ -1481,6 +1668,9 @@ async function sendAgentMessage(input, agentHistory, provider, project, spiralEn
|
|
|
1481
1668
|
undoStack,
|
|
1482
1669
|
spiralEngine,
|
|
1483
1670
|
bugJournal,
|
|
1671
|
+
browserController,
|
|
1672
|
+
visionProcessor,
|
|
1673
|
+
onBrowserScreenshot: onBrowserScreenshot ?? undefined,
|
|
1484
1674
|
},
|
|
1485
1675
|
checkpointStore,
|
|
1486
1676
|
sessionBuffer,
|