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.
@@ -1 +1 @@
1
- {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAwDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYjG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAsD5G;AA6CD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAuB5E"}
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAwDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYjG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAsD5G;AA6CD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,EAAE,CA8B5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"evolve.d.ts","sourceRoot":"","sources":["../src/evolve.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAYrC,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CAChB;AAUD,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAgDzF;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAuBtD;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAyCjG;AAID,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAyDxF;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8BzD;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,GAAG,YAAY,EAAE,CAmBtF;AAID,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CA8BjF"}
1
+ {"version":3,"file":"evolve.d.ts","sourceRoot":"","sources":["../src/evolve.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAYrC,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CAChB;AAWD,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAkEzF;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAuBtD;AAeD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAyCjG;AAID,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAyDxF;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8BzD;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,GAAG,YAAY,EAAE,CAwBtF;AAID,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CA8BjF"}
@@ -1 +1 @@
1
- {"version":3,"file":"grow.d.ts","sourceRoot":"","sources":["../src/grow.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAwC5E"}
1
+ {"version":3,"file":"grow.d.ts","sourceRoot":"","sources":["../src/grow.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CA2C5E"}
package/dist/index.js CHANGED
@@ -400,6 +400,9 @@ function growNeuron(brainRoot, neuronPath) {
400
400
  const counter = fireNeuron(brainRoot, neuronPath);
401
401
  return { action: "fired", path: neuronPath, counter };
402
402
  }
403
+ if (neuronPath.includes("..") || neuronPath.startsWith("/")) {
404
+ throw new Error(`Invalid neuron path: "${neuronPath}" (path traversal not allowed)`);
405
+ }
403
406
  const parts = neuronPath.split("/");
404
407
  const regionName = parts[0];
405
408
  if (!REGIONS.includes(regionName)) {
@@ -1295,10 +1298,20 @@ function json(res, data, status = 200) {
1295
1298
  function error(res, message, status = 400) {
1296
1299
  json(res, { error: message }, status);
1297
1300
  }
1301
+ var MAX_BODY_BYTES = 1048576;
1298
1302
  async function readBody(req) {
1299
1303
  return new Promise((resolve3, reject) => {
1300
1304
  const chunks = [];
1301
- req.on("data", (chunk) => chunks.push(chunk));
1305
+ let total = 0;
1306
+ req.on("data", (chunk) => {
1307
+ total += chunk.length;
1308
+ if (total > MAX_BODY_BYTES) {
1309
+ reject(new Error("Request body too large"));
1310
+ req.destroy();
1311
+ return;
1312
+ }
1313
+ chunks.push(chunk);
1314
+ });
1302
1315
  req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
1303
1316
  req.on("error", reject);
1304
1317
  });
@@ -1795,6 +1808,8 @@ function extractCorrections(messages) {
1795
1808
  if (text.length < MIN_CORRECTION_LENGTH) continue;
1796
1809
  if (/^[\/!]/.test(text.trim())) continue;
1797
1810
  if (text.trim().endsWith("?")) continue;
1811
+ if (/^<[a-zA-Z]/.test(text.trim())) continue;
1812
+ if (/^Base directory for this skill:/i.test(text.trim())) continue;
1798
1813
  const correction = detectCorrection(text);
1799
1814
  if (correction) {
1800
1815
  corrections.push(correction);
@@ -1978,6 +1993,10 @@ function writeAuditLog(brainRoot, sessionId, entries) {
1978
1993
  writeFileSync11(logPath, lines.join("\n") + (lines.length > 0 ? "\n" : ""), "utf8");
1979
1994
  }
1980
1995
 
1996
+ // src/evolve.ts
1997
+ import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync13 } from "fs";
1998
+ import { join as join17 } from "path";
1999
+
1981
2000
  // src/outcome.ts
1982
2001
  import { execSync as execSync3 } from "child_process";
1983
2002
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12, readFileSync as readFileSync7, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync4 } from "fs";
@@ -2154,8 +2173,9 @@ function buildOutcomeSummary(brainRoot) {
2154
2173
  });
2155
2174
  for (const [neuron, s] of sorted) {
2156
2175
  const ratio = s.sessions > 0 ? (s.reverts / s.sessions).toFixed(2) : "0.00";
2157
- const trend = parseFloat(ratio) > 0.5 ? "\u2190 act on this" : parseFloat(ratio) > 0.3 ? "\u2190 watch" : "";
2158
- lines.push(`- ${neuron}: sessions=${s.sessions} reverts=${s.reverts} acceptances=${s.acceptances} contra_ratio=${ratio} ${trend}`);
2176
+ const trend = parseFloat(ratio) > 0.5 ? "act on this" : parseFloat(ratio) > 0.3 ? "watch" : "";
2177
+ const safePath = neuron.replace(/[\n\r#]/g, " ").trim();
2178
+ lines.push(`- ${safePath}: sessions=${s.sessions} reverts=${s.reverts} acceptances=${s.acceptances} contra_ratio=${ratio} ${trend}`);
2159
2179
  }
2160
2180
  lines.push("");
2161
2181
  return lines.join("\n");
@@ -2204,12 +2224,26 @@ var PROTECTED_REGIONS = ["brainstem", "limbic", "sensors"];
2204
2224
  var DEFAULT_MODEL = "gemini-2.0-flash-lite";
2205
2225
  var API_TIMEOUT = 3e4;
2206
2226
  var RETRY_DELAY = 5e3;
2227
+ var EVOLVE_COOLDOWN_FILE = "hippocampus/evolve_last_run";
2207
2228
  async function runEvolve(brainRoot, dryRun) {
2208
2229
  const apiKey = process.env.GEMINI_API_KEY;
2209
2230
  if (!apiKey) {
2210
2231
  console.error("\u274C GEMINI_API_KEY not set. Get one at https://aistudio.google.com/apikey");
2211
2232
  return { actions: [], executed: 0, skipped: 0, dryRun };
2212
2233
  }
2234
+ if (!dryRun && process.env.EVOLVE_NO_COOLDOWN !== "1") {
2235
+ const cooldownMs = (parseInt(process.env.EVOLVE_COOLDOWN_SECONDS ?? "60", 10) || 60) * 1e3;
2236
+ const cooldownPath = join17(brainRoot, EVOLVE_COOLDOWN_FILE);
2237
+ if (existsSync16(cooldownPath)) {
2238
+ const lastRun = parseInt(readFileSync8(cooldownPath, "utf8").trim(), 10);
2239
+ const elapsed = Date.now() - lastRun;
2240
+ if (elapsed < cooldownMs) {
2241
+ const remaining = Math.ceil((cooldownMs - elapsed) / 1e3);
2242
+ console.log(`\u23F3 evolve cooldown: ${remaining}s remaining (use EVOLVE_NO_COOLDOWN=1 to bypass)`);
2243
+ return { actions: [], executed: 0, skipped: 0, dryRun };
2244
+ }
2245
+ }
2246
+ }
2213
2247
  const episodes = readEpisodes(brainRoot);
2214
2248
  const brain = scanBrain(brainRoot);
2215
2249
  const summary = buildBrainSummary(brain);
@@ -2240,6 +2274,7 @@ async function runEvolve(brainRoot, dryRun) {
2240
2274
  const executed = executeActions(brainRoot, actions);
2241
2275
  logEpisode(brainRoot, "evolve", "", `${executed} action(s) executed, ${skipped} skipped`);
2242
2276
  console.log(`\u{1F9E0} evolve: ${executed} action(s) executed, ${skipped} skipped`);
2277
+ writeFileSync13(join17(brainRoot, EVOLVE_COOLDOWN_FILE), String(Date.now()), "utf8");
2243
2278
  return { actions, executed, skipped, dryRun: false };
2244
2279
  }
2245
2280
  function buildBrainSummary(brain) {
@@ -2262,8 +2297,12 @@ function buildBrainSummary(brain) {
2262
2297
  }
2263
2298
  return lines.join("\n");
2264
2299
  }
2300
+ function sanitizeForPrompt(text) {
2301
+ const firstLine = (text.split("\n")[0] ?? "").trim();
2302
+ return firstLine.replace(/^#+\s*/g, "").slice(0, 200);
2303
+ }
2265
2304
  function buildPrompt(summary, episodes, outcomeSummary) {
2266
- const episodeLines = episodes.length > 0 ? episodes.map((e) => `- [${e.ts}] ${e.type}: ${e.path} \u2014 ${e.detail}`).join("\n") : "(no recent episodes)";
2305
+ const episodeLines = episodes.length > 0 ? episodes.map((e) => `- [${e.ts}] ${e.type}: ${e.path} \u2014 ${sanitizeForPrompt(e.detail)}`).join("\n") : "(no recent episodes)";
2267
2306
  const outcomeSection = outcomeSummary || "";
2268
2307
  return `You are the evolve engine for a hebbian brain \u2014 a filesystem-based memory system for AI agents.
2269
2308
 
@@ -2302,7 +2341,7 @@ Respond with a JSON array of actions:
2302
2341
  }
2303
2342
  async function callGemini(prompt, apiKey) {
2304
2343
  const model = process.env.EVOLVE_MODEL || DEFAULT_MODEL;
2305
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
2344
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
2306
2345
  const body = {
2307
2346
  contents: [{ parts: [{ text: prompt }] }],
2308
2347
  generationConfig: {
@@ -2318,7 +2357,7 @@ async function callGemini(prompt, apiKey) {
2318
2357
  try {
2319
2358
  const res = await fetch(url, {
2320
2359
  method: "POST",
2321
- headers: { "Content-Type": "application/json" },
2360
+ headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
2322
2361
  body: JSON.stringify(body),
2323
2362
  signal: AbortSignal.timeout(API_TIMEOUT)
2324
2363
  });
@@ -2372,6 +2411,10 @@ function parseActions(text) {
2372
2411
  }
2373
2412
  function validateActions(actions, _brain) {
2374
2413
  return actions.filter((action) => {
2414
+ if (action.path.includes("..") || action.path.startsWith("/")) {
2415
+ console.log(` \u26A0\uFE0F blocked: ${action.type} ${action.path} (path traversal)`);
2416
+ return false;
2417
+ }
2375
2418
  const region = action.path.split("/")[0];
2376
2419
  if (!region || PROTECTED_REGIONS.includes(region)) {
2377
2420
  console.log(` \u{1F6E1}\uFE0F blocked: ${action.type} ${action.path} (protected region)`);