helixmind 0.2.29 → 0.2.30

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.

@@ -35,6 +35,7 @@ import { selectMenu } from '../ui/select-menu.js';
35
35
  import { BugJournal } from '../bugs/journal.js';
36
36
  import { detectBugReport } from '../bugs/detector.js';
37
37
  import { JarvisQueue } from '../jarvis/queue.js';
38
+ import { runJarvisDaemon } from '../jarvis/daemon.js';
38
39
  import { BrowserController } from '../browser/controller.js';
39
40
  import { VisionProcessor } from '../browser/vision.js';
40
41
  import { classifyTask } from '../validation/classifier.js';
@@ -459,8 +460,8 @@ export async function chatCommand(options) {
459
460
  // === Register CLI ↔ Web control protocol ===
460
461
  if (brainUrl) {
461
462
  try {
462
- const { registerControlHandlers, setInstanceMeta, getBrainToken, pushSessionCreated, pushSessionUpdate, pushSessionRemoved, pushOutputLine, pushBugCreated, pushBugUpdated, pushBrowserScreenshot, pushControlEvent, startRelayClient, } = await import('../brain/generator.js');
463
- const { serializeSession, buildInstanceMeta, resetInstanceStartTime } = await import('../brain/control-protocol.js');
463
+ const { registerControlHandlers, setInstanceMeta, getBrainToken, pushSessionCreated, pushSessionUpdate, pushSessionRemoved, pushOutputLine, pushBugCreated, pushBugUpdated, pushBrowserScreenshot, pushControlEvent, startRelayClient, pushJarvisTaskCreated, pushJarvisTaskUpdated, pushJarvisStatusChanged, } = await import('../brain/generator.js');
464
+ const { serializeSession, buildInstanceMeta, resetInstanceStartTime, serializeJarvisTask } = await import('../brain/control-protocol.js');
464
465
  resetInstanceStartTime();
465
466
  // Collected findings for getFindings() handler
466
467
  const collectedFindings = [];
@@ -714,14 +715,79 @@ export async function chatCommand(options) {
714
715
  fixedAt: b.fixedAt,
715
716
  fixDescription: b.fixDescription,
716
717
  })),
717
- // Jarvis stubs (not yet implemented)
718
- startJarvis: () => '',
719
- stopJarvis: () => false,
720
- pauseJarvis: () => false,
721
- resumeJarvis: () => false,
722
- addJarvisTask: (_t, _d) => ({ id: 0, title: '', description: '', status: 'pending', priority: 'medium', createdAt: Date.now(), updatedAt: Date.now(), retries: 0, maxRetries: 3, tags: [] }),
723
- listJarvisTasks: () => [],
724
- getJarvisStatus: () => ({ daemonState: 'stopped', currentTaskId: null, pendingCount: 0, completedCount: 0, failedCount: 0, totalCount: 0, uptimeMs: 0 }),
718
+ startJarvis: () => {
719
+ if (jarvisDaemonSession && jarvisDaemonSession.status === 'running')
720
+ return jarvisDaemonSession.id;
721
+ const bgSession = sessionMgr.create('\u{1F916} Jarvis', '\u{1F916}', agentHistory);
722
+ bgSession.start();
723
+ wireSessionOutput(bgSession);
724
+ pushSessionCreated(serializeSession(bgSession));
725
+ jarvisDaemonSession = bgSession;
726
+ jarvisPaused = false;
727
+ (async () => {
728
+ try {
729
+ await runJarvisDaemon(jarvisQueue, {
730
+ sendMessage: async (prompt) => {
731
+ bgSession.controller.reset();
732
+ const rth = { text: '' };
733
+ const orig = bgSession.buffer.addAssistantSummary.bind(bgSession.buffer);
734
+ bgSession.buffer.addAssistantSummary = (t) => { rth.text = t; orig(t); };
735
+ await sendAgentMessage(prompt, bgSession.history, provider, project, spiralEngine, config, permissions, bgSession.undoStack, checkpointStore, bgSession.controller, new ActivityIndicator(), bgSession.buffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; }, undefined, { enabled: false, verbose: false, strict: false });
736
+ bgSession.buffer.addAssistantSummary = orig;
737
+ return rth.text;
738
+ },
739
+ isAborted: () => bgSession.controller.isAborted,
740
+ isPaused: () => jarvisPaused,
741
+ onTaskStart: (task) => { bgSession.capture(`\u25B6 Task #${task.id}: ${task.title}`); pushSessionUpdate(serializeSession(bgSession)); },
742
+ onTaskComplete: (task, result) => { bgSession.capture(`\u2713 Task #${task.id} done: ${result.slice(0, 100)}`); pushSessionUpdate(serializeSession(bgSession)); },
743
+ onTaskFailed: (task, error) => { bgSession.capture(`\u2717 Task #${task.id} failed: ${error.slice(0, 100)}`); pushSessionUpdate(serializeSession(bgSession)); },
744
+ updateStatus: () => updateStatusBar(),
745
+ storeInSpiral: spiralEngine ? async (content, type, tags) => {
746
+ try {
747
+ await spiralEngine.add(content, { type: type, tags });
748
+ }
749
+ catch { }
750
+ } : undefined,
751
+ });
752
+ }
753
+ catch (err) {
754
+ if (!(err instanceof AgentAbortError))
755
+ bgSession.capture(`Jarvis error: ${err}`);
756
+ }
757
+ sessionMgr.complete(bgSession.id, { text: 'Jarvis stopped', steps: [], errors: bgSession.controller.isAborted ? ['Stopped'] : [], durationMs: bgSession.elapsed });
758
+ pushSessionUpdate(serializeSession(bgSession));
759
+ jarvisDaemonSession = null;
760
+ })();
761
+ return bgSession.id;
762
+ },
763
+ stopJarvis: () => {
764
+ if (!jarvisDaemonSession)
765
+ return false;
766
+ jarvisDaemonSession.abort();
767
+ pushSessionUpdate(serializeSession(jarvisDaemonSession));
768
+ jarvisDaemonSession = null;
769
+ return true;
770
+ },
771
+ pauseJarvis: () => {
772
+ if (!jarvisDaemonSession || jarvisPaused)
773
+ return false;
774
+ jarvisPaused = true;
775
+ jarvisQueue.setDaemonState('paused');
776
+ return true;
777
+ },
778
+ resumeJarvis: () => {
779
+ if (!jarvisDaemonSession || !jarvisPaused)
780
+ return false;
781
+ jarvisPaused = false;
782
+ jarvisQueue.setDaemonState('running');
783
+ return true;
784
+ },
785
+ addJarvisTask: (title, description, opts) => {
786
+ const task = jarvisQueue.addTask(title, description, opts);
787
+ return serializeJarvisTask(task);
788
+ },
789
+ listJarvisTasks: () => jarvisQueue.getAllTasks().map(serializeJarvisTask),
790
+ getJarvisStatus: () => jarvisQueue.getStatus(),
725
791
  });
726
792
  // Wire bug journal change events to brain server
727
793
  bugJournal.setOnChange((event, bug) => {
@@ -743,6 +809,16 @@ export async function chatCommand(options) {
743
809
  pushBugUpdated(bugInfo);
744
810
  }
745
811
  });
812
+ // Wire Jarvis queue change events to brain server
813
+ jarvisQueue.setOnChange((event, task) => {
814
+ const info = serializeJarvisTask(task);
815
+ if (event === 'task_created') {
816
+ pushJarvisTaskCreated(info);
817
+ }
818
+ else {
819
+ pushJarvisTaskUpdated(info);
820
+ }
821
+ });
746
822
  // Wire browser screenshots to brain server
747
823
  pushScreenshotToBrainFn = (info) => {
748
824
  pushBrowserScreenshot({
@@ -1715,7 +1791,64 @@ export async function chatCommand(options) {
1715
1791
  default:
1716
1792
  renderInfo(`Validation Matrix: ${validationEnabled ? 'ON' : 'OFF'} | Verbose: ${validationVerbose ? 'ON' : 'OFF'} | Strict: ${validationStrict ? 'ON' : 'OFF'}`);
1717
1793
  }
1718
- }, sessionMgr, registerBrainHandlers, (active) => { isAtPrompt = active; }, bugJournal);
1794
+ }, sessionMgr, registerBrainHandlers, (active) => { isAtPrompt = active; }, bugJournal, {
1795
+ queue: jarvisQueue,
1796
+ getSession: () => jarvisDaemonSession,
1797
+ setSession: (s) => { jarvisDaemonSession = s; },
1798
+ isPaused: () => jarvisPaused,
1799
+ setPaused: (v) => { jarvisPaused = v; },
1800
+ startDaemon: () => {
1801
+ const bgSession = sessionMgr.create('\u{1F916} Jarvis', '\u{1F916}', agentHistory);
1802
+ bgSession.start();
1803
+ jarvisDaemonSession = bgSession;
1804
+ jarvisPaused = false;
1805
+ renderInfo(chalk.hex('#ff00ff').bold('\u{1F916} Jarvis daemon started'));
1806
+ renderInfo(chalk.dim('Add tasks with /jarvis task "description"'));
1807
+ (async () => {
1808
+ try {
1809
+ await runJarvisDaemon(jarvisQueue, {
1810
+ sendMessage: async (prompt) => {
1811
+ bgSession.controller.reset();
1812
+ const rth = { text: '' };
1813
+ const orig = bgSession.buffer.addAssistantSummary.bind(bgSession.buffer);
1814
+ bgSession.buffer.addAssistantSummary = (t) => { rth.text = t; orig(t); };
1815
+ await sendAgentMessage(prompt, bgSession.history, provider, project, spiralEngine, config, permissions, bgSession.undoStack, checkpointStore, bgSession.controller, new ActivityIndicator(), bgSession.buffer, (inp, out) => { sessionTokensInput += inp; sessionTokensOutput += out; }, () => { sessionToolCalls++; }, undefined, { enabled: false, verbose: false, strict: false });
1816
+ bgSession.buffer.addAssistantSummary = orig;
1817
+ return rth.text;
1818
+ },
1819
+ isAborted: () => bgSession.controller.isAborted,
1820
+ isPaused: () => jarvisPaused,
1821
+ onTaskStart: (task) => {
1822
+ bgSession.capture(`\u25B6 Task #${task.id}: ${task.title}`);
1823
+ renderInfo(chalk.hex('#ff00ff')(`\u25B6 Jarvis: Starting task #${task.id} \u2014 ${task.title}`));
1824
+ },
1825
+ onTaskComplete: (task, result) => {
1826
+ bgSession.capture(`\u2713 Task #${task.id} done: ${result.slice(0, 100)}`);
1827
+ renderInfo(chalk.hex('#ff00ff')(`\u2713 Jarvis: Task #${task.id} completed`));
1828
+ },
1829
+ onTaskFailed: (task, error) => {
1830
+ bgSession.capture(`\u2717 Task #${task.id} failed: ${error.slice(0, 100)}`);
1831
+ renderInfo(chalk.hex('#ff00ff')(`\u2717 Jarvis: Task #${task.id} failed \u2014 ${error.slice(0, 60)}`));
1832
+ },
1833
+ updateStatus: () => updateStatusBar(),
1834
+ storeInSpiral: spiralEngine ? async (content, type, tags) => {
1835
+ try {
1836
+ await spiralEngine.add(content, { type: type, tags });
1837
+ }
1838
+ catch { }
1839
+ } : undefined,
1840
+ });
1841
+ }
1842
+ catch (err) {
1843
+ if (!(err instanceof AgentAbortError))
1844
+ bgSession.capture(`Jarvis error: ${err}`);
1845
+ }
1846
+ sessionMgr.complete(bgSession.id, { text: 'Jarvis stopped', steps: [], errors: bgSession.controller.isAborted ? ['Stopped'] : [], durationMs: bgSession.elapsed });
1847
+ jarvisDaemonSession = null;
1848
+ renderInfo(chalk.hex('#ff00ff')('\u{1F916} Jarvis daemon stopped'));
1849
+ })();
1850
+ },
1851
+ });
1719
1852
  if (handled === 'exit') {
1720
1853
  spiralEngine?.close();
1721
1854
  rl.close();
@@ -2289,7 +2422,7 @@ function showFullAutonomousWarning() {
2289
2422
  process.stdout.write(d('\u2502 ') + d('ESC = stop agent if needed.') + d(' \u2502') + '\n');
2290
2423
  process.stdout.write(d('\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F') + '\n\n');
2291
2424
  }
2292
- async function handleSlashCommand(input, messages, agentHistory, config, spiralEngine, store, rl, permissions, undoStack, checkpointStore, sessionBuffer, sessionTokens, sessionToolCalls, onProviderSwitch, onBrainSwitch, currentBrainScope, onAutonomous, onValidation, sessionManager, onRegisterBrainHandlers, onSubPrompt, bugJournal) {
2425
+ async function handleSlashCommand(input, messages, agentHistory, config, spiralEngine, store, rl, permissions, undoStack, checkpointStore, sessionBuffer, sessionTokens, sessionToolCalls, onProviderSwitch, onBrainSwitch, currentBrainScope, onAutonomous, onValidation, sessionManager, onRegisterBrainHandlers, onSubPrompt, bugJournal, jarvisCtx) {
2293
2426
  const parts = input.split(/\s+/);
2294
2427
  const cmd = parts[0].toLowerCase();
2295
2428
  switch (cmd) {
@@ -2312,7 +2445,7 @@ async function handleSlashCommand(input, messages, agentHistory, config, spiralE
2312
2445
  rl.resume();
2313
2446
  if (helpIdx >= 0 && helpCmds[helpIdx]) {
2314
2447
  // Execute the selected command
2315
- return handleSlashCommand(helpCmds[helpIdx], messages, agentHistory, config, spiralEngine, store, rl, permissions, undoStack, checkpointStore, sessionBuffer, sessionTokens, sessionToolCalls, onProviderSwitch, onBrainSwitch, currentBrainScope, onAutonomous, onValidation, sessionManager, onRegisterBrainHandlers, onSubPrompt, bugJournal);
2448
+ return handleSlashCommand(helpCmds[helpIdx], messages, agentHistory, config, spiralEngine, store, rl, permissions, undoStack, checkpointStore, sessionBuffer, sessionTokens, sessionToolCalls, onProviderSwitch, onBrainSwitch, currentBrainScope, onAutonomous, onValidation, sessionManager, onRegisterBrainHandlers, onSubPrompt, bugJournal, jarvisCtx);
2316
2449
  }
2317
2450
  break;
2318
2451
  }
@@ -2868,6 +3001,150 @@ async function handleSlashCommand(input, messages, agentHistory, config, spiralE
2868
3001
  await onAutonomous('security');
2869
3002
  }
2870
3003
  break;
3004
+ case '/jarvis': {
3005
+ if (!jarvisCtx) {
3006
+ renderInfo('Jarvis not available.');
3007
+ break;
3008
+ }
3009
+ const sub = parts[1]?.toLowerCase();
3010
+ const jq = jarvisCtx.queue;
3011
+ if (!sub || sub === 'start') {
3012
+ const existing = jarvisCtx.getSession();
3013
+ if (existing && existing.status === 'running') {
3014
+ renderInfo(chalk.hex('#ff00ff')('Jarvis is already running.'));
3015
+ const sl = jq.getStatusLine();
3016
+ if (sl)
3017
+ renderInfo(sl);
3018
+ break;
3019
+ }
3020
+ jarvisCtx.startDaemon();
3021
+ }
3022
+ else if (sub === 'task') {
3023
+ const taskText = input.replace(/^\/jarvis\s+task\s*/i, '').trim();
3024
+ if (!taskText) {
3025
+ renderInfo('Usage: /jarvis task "Do something"');
3026
+ break;
3027
+ }
3028
+ let title = taskText;
3029
+ let description = '';
3030
+ const quoteMatch = taskText.match(/^["'](.+?)["']\s*(.*)/s);
3031
+ if (quoteMatch) {
3032
+ title = quoteMatch[1];
3033
+ description = quoteMatch[2] || title;
3034
+ }
3035
+ else if (taskText.includes('\u2014') || taskText.includes('--')) {
3036
+ const sep = taskText.includes('\u2014') ? '\u2014' : '--';
3037
+ const sepIdx = taskText.indexOf(sep);
3038
+ title = taskText.slice(0, sepIdx).trim();
3039
+ description = taskText.slice(sepIdx + sep.length).trim() || title;
3040
+ }
3041
+ else {
3042
+ description = title;
3043
+ }
3044
+ const task = jq.addTask(title, description);
3045
+ renderInfo(chalk.hex('#ff00ff')(`\u2714 Task #${task.id} added: ${task.title}`));
3046
+ }
3047
+ else if (sub === 'tasks') {
3048
+ const tasks = jq.getAllTasks();
3049
+ if (tasks.length === 0) {
3050
+ renderInfo('No tasks in queue. Add with /jarvis task "..."');
3051
+ break;
3052
+ }
3053
+ const statusIcons = {
3054
+ pending: chalk.yellow('\u25CB'), running: chalk.hex('#ff00ff')('\u25CF'),
3055
+ completed: chalk.green('\u2713'), failed: chalk.red('\u2717'), paused: chalk.dim('\u25CB'),
3056
+ };
3057
+ process.stdout.write('\n');
3058
+ for (const t of tasks) {
3059
+ const icon = statusIcons[t.status] || '\u25CB';
3060
+ const priColor = t.priority === 'high' ? '#ff3333' : t.priority === 'medium' ? '#ffaa00' : '#888888';
3061
+ process.stdout.write(` ${icon} ${chalk.bold('#' + t.id)} ${chalk.hex(priColor)(`[${t.priority}]`)} ${t.title}\n`);
3062
+ if (t.result)
3063
+ process.stdout.write(chalk.dim(` Result: ${t.result.slice(0, 80)}\n`));
3064
+ if (t.error)
3065
+ process.stdout.write(chalk.red(` Error: ${t.error.slice(0, 80)}\n`));
3066
+ }
3067
+ process.stdout.write('\n');
3068
+ const sl = jq.getStatusLine();
3069
+ if (sl)
3070
+ renderInfo(sl);
3071
+ }
3072
+ else if (sub === 'status') {
3073
+ const taskId = parts[2] ? parseInt(parts[2], 10) : NaN;
3074
+ if (!isNaN(taskId)) {
3075
+ const task = jq.getAllTasks().find((t) => t.id === taskId);
3076
+ if (!task) {
3077
+ renderInfo(`Task #${taskId} not found.`);
3078
+ }
3079
+ else {
3080
+ process.stdout.write('\n');
3081
+ process.stdout.write(chalk.hex('#ff00ff').bold(` Task #${task.id}: ${task.title}\n`));
3082
+ process.stdout.write(` Status: ${task.status} | Priority: ${task.priority}\n`);
3083
+ process.stdout.write(` Created: ${new Date(task.createdAt).toLocaleString()}\n`);
3084
+ if (task.startedAt)
3085
+ process.stdout.write(` Started: ${new Date(task.startedAt).toLocaleString()}\n`);
3086
+ if (task.completedAt)
3087
+ process.stdout.write(` Completed: ${new Date(task.completedAt).toLocaleString()}\n`);
3088
+ if (task.description !== task.title)
3089
+ process.stdout.write(chalk.dim(` Description: ${task.description}\n`));
3090
+ if (task.result)
3091
+ process.stdout.write(chalk.green(` Result: ${task.result}\n`));
3092
+ if (task.error)
3093
+ process.stdout.write(chalk.red(` Error: ${task.error}\n`));
3094
+ process.stdout.write('\n');
3095
+ }
3096
+ }
3097
+ else {
3098
+ const status = jq.getStatus();
3099
+ process.stdout.write('\n');
3100
+ process.stdout.write(chalk.hex('#ff00ff').bold(' \u{1F916} Jarvis Status\n'));
3101
+ process.stdout.write(` Daemon: ${status.daemonState}`);
3102
+ if (status.currentTaskId)
3103
+ process.stdout.write(` (task #${status.currentTaskId})`);
3104
+ process.stdout.write('\n');
3105
+ process.stdout.write(` Pending: ${status.pendingCount} | Completed: ${status.completedCount} | Failed: ${status.failedCount}\n`);
3106
+ if (status.uptimeMs > 0)
3107
+ process.stdout.write(` Uptime: ${Math.floor(status.uptimeMs / 1000)}s\n`);
3108
+ process.stdout.write('\n');
3109
+ }
3110
+ }
3111
+ else if (sub === 'stop') {
3112
+ const s = jarvisCtx.getSession();
3113
+ if (!s) {
3114
+ renderInfo('Jarvis is not running.');
3115
+ break;
3116
+ }
3117
+ s.abort();
3118
+ jarvisCtx.setSession(null);
3119
+ renderInfo(chalk.hex('#ff00ff')('\u{1F916} Jarvis daemon stopped'));
3120
+ }
3121
+ else if (sub === 'pause') {
3122
+ if (!jarvisCtx.getSession() || jarvisCtx.isPaused()) {
3123
+ renderInfo(jarvisCtx.isPaused() ? 'Jarvis is already paused.' : 'Jarvis is not running.');
3124
+ break;
3125
+ }
3126
+ jarvisCtx.setPaused(true);
3127
+ jq.setDaemonState('paused');
3128
+ renderInfo(chalk.hex('#ff00ff')('\u23F8 Jarvis paused'));
3129
+ }
3130
+ else if (sub === 'resume') {
3131
+ if (!jarvisCtx.getSession() || !jarvisCtx.isPaused()) {
3132
+ renderInfo(!jarvisCtx.isPaused() ? 'Jarvis is not paused.' : 'Jarvis is not running.');
3133
+ break;
3134
+ }
3135
+ jarvisCtx.setPaused(false);
3136
+ jq.setDaemonState('running');
3137
+ renderInfo(chalk.hex('#ff00ff')('\u25B6 Jarvis resumed'));
3138
+ }
3139
+ else if (sub === 'clear') {
3140
+ const removed = jq.clearCompleted();
3141
+ renderInfo(`Cleared ${removed} completed task(s).`);
3142
+ }
3143
+ else {
3144
+ renderInfo('Usage: /jarvis [start|task|tasks|status|stop|pause|resume|clear]');
3145
+ }
3146
+ break;
3147
+ }
2871
3148
  case '/monitor': {
2872
3149
  if (!onAutonomous)
2873
3150
  break;