helixmind 0.7.2 → 0.8.0
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/README.md +34 -3
- package/dist/cli/agent/loop.d.ts.map +1 -1
- package/dist/cli/agent/loop.js +8 -4
- package/dist/cli/agent/loop.js.map +1 -1
- package/dist/cli/agent/permissions.d.ts +8 -2
- package/dist/cli/agent/permissions.d.ts.map +1 -1
- package/dist/cli/agent/permissions.js +19 -8
- package/dist/cli/agent/permissions.js.map +1 -1
- package/dist/cli/agent/tools/bug-list.js +8 -1
- package/dist/cli/agent/tools/bug-list.js.map +1 -1
- package/dist/cli/agent/tools/git-commit.js +12 -13
- package/dist/cli/agent/tools/git-commit.js.map +1 -1
- package/dist/cli/agent/tools/git-diff.js +3 -4
- package/dist/cli/agent/tools/git-diff.js.map +1 -1
- package/dist/cli/agent/tools/git-exec.d.ts +36 -0
- package/dist/cli/agent/tools/git-exec.d.ts.map +1 -0
- package/dist/cli/agent/tools/git-exec.js +56 -0
- package/dist/cli/agent/tools/git-exec.js.map +1 -0
- package/dist/cli/agent/tools/git-log.js +4 -5
- package/dist/cli/agent/tools/git-log.js.map +1 -1
- package/dist/cli/agent/tools/git-status.js +6 -6
- package/dist/cli/agent/tools/git-status.js.map +1 -1
- package/dist/cli/agent/tools/registry.d.ts +9 -0
- package/dist/cli/agent/tools/registry.d.ts.map +1 -1
- package/dist/cli/agent/tools/registry.js.map +1 -1
- package/dist/cli/agent/tools/run-command.js +22 -0
- package/dist/cli/agent/tools/run-command.js.map +1 -1
- package/dist/cli/agent/tools/web-research.js +7 -3
- package/dist/cli/agent/tools/web-research.js.map +1 -1
- package/dist/cli/bugs/journal.d.ts +11 -0
- package/dist/cli/bugs/journal.d.ts.map +1 -1
- package/dist/cli/bugs/journal.js +67 -2
- package/dist/cli/bugs/journal.js.map +1 -1
- package/dist/cli/bugs/types.d.ts +2 -0
- package/dist/cli/bugs/types.d.ts.map +1 -1
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +167 -16
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/core/stdin-envelope.d.ts +41 -0
- package/dist/cli/core/stdin-envelope.d.ts.map +1 -0
- package/dist/cli/core/stdin-envelope.js +107 -0
- package/dist/cli/core/stdin-envelope.js.map +1 -0
- package/dist/cli/jarvis/notifications.js +1 -1
- package/dist/cli/jarvis/telemetry.js +1 -1
- package/dist/cli/jarvis/types.d.ts +2 -2
- package/dist/cli/jarvis/types.d.ts.map +1 -1
- package/dist/cli/jarvis/types.js.map +1 -1
- package/dist/cli/license/checker.js +1 -1
- package/dist/cli/ui/select-menu.d.ts +12 -0
- package/dist/cli/ui/select-menu.d.ts.map +1 -1
- package/dist/cli/ui/select-menu.js +47 -27
- package/dist/cli/ui/select-menu.js.map +1 -1
- package/dist/cli/validation/static-checks.d.ts.map +1 -1
- package/dist/cli/validation/static-checks.js +62 -3
- package/dist/cli/validation/static-checks.js.map +1 -1
- package/dist/cli/validation/stats.d.ts.map +1 -1
- package/dist/cli/validation/stats.js +5 -3
- package/dist/cli/validation/stats.js.map +1 -1
- package/dist/spiral/cloud/search-provider.d.ts.map +1 -1
- package/dist/spiral/cloud/search-provider.js +29 -1
- package/dist/spiral/cloud/search-provider.js.map +1 -1
- package/dist/spiral/engine.d.ts +13 -0
- package/dist/spiral/engine.d.ts.map +1 -1
- package/dist/spiral/engine.js +35 -0
- package/dist/spiral/engine.js.map +1 -1
- package/dist/spiral/injection.d.ts +13 -0
- package/dist/spiral/injection.d.ts.map +1 -1
- package/dist/spiral/injection.js +66 -1
- package/dist/spiral/injection.js.map +1 -1
- package/dist/spiral/provenance.d.ts +37 -0
- package/dist/spiral/provenance.d.ts.map +1 -0
- package/dist/spiral/provenance.js +167 -0
- package/dist/spiral/provenance.js.map +1 -0
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import { Writable } from 'node:stream';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join, basename } from 'node:path';
|
|
5
5
|
import { writeFileSync } from 'node:fs';
|
|
6
|
+
import { markPrepended } from '../core/stdin-envelope.js';
|
|
6
7
|
import { ConfigStore } from '../config/store.js';
|
|
7
8
|
import { createProvider, registerFreeModel } from '../providers/registry.js';
|
|
8
9
|
import { registerModelContextLength } from '../providers/model-limits.js';
|
|
@@ -517,6 +518,71 @@ export async function chatCommand(options) {
|
|
|
517
518
|
const roundToolCounts = new Map();
|
|
518
519
|
const roundFilesTouched = new Set();
|
|
519
520
|
let recapEnabled = true; // Can be toggled via /recap
|
|
521
|
+
// audit-016/v0.8.0: brain growth telemetry for the session-exit display.
|
|
522
|
+
// Captured lazily on first spiral access; the diff is rendered before
|
|
523
|
+
// graceful exit so users SEE memory compounding ("+12 patterns learned,
|
|
524
|
+
// 3 stale retired"). The first session shows just the absolute count.
|
|
525
|
+
let brainStartNodes = null;
|
|
526
|
+
let brainStartConfidence = null;
|
|
527
|
+
function captureBrainStart() {
|
|
528
|
+
if (brainStartNodes !== null)
|
|
529
|
+
return;
|
|
530
|
+
try {
|
|
531
|
+
brainStartNodes = spiralEngine?.status?.().total_nodes ?? null;
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
brainStartNodes = null;
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
brainStartConfidence = jarvisIdentity?.getIdentity?.()?.traits?.confidence ?? null;
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
brainStartConfidence = null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function renderBrainGrowth() {
|
|
544
|
+
if (!spiralEngine)
|
|
545
|
+
return;
|
|
546
|
+
let nodesNow;
|
|
547
|
+
try {
|
|
548
|
+
nodesNow = spiralEngine.status().total_nodes;
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const stale = (() => {
|
|
554
|
+
try {
|
|
555
|
+
return spiralEngine.getStaleSeenLifetime?.() ?? 0;
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
})();
|
|
561
|
+
let confNow = null;
|
|
562
|
+
try {
|
|
563
|
+
confNow = jarvisIdentity?.getIdentity?.()?.traits?.confidence ?? null;
|
|
564
|
+
}
|
|
565
|
+
catch { /* ignore */ }
|
|
566
|
+
const parts = [];
|
|
567
|
+
if (brainStartNodes !== null) {
|
|
568
|
+
const delta = nodesNow - brainStartNodes;
|
|
569
|
+
if (delta > 0)
|
|
570
|
+
parts.push(chalk.green(`+${delta} pattern${delta !== 1 ? 's' : ''} learned`));
|
|
571
|
+
else if (delta < 0)
|
|
572
|
+
parts.push(chalk.dim(`${delta} compacted`));
|
|
573
|
+
}
|
|
574
|
+
if (stale > 0)
|
|
575
|
+
parts.push(chalk.yellow(`${stale} stale retired`));
|
|
576
|
+
if (confNow !== null && brainStartConfidence !== null && Math.abs(confNow - brainStartConfidence) > 0.001) {
|
|
577
|
+
const delta = confNow - brainStartConfidence;
|
|
578
|
+
const arrow = delta > 0 ? chalk.green('↑') : chalk.red('↓');
|
|
579
|
+
parts.push(`confidence ${brainStartConfidence.toFixed(2)}${arrow}${confNow.toFixed(2)}`);
|
|
580
|
+
}
|
|
581
|
+
if (parts.length === 0) {
|
|
582
|
+
parts.push(chalk.dim(`brain has ${nodesNow} pattern${nodesNow !== 1 ? 's' : ''}`));
|
|
583
|
+
}
|
|
584
|
+
process.stdout.write(chalk.dim(' 🧠 ') + parts.join(chalk.dim(' · ')) + '\n');
|
|
585
|
+
}
|
|
520
586
|
function recordRoundTool(toolName, input) {
|
|
521
587
|
roundToolCounts.set(toolName, (roundToolCounts.get(toolName) ?? 0) + 1);
|
|
522
588
|
const fileTools = new Set(['read_file', 'write_file', 'edit_file']);
|
|
@@ -738,6 +804,11 @@ export async function chatCommand(options) {
|
|
|
738
804
|
const dataDir = resolveSpiralDir(scope, process.cwd());
|
|
739
805
|
const spiralConfig = loadSpiralConfig(dataDir);
|
|
740
806
|
const engine = new SpiralEngine(spiralConfig);
|
|
807
|
+
// audit-014/v0.8.0 "Compounds": pin engine to project root so
|
|
808
|
+
// store() can capture file provenance and the injection layer can
|
|
809
|
+
// detect stale recall. Without this, the [STALE] marking is silent
|
|
810
|
+
// and the compounds feature is effectively disabled.
|
|
811
|
+
engine.setProjectRoot(projectRoot);
|
|
741
812
|
await engine.initialize();
|
|
742
813
|
return engine;
|
|
743
814
|
}
|
|
@@ -757,6 +828,8 @@ export async function chatCommand(options) {
|
|
|
757
828
|
spiralInitPromise = initSpiralEngine(scope).then(async (engine) => {
|
|
758
829
|
spiralEngine = engine;
|
|
759
830
|
spiralInitPromise = null;
|
|
831
|
+
// audit-016: capture session-start metrics for the growth display.
|
|
832
|
+
captureBrainStart();
|
|
760
833
|
if (engine && brainUrl) {
|
|
761
834
|
try {
|
|
762
835
|
const { startLiveBrain } = await import('../brain/generator.js');
|
|
@@ -2515,6 +2588,13 @@ export async function chatCommand(options) {
|
|
|
2515
2588
|
ctrlCCount++;
|
|
2516
2589
|
if (ctrlCCount >= 2) {
|
|
2517
2590
|
process.stdout.write('\n');
|
|
2591
|
+
// audit-016: brain growth display before saving + exit. Single
|
|
2592
|
+
// line, only when a spiral exists, never blocks. Compounding made
|
|
2593
|
+
// visible \u2014 the User-sp\u00fcrbare Pro-tier value moment.
|
|
2594
|
+
try {
|
|
2595
|
+
renderBrainGrowth();
|
|
2596
|
+
}
|
|
2597
|
+
catch { /* never block exit */ }
|
|
2518
2598
|
renderInfo('Force exit \u2014 saving state...');
|
|
2519
2599
|
if (spiralEngine) {
|
|
2520
2600
|
try {
|
|
@@ -2681,7 +2761,12 @@ export async function chatCommand(options) {
|
|
|
2681
2761
|
{
|
|
2682
2762
|
let lastRawEscTime = 0;
|
|
2683
2763
|
const RAW_ESC_THRESHOLD = 800; // ms
|
|
2684
|
-
|
|
2764
|
+
// audit-005: tag the raw ESC detector so any modal-stdin envelope (rewind
|
|
2765
|
+
// browser, plan browser, …) restores it via prependListener instead of
|
|
2766
|
+
// .on(), preserving its first-in-line position. Without the tag, after
|
|
2767
|
+
// the first modal exit readline's data parser could land before the ESC
|
|
2768
|
+
// detector and break double-ESC + bracketed-paste detection.
|
|
2769
|
+
const escDetector = markPrepended(async (chunk) => {
|
|
2685
2770
|
if (fullScreenBrowserOpen)
|
|
2686
2771
|
return;
|
|
2687
2772
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
@@ -2790,6 +2875,7 @@ export async function chatCommand(options) {
|
|
|
2790
2875
|
// Everything else (text, arrow keys, etc.) — don't reset timer
|
|
2791
2876
|
// because arrow key \x1b[A could arrive just after a real ESC press
|
|
2792
2877
|
});
|
|
2878
|
+
process.stdin.prependListener('data', escDetector);
|
|
2793
2879
|
async function openRewindBrowser() {
|
|
2794
2880
|
// Don't re-enter if already open
|
|
2795
2881
|
if (fullScreenBrowserOpen)
|
|
@@ -2820,13 +2906,20 @@ export async function chatCommand(options) {
|
|
|
2820
2906
|
// Fullscreen overlay: suspend=false removes stdout hook entirely so the
|
|
2821
2907
|
// Rewind browser can write directly to the terminal without buffering.
|
|
2822
2908
|
chrome.deactivate({ suspend: false });
|
|
2823
|
-
//
|
|
2824
|
-
//
|
|
2825
|
-
//
|
|
2909
|
+
// audit-011: snapshot+restore data listeners only. Do NOT touch
|
|
2910
|
+
// 'keypress' listeners — Node's readline attaches an internal data
|
|
2911
|
+
// parser when the first keypress listener is added, and removeAll +
|
|
2912
|
+
// re-add can cause it to register a second parser, which makes every
|
|
2913
|
+
// typed character afterwards fire twice ("double-write" symptom).
|
|
2914
|
+
// Keypress listeners are gated by fullScreenBrowserOpen anyway, so
|
|
2915
|
+
// they're safe to leave attached during the modal.
|
|
2916
|
+
//
|
|
2917
|
+
// Restore data listeners with prepend-symmetry: any listener tagged
|
|
2918
|
+
// via markPrepended() (the raw ESC detector at chat.ts:2915) is
|
|
2919
|
+
// re-attached as prepended so readline's data parser cannot land in
|
|
2920
|
+
// front of it after rl.resume().
|
|
2826
2921
|
const savedDataListeners = process.stdin.rawListeners('data').slice();
|
|
2827
|
-
const savedKeypressListeners = process.stdin.rawListeners('keypress').slice();
|
|
2828
2922
|
process.stdin.removeAllListeners('data');
|
|
2829
|
-
process.stdin.removeAllListeners('keypress');
|
|
2830
2923
|
let didRevertWithMessage = false;
|
|
2831
2924
|
try {
|
|
2832
2925
|
try {
|
|
@@ -2856,11 +2949,16 @@ export async function chatCommand(options) {
|
|
|
2856
2949
|
}
|
|
2857
2950
|
finally {
|
|
2858
2951
|
// Guaranteed listener restore, even if the browser or revert threw.
|
|
2952
|
+
// Tagged listeners (raw ESC detector) re-attach as prepended.
|
|
2953
|
+
const PREPENDED = Symbol.for('helixmind.stdin.prepended');
|
|
2859
2954
|
for (const listener of savedDataListeners) {
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2955
|
+
const isPrepended = Boolean(listener[PREPENDED]);
|
|
2956
|
+
if (isPrepended) {
|
|
2957
|
+
process.stdin.prependListener('data', listener);
|
|
2958
|
+
}
|
|
2959
|
+
else {
|
|
2960
|
+
process.stdin.on('data', listener);
|
|
2961
|
+
}
|
|
2864
2962
|
}
|
|
2865
2963
|
if (!didRevertWithMessage) {
|
|
2866
2964
|
inputMgr.setLine('');
|
|
@@ -3706,13 +3804,20 @@ export async function chatCommand(options) {
|
|
|
3706
3804
|
fullScreenBrowserOpen = true;
|
|
3707
3805
|
rl.pause();
|
|
3708
3806
|
chrome.deactivate({ suspend: false });
|
|
3709
|
-
//
|
|
3807
|
+
// audit-011: same envelope as openRewindBrowser. Snapshot only data
|
|
3808
|
+
// listeners; restore with prepend-symmetry for tagged ones.
|
|
3710
3809
|
const savedPlanListeners = process.stdin.rawListeners('data').slice();
|
|
3711
3810
|
process.stdin.removeAllListeners('data');
|
|
3712
3811
|
const { choice } = await runPlanBrowser(plan);
|
|
3713
|
-
|
|
3812
|
+
const PREPENDED = Symbol.for('helixmind.stdin.prepended');
|
|
3714
3813
|
for (const listener of savedPlanListeners) {
|
|
3715
|
-
|
|
3814
|
+
const isPrepended = Boolean(listener[PREPENDED]);
|
|
3815
|
+
if (isPrepended) {
|
|
3816
|
+
process.stdin.prependListener('data', listener);
|
|
3817
|
+
}
|
|
3818
|
+
else {
|
|
3819
|
+
process.stdin.on('data', listener);
|
|
3820
|
+
}
|
|
3716
3821
|
}
|
|
3717
3822
|
inputMgr.setLine('');
|
|
3718
3823
|
chrome.activate();
|
|
@@ -3918,7 +4023,27 @@ export async function chatCommand(options) {
|
|
|
3918
4023
|
else {
|
|
3919
4024
|
currentStepFile = '';
|
|
3920
4025
|
}
|
|
3921
|
-
}, jarvisLearning
|
|
4026
|
+
}, jarvisLearning, undefined, // runtime
|
|
4027
|
+
undefined, // skillManager
|
|
4028
|
+
// audit-006: feed validation outcome into identity calibration.
|
|
4029
|
+
(status, taskCategory) => {
|
|
4030
|
+
if (!jarvisIdentity)
|
|
4031
|
+
return;
|
|
4032
|
+
if (status === 'passed') {
|
|
4033
|
+
jarvisIdentity.recordEvent({
|
|
4034
|
+
type: 'task_completed',
|
|
4035
|
+
taskId: `validation:${taskCategory}`,
|
|
4036
|
+
summary: 'validation passed',
|
|
4037
|
+
});
|
|
4038
|
+
}
|
|
4039
|
+
else if (status === 'errors' || status === 'max_loops') {
|
|
4040
|
+
jarvisIdentity.recordEvent({
|
|
4041
|
+
type: 'task_failed',
|
|
4042
|
+
taskId: `validation:${taskCategory}`,
|
|
4043
|
+
error: `validation ${status}`,
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
});
|
|
3922
4047
|
}
|
|
3923
4048
|
}
|
|
3924
4049
|
agentRunning = false;
|
|
@@ -4121,6 +4246,13 @@ export async function chatCommand(options) {
|
|
|
4121
4246
|
await spiralEngine.saveState(messages);
|
|
4122
4247
|
}
|
|
4123
4248
|
catch { /* best effort */ }
|
|
4249
|
+
// audit-016: render the growth line BEFORE close so the spiral is
|
|
4250
|
+
// still queryable. The graceful path (rl close → here) is the
|
|
4251
|
+
// common exit; the force-exit path renders earlier (Ctrl+C×2).
|
|
4252
|
+
try {
|
|
4253
|
+
renderBrainGrowth();
|
|
4254
|
+
}
|
|
4255
|
+
catch { /* never block exit */ }
|
|
4124
4256
|
spiralEngine.close();
|
|
4125
4257
|
}
|
|
4126
4258
|
// Close browser if open
|
|
@@ -4157,7 +4289,13 @@ async function runBackgroundSession(session, prompt, provider, project, spiralEn
|
|
|
4157
4289
|
durationMs: session.elapsed,
|
|
4158
4290
|
};
|
|
4159
4291
|
}
|
|
4160
|
-
async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal, browserController, visionProcessor, onBrowserScreenshot, jarvisContext, onStepInfo, learningJournal, runtime, skillManager
|
|
4292
|
+
async function sendAgentMessage(input, agentHistory, provider, project, spiralEngine, config, permissions, undoStack, checkpointStore, controller, activity, sessionBuffer, onTokens, onToolCall, onAgentStart, validationOpts, bugJournal, browserController, visionProcessor, onBrowserScreenshot, jarvisContext, onStepInfo, learningJournal, runtime, skillManager,
|
|
4293
|
+
// audit-006: optional callback fired once per validation run. Used by the
|
|
4294
|
+
// foreground chat path to feed the result into JarvisIdentityManager so
|
|
4295
|
+
// confidence calibration actually updates after successful agent turns.
|
|
4296
|
+
// Background-session callers leave this undefined so they do not pollute
|
|
4297
|
+
// the user's confidence score with their own outcomes.
|
|
4298
|
+
onValidationComplete) {
|
|
4161
4299
|
// User message was rendered by renderUserMessage() in the caller before entering here.
|
|
4162
4300
|
const executionRoot = runtime?.executionRoot ?? process.cwd();
|
|
4163
4301
|
// Intent Detection: Check if user wants to feed the codebase
|
|
@@ -4345,6 +4483,19 @@ async function sendAgentMessage(input, agentHistory, provider, project, spiralEn
|
|
|
4345
4483
|
process.stdout.write(renderValidationSummary(valResult, validationOpts.verbose) + '\n');
|
|
4346
4484
|
// Store stats in spiral
|
|
4347
4485
|
await storeValidationResult(valResult, classification.category, spiralEngine || undefined);
|
|
4486
|
+
// audit-006: bridge validation outcome into the identity
|
|
4487
|
+
// calibration layer. Previously the confidence score was
|
|
4488
|
+
// initialized at 0.5 and only nudged by proposal approvals and
|
|
4489
|
+
// the autonomous-task scheduler; the regular agent turn that
|
|
4490
|
+
// ran validation never updated it. This is why the user-visible
|
|
4491
|
+
// confidence stayed stuck at 0.52 across many sessions despite
|
|
4492
|
+
// successful fixes. We treat 'passed' as task_completed and
|
|
4493
|
+
// 'errors'/'max_loops' as task_failed; 'warnings' is neutral
|
|
4494
|
+
// (do not penalize).
|
|
4495
|
+
try {
|
|
4496
|
+
onValidationComplete?.(valResult.status, classification.category);
|
|
4497
|
+
}
|
|
4498
|
+
catch { /* never block on a calibration callback */ }
|
|
4348
4499
|
}
|
|
4349
4500
|
}
|
|
4350
4501
|
}
|
|
@@ -5831,7 +5982,7 @@ async function handleSlashCommand(input, messages, agentHistory, config, spiralE
|
|
|
5831
5982
|
cancelLabel: 'Cancel',
|
|
5832
5983
|
});
|
|
5833
5984
|
if (modeIdx === docsItemIdx) {
|
|
5834
|
-
const docsUrl = 'https://
|
|
5985
|
+
const docsUrl = 'https://helix-mind.ai/docs/security-monitor';
|
|
5835
5986
|
const { exec } = await import('node:child_process');
|
|
5836
5987
|
const { platform } = await import('node:os');
|
|
5837
5988
|
const openCmd = platform() === 'win32' ? `start "" "${docsUrl}"`
|