@zenera/neo 1.1.10 → 1.1.12
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 +3 -3
- package/dist/agent.d.ts +8 -7
- package/dist/agent.js +15 -8
- package/dist/architecture.d.ts +7 -5
- package/dist/architecture.js +20 -12
- package/dist/embedding.d.ts +20 -1
- package/dist/embedding.js +3 -2
- package/dist/embeddings/fanout.d.ts +51 -0
- package/dist/embeddings/fanout.js +76 -0
- package/dist/embeddings/gemini.d.ts +3 -10
- package/dist/embeddings/gemini.js +46 -30
- package/dist/embeddings/index.d.ts +3 -0
- package/dist/embeddings/index.js +3 -0
- package/dist/embeddings/limiter.d.ts +36 -0
- package/dist/embeddings/limiter.js +182 -0
- package/dist/embeddings/openai.d.ts +3 -2
- package/dist/embeddings/openai.js +54 -13
- package/dist/embeddings/openrouter.d.ts +2 -1
- package/dist/embeddings/openrouter.js +52 -23
- package/dist/embeddings/rate-limit.d.ts +16 -0
- package/dist/embeddings/rate-limit.js +131 -0
- package/dist/failure.d.ts +82 -0
- package/dist/failure.js +323 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +13 -2
- package/dist/inspect/client.d.ts +1 -1
- package/dist/inspect/client.js +42 -26
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +40 -23
- package/dist/memory/files.d.ts +24 -0
- package/dist/memory/files.js +85 -0
- package/dist/memory/graph.d.ts +56 -0
- package/dist/memory/graph.js +159 -0
- package/dist/memory/index.d.ts +93 -0
- package/dist/memory/index.js +357 -0
- package/dist/memory/instructions.d.ts +15 -0
- package/dist/memory/instructions.js +103 -0
- package/dist/memory/page.d.ts +8 -0
- package/dist/memory/page.js +797 -0
- package/dist/memory/recall.d.ts +35 -0
- package/dist/memory/recall.js +161 -0
- package/dist/memory/render.d.ts +10 -0
- package/dist/memory/render.js +228 -0
- package/dist/memory/report.d.ts +57 -0
- package/dist/memory/report.js +113 -0
- package/dist/memory/store.d.ts +49 -0
- package/dist/memory/store.js +226 -0
- package/dist/memory/tools.d.ts +11 -0
- package/dist/memory/tools.js +310 -0
- package/dist/memory/types.d.ts +192 -0
- package/dist/memory/types.js +105 -0
- package/dist/memory/vectors.d.ts +29 -0
- package/dist/memory/vectors.js +152 -0
- package/dist/models/anthropic.d.ts +2 -1
- package/dist/models/anthropic.js +11 -4
- package/dist/models/factory.d.ts +15 -0
- package/dist/models/factory.js +49 -10
- package/dist/models/gemini.d.ts +2 -1
- package/dist/models/gemini.js +8 -3
- package/dist/models/openai-chat.d.ts +2 -1
- package/dist/models/openai-chat.js +10 -5
- package/dist/models/openai-responses.d.ts +2 -1
- package/dist/models/openai-responses.js +22 -7
- package/dist/models/openrouter.d.ts +2 -1
- package/dist/models/openrouter.js +14 -6
- package/dist/project/config.d.ts +55 -3
- package/dist/project/config.js +76 -1
- package/dist/project/index.d.ts +2 -2
- package/dist/project/index.js +1 -1
- package/dist/project/load.d.ts +16 -2
- package/dist/project/load.js +67 -1
- package/dist/runner.d.ts +4 -2
- package/dist/runner.js +29 -30
- package/dist/services.d.ts +23 -6
- package/dist/services.js +25 -15
- package/dist/tools/workspace.js +195 -17
- package/dist/trajectory.d.ts +14 -12
- package/dist/types.d.ts +5 -5
- package/dist/types.js +3 -3
- package/package.json +2 -5
- package/dist/memory-stores/factory.d.ts +0 -15
- package/dist/memory-stores/factory.js +0 -29
- package/dist/memory-stores/file.d.ts +0 -37
- package/dist/memory-stores/file.js +0 -174
- package/dist/memory-stores/in-memory.d.ts +0 -17
- package/dist/memory-stores/in-memory.js +0 -80
- package/dist/memory-stores/index.d.ts +0 -4
- package/dist/memory-stores/index.js +0 -5
- package/dist/memory.d.ts +0 -115
- package/dist/memory.js +0 -213
package/dist/tools/workspace.js
CHANGED
|
@@ -358,6 +358,10 @@ function toLines(body) {
|
|
|
358
358
|
}
|
|
359
359
|
return lines;
|
|
360
360
|
}
|
|
361
|
+
/** Whether every line of the file ends CRLF, so converting the lot is lossless. */
|
|
362
|
+
function isCrlf(body) {
|
|
363
|
+
return body.includes('\r\n') && !/(^|[^\r])\n/.test(body);
|
|
364
|
+
}
|
|
361
365
|
// ---------------------------------------------------------------------------
|
|
362
366
|
// The patch format
|
|
363
367
|
//
|
|
@@ -476,7 +480,9 @@ function parsePatch(text) {
|
|
|
476
480
|
chunk = undefined;
|
|
477
481
|
}
|
|
478
482
|
const heading = body.slice(2).trim();
|
|
479
|
-
|
|
483
|
+
// `@@ ...` is a model eliding the lines between chunks, not
|
|
484
|
+
// naming a block, and there is no such line to find.
|
|
485
|
+
if (heading && !/^[.\u2026]+$/.test(heading)) {
|
|
480
486
|
open().headings.push(heading);
|
|
481
487
|
}
|
|
482
488
|
else {
|
|
@@ -557,26 +563,143 @@ function locate(lines, want, from) {
|
|
|
557
563
|
}
|
|
558
564
|
return { index: -1, fuzz: 0 };
|
|
559
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Finds the line a `@@ heading` names. Exact, then trimmed, then any line that
|
|
568
|
+
* contains it: a model writes `@@ check_risk` where the file says
|
|
569
|
+
* `def check_risk(entry):`, and the worked example in the tool description —
|
|
570
|
+
* `@@ class Server` — teaches exactly that. Separate sweeps, so an exact match
|
|
571
|
+
* anywhere beats a loose one earlier.
|
|
572
|
+
*/
|
|
560
573
|
function findHeading(lines, heading, from) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
574
|
+
const want = heading.trim();
|
|
575
|
+
const passes = [
|
|
576
|
+
(line) => line === heading,
|
|
577
|
+
(line) => line.trim() === want,
|
|
578
|
+
(line) => line.includes(want),
|
|
579
|
+
];
|
|
580
|
+
for (const same of passes) {
|
|
581
|
+
for (let i = from; i < lines.length; i++) {
|
|
582
|
+
if (same(lines[i])) {
|
|
583
|
+
return i;
|
|
584
|
+
}
|
|
564
585
|
}
|
|
565
586
|
}
|
|
566
587
|
return -1;
|
|
567
588
|
}
|
|
589
|
+
function indentOf(line) {
|
|
590
|
+
return line.slice(0, line.length - line.trimStart().length);
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* The single shift that turns the patch's indentation into the file's, replayed
|
|
594
|
+
* onto `after`. Null when the lines disagree about it: that is not a
|
|
595
|
+
* mis-indented chunk, it is a chunk matched in the wrong place.
|
|
596
|
+
*
|
|
597
|
+
* Prefix arithmetic rather than column counts, because a tab and four spaces
|
|
598
|
+
* are different bytes and Python does not treat them alike.
|
|
599
|
+
*/
|
|
600
|
+
function reindent(after, file, want, at) {
|
|
601
|
+
let add;
|
|
602
|
+
let drop;
|
|
603
|
+
for (let k = 0; k < want.length; k++) {
|
|
604
|
+
if (want[k].trim() === '') {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const have = indentOf(file[at + k]);
|
|
608
|
+
const said = indentOf(want[k]);
|
|
609
|
+
const step = have.startsWith(said)
|
|
610
|
+
? { add: have.slice(said.length), drop: '' }
|
|
611
|
+
: said.startsWith(have)
|
|
612
|
+
? { add: '', drop: said.slice(have.length) }
|
|
613
|
+
: undefined;
|
|
614
|
+
if (!step || (add !== undefined && (add !== step.add || drop !== step.drop))) {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
add = step.add;
|
|
618
|
+
drop = step.drop;
|
|
619
|
+
}
|
|
620
|
+
if (!add && !drop) {
|
|
621
|
+
return after;
|
|
622
|
+
}
|
|
623
|
+
return after.map((line) => {
|
|
624
|
+
if (line.trim() === '') {
|
|
625
|
+
return line;
|
|
626
|
+
}
|
|
627
|
+
const bare = drop && line.startsWith(drop) ? line.slice(drop.length) : line;
|
|
628
|
+
return add ? add + bare : bare;
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* How many lines of `want` the closest near-miss got through, so an error can
|
|
633
|
+
* name the line that actually diverged. Naming the first line of the block
|
|
634
|
+
* instead sends the model to verify a line that was never the problem.
|
|
635
|
+
*/
|
|
636
|
+
function matchedUpTo(lines, want, from) {
|
|
637
|
+
let best = 0;
|
|
638
|
+
for (let start = from; start < lines.length; start++) {
|
|
639
|
+
let k = 0;
|
|
640
|
+
while (k < want.length && start + k < lines.length) {
|
|
641
|
+
if (lines[start + k].trim() !== want[k].trim()) {
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
k++;
|
|
645
|
+
}
|
|
646
|
+
if (k > best) {
|
|
647
|
+
best = k;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return best;
|
|
651
|
+
}
|
|
652
|
+
/** How many places `want` occurs at or after `from`, counted no further than two. */
|
|
653
|
+
function countMatches(lines, want, from) {
|
|
654
|
+
if (want.length === 0) {
|
|
655
|
+
return 2;
|
|
656
|
+
}
|
|
657
|
+
let found = 0;
|
|
658
|
+
let at = from;
|
|
659
|
+
while (found < 2) {
|
|
660
|
+
const hit = locate(lines, want, at);
|
|
661
|
+
if (hit.index < 0) {
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
found++;
|
|
665
|
+
at = hit.index + 1;
|
|
666
|
+
}
|
|
667
|
+
return found;
|
|
668
|
+
}
|
|
568
669
|
/** Applies one file's chunks to its lines. Throws `PatchError` on a miss. */
|
|
569
670
|
function patchLines(lines, chunks, file) {
|
|
570
671
|
const out = lines.slice();
|
|
571
672
|
let cursor = 0;
|
|
572
673
|
let fuzz = 0;
|
|
674
|
+
let ignored = 0;
|
|
573
675
|
for (const chunk of chunks) {
|
|
676
|
+
// Nothing to search for and no marker to append after: the top of the file
|
|
677
|
+
// is a guess, and a guess that reports success is worse than a refusal.
|
|
678
|
+
if (chunk.before.length === 0 && !chunk.eof && chunk.headings.length === 0) {
|
|
679
|
+
throw new PatchError(`${file}: the chunk at patch line ${chunk.at} adds lines without any context, ` +
|
|
680
|
+
'so there is nowhere to put them — keep a line of the file above the ' +
|
|
681
|
+
'insertion, or end the chunk with *** End of File to append');
|
|
682
|
+
}
|
|
574
683
|
for (const heading of chunk.headings) {
|
|
575
684
|
const at = findHeading(out, heading, cursor);
|
|
576
685
|
if (at < 0) {
|
|
577
|
-
|
|
686
|
+
// A heading only narrows the search. Models invent them —
|
|
687
|
+
// `@@ class DfwRiskScanner` for a file of plain functions — and
|
|
688
|
+
// when the context occurs once the heading was never needed, so
|
|
689
|
+
// refusing spends a turn to learn nothing.
|
|
690
|
+
if (countMatches(out, chunk.before, cursor) === 1) {
|
|
691
|
+
ignored++;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
throw new PatchError(`${file}: no line matching '@@ ${heading}' after the previous chunk` +
|
|
695
|
+
(countMatches(out, chunk.before, cursor) === 0
|
|
696
|
+
? ", and the chunk's context is not in the file either"
|
|
697
|
+
: ", and the chunk's context occurs more than once, so a heading " +
|
|
698
|
+
'that matches a line of the file is needed'));
|
|
578
699
|
}
|
|
579
|
-
|
|
700
|
+
// Not `at + 1`: models routinely repeat the heading as the first
|
|
701
|
+
// context line, and consuming it makes that line unfindable.
|
|
702
|
+
cursor = at;
|
|
580
703
|
}
|
|
581
704
|
// An end-of-file chunk is tried against the tail first; failing that it
|
|
582
705
|
// is an ordinary search, because the marker is often optimistic.
|
|
@@ -597,15 +720,42 @@ function patchLines(lines, chunks, file) {
|
|
|
597
720
|
found = locate(out, chunk.before, cursor);
|
|
598
721
|
}
|
|
599
722
|
if (found.index < 0) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
723
|
+
// Saying "not in the file" about a line that is plainly in it, just
|
|
724
|
+
// above the cursor, sends the model back to re-read and retry the
|
|
725
|
+
// same patch. Look once more from the top to tell the two apart.
|
|
726
|
+
if (cursor > 0 && locate(out, chunk.before, 0).index >= 0) {
|
|
727
|
+
throw new PatchError(`${file}: the chunk at patch line ${chunk.at} matches earlier in the file ` +
|
|
728
|
+
`than the previous chunk — chunks must be in file order`);
|
|
729
|
+
}
|
|
730
|
+
const k = matchedUpTo(out, chunk.before, cursor);
|
|
731
|
+
const missed = chunk.before[k] ?? chunk.before[0] ?? '';
|
|
732
|
+
throw new PatchError(`${file}: the chunk at patch line ${chunk.at} does not match — line ${k + 1} of ` +
|
|
733
|
+
`its context is not in the file` +
|
|
734
|
+
(missed ? `: ${missed.trim()}` : '') +
|
|
735
|
+
// A model that has been reading escaped tool output writes
|
|
736
|
+
// one patch line where the file has two.
|
|
737
|
+
(missed.includes('\\n')
|
|
738
|
+
? " — that line holds a literal '\\n'; each line of a patch is one line " +
|
|
739
|
+
'of the file'
|
|
740
|
+
: ''));
|
|
603
741
|
}
|
|
604
742
|
fuzz += found.fuzz > 0 ? 1 : 0;
|
|
605
|
-
|
|
606
|
-
|
|
743
|
+
// Pass 2 matched by ignoring indentation, so writing `after` as the
|
|
744
|
+
// patch spelled it would put the model's wrong indentation in the file
|
|
745
|
+
// — a syntax error in Python, and silent, since the chunk "applied".
|
|
746
|
+
let after = chunk.after;
|
|
747
|
+
if (found.fuzz === 2 && after.length > 0) {
|
|
748
|
+
const fixed = reindent(after, out, chunk.before, found.index);
|
|
749
|
+
if (!fixed) {
|
|
750
|
+
throw new PatchError(`${file}: the chunk at patch line ${chunk.at} matches only when indentation ` +
|
|
751
|
+
`is ignored, and its lines disagree about how far out they are`);
|
|
752
|
+
}
|
|
753
|
+
after = fixed;
|
|
754
|
+
}
|
|
755
|
+
out.splice(found.index, chunk.before.length, ...after);
|
|
756
|
+
cursor = found.index + after.length;
|
|
607
757
|
}
|
|
608
|
-
return { lines: out, fuzz };
|
|
758
|
+
return { lines: out, fuzz, ignored };
|
|
609
759
|
}
|
|
610
760
|
// ---------------------------------------------------------------------------
|
|
611
761
|
// The tools
|
|
@@ -713,7 +863,10 @@ export function workspaceTools(opts) {
|
|
|
713
863
|
lines: total,
|
|
714
864
|
start_line: from,
|
|
715
865
|
end_line: from + taken.length - 1,
|
|
716
|
-
|
|
866
|
+
// Where the range started is not truncation: a read that ends
|
|
867
|
+
// on the last line withheld nothing, and saying otherwise
|
|
868
|
+
// teaches the model to distrust every ranged read it makes.
|
|
869
|
+
truncated: cut || !whole || to < total ? true : undefined,
|
|
717
870
|
content: taken.join('\n'),
|
|
718
871
|
};
|
|
719
872
|
},
|
|
@@ -1046,7 +1199,15 @@ async function runPatch(ws, patch) {
|
|
|
1046
1199
|
const steps = [];
|
|
1047
1200
|
const touched = [];
|
|
1048
1201
|
const seen = new Set();
|
|
1202
|
+
// Where a move lands is a path this patch writes as surely as one it names,
|
|
1203
|
+
// and `rename` overwrites: without this, the last step to touch a path wins
|
|
1204
|
+
// and the others are lost with the patch still reporting them applied.
|
|
1205
|
+
const targets = new Set();
|
|
1206
|
+
// Steps run in patch order, so a path deleted by an earlier op is free by the
|
|
1207
|
+
// time a later move lands on it, however occupied it still looks on disk.
|
|
1208
|
+
const removed = new Set();
|
|
1049
1209
|
let fuzz = 0;
|
|
1210
|
+
let ignored = 0;
|
|
1050
1211
|
for (const op of ops) {
|
|
1051
1212
|
const at = patchPath(ws, op.path);
|
|
1052
1213
|
if (ws.isRoot(at)) {
|
|
@@ -1055,6 +1216,9 @@ async function runPatch(ws, patch) {
|
|
|
1055
1216
|
if (seen.has(at)) {
|
|
1056
1217
|
throw new PatchError(`${ws.show(at)} appears twice in the patch`);
|
|
1057
1218
|
}
|
|
1219
|
+
if (targets.has(at)) {
|
|
1220
|
+
throw new PatchError(`${ws.show(at)} is where an earlier part of this patch moves a file`);
|
|
1221
|
+
}
|
|
1058
1222
|
seen.add(at);
|
|
1059
1223
|
if (op.kind === 'delete') {
|
|
1060
1224
|
const info = await statOrNull(at);
|
|
@@ -1065,6 +1229,7 @@ async function runPatch(ws, patch) {
|
|
|
1065
1229
|
throw new PatchError(`${ws.show(at)} is a directory`);
|
|
1066
1230
|
}
|
|
1067
1231
|
steps.push({ kind: 'delete', at });
|
|
1232
|
+
removed.add(at);
|
|
1068
1233
|
touched.push({ path: ws.show(at), action: 'deleted' });
|
|
1069
1234
|
continue;
|
|
1070
1235
|
}
|
|
@@ -1100,12 +1265,18 @@ async function runPatch(ws, patch) {
|
|
|
1100
1265
|
throw new PatchError(`${ws.show(at)} is a ${shape.format} file, not text`);
|
|
1101
1266
|
}
|
|
1102
1267
|
const body = await readFile(at, 'utf8');
|
|
1103
|
-
|
|
1268
|
+
// Matched against LF-only lines. A patch never carries a CR, so a CRLF
|
|
1269
|
+
// file would miss exactly, match on the trailing-whitespace pass, and be
|
|
1270
|
+
// spliced with bare LF — mixed endings behind a successful apply.
|
|
1271
|
+
const eol = isCrlf(body) ? '\r\n' : '\n';
|
|
1272
|
+
const flat = eol === '\n' ? body : body.split(eol).join('\n');
|
|
1273
|
+
const patched = patchLines(toLines(flat), op.chunks, ws.show(at));
|
|
1104
1274
|
fuzz += patched.fuzz;
|
|
1275
|
+
ignored += patched.ignored;
|
|
1105
1276
|
// Whether the file ended in a newline is a property of the file, not of
|
|
1106
1277
|
// the patch, so it survives the edit.
|
|
1107
|
-
const trailing =
|
|
1108
|
-
const content = patched.lines.length === 0 ? '' : patched.lines.join(
|
|
1278
|
+
const trailing = flat === '' || flat.endsWith('\n');
|
|
1279
|
+
const content = patched.lines.length === 0 ? '' : patched.lines.join(eol) + (trailing ? eol : '');
|
|
1109
1280
|
if (Buffer.byteLength(content) > MAX_WRITE) {
|
|
1110
1281
|
throw new PatchError(`${ws.show(at)} would exceed ${MAX_WRITE} bytes`);
|
|
1111
1282
|
}
|
|
@@ -1123,9 +1294,13 @@ async function runPatch(ws, patch) {
|
|
|
1123
1294
|
throw new PatchError('cannot move a file onto the workspace root');
|
|
1124
1295
|
}
|
|
1125
1296
|
if (to !== at) {
|
|
1126
|
-
if (
|
|
1297
|
+
if (targets.has(to) || (seen.has(to) && !removed.has(to))) {
|
|
1298
|
+
throw new PatchError(`${ws.show(to)} is already written by another part of this patch`);
|
|
1299
|
+
}
|
|
1300
|
+
if (!removed.has(to) && (await statOrNull(to))) {
|
|
1127
1301
|
throw new PatchError(`${ws.show(to)} already exists`);
|
|
1128
1302
|
}
|
|
1303
|
+
targets.add(to);
|
|
1129
1304
|
steps.push({ kind: 'move', from: at, to });
|
|
1130
1305
|
entry.action = 'moved';
|
|
1131
1306
|
entry.to = ws.show(to);
|
|
@@ -1155,6 +1330,9 @@ async function runPatch(ws, patch) {
|
|
|
1155
1330
|
// ignored landed where the tool thinks it should, not where the patch
|
|
1156
1331
|
// said, and that is a thing to check.
|
|
1157
1332
|
fuzzy: fuzz > 0 ? fuzz : undefined,
|
|
1333
|
+
// A heading that named nothing, applied anyway because the context was
|
|
1334
|
+
// unique. Says the heading was wrong without having refused the edit.
|
|
1335
|
+
ignoredHeadings: ignored > 0 ? ignored : undefined,
|
|
1158
1336
|
};
|
|
1159
1337
|
}
|
|
1160
1338
|
async function statOrNull(at) {
|
package/dist/trajectory.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MemoryQuery } from './memory.ts';
|
|
1
|
+
import type { MemoryEdge, MemoryQuery } from './memory/types.ts';
|
|
2
2
|
import type { Payload, PayloadResolver } from './payload.ts';
|
|
3
3
|
import type { PromptSource } from './prompt.ts';
|
|
4
4
|
import { type MediaKind, type Message, type TokenUsage } from './types.ts';
|
|
@@ -53,28 +53,30 @@ export interface LoadSkillsNode extends NodeBase {
|
|
|
53
53
|
}
|
|
54
54
|
export interface MemoryRecallNode extends NodeBase {
|
|
55
55
|
type: 'memory_recall';
|
|
56
|
-
store: string;
|
|
57
|
-
scope: string;
|
|
58
56
|
query: MemoryQuery;
|
|
57
|
+
/** the matches; the rest of `nodes` is the subgraph stitched around them */
|
|
58
|
+
seeds: string[];
|
|
59
59
|
/** ids, not bodies — the rendered block is the payload */
|
|
60
|
-
|
|
60
|
+
nodes: {
|
|
61
61
|
id: string;
|
|
62
|
+
kind: string;
|
|
62
63
|
score: number;
|
|
63
|
-
revision: number;
|
|
64
64
|
}[];
|
|
65
|
+
edges: MemoryEdge[];
|
|
65
66
|
content: Payload;
|
|
66
67
|
}
|
|
67
68
|
export interface MemoryOpNode extends NodeBase {
|
|
68
69
|
type: 'memory_op';
|
|
69
|
-
op: '
|
|
70
|
-
store: string;
|
|
71
|
-
scope: string;
|
|
70
|
+
op: 'commit' | 'forget';
|
|
72
71
|
/** sha256(runId, callId) — deterministic, so a replay is deduplicated */
|
|
73
72
|
opId: string;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
nodes: {
|
|
74
|
+
id: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
revision: number;
|
|
77
|
+
}[];
|
|
78
|
+
edges: MemoryEdge[];
|
|
79
|
+
files: number;
|
|
78
80
|
}
|
|
79
81
|
export interface LlmCallNode extends NodeBase {
|
|
80
82
|
type: 'llm_call';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Agent } from './agent.ts';
|
|
2
|
-
import type { MemoryOpSpec } from './memory.ts';
|
|
2
|
+
import type { MemoryOpSpec, MemoryRecallSpec } from './memory/types.ts';
|
|
3
3
|
import type { Services } from './services.ts';
|
|
4
4
|
import type { SkillLoadSpec } from './skills.ts';
|
|
5
5
|
import type { AgentState } from './state.ts';
|
|
@@ -126,7 +126,7 @@ export interface ToolOutcome {
|
|
|
126
126
|
}
|
|
127
127
|
export type ToolEffect = {
|
|
128
128
|
kind: 'memory_op';
|
|
129
|
-
spec: MemoryOpSpec;
|
|
129
|
+
spec: MemoryOpSpec | MemoryRecallSpec;
|
|
130
130
|
} | {
|
|
131
131
|
kind: 'skill_load';
|
|
132
132
|
spec: SkillLoadSpec;
|
|
@@ -184,9 +184,9 @@ export declare const HANDOFF_PREFIX = "transfer_to_";
|
|
|
184
184
|
export declare const FORK_TOOL = "fork";
|
|
185
185
|
export declare const FINAL_OUTPUT_TOOL = "final_output";
|
|
186
186
|
export declare const MEMORY_SEARCH_TOOL = "memory_search";
|
|
187
|
-
export declare const
|
|
188
|
-
export declare const
|
|
189
|
-
export declare const
|
|
187
|
+
export declare const MEMORY_LOAD_TOOL = "memory_load";
|
|
188
|
+
export declare const MEMORY_COMMIT_TOOL = "memory_commit";
|
|
189
|
+
export declare const MEMORY_FORGET_TOOL = "memory_forget";
|
|
190
190
|
export declare const SKILL_SEARCH_TOOL = "skill_search";
|
|
191
191
|
export declare const SKILL_LOAD_TOOL = "skill_load";
|
|
192
192
|
export declare function parseArgs(raw: string): Record<string, unknown>;
|
package/dist/types.js
CHANGED
|
@@ -129,9 +129,9 @@ export const HANDOFF_PREFIX = 'transfer_to_';
|
|
|
129
129
|
export const FORK_TOOL = 'fork';
|
|
130
130
|
export const FINAL_OUTPUT_TOOL = 'final_output';
|
|
131
131
|
export const MEMORY_SEARCH_TOOL = 'memory_search';
|
|
132
|
-
export const
|
|
133
|
-
export const
|
|
134
|
-
export const
|
|
132
|
+
export const MEMORY_LOAD_TOOL = 'memory_load';
|
|
133
|
+
export const MEMORY_COMMIT_TOOL = 'memory_commit';
|
|
134
|
+
export const MEMORY_FORGET_TOOL = 'memory_forget';
|
|
135
135
|
export const SKILL_SEARCH_TOOL = 'skill_search';
|
|
136
136
|
export const SKILL_LOAD_TOOL = 'skill_load';
|
|
137
137
|
export function parseArgs(raw) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/neo",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"description": "A multi-agent runtime for Node.js: agents, models, tools, skills, memory and an append-only trajectory.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -38,10 +38,6 @@
|
|
|
38
38
|
"types": "./dist/payload-stores/index.d.ts",
|
|
39
39
|
"default": "./dist/payload-stores/index.js"
|
|
40
40
|
},
|
|
41
|
-
"./memory-stores": {
|
|
42
|
-
"types": "./dist/memory-stores/index.d.ts",
|
|
43
|
-
"default": "./dist/memory-stores/index.js"
|
|
44
|
-
},
|
|
45
41
|
"./skill-providers": {
|
|
46
42
|
"types": "./dist/skill-providers/index.d.ts",
|
|
47
43
|
"default": "./dist/skill-providers/index.js"
|
|
@@ -66,6 +62,7 @@
|
|
|
66
62
|
"prepack": "tsc -b"
|
|
67
63
|
},
|
|
68
64
|
"dependencies": {
|
|
65
|
+
"graphology": "^0.26.0",
|
|
69
66
|
"yaml": "^2.9.0",
|
|
70
67
|
"zod": "^4.4.3"
|
|
71
68
|
},
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { MemoryStore } from '../memory.ts';
|
|
2
|
-
import { type FileMemoryStoreOptions } from './file.ts';
|
|
3
|
-
export interface MemoryMemoryStoreSpec {
|
|
4
|
-
kind: 'memory';
|
|
5
|
-
id?: string;
|
|
6
|
-
}
|
|
7
|
-
export interface FileMemoryStoreSpec extends FileMemoryStoreOptions {
|
|
8
|
-
kind: 'file';
|
|
9
|
-
}
|
|
10
|
-
/** Grows a member per backend (`pgvector`, `redis`, …). */
|
|
11
|
-
export type MemoryStoreSpec = MemoryMemoryStoreSpec | FileMemoryStoreSpec;
|
|
12
|
-
/** Shorthand: `mem`, `memory`, `file:./.data/memory`. */
|
|
13
|
-
export type MemoryStoreRef = MemoryStoreSpec | string;
|
|
14
|
-
export declare function createMemoryStore(ref: MemoryStoreRef): MemoryStore;
|
|
15
|
-
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { FileMemoryStore } from "./file.js";
|
|
2
|
-
import { InMemoryMemoryStore } from "./in-memory.js";
|
|
3
|
-
export function createMemoryStore(ref) {
|
|
4
|
-
const spec = typeof ref === 'string' ? parseRef(ref) : ref;
|
|
5
|
-
switch (spec.kind) {
|
|
6
|
-
case 'memory':
|
|
7
|
-
return new InMemoryMemoryStore(spec.id);
|
|
8
|
-
case 'file':
|
|
9
|
-
return new FileMemoryStore(spec);
|
|
10
|
-
default:
|
|
11
|
-
throw new TypeError(`unknown memory store kind: ${spec.kind}`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function parseRef(ref) {
|
|
15
|
-
if (ref === 'mem' || ref === 'memory') {
|
|
16
|
-
return { kind: 'memory' };
|
|
17
|
-
}
|
|
18
|
-
const colon = ref.indexOf(':');
|
|
19
|
-
const scheme = colon < 0 ? ref : ref.slice(0, colon);
|
|
20
|
-
const rest = colon < 0 ? '' : ref.slice(colon + 1);
|
|
21
|
-
if (scheme === 'file') {
|
|
22
|
-
if (!rest) {
|
|
23
|
-
throw new TypeError(`missing directory in "${ref}" (expected file:<dir>)`);
|
|
24
|
-
}
|
|
25
|
-
return { kind: 'file', dir: rest };
|
|
26
|
-
}
|
|
27
|
-
throw new TypeError(`unknown memory store ref: "${ref}"`);
|
|
28
|
-
}
|
|
29
|
-
//# sourceMappingURL=factory.js.map
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { type MemoryDraft, type MemoryHit, type MemoryPatch, type MemoryQuery, type MemoryRecord, type MemoryStore } from '../memory.ts';
|
|
2
|
-
export interface FileMemoryStoreOptions {
|
|
3
|
-
/** root directory; created on demand */
|
|
4
|
-
dir: string;
|
|
5
|
-
/** logical store id agents bind to; defaults to `file` */
|
|
6
|
-
id?: string;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* One JSON file per record, under one directory per scope:
|
|
10
|
-
*
|
|
11
|
-
* ```
|
|
12
|
-
* <dir>/<scope>/scope.json the unescaped scope name, for humans
|
|
13
|
-
* <dir>/<scope>/records/<id>.json
|
|
14
|
-
* <dir>/<scope>/ops/<opId>.json idempotency ledger
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* A file per record (rather than one appended log) keeps writes atomic and
|
|
18
|
-
* deletes cheap without a compaction step. Search reads the whole scope and
|
|
19
|
-
* ranks in process, which is exactly the trade this backend makes: no server,
|
|
20
|
-
* no index, fine up to a few thousand records per scope.
|
|
21
|
-
*
|
|
22
|
-
* There is no cross-process lock. Concurrent writers to the *same* record can
|
|
23
|
-
* lose an update; `expectedRevision` narrows that window, and a deployment
|
|
24
|
-
* that needs real isolation should use a database-backed store.
|
|
25
|
-
*/
|
|
26
|
-
export declare class FileMemoryStore implements MemoryStore {
|
|
27
|
-
#private;
|
|
28
|
-
readonly id: string;
|
|
29
|
-
constructor(opts: FileMemoryStoreOptions | string);
|
|
30
|
-
get dir(): string;
|
|
31
|
-
search(scope: string, q: MemoryQuery): Promise<MemoryHit[]>;
|
|
32
|
-
get(scope: string, id: string): Promise<MemoryRecord | undefined>;
|
|
33
|
-
write(scope: string, rec: MemoryDraft, opId: string): Promise<MemoryRecord>;
|
|
34
|
-
update(scope: string, id: string, patch: MemoryPatch, opId: string): Promise<MemoryRecord>;
|
|
35
|
-
delete(scope: string, id: string, opId: string): Promise<void>;
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=file.d.ts.map
|