hebbian 0.5.0 → 0.5.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/dist/api.d.ts.map +1 -1
- package/dist/bin/hebbian.js +60 -19
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/digest.d.ts.map +1 -1
- package/dist/evolve.d.ts.map +1 -1
- package/dist/grow.d.ts.map +1 -1
- package/dist/index.js +49 -6
- package/dist/index.js.map +1 -1
- package/dist/outcome.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AAgBpF,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;CACjD;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AAgBpF,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;CACjD;AAgQD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAO,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CA4BxF;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,EAAE,CAEjD;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC"}
|
package/dist/bin/hebbian.js
CHANGED
|
@@ -876,6 +876,9 @@ function growNeuron(brainRoot, neuronPath) {
|
|
|
876
876
|
const counter = fireNeuron(brainRoot, neuronPath);
|
|
877
877
|
return { action: "fired", path: neuronPath, counter };
|
|
878
878
|
}
|
|
879
|
+
if (neuronPath.includes("..") || neuronPath.startsWith("/")) {
|
|
880
|
+
throw new Error(`Invalid neuron path: "${neuronPath}" (path traversal not allowed)`);
|
|
881
|
+
}
|
|
879
882
|
const parts = neuronPath.split("/");
|
|
880
883
|
const regionName = parts[0];
|
|
881
884
|
if (!REGIONS.includes(regionName)) {
|
|
@@ -1645,7 +1648,16 @@ function error(res, message, status = 400) {
|
|
|
1645
1648
|
async function readBody(req) {
|
|
1646
1649
|
return new Promise((resolve4, reject) => {
|
|
1647
1650
|
const chunks = [];
|
|
1648
|
-
|
|
1651
|
+
let total = 0;
|
|
1652
|
+
req.on("data", (chunk) => {
|
|
1653
|
+
total += chunk.length;
|
|
1654
|
+
if (total > MAX_BODY_BYTES) {
|
|
1655
|
+
reject(new Error("Request body too large"));
|
|
1656
|
+
req.destroy();
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
chunks.push(chunk);
|
|
1660
|
+
});
|
|
1649
1661
|
req.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
1650
1662
|
req.on("error", reject);
|
|
1651
1663
|
});
|
|
@@ -1835,7 +1847,7 @@ function getPendingReports() {
|
|
|
1835
1847
|
function clearReports() {
|
|
1836
1848
|
pendingReports.length = 0;
|
|
1837
1849
|
}
|
|
1838
|
-
var lastAPIActivity, pendingReports;
|
|
1850
|
+
var lastAPIActivity, pendingReports, MAX_BODY_BYTES;
|
|
1839
1851
|
var init_api = __esm({
|
|
1840
1852
|
"src/api.ts"() {
|
|
1841
1853
|
"use strict";
|
|
@@ -1852,6 +1864,7 @@ var init_api = __esm({
|
|
|
1852
1864
|
init_constants();
|
|
1853
1865
|
lastAPIActivity = Date.now();
|
|
1854
1866
|
pendingReports = [];
|
|
1867
|
+
MAX_BODY_BYTES = 1048576;
|
|
1855
1868
|
}
|
|
1856
1869
|
});
|
|
1857
1870
|
|
|
@@ -2145,6 +2158,8 @@ function extractCorrections(messages) {
|
|
|
2145
2158
|
if (text.length < MIN_CORRECTION_LENGTH) continue;
|
|
2146
2159
|
if (/^[\/!]/.test(text.trim())) continue;
|
|
2147
2160
|
if (text.trim().endsWith("?")) continue;
|
|
2161
|
+
if (/^<[a-zA-Z]/.test(text.trim())) continue;
|
|
2162
|
+
if (/^Base directory for this skill:/i.test(text.trim())) continue;
|
|
2148
2163
|
const correction = detectCorrection(text);
|
|
2149
2164
|
if (correction) {
|
|
2150
2165
|
corrections.push(correction);
|
|
@@ -2556,8 +2571,9 @@ function buildOutcomeSummary(brainRoot) {
|
|
|
2556
2571
|
});
|
|
2557
2572
|
for (const [neuron, s] of sorted) {
|
|
2558
2573
|
const ratio = s.sessions > 0 ? (s.reverts / s.sessions).toFixed(2) : "0.00";
|
|
2559
|
-
const trend = parseFloat(ratio) > 0.5 ? "
|
|
2560
|
-
|
|
2574
|
+
const trend = parseFloat(ratio) > 0.5 ? "act on this" : parseFloat(ratio) > 0.3 ? "watch" : "";
|
|
2575
|
+
const safePath = neuron.replace(/[\n\r#]/g, " ").trim();
|
|
2576
|
+
lines.push(`- ${safePath}: sessions=${s.sessions} reverts=${s.reverts} acceptances=${s.acceptances} contra_ratio=${ratio} ${trend}`);
|
|
2561
2577
|
}
|
|
2562
2578
|
lines.push("");
|
|
2563
2579
|
return lines.join("\n");
|
|
@@ -2621,12 +2637,27 @@ __export(evolve_exports, {
|
|
|
2621
2637
|
runEvolve: () => runEvolve,
|
|
2622
2638
|
validateActions: () => validateActions
|
|
2623
2639
|
});
|
|
2640
|
+
import { existsSync as existsSync17, readFileSync as readFileSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
2641
|
+
import { join as join18 } from "path";
|
|
2624
2642
|
async function runEvolve(brainRoot, dryRun) {
|
|
2625
2643
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
2626
2644
|
if (!apiKey) {
|
|
2627
2645
|
console.error("\u274C GEMINI_API_KEY not set. Get one at https://aistudio.google.com/apikey");
|
|
2628
2646
|
return { actions: [], executed: 0, skipped: 0, dryRun };
|
|
2629
2647
|
}
|
|
2648
|
+
if (!dryRun && process.env.EVOLVE_NO_COOLDOWN !== "1") {
|
|
2649
|
+
const cooldownMs = (parseInt(process.env.EVOLVE_COOLDOWN_SECONDS ?? "60", 10) || 60) * 1e3;
|
|
2650
|
+
const cooldownPath = join18(brainRoot, EVOLVE_COOLDOWN_FILE);
|
|
2651
|
+
if (existsSync17(cooldownPath)) {
|
|
2652
|
+
const lastRun = parseInt(readFileSync9(cooldownPath, "utf8").trim(), 10);
|
|
2653
|
+
const elapsed = Date.now() - lastRun;
|
|
2654
|
+
if (elapsed < cooldownMs) {
|
|
2655
|
+
const remaining = Math.ceil((cooldownMs - elapsed) / 1e3);
|
|
2656
|
+
console.log(`\u23F3 evolve cooldown: ${remaining}s remaining (use EVOLVE_NO_COOLDOWN=1 to bypass)`);
|
|
2657
|
+
return { actions: [], executed: 0, skipped: 0, dryRun };
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2630
2661
|
const episodes = readEpisodes(brainRoot);
|
|
2631
2662
|
const brain = scanBrain(brainRoot);
|
|
2632
2663
|
const summary = buildBrainSummary(brain);
|
|
@@ -2657,6 +2688,7 @@ async function runEvolve(brainRoot, dryRun) {
|
|
|
2657
2688
|
const executed = executeActions(brainRoot, actions);
|
|
2658
2689
|
logEpisode(brainRoot, "evolve", "", `${executed} action(s) executed, ${skipped} skipped`);
|
|
2659
2690
|
console.log(`\u{1F9E0} evolve: ${executed} action(s) executed, ${skipped} skipped`);
|
|
2691
|
+
writeFileSync14(join18(brainRoot, EVOLVE_COOLDOWN_FILE), String(Date.now()), "utf8");
|
|
2660
2692
|
return { actions, executed, skipped, dryRun: false };
|
|
2661
2693
|
}
|
|
2662
2694
|
function buildBrainSummary(brain) {
|
|
@@ -2679,8 +2711,12 @@ function buildBrainSummary(brain) {
|
|
|
2679
2711
|
}
|
|
2680
2712
|
return lines.join("\n");
|
|
2681
2713
|
}
|
|
2714
|
+
function sanitizeForPrompt(text) {
|
|
2715
|
+
const firstLine = (text.split("\n")[0] ?? "").trim();
|
|
2716
|
+
return firstLine.replace(/^#+\s*/g, "").slice(0, 200);
|
|
2717
|
+
}
|
|
2682
2718
|
function buildPrompt(summary, episodes, outcomeSummary) {
|
|
2683
|
-
const episodeLines = episodes.length > 0 ? episodes.map((e) => `- [${e.ts}] ${e.type}: ${e.path} \u2014 ${e.detail}`).join("\n") : "(no recent episodes)";
|
|
2719
|
+
const episodeLines = episodes.length > 0 ? episodes.map((e) => `- [${e.ts}] ${e.type}: ${e.path} \u2014 ${sanitizeForPrompt(e.detail)}`).join("\n") : "(no recent episodes)";
|
|
2684
2720
|
const outcomeSection = outcomeSummary || "";
|
|
2685
2721
|
return `You are the evolve engine for a hebbian brain \u2014 a filesystem-based memory system for AI agents.
|
|
2686
2722
|
|
|
@@ -2719,7 +2755,7 @@ Respond with a JSON array of actions:
|
|
|
2719
2755
|
}
|
|
2720
2756
|
async function callGemini(prompt, apiKey) {
|
|
2721
2757
|
const model = process.env.EVOLVE_MODEL || DEFAULT_MODEL;
|
|
2722
|
-
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent
|
|
2758
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
|
|
2723
2759
|
const body = {
|
|
2724
2760
|
contents: [{ parts: [{ text: prompt }] }],
|
|
2725
2761
|
generationConfig: {
|
|
@@ -2735,7 +2771,7 @@ async function callGemini(prompt, apiKey) {
|
|
|
2735
2771
|
try {
|
|
2736
2772
|
const res = await fetch(url, {
|
|
2737
2773
|
method: "POST",
|
|
2738
|
-
headers: { "Content-Type": "application/json" },
|
|
2774
|
+
headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
|
|
2739
2775
|
body: JSON.stringify(body),
|
|
2740
2776
|
signal: AbortSignal.timeout(API_TIMEOUT)
|
|
2741
2777
|
});
|
|
@@ -2789,6 +2825,10 @@ function parseActions(text) {
|
|
|
2789
2825
|
}
|
|
2790
2826
|
function validateActions(actions, _brain) {
|
|
2791
2827
|
return actions.filter((action) => {
|
|
2828
|
+
if (action.path.includes("..") || action.path.startsWith("/")) {
|
|
2829
|
+
console.log(` \u26A0\uFE0F blocked: ${action.type} ${action.path} (path traversal)`);
|
|
2830
|
+
return false;
|
|
2831
|
+
}
|
|
2792
2832
|
const region = action.path.split("/")[0];
|
|
2793
2833
|
if (!region || PROTECTED_REGIONS.includes(region)) {
|
|
2794
2834
|
console.log(` \u{1F6E1}\uFE0F blocked: ${action.type} ${action.path} (protected region)`);
|
|
@@ -2850,7 +2890,7 @@ function actionIcon(type) {
|
|
|
2850
2890
|
return "\u2753";
|
|
2851
2891
|
}
|
|
2852
2892
|
}
|
|
2853
|
-
var MAX_ACTIONS, PROTECTED_REGIONS, DEFAULT_MODEL, API_TIMEOUT, RETRY_DELAY;
|
|
2893
|
+
var MAX_ACTIONS, PROTECTED_REGIONS, DEFAULT_MODEL, API_TIMEOUT, RETRY_DELAY, EVOLVE_COOLDOWN_FILE;
|
|
2854
2894
|
var init_evolve = __esm({
|
|
2855
2895
|
"src/evolve.ts"() {
|
|
2856
2896
|
"use strict";
|
|
@@ -2868,6 +2908,7 @@ var init_evolve = __esm({
|
|
|
2868
2908
|
DEFAULT_MODEL = "gemini-2.0-flash-lite";
|
|
2869
2909
|
API_TIMEOUT = 3e4;
|
|
2870
2910
|
RETRY_DELAY = 5e3;
|
|
2911
|
+
EVOLVE_COOLDOWN_FILE = "hippocampus/evolve_last_run";
|
|
2871
2912
|
}
|
|
2872
2913
|
});
|
|
2873
2914
|
|
|
@@ -2876,8 +2917,8 @@ var doctor_exports = {};
|
|
|
2876
2917
|
__export(doctor_exports, {
|
|
2877
2918
|
runDoctor: () => runDoctor
|
|
2878
2919
|
});
|
|
2879
|
-
import { existsSync as
|
|
2880
|
-
import { join as
|
|
2920
|
+
import { existsSync as existsSync18, readFileSync as readFileSync10, readdirSync as readdirSync11 } from "fs";
|
|
2921
|
+
import { join as join19 } from "path";
|
|
2881
2922
|
import { execSync as execSync4 } from "child_process";
|
|
2882
2923
|
async function runDoctor(brainRoot) {
|
|
2883
2924
|
let passed = 0, warnings = 0, failed = 0;
|
|
@@ -2907,7 +2948,7 @@ async function runDoctor(brainRoot) {
|
|
|
2907
2948
|
console.log("\nnpm package");
|
|
2908
2949
|
try {
|
|
2909
2950
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
2910
|
-
const pkg = JSON.parse(
|
|
2951
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
2911
2952
|
const local = pkg.version || "unknown";
|
|
2912
2953
|
let remote = "";
|
|
2913
2954
|
try {
|
|
@@ -2924,13 +2965,13 @@ async function runDoctor(brainRoot) {
|
|
|
2924
2965
|
warn("Could not read package.json");
|
|
2925
2966
|
}
|
|
2926
2967
|
console.log("\nbrain structure");
|
|
2927
|
-
if (!
|
|
2968
|
+
if (!existsSync18(brainRoot)) {
|
|
2928
2969
|
fail(`Brain not found at ${brainRoot}`, "hebbian init ./brain");
|
|
2929
2970
|
} else {
|
|
2930
2971
|
ok(`Brain root: ${brainRoot}`);
|
|
2931
2972
|
for (const region of REGIONS) {
|
|
2932
|
-
const regionDir =
|
|
2933
|
-
if (
|
|
2973
|
+
const regionDir = join19(brainRoot, region);
|
|
2974
|
+
if (existsSync18(regionDir)) {
|
|
2934
2975
|
ok(`Region: ${region}`);
|
|
2935
2976
|
} else {
|
|
2936
2977
|
warn(`Missing region: ${region}`, `mkdir -p ${regionDir}`);
|
|
@@ -2938,12 +2979,12 @@ async function runDoctor(brainRoot) {
|
|
|
2938
2979
|
}
|
|
2939
2980
|
}
|
|
2940
2981
|
console.log("\nClaude Code hooks");
|
|
2941
|
-
const settingsPath =
|
|
2942
|
-
if (!
|
|
2982
|
+
const settingsPath = join19(process.cwd(), ".claude", "settings.local.json");
|
|
2983
|
+
if (!existsSync18(settingsPath)) {
|
|
2943
2984
|
warn("No .claude/settings.local.json found", "hebbian claude install");
|
|
2944
2985
|
} else {
|
|
2945
2986
|
try {
|
|
2946
|
-
const settings = JSON.parse(
|
|
2987
|
+
const settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
2947
2988
|
const hooks = settings.hooks || {};
|
|
2948
2989
|
const hasStop = Object.entries(hooks).some(
|
|
2949
2990
|
([event, entries]) => event === "Stop" && Array.isArray(entries) && entries.some(
|
|
@@ -2976,8 +3017,8 @@ async function runDoctor(brainRoot) {
|
|
|
2976
3017
|
try {
|
|
2977
3018
|
let total = 0;
|
|
2978
3019
|
for (const region of REGIONS) {
|
|
2979
|
-
const candidateDir =
|
|
2980
|
-
if (
|
|
3020
|
+
const candidateDir = join19(brainRoot, region, "_candidates");
|
|
3021
|
+
if (existsSync18(candidateDir)) {
|
|
2981
3022
|
const entries = readdirSync11(candidateDir, { withFileTypes: true });
|
|
2982
3023
|
const count = entries.filter((e) => e.isDirectory()).length;
|
|
2983
3024
|
total += count;
|