hebbian 0.7.1 → 0.8.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/bin/hebbian.js +506 -112
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/candidates.d.ts +8 -0
- package/dist/candidates.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/cron.d.ts +24 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/digest.d.ts +13 -1
- package/dist/digest.d.ts.map +1 -1
- package/dist/evolve.d.ts.map +1 -1
- package/dist/feedback.d.ts +23 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +427 -93
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/scanner.d.ts +6 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/hebbian.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(constants_exports, {
|
|
|
25
25
|
MAX_DEPTH: () => MAX_DEPTH,
|
|
26
26
|
MIN_CORRECTION_LENGTH: () => MIN_CORRECTION_LENGTH,
|
|
27
27
|
OUTCOME_TYPES: () => OUTCOME_TYPES,
|
|
28
|
+
PROPAGATION_EPISODE_TYPES: () => PROPAGATION_EPISODE_TYPES,
|
|
28
29
|
PROTECTED_REGIONS_CONTRA: () => PROTECTED_REGIONS_CONTRA,
|
|
29
30
|
REGIONS: () => REGIONS,
|
|
30
31
|
REGION_ICONS: () => REGION_ICONS,
|
|
@@ -33,6 +34,7 @@ __export(constants_exports, {
|
|
|
33
34
|
SESSION_STATE_DIR: () => SESSION_STATE_DIR,
|
|
34
35
|
SHARED_DIR: () => SHARED_DIR,
|
|
35
36
|
SIGNAL_TYPES: () => SIGNAL_TYPES,
|
|
37
|
+
SKILLS_DIR: () => SKILLS_DIR,
|
|
36
38
|
SPOTLIGHT_DAYS: () => SPOTLIGHT_DAYS,
|
|
37
39
|
resolveAgentBrain: () => resolveAgentBrain,
|
|
38
40
|
resolveBrainRoot: () => resolveBrainRoot,
|
|
@@ -52,7 +54,7 @@ function resolveAgentBrain(brainRoot, agentName) {
|
|
|
52
54
|
function resolveSharedBrain(brainRoot) {
|
|
53
55
|
return resolve(brainRoot, "shared");
|
|
54
56
|
}
|
|
55
|
-
var REGIONS, REGION_PRIORITY, REGION_ICONS, REGION_KO, EMIT_THRESHOLD, SPOTLIGHT_DAYS, JACCARD_THRESHOLD, DECAY_DAYS, MAX_DEPTH, EMIT_TARGETS, SIGNAL_TYPES, MARKER_START, MARKER_END, HOOK_MARKER, MAX_CORRECTIONS_PER_SESSION, MIN_CORRECTION_LENGTH, DIGEST_LOG_DIR, OUTCOME_TYPES, SESSION_STATE_DIR, PROTECTED_REGIONS_CONTRA, AGENTS_DIR, SHARED_DIR;
|
|
57
|
+
var REGIONS, REGION_PRIORITY, REGION_ICONS, REGION_KO, EMIT_THRESHOLD, SPOTLIGHT_DAYS, JACCARD_THRESHOLD, DECAY_DAYS, MAX_DEPTH, EMIT_TARGETS, SIGNAL_TYPES, MARKER_START, MARKER_END, HOOK_MARKER, MAX_CORRECTIONS_PER_SESSION, MIN_CORRECTION_LENGTH, DIGEST_LOG_DIR, OUTCOME_TYPES, SESSION_STATE_DIR, PROTECTED_REGIONS_CONTRA, AGENTS_DIR, SHARED_DIR, SKILLS_DIR, PROPAGATION_EPISODE_TYPES;
|
|
56
58
|
var init_constants = __esm({
|
|
57
59
|
"src/constants.ts"() {
|
|
58
60
|
"use strict";
|
|
@@ -116,6 +118,8 @@ var init_constants = __esm({
|
|
|
116
118
|
PROTECTED_REGIONS_CONTRA = ["brainstem", "limbic", "sensors"];
|
|
117
119
|
AGENTS_DIR = "agents";
|
|
118
120
|
SHARED_DIR = "shared";
|
|
121
|
+
SKILLS_DIR = "skills";
|
|
122
|
+
PROPAGATION_EPISODE_TYPES = ["tool-failure", "retry-pattern"];
|
|
119
123
|
}
|
|
120
124
|
});
|
|
121
125
|
|
|
@@ -156,6 +160,12 @@ ${template.description}
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
mkdirSync(join(brainPath, "_agents", "global_inbox"), { recursive: true });
|
|
163
|
+
mkdirSync(join(brainPath, "skills"), { recursive: true });
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(brainPath, "skills", "_rules.md"),
|
|
166
|
+
"# Skills Library\n\nExecutable patterns learned through experience.\nNot part of the subsumption cascade \u2014 retrieval only.\n",
|
|
167
|
+
"utf8"
|
|
168
|
+
);
|
|
159
169
|
autoGitignore(brainPath);
|
|
160
170
|
console.log(`\u{1F9E0} Brain initialized at ${brainPath}`);
|
|
161
171
|
console.log(` 7 regions created: ${REGIONS.join(", ")}`);
|
|
@@ -229,7 +239,8 @@ var init_init = __esm({
|
|
|
229
239
|
// src/scanner.ts
|
|
230
240
|
var scanner_exports = {};
|
|
231
241
|
__export(scanner_exports, {
|
|
232
|
-
scanBrain: () => scanBrain
|
|
242
|
+
scanBrain: () => scanBrain,
|
|
243
|
+
scanSkills: () => scanSkills
|
|
233
244
|
});
|
|
234
245
|
import { readdirSync as readdirSync2, statSync, readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
235
246
|
import { join as join2, relative, sep } from "path";
|
|
@@ -354,6 +365,11 @@ function walkRegion(dir, regionRoot, depth) {
|
|
|
354
365
|
}
|
|
355
366
|
return neurons;
|
|
356
367
|
}
|
|
368
|
+
function scanSkills(brainRoot) {
|
|
369
|
+
const skillsPath = join2(brainRoot, SKILLS_DIR);
|
|
370
|
+
if (!existsSync3(skillsPath)) return [];
|
|
371
|
+
return walkRegion(skillsPath, skillsPath, 0);
|
|
372
|
+
}
|
|
357
373
|
function readAxons(regionPath) {
|
|
358
374
|
const axonPath = join2(regionPath, ".axon");
|
|
359
375
|
if (!existsSync3(axonPath)) return [];
|
|
@@ -944,8 +960,8 @@ function growNeuron(brainRoot, neuronPath) {
|
|
|
944
960
|
}
|
|
945
961
|
const parts = neuronPath.split("/");
|
|
946
962
|
const regionName = parts[0];
|
|
947
|
-
if (!REGIONS.includes(regionName)) {
|
|
948
|
-
throw new Error(`Invalid region: ${regionName}. Valid: ${REGIONS.join(", ")}`);
|
|
963
|
+
if (regionName !== SKILLS_DIR && !REGIONS.includes(regionName)) {
|
|
964
|
+
throw new Error(`Invalid region: ${regionName}. Valid: ${REGIONS.join(", ")}, ${SKILLS_DIR}`);
|
|
949
965
|
}
|
|
950
966
|
const leafName = parts[parts.length - 1];
|
|
951
967
|
const newPrefix = leafName.match(/^(NO|DO|MUST|WARN)_/)?.[1] || "";
|
|
@@ -1314,6 +1330,80 @@ var init_watch = __esm({
|
|
|
1314
1330
|
}
|
|
1315
1331
|
});
|
|
1316
1332
|
|
|
1333
|
+
// src/episode.ts
|
|
1334
|
+
var episode_exports = {};
|
|
1335
|
+
__export(episode_exports, {
|
|
1336
|
+
logEpisode: () => logEpisode,
|
|
1337
|
+
readEpisodes: () => readEpisodes
|
|
1338
|
+
});
|
|
1339
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync9, mkdirSync as mkdirSync6, existsSync as existsSync11 } from "fs";
|
|
1340
|
+
import { join as join12 } from "path";
|
|
1341
|
+
function logEpisode(brainRoot, type, path, detail, extra) {
|
|
1342
|
+
const logDir = join12(brainRoot, SESSION_LOG_DIR);
|
|
1343
|
+
if (!existsSync11(logDir)) {
|
|
1344
|
+
mkdirSync6(logDir, { recursive: true });
|
|
1345
|
+
}
|
|
1346
|
+
const nextSlot = getNextSlot(logDir);
|
|
1347
|
+
const episode = {
|
|
1348
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1349
|
+
type,
|
|
1350
|
+
path,
|
|
1351
|
+
detail,
|
|
1352
|
+
...extra?.outcome ? { outcome: extra.outcome } : {},
|
|
1353
|
+
...extra?.neurons ? { neurons: extra.neurons } : {}
|
|
1354
|
+
};
|
|
1355
|
+
writeFileSync9(
|
|
1356
|
+
join12(logDir, `memory${nextSlot}.neuron`),
|
|
1357
|
+
JSON.stringify(episode),
|
|
1358
|
+
"utf8"
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
function readEpisodes(brainRoot) {
|
|
1362
|
+
const logDir = join12(brainRoot, SESSION_LOG_DIR);
|
|
1363
|
+
if (!existsSync11(logDir)) return [];
|
|
1364
|
+
const episodes = [];
|
|
1365
|
+
let entries;
|
|
1366
|
+
try {
|
|
1367
|
+
entries = readdirSync7(logDir);
|
|
1368
|
+
} catch {
|
|
1369
|
+
return [];
|
|
1370
|
+
}
|
|
1371
|
+
for (const entry of entries) {
|
|
1372
|
+
if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
|
|
1373
|
+
try {
|
|
1374
|
+
const content = readFileSync5(join12(logDir, entry), "utf8");
|
|
1375
|
+
if (content.trim()) {
|
|
1376
|
+
episodes.push(JSON.parse(content));
|
|
1377
|
+
}
|
|
1378
|
+
} catch {
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
episodes.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
1382
|
+
return episodes;
|
|
1383
|
+
}
|
|
1384
|
+
function getNextSlot(logDir) {
|
|
1385
|
+
let maxSlot = 0;
|
|
1386
|
+
try {
|
|
1387
|
+
for (const entry of readdirSync7(logDir)) {
|
|
1388
|
+
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
1389
|
+
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
1390
|
+
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
const next = maxSlot + 1;
|
|
1396
|
+
return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
|
|
1397
|
+
}
|
|
1398
|
+
var MAX_EPISODES, SESSION_LOG_DIR;
|
|
1399
|
+
var init_episode = __esm({
|
|
1400
|
+
"src/episode.ts"() {
|
|
1401
|
+
"use strict";
|
|
1402
|
+
MAX_EPISODES = 100;
|
|
1403
|
+
SESSION_LOG_DIR = "hippocampus/session_log";
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1317
1407
|
// src/candidates.ts
|
|
1318
1408
|
var candidates_exports = {};
|
|
1319
1409
|
__export(candidates_exports, {
|
|
@@ -1323,10 +1413,11 @@ __export(candidates_exports, {
|
|
|
1323
1413
|
growCandidate: () => growCandidate,
|
|
1324
1414
|
listCandidates: () => listCandidates,
|
|
1325
1415
|
promoteCandidates: () => promoteCandidates,
|
|
1416
|
+
propagateToShared: () => propagateToShared,
|
|
1326
1417
|
toCandidatePath: () => toCandidatePath
|
|
1327
1418
|
});
|
|
1328
|
-
import { existsSync as
|
|
1329
|
-
import { join as
|
|
1419
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readdirSync as readdirSync8, renameSync as renameSync3, rmSync, statSync as statSync4 } from "fs";
|
|
1420
|
+
import { join as join13, dirname as dirname3, relative as relative3 } from "path";
|
|
1330
1421
|
function toCandidatePath(neuronPath) {
|
|
1331
1422
|
const slash = neuronPath.indexOf("/");
|
|
1332
1423
|
if (slash === -1) throw new Error(`Invalid neuron path (missing region): ${neuronPath}`);
|
|
@@ -1340,20 +1431,21 @@ function growCandidate(brainRoot, neuronPath) {
|
|
|
1340
1431
|
const result = growNeuron(brainRoot, candidatePath);
|
|
1341
1432
|
if (result.counter >= CANDIDATE_THRESHOLD) {
|
|
1342
1433
|
const ok = moveCandidate(brainRoot, candidatePath, neuronPath);
|
|
1434
|
+
if (ok) propagateToShared(brainRoot, neuronPath);
|
|
1343
1435
|
return { ...result, path: ok ? neuronPath : result.path, promoted: ok };
|
|
1344
1436
|
}
|
|
1345
1437
|
console.log(` \u{1F331} candidate (${result.counter}/${CANDIDATE_THRESHOLD}): ${candidatePath}`);
|
|
1346
1438
|
return { ...result, promoted: false };
|
|
1347
1439
|
}
|
|
1348
1440
|
function moveCandidate(brainRoot, candidatePath, targetPath) {
|
|
1349
|
-
const src =
|
|
1350
|
-
if (!
|
|
1351
|
-
const dst =
|
|
1352
|
-
if (
|
|
1441
|
+
const src = join13(brainRoot, candidatePath);
|
|
1442
|
+
if (!existsSync12(src)) return false;
|
|
1443
|
+
const dst = join13(brainRoot, targetPath);
|
|
1444
|
+
if (existsSync12(dst)) {
|
|
1353
1445
|
fireNeuron(brainRoot, targetPath);
|
|
1354
1446
|
rmSync(src, { recursive: true, force: true });
|
|
1355
1447
|
} else {
|
|
1356
|
-
|
|
1448
|
+
mkdirSync7(dirname3(dst), { recursive: true });
|
|
1357
1449
|
renameSync3(src, dst);
|
|
1358
1450
|
}
|
|
1359
1451
|
console.log(`\u{1F393} promoted: ${candidatePath} \u2192 ${targetPath}`);
|
|
@@ -1365,15 +1457,16 @@ function promoteCandidates(brainRoot) {
|
|
|
1365
1457
|
const decayMs = CANDIDATE_DECAY_DAYS * 24 * 60 * 60 * 1e3;
|
|
1366
1458
|
const now = Date.now();
|
|
1367
1459
|
for (const region of REGIONS) {
|
|
1368
|
-
const candidateRoot =
|
|
1460
|
+
const candidateRoot = join13(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1369
1461
|
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1370
|
-
const rel = relative3(
|
|
1462
|
+
const rel = relative3(join13(brainRoot, region), neuronDir);
|
|
1371
1463
|
const candidatePath = `${region}/${rel}`;
|
|
1372
1464
|
const targetPath = fromCandidatePath(candidatePath);
|
|
1373
1465
|
const counter = readCounter(neuronDir);
|
|
1374
1466
|
const mtime = statSync4(neuronDir).mtimeMs;
|
|
1375
1467
|
if (counter >= CANDIDATE_THRESHOLD) {
|
|
1376
1468
|
moveCandidate(brainRoot, candidatePath, targetPath);
|
|
1469
|
+
propagateToShared(brainRoot, targetPath);
|
|
1377
1470
|
promoted.push(targetPath);
|
|
1378
1471
|
} else if (now - mtime > decayMs) {
|
|
1379
1472
|
rmSync(neuronDir, { recursive: true, force: true });
|
|
@@ -1388,9 +1481,9 @@ function listCandidates(brainRoot) {
|
|
|
1388
1481
|
const results = [];
|
|
1389
1482
|
const now = Date.now();
|
|
1390
1483
|
for (const region of REGIONS) {
|
|
1391
|
-
const candidateRoot =
|
|
1484
|
+
const candidateRoot = join13(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1392
1485
|
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1393
|
-
const rel = relative3(
|
|
1486
|
+
const rel = relative3(join13(brainRoot, region), neuronDir);
|
|
1394
1487
|
const candidatePath = `${region}/${rel}`;
|
|
1395
1488
|
const targetPath = fromCandidatePath(candidatePath);
|
|
1396
1489
|
const counter = readCounter(neuronDir);
|
|
@@ -1402,9 +1495,9 @@ function listCandidates(brainRoot) {
|
|
|
1402
1495
|
return results;
|
|
1403
1496
|
}
|
|
1404
1497
|
function walkNeuronDirs(dir, cb) {
|
|
1405
|
-
if (!
|
|
1498
|
+
if (!existsSync12(dir)) return;
|
|
1406
1499
|
try {
|
|
1407
|
-
const entries =
|
|
1500
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
1408
1501
|
const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
|
|
1409
1502
|
if (hasNeuron) {
|
|
1410
1503
|
cb(dir);
|
|
@@ -1412,7 +1505,7 @@ function walkNeuronDirs(dir, cb) {
|
|
|
1412
1505
|
}
|
|
1413
1506
|
for (const entry of entries) {
|
|
1414
1507
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
1415
|
-
walkNeuronDirs(
|
|
1508
|
+
walkNeuronDirs(join13(dir, entry.name), cb);
|
|
1416
1509
|
}
|
|
1417
1510
|
}
|
|
1418
1511
|
} catch {
|
|
@@ -1420,13 +1513,33 @@ function walkNeuronDirs(dir, cb) {
|
|
|
1420
1513
|
}
|
|
1421
1514
|
function readCounter(dir) {
|
|
1422
1515
|
try {
|
|
1423
|
-
const files =
|
|
1516
|
+
const files = readdirSync8(dir).filter((f) => /^\d+\.neuron$/.test(f));
|
|
1424
1517
|
if (files.length === 0) return 0;
|
|
1425
1518
|
return Math.max(...files.map((f) => parseInt(f, 10)));
|
|
1426
1519
|
} catch {
|
|
1427
1520
|
return 0;
|
|
1428
1521
|
}
|
|
1429
1522
|
}
|
|
1523
|
+
function propagateToShared(brainRoot, targetPath) {
|
|
1524
|
+
try {
|
|
1525
|
+
const agentsIdx = brainRoot.indexOf("/agents/");
|
|
1526
|
+
if (agentsIdx === -1) return false;
|
|
1527
|
+
const multiBrainRoot = brainRoot.slice(0, agentsIdx);
|
|
1528
|
+
const sharedRoot = join13(multiBrainRoot, "shared");
|
|
1529
|
+
if (!existsSync12(sharedRoot)) return false;
|
|
1530
|
+
const episodes = readEpisodes(brainRoot);
|
|
1531
|
+
const neuronName = targetPath.split("/").pop() || "";
|
|
1532
|
+
const hasRelevantEpisode = episodes.some(
|
|
1533
|
+
(ep) => PROPAGATION_EPISODE_TYPES.includes(ep.type) && (ep.path.includes(neuronName) || ep.detail.includes(neuronName))
|
|
1534
|
+
);
|
|
1535
|
+
if (!hasRelevantEpisode) return false;
|
|
1536
|
+
growNeuron(sharedRoot, targetPath);
|
|
1537
|
+
console.log(` \u{1F4E1} propagated to shared: ${targetPath}`);
|
|
1538
|
+
return true;
|
|
1539
|
+
} catch {
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1430
1543
|
var CANDIDATE_THRESHOLD, CANDIDATE_DECAY_DAYS, CANDIDATE_SEGMENT;
|
|
1431
1544
|
var init_candidates = __esm({
|
|
1432
1545
|
"src/candidates.ts"() {
|
|
@@ -1434,86 +1547,13 @@ var init_candidates = __esm({
|
|
|
1434
1547
|
init_constants();
|
|
1435
1548
|
init_grow();
|
|
1436
1549
|
init_fire();
|
|
1550
|
+
init_episode();
|
|
1437
1551
|
CANDIDATE_THRESHOLD = 3;
|
|
1438
1552
|
CANDIDATE_DECAY_DAYS = 14;
|
|
1439
1553
|
CANDIDATE_SEGMENT = "_candidates";
|
|
1440
1554
|
}
|
|
1441
1555
|
});
|
|
1442
1556
|
|
|
1443
|
-
// src/episode.ts
|
|
1444
|
-
var episode_exports = {};
|
|
1445
|
-
__export(episode_exports, {
|
|
1446
|
-
logEpisode: () => logEpisode,
|
|
1447
|
-
readEpisodes: () => readEpisodes
|
|
1448
|
-
});
|
|
1449
|
-
import { readdirSync as readdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync12 } from "fs";
|
|
1450
|
-
import { join as join13 } from "path";
|
|
1451
|
-
function logEpisode(brainRoot, type, path, detail, extra) {
|
|
1452
|
-
const logDir = join13(brainRoot, SESSION_LOG_DIR);
|
|
1453
|
-
if (!existsSync12(logDir)) {
|
|
1454
|
-
mkdirSync7(logDir, { recursive: true });
|
|
1455
|
-
}
|
|
1456
|
-
const nextSlot = getNextSlot(logDir);
|
|
1457
|
-
const episode = {
|
|
1458
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1459
|
-
type,
|
|
1460
|
-
path,
|
|
1461
|
-
detail,
|
|
1462
|
-
...extra?.outcome ? { outcome: extra.outcome } : {},
|
|
1463
|
-
...extra?.neurons ? { neurons: extra.neurons } : {}
|
|
1464
|
-
};
|
|
1465
|
-
writeFileSync9(
|
|
1466
|
-
join13(logDir, `memory${nextSlot}.neuron`),
|
|
1467
|
-
JSON.stringify(episode),
|
|
1468
|
-
"utf8"
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
function readEpisodes(brainRoot) {
|
|
1472
|
-
const logDir = join13(brainRoot, SESSION_LOG_DIR);
|
|
1473
|
-
if (!existsSync12(logDir)) return [];
|
|
1474
|
-
const episodes = [];
|
|
1475
|
-
let entries;
|
|
1476
|
-
try {
|
|
1477
|
-
entries = readdirSync8(logDir);
|
|
1478
|
-
} catch {
|
|
1479
|
-
return [];
|
|
1480
|
-
}
|
|
1481
|
-
for (const entry of entries) {
|
|
1482
|
-
if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
|
|
1483
|
-
try {
|
|
1484
|
-
const content = readFileSync5(join13(logDir, entry), "utf8");
|
|
1485
|
-
if (content.trim()) {
|
|
1486
|
-
episodes.push(JSON.parse(content));
|
|
1487
|
-
}
|
|
1488
|
-
} catch {
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
episodes.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
1492
|
-
return episodes;
|
|
1493
|
-
}
|
|
1494
|
-
function getNextSlot(logDir) {
|
|
1495
|
-
let maxSlot = 0;
|
|
1496
|
-
try {
|
|
1497
|
-
for (const entry of readdirSync8(logDir)) {
|
|
1498
|
-
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
1499
|
-
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
1500
|
-
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
} catch {
|
|
1504
|
-
}
|
|
1505
|
-
const next = maxSlot + 1;
|
|
1506
|
-
return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
|
|
1507
|
-
}
|
|
1508
|
-
var MAX_EPISODES, SESSION_LOG_DIR;
|
|
1509
|
-
var init_episode = __esm({
|
|
1510
|
-
"src/episode.ts"() {
|
|
1511
|
-
"use strict";
|
|
1512
|
-
MAX_EPISODES = 100;
|
|
1513
|
-
SESSION_LOG_DIR = "hippocampus/session_log";
|
|
1514
|
-
}
|
|
1515
|
-
});
|
|
1516
|
-
|
|
1517
1557
|
// src/inbox.ts
|
|
1518
1558
|
var inbox_exports = {};
|
|
1519
1559
|
__export(inbox_exports, {
|
|
@@ -2129,6 +2169,7 @@ var init_hooks = __esm({
|
|
|
2129
2169
|
var digest_exports = {};
|
|
2130
2170
|
__export(digest_exports, {
|
|
2131
2171
|
detectRetryPatterns: () => detectRetryPatterns,
|
|
2172
|
+
detectSoftFailure: () => detectSoftFailure,
|
|
2132
2173
|
detectToolFailure: () => detectToolFailure,
|
|
2133
2174
|
digestTranscript: () => digestTranscript,
|
|
2134
2175
|
extractCorrections: () => extractCorrections,
|
|
@@ -2157,12 +2198,21 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
2157
2198
|
const resolvedSessionId = sessionId || basename(transcriptPath, ".jsonl");
|
|
2158
2199
|
const logDir = join16(brainRoot, DIGEST_LOG_DIR);
|
|
2159
2200
|
const logPath = join16(logDir, `${resolvedSessionId}.jsonl`);
|
|
2160
|
-
|
|
2201
|
+
const content = readFileSync8(transcriptPath, "utf8");
|
|
2202
|
+
const allLines = content.split("\n").filter(Boolean);
|
|
2203
|
+
const totalLines = allLines.length;
|
|
2204
|
+
const meta = readAuditMeta(logPath);
|
|
2205
|
+
if (existsSync15(logPath) && !meta) {
|
|
2161
2206
|
console.log(`\u23ED already digested session ${resolvedSessionId}, skip`);
|
|
2162
2207
|
return { corrections: 0, skipped: 0, toolFailures: 0, transcriptPath, sessionId: resolvedSessionId };
|
|
2163
2208
|
}
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2209
|
+
const skipLines = meta ? meta.lineCount : 0;
|
|
2210
|
+
if (skipLines >= totalLines) {
|
|
2211
|
+
return { corrections: 0, skipped: 0, toolFailures: 0, transcriptPath, sessionId: resolvedSessionId };
|
|
2212
|
+
}
|
|
2213
|
+
const newLines = allLines.slice(skipLines);
|
|
2214
|
+
const messages = parseTranscriptFromLines(newLines);
|
|
2215
|
+
const toolFailures = parseToolResultsFromLines(newLines);
|
|
2166
2216
|
for (const failure of toolFailures) {
|
|
2167
2217
|
logEpisode(brainRoot, "tool-failure", failure.toolName, failure.errorText);
|
|
2168
2218
|
}
|
|
@@ -2177,11 +2227,11 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
2177
2227
|
const corrections = extractCorrections(messages);
|
|
2178
2228
|
if (corrections.length === 0 && toolFailures.length === 0) {
|
|
2179
2229
|
console.log(`\u{1F4DD} digest: no corrections found in session ${resolvedSessionId}`);
|
|
2180
|
-
writeAuditLog(brainRoot, resolvedSessionId, []);
|
|
2230
|
+
writeAuditLog(brainRoot, resolvedSessionId, [], totalLines);
|
|
2181
2231
|
return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
|
|
2182
2232
|
}
|
|
2183
2233
|
if (corrections.length === 0) {
|
|
2184
|
-
writeAuditLog(brainRoot, resolvedSessionId, []);
|
|
2234
|
+
writeAuditLog(brainRoot, resolvedSessionId, [], totalLines);
|
|
2185
2235
|
return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
|
|
2186
2236
|
}
|
|
2187
2237
|
let applied = 0;
|
|
@@ -2197,7 +2247,7 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
2197
2247
|
auditEntries.push({ correction, applied: false });
|
|
2198
2248
|
}
|
|
2199
2249
|
}
|
|
2200
|
-
writeAuditLog(brainRoot, resolvedSessionId, auditEntries);
|
|
2250
|
+
writeAuditLog(brainRoot, resolvedSessionId, auditEntries, totalLines);
|
|
2201
2251
|
console.log(`\u{1F4DD} digest: ${applied} correction(s) from session ${resolvedSessionId}`);
|
|
2202
2252
|
return {
|
|
2203
2253
|
corrections: applied,
|
|
@@ -2207,9 +2257,7 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
2207
2257
|
sessionId: resolvedSessionId
|
|
2208
2258
|
};
|
|
2209
2259
|
}
|
|
2210
|
-
function
|
|
2211
|
-
const content = readFileSync8(transcriptPath, "utf8");
|
|
2212
|
-
const lines = content.split("\n").filter(Boolean);
|
|
2260
|
+
function parseTranscriptFromLines(lines) {
|
|
2213
2261
|
const messages = [];
|
|
2214
2262
|
for (const line of lines) {
|
|
2215
2263
|
let entry;
|
|
@@ -2236,7 +2284,9 @@ function extractText(content) {
|
|
|
2236
2284
|
}
|
|
2237
2285
|
function parseToolResults(transcriptPath) {
|
|
2238
2286
|
const content = readFileSync8(transcriptPath, "utf8");
|
|
2239
|
-
|
|
2287
|
+
return parseToolResultsFromLines(content.split("\n").filter(Boolean));
|
|
2288
|
+
}
|
|
2289
|
+
function parseToolResultsFromLines(lines) {
|
|
2240
2290
|
const failures = [];
|
|
2241
2291
|
for (const line of lines) {
|
|
2242
2292
|
if (failures.length >= MAX_FAILURES_PER_SESSION) break;
|
|
@@ -2250,9 +2300,13 @@ function parseToolResults(transcriptPath) {
|
|
|
2250
2300
|
if (!entry.message || !Array.isArray(entry.message.content)) continue;
|
|
2251
2301
|
for (const block of entry.message.content) {
|
|
2252
2302
|
if (block.type !== "tool_result") continue;
|
|
2253
|
-
if (
|
|
2254
|
-
|
|
2255
|
-
|
|
2303
|
+
if (block.is_error) {
|
|
2304
|
+
const failure = detectToolFailure(block, entry.toolUseResult);
|
|
2305
|
+
if (failure) failures.push(failure);
|
|
2306
|
+
} else {
|
|
2307
|
+
const failure = detectSoftFailure(block, entry.toolUseResult);
|
|
2308
|
+
if (failure) failures.push(failure);
|
|
2309
|
+
}
|
|
2256
2310
|
}
|
|
2257
2311
|
}
|
|
2258
2312
|
return failures;
|
|
@@ -2291,6 +2345,30 @@ function detectToolFailure(block, toolUseResult) {
|
|
|
2291
2345
|
const toolName = firstLine.trim().slice(0, 80);
|
|
2292
2346
|
return { toolName, exitCode, errorText: errorText.slice(0, 500) };
|
|
2293
2347
|
}
|
|
2348
|
+
function detectSoftFailure(block, toolUseResult) {
|
|
2349
|
+
let text = "";
|
|
2350
|
+
if (typeof block.content === "string") {
|
|
2351
|
+
text = block.content;
|
|
2352
|
+
} else if (Array.isArray(block.content)) {
|
|
2353
|
+
text = block.content.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
|
|
2354
|
+
}
|
|
2355
|
+
if (toolUseResult && typeof toolUseResult === "object") {
|
|
2356
|
+
if (toolUseResult.stderr) text += "\n" + toolUseResult.stderr;
|
|
2357
|
+
}
|
|
2358
|
+
if (!text) return null;
|
|
2359
|
+
for (const pattern of SOFT_ERROR_PATTERNS) {
|
|
2360
|
+
const match = text.match(pattern);
|
|
2361
|
+
if (match) {
|
|
2362
|
+
const matchedLine = text.split("\n").find((l) => pattern.test(l)) || "unknown";
|
|
2363
|
+
return {
|
|
2364
|
+
toolName: `[soft] ${matchedLine.trim().slice(0, 70)}`,
|
|
2365
|
+
exitCode: 0,
|
|
2366
|
+
errorText: text.slice(0, 500)
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2294
2372
|
function extractCorrections(messages) {
|
|
2295
2373
|
const corrections = [];
|
|
2296
2374
|
for (const text of messages) {
|
|
@@ -2465,13 +2543,28 @@ function extractKeywords(text) {
|
|
|
2465
2543
|
]);
|
|
2466
2544
|
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9\u3000-\u9FFF\uAC00-\uD7AF]+/g, " ").toLowerCase().split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
2467
2545
|
}
|
|
2468
|
-
function
|
|
2546
|
+
function readAuditMeta(logPath) {
|
|
2547
|
+
if (!existsSync15(logPath)) return null;
|
|
2548
|
+
try {
|
|
2549
|
+
const content = readFileSync8(logPath, "utf8");
|
|
2550
|
+
const firstLine = content.split("\n")[0];
|
|
2551
|
+
if (!firstLine) return null;
|
|
2552
|
+
const parsed = JSON.parse(firstLine);
|
|
2553
|
+
if (parsed._meta && typeof parsed.lineCount === "number") {
|
|
2554
|
+
return { lineCount: parsed.lineCount };
|
|
2555
|
+
}
|
|
2556
|
+
} catch {
|
|
2557
|
+
}
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
function writeAuditLog(brainRoot, sessionId, entries, lineCount) {
|
|
2469
2561
|
const logDir = join16(brainRoot, DIGEST_LOG_DIR);
|
|
2470
2562
|
if (!existsSync15(logDir)) {
|
|
2471
2563
|
mkdirSync10(logDir, { recursive: true });
|
|
2472
2564
|
}
|
|
2473
2565
|
const logPath = join16(logDir, `${sessionId}.jsonl`);
|
|
2474
|
-
const
|
|
2566
|
+
const metaLine = JSON.stringify({ _meta: true, lineCount, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2567
|
+
const entryLines = entries.map(
|
|
2475
2568
|
(e) => JSON.stringify({
|
|
2476
2569
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2477
2570
|
path: e.correction.path,
|
|
@@ -2481,9 +2574,9 @@ function writeAuditLog(brainRoot, sessionId, entries) {
|
|
|
2481
2574
|
applied: e.applied
|
|
2482
2575
|
})
|
|
2483
2576
|
);
|
|
2484
|
-
writeFileSync12(logPath,
|
|
2577
|
+
writeFileSync12(logPath, [metaLine, ...entryLines].join("\n") + "\n", "utf8");
|
|
2485
2578
|
}
|
|
2486
|
-
var NEGATION_PATTERNS, AFFIRMATION_PATTERNS, MUST_PATTERNS, WARN_PATTERNS, MAX_FAILURES_PER_SESSION;
|
|
2579
|
+
var NEGATION_PATTERNS, AFFIRMATION_PATTERNS, MUST_PATTERNS, WARN_PATTERNS, MAX_FAILURES_PER_SESSION, SOFT_ERROR_PATTERNS;
|
|
2487
2580
|
var init_digest = __esm({
|
|
2488
2581
|
"src/digest.ts"() {
|
|
2489
2582
|
"use strict";
|
|
@@ -2530,6 +2623,14 @@ var init_digest = __esm({
|
|
|
2530
2623
|
/주의/
|
|
2531
2624
|
];
|
|
2532
2625
|
MAX_FAILURES_PER_SESSION = 20;
|
|
2626
|
+
SOFT_ERROR_PATTERNS = [
|
|
2627
|
+
/(?:^|\n)\S*(?:\(\w+\):\d+: )?command not found:/m,
|
|
2628
|
+
// shell: command not found
|
|
2629
|
+
/(?:^|\n)npm error\b/m,
|
|
2630
|
+
// npm error (not npm warn)
|
|
2631
|
+
/(?:^|\n)fatal: /m
|
|
2632
|
+
// git fatal
|
|
2633
|
+
];
|
|
2533
2634
|
}
|
|
2534
2635
|
});
|
|
2535
2636
|
|
|
@@ -3013,6 +3114,7 @@ function validateActions(actions, _brain) {
|
|
|
3013
3114
|
return false;
|
|
3014
3115
|
}
|
|
3015
3116
|
const region = action.path.split("/")[0];
|
|
3117
|
+
if (region === SKILLS_DIR) return true;
|
|
3016
3118
|
if (!region || PROTECTED_REGIONS.includes(region)) {
|
|
3017
3119
|
console.log(` \u{1F6E1}\uFE0F blocked: ${action.type} ${action.path} (protected region)`);
|
|
3018
3120
|
return false;
|
|
@@ -3037,7 +3139,11 @@ function executeActions(brainRoot, actions) {
|
|
|
3037
3139
|
fireNeuron(brainRoot, action.path);
|
|
3038
3140
|
break;
|
|
3039
3141
|
case "grow":
|
|
3040
|
-
|
|
3142
|
+
if (action.path.startsWith(SKILLS_DIR + "/")) {
|
|
3143
|
+
growNeuron(brainRoot, action.path);
|
|
3144
|
+
} else {
|
|
3145
|
+
growCandidate(brainRoot, action.path);
|
|
3146
|
+
}
|
|
3041
3147
|
break;
|
|
3042
3148
|
case "signal":
|
|
3043
3149
|
signalNeuron(brainRoot, action.path, action.signal || "dopamine");
|
|
@@ -3082,6 +3188,7 @@ var init_evolve = __esm({
|
|
|
3082
3188
|
init_constants();
|
|
3083
3189
|
init_fire();
|
|
3084
3190
|
init_candidates();
|
|
3191
|
+
init_grow();
|
|
3085
3192
|
init_signal();
|
|
3086
3193
|
init_rollback();
|
|
3087
3194
|
init_decay();
|
|
@@ -3235,11 +3342,260 @@ var init_doctor = __esm({
|
|
|
3235
3342
|
}
|
|
3236
3343
|
});
|
|
3237
3344
|
|
|
3345
|
+
// src/cron.ts
|
|
3346
|
+
var cron_exports = {};
|
|
3347
|
+
__export(cron_exports, {
|
|
3348
|
+
checkCron: () => checkCron,
|
|
3349
|
+
generateFeedbackPlist: () => generateFeedbackPlist,
|
|
3350
|
+
generatePrunePlist: () => generatePrunePlist,
|
|
3351
|
+
installCron: () => installCron,
|
|
3352
|
+
uninstallCron: () => uninstallCron
|
|
3353
|
+
});
|
|
3354
|
+
import { writeFileSync as writeFileSync15, existsSync as existsSync19, unlinkSync as unlinkSync2 } from "fs";
|
|
3355
|
+
import { join as join20 } from "path";
|
|
3356
|
+
import { execSync as execSync5 } from "child_process";
|
|
3357
|
+
function getLaunchAgentsDir() {
|
|
3358
|
+
return join20(process.env.HOME || "~", "Library", "LaunchAgents");
|
|
3359
|
+
}
|
|
3360
|
+
function getPlistPath(label) {
|
|
3361
|
+
return join20(getLaunchAgentsDir(), `${label}.plist`);
|
|
3362
|
+
}
|
|
3363
|
+
function getNpxPath() {
|
|
3364
|
+
try {
|
|
3365
|
+
return execSync5("which npx", { encoding: "utf8" }).trim();
|
|
3366
|
+
} catch {
|
|
3367
|
+
return "/opt/homebrew/bin/npx";
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
function generatePrunePlist(brainRoot, hour = 2, minute = 0) {
|
|
3371
|
+
const npx = getNpxPath();
|
|
3372
|
+
const apiKey = process.env.GEMINI_API_KEY || "";
|
|
3373
|
+
const home = process.env.HOME || "/Users/sweetheart";
|
|
3374
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3375
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3376
|
+
<plist version="1.0">
|
|
3377
|
+
<dict>
|
|
3378
|
+
<key>Label</key>
|
|
3379
|
+
<string>${PLIST_LABEL}</string>
|
|
3380
|
+
<key>ProgramArguments</key>
|
|
3381
|
+
<array>
|
|
3382
|
+
<string>${npx}</string>
|
|
3383
|
+
<string>hebbian</string>
|
|
3384
|
+
<string>evolve</string>
|
|
3385
|
+
<string>prune</string>
|
|
3386
|
+
<string>--brain</string>
|
|
3387
|
+
<string>${brainRoot}</string>
|
|
3388
|
+
</array>
|
|
3389
|
+
<key>EnvironmentVariables</key>
|
|
3390
|
+
<dict>
|
|
3391
|
+
<key>GEMINI_API_KEY</key>
|
|
3392
|
+
<string>${apiKey}</string>
|
|
3393
|
+
<key>PATH</key>
|
|
3394
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
3395
|
+
</dict>
|
|
3396
|
+
<key>StartCalendarInterval</key>
|
|
3397
|
+
<dict>
|
|
3398
|
+
<key>Hour</key>
|
|
3399
|
+
<integer>${hour}</integer>
|
|
3400
|
+
<key>Minute</key>
|
|
3401
|
+
<integer>${minute}</integer>
|
|
3402
|
+
</dict>
|
|
3403
|
+
<key>StandardOutPath</key>
|
|
3404
|
+
<string>${home}/Library/Logs/hebbian-prune.log</string>
|
|
3405
|
+
<key>StandardErrorPath</key>
|
|
3406
|
+
<string>${home}/Library/Logs/hebbian-prune.log</string>
|
|
3407
|
+
</dict>
|
|
3408
|
+
</plist>`;
|
|
3409
|
+
}
|
|
3410
|
+
function generateFeedbackPlist(brainRoot, intervalMinutes = 15) {
|
|
3411
|
+
const npx = getNpxPath();
|
|
3412
|
+
const home = process.env.HOME || "/Users/sweetheart";
|
|
3413
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3414
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3415
|
+
<plist version="1.0">
|
|
3416
|
+
<dict>
|
|
3417
|
+
<key>Label</key>
|
|
3418
|
+
<string>${FEEDBACK_PLIST_LABEL}</string>
|
|
3419
|
+
<key>ProgramArguments</key>
|
|
3420
|
+
<array>
|
|
3421
|
+
<string>${npx}</string>
|
|
3422
|
+
<string>hebbian</string>
|
|
3423
|
+
<string>feedback</string>
|
|
3424
|
+
<string>scan</string>
|
|
3425
|
+
<string>--brain</string>
|
|
3426
|
+
<string>${brainRoot}</string>
|
|
3427
|
+
</array>
|
|
3428
|
+
<key>EnvironmentVariables</key>
|
|
3429
|
+
<dict>
|
|
3430
|
+
<key>PATH</key>
|
|
3431
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
3432
|
+
</dict>
|
|
3433
|
+
<key>StartInterval</key>
|
|
3434
|
+
<integer>${intervalMinutes * 60}</integer>
|
|
3435
|
+
<key>StandardOutPath</key>
|
|
3436
|
+
<string>${home}/Library/Logs/hebbian-feedback.log</string>
|
|
3437
|
+
<key>StandardErrorPath</key>
|
|
3438
|
+
<string>${home}/Library/Logs/hebbian-feedback.log</string>
|
|
3439
|
+
</dict>
|
|
3440
|
+
</plist>`;
|
|
3441
|
+
}
|
|
3442
|
+
function installCron(brainRoot, type = "prune") {
|
|
3443
|
+
const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
|
|
3444
|
+
const plistPath = getPlistPath(label);
|
|
3445
|
+
const plistContent = type === "prune" ? generatePrunePlist(brainRoot) : generateFeedbackPlist(brainRoot);
|
|
3446
|
+
try {
|
|
3447
|
+
execSync5(`launchctl unload ${plistPath} 2>/dev/null`, { encoding: "utf8" });
|
|
3448
|
+
} catch {
|
|
3449
|
+
}
|
|
3450
|
+
writeFileSync15(plistPath, plistContent, "utf8");
|
|
3451
|
+
execSync5(`launchctl load ${plistPath}`, { encoding: "utf8" });
|
|
3452
|
+
console.log(`\u2705 ${type} cron installed: ${plistPath}`);
|
|
3453
|
+
}
|
|
3454
|
+
function uninstallCron(type = "prune") {
|
|
3455
|
+
const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
|
|
3456
|
+
const plistPath = getPlistPath(label);
|
|
3457
|
+
if (!existsSync19(plistPath)) {
|
|
3458
|
+
console.log(`\u26A0\uFE0F ${type} cron not installed`);
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
try {
|
|
3462
|
+
execSync5(`launchctl unload ${plistPath}`, { encoding: "utf8" });
|
|
3463
|
+
} catch {
|
|
3464
|
+
}
|
|
3465
|
+
unlinkSync2(plistPath);
|
|
3466
|
+
console.log(`\u{1F5D1}\uFE0F ${type} cron uninstalled`);
|
|
3467
|
+
}
|
|
3468
|
+
function checkCron(type = "prune") {
|
|
3469
|
+
const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
|
|
3470
|
+
const plistPath = getPlistPath(label);
|
|
3471
|
+
return { installed: existsSync19(plistPath), path: plistPath };
|
|
3472
|
+
}
|
|
3473
|
+
var PLIST_LABEL, FEEDBACK_PLIST_LABEL;
|
|
3474
|
+
var init_cron = __esm({
|
|
3475
|
+
"src/cron.ts"() {
|
|
3476
|
+
"use strict";
|
|
3477
|
+
PLIST_LABEL = "com.hebbian.nightly-prune";
|
|
3478
|
+
FEEDBACK_PLIST_LABEL = "com.hebbian.feedback";
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
|
|
3482
|
+
// src/feedback.ts
|
|
3483
|
+
var feedback_exports = {};
|
|
3484
|
+
__export(feedback_exports, {
|
|
3485
|
+
propagateToAgents: () => propagateToAgents,
|
|
3486
|
+
runFeedback: () => runFeedback,
|
|
3487
|
+
scanSharedBrain: () => scanSharedBrain
|
|
3488
|
+
});
|
|
3489
|
+
import { existsSync as existsSync20, readdirSync as readdirSync12, statSync as statSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync16, mkdirSync as mkdirSync12 } from "fs";
|
|
3490
|
+
import { join as join21 } from "path";
|
|
3491
|
+
function scanSharedBrain(brainRoot) {
|
|
3492
|
+
const sharedRoot = join21(brainRoot, SHARED_DIR);
|
|
3493
|
+
if (!existsSync20(sharedRoot)) return [];
|
|
3494
|
+
const watermark = readWatermark(sharedRoot);
|
|
3495
|
+
const deltas = [];
|
|
3496
|
+
for (const region of REGIONS) {
|
|
3497
|
+
const regionPath = join21(sharedRoot, region);
|
|
3498
|
+
if (!existsSync20(regionPath)) continue;
|
|
3499
|
+
walkForNeurons(regionPath, regionPath, (neuronDir, counter) => {
|
|
3500
|
+
const modTime = statSync6(neuronDir).mtime;
|
|
3501
|
+
if (modTime.getTime() <= watermark) return;
|
|
3502
|
+
const name = neuronDir.split("/").pop() || "";
|
|
3503
|
+
if (name.startsWith(WARN_PREFIX)) return;
|
|
3504
|
+
const relPath = region + "/" + neuronDir.slice(regionPath.length + 1);
|
|
3505
|
+
deltas.push({ path: relPath, counter, modTime });
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
return deltas;
|
|
3509
|
+
}
|
|
3510
|
+
function propagateToAgents(brainRoot, deltas) {
|
|
3511
|
+
const agentsDir = join21(brainRoot, AGENTS_DIR);
|
|
3512
|
+
if (!existsSync20(agentsDir) || deltas.length === 0) {
|
|
3513
|
+
return { scanned: deltas.length, propagated: 0, agents: [] };
|
|
3514
|
+
}
|
|
3515
|
+
const agentNames = readdirSync12(agentsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_")).map((e) => e.name);
|
|
3516
|
+
let propagated = 0;
|
|
3517
|
+
const touchedAgents = /* @__PURE__ */ new Set();
|
|
3518
|
+
for (const delta of deltas) {
|
|
3519
|
+
const neuronName = delta.path.split("/").pop() || "";
|
|
3520
|
+
const warnPath = delta.path.replace(/\/([^/]+)$/, `/${WARN_PREFIX}${neuronName}`);
|
|
3521
|
+
for (const agent of agentNames) {
|
|
3522
|
+
const agentBrain = join21(agentsDir, agent);
|
|
3523
|
+
if (!existsSync20(join21(agentBrain, "cortex")) && !existsSync20(join21(agentBrain, "brainstem"))) continue;
|
|
3524
|
+
try {
|
|
3525
|
+
growNeuron(agentBrain, warnPath);
|
|
3526
|
+
logEpisode(agentBrain, "feedback", warnPath, `shared learning: ${delta.path}`);
|
|
3527
|
+
propagated++;
|
|
3528
|
+
touchedAgents.add(agent);
|
|
3529
|
+
} catch {
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return { scanned: deltas.length, propagated, agents: [...touchedAgents] };
|
|
3534
|
+
}
|
|
3535
|
+
function runFeedback(brainRoot) {
|
|
3536
|
+
const deltas = scanSharedBrain(brainRoot);
|
|
3537
|
+
if (deltas.length === 0) {
|
|
3538
|
+
console.log("\u{1F4E1} feedback: no new shared neurons");
|
|
3539
|
+
return { scanned: 0, propagated: 0, agents: [] };
|
|
3540
|
+
}
|
|
3541
|
+
const result = propagateToAgents(brainRoot, deltas);
|
|
3542
|
+
const latestTime = Math.max(...deltas.map((d) => d.modTime.getTime()));
|
|
3543
|
+
writeWatermark(join21(brainRoot, SHARED_DIR), latestTime);
|
|
3544
|
+
console.log(`\u{1F4E1} feedback: ${result.scanned} shared neuron(s) \u2192 ${result.propagated} warning(s) to ${result.agents.join(", ")}`);
|
|
3545
|
+
return result;
|
|
3546
|
+
}
|
|
3547
|
+
function readWatermark(sharedRoot) {
|
|
3548
|
+
const wmPath = join21(sharedRoot, WATERMARK_FILE);
|
|
3549
|
+
if (!existsSync20(wmPath)) return 0;
|
|
3550
|
+
try {
|
|
3551
|
+
const data = JSON.parse(readFileSync12(wmPath, "utf8"));
|
|
3552
|
+
return data.timestamp || 0;
|
|
3553
|
+
} catch {
|
|
3554
|
+
return 0;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
function writeWatermark(sharedRoot, timestamp) {
|
|
3558
|
+
const wmPath = join21(sharedRoot, WATERMARK_FILE);
|
|
3559
|
+
mkdirSync12(sharedRoot, { recursive: true });
|
|
3560
|
+
writeFileSync16(wmPath, JSON.stringify({ timestamp, ts: new Date(timestamp).toISOString() }), "utf8");
|
|
3561
|
+
}
|
|
3562
|
+
function walkForNeurons(dir, regionRoot, cb) {
|
|
3563
|
+
let entries;
|
|
3564
|
+
try {
|
|
3565
|
+
entries = readdirSync12(dir, { withFileTypes: true });
|
|
3566
|
+
} catch {
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
const neuronFiles = entries.filter((e) => e.isFile() && /^\d+\.neuron$/.test(e.name));
|
|
3570
|
+
if (neuronFiles.length > 0) {
|
|
3571
|
+
const counter = Math.max(...neuronFiles.map((f) => parseInt(f.name, 10)));
|
|
3572
|
+
cb(dir, counter);
|
|
3573
|
+
return;
|
|
3574
|
+
}
|
|
3575
|
+
for (const entry of entries) {
|
|
3576
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
3577
|
+
if (entry.isDirectory()) {
|
|
3578
|
+
walkForNeurons(join21(dir, entry.name), regionRoot, cb);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
var WATERMARK_FILE, WARN_PREFIX;
|
|
3583
|
+
var init_feedback = __esm({
|
|
3584
|
+
"src/feedback.ts"() {
|
|
3585
|
+
"use strict";
|
|
3586
|
+
init_constants();
|
|
3587
|
+
init_grow();
|
|
3588
|
+
init_episode();
|
|
3589
|
+
WATERMARK_FILE = "_feedback_watermark.json";
|
|
3590
|
+
WARN_PREFIX = "WARN_shared_";
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
|
|
3238
3594
|
// src/cli.ts
|
|
3239
3595
|
init_constants();
|
|
3240
3596
|
import { parseArgs } from "util";
|
|
3241
3597
|
import { resolve as resolve3 } from "path";
|
|
3242
|
-
var VERSION = "0.
|
|
3598
|
+
var VERSION = "0.8.0";
|
|
3243
3599
|
var HELP = `
|
|
3244
3600
|
hebbian v${VERSION} \u2014 Folder-as-neuron brain for any AI agent.
|
|
3245
3601
|
|
|
@@ -3550,6 +3906,44 @@ Total: ${episodes.length} sessions`);
|
|
|
3550
3906
|
await runDoctor2(brainRoot);
|
|
3551
3907
|
break;
|
|
3552
3908
|
}
|
|
3909
|
+
case "cron": {
|
|
3910
|
+
const sub = positionals[1];
|
|
3911
|
+
const { installCron: installCron2, uninstallCron: uninstallCron2, checkCron: checkCron2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
3912
|
+
switch (sub) {
|
|
3913
|
+
case "install":
|
|
3914
|
+
installCron2(brainRoot, "prune");
|
|
3915
|
+
break;
|
|
3916
|
+
case "uninstall":
|
|
3917
|
+
uninstallCron2("prune");
|
|
3918
|
+
break;
|
|
3919
|
+
case "status": {
|
|
3920
|
+
const status = checkCron2("prune");
|
|
3921
|
+
console.log(`Pruning cron: ${status.installed ? "\u2705 installed" : "\u274C not installed"}`);
|
|
3922
|
+
if (status.installed) console.log(` ${status.path}`);
|
|
3923
|
+
const fbStatus = checkCron2("feedback");
|
|
3924
|
+
console.log(`Feedback cron: ${fbStatus.installed ? "\u2705 installed" : "\u274C not installed"}`);
|
|
3925
|
+
if (fbStatus.installed) console.log(` ${fbStatus.path}`);
|
|
3926
|
+
break;
|
|
3927
|
+
}
|
|
3928
|
+
default:
|
|
3929
|
+
console.error("Usage: hebbian cron <install|uninstall|status>");
|
|
3930
|
+
process.exit(1);
|
|
3931
|
+
}
|
|
3932
|
+
break;
|
|
3933
|
+
}
|
|
3934
|
+
case "feedback": {
|
|
3935
|
+
const sub = positionals[1];
|
|
3936
|
+
const { runFeedback: runFeedback2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
|
|
3937
|
+
switch (sub) {
|
|
3938
|
+
case "scan":
|
|
3939
|
+
runFeedback2(brainRoot);
|
|
3940
|
+
break;
|
|
3941
|
+
default:
|
|
3942
|
+
console.error("Usage: hebbian feedback <scan>");
|
|
3943
|
+
process.exit(1);
|
|
3944
|
+
}
|
|
3945
|
+
break;
|
|
3946
|
+
}
|
|
3553
3947
|
case "diag":
|
|
3554
3948
|
case "stats": {
|
|
3555
3949
|
const { scanBrain: scanBrain2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|