@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.
Files changed (90) hide show
  1. package/README.md +3 -3
  2. package/dist/agent.d.ts +8 -7
  3. package/dist/agent.js +15 -8
  4. package/dist/architecture.d.ts +7 -5
  5. package/dist/architecture.js +20 -12
  6. package/dist/embedding.d.ts +20 -1
  7. package/dist/embedding.js +3 -2
  8. package/dist/embeddings/fanout.d.ts +51 -0
  9. package/dist/embeddings/fanout.js +76 -0
  10. package/dist/embeddings/gemini.d.ts +3 -10
  11. package/dist/embeddings/gemini.js +46 -30
  12. package/dist/embeddings/index.d.ts +3 -0
  13. package/dist/embeddings/index.js +3 -0
  14. package/dist/embeddings/limiter.d.ts +36 -0
  15. package/dist/embeddings/limiter.js +182 -0
  16. package/dist/embeddings/openai.d.ts +3 -2
  17. package/dist/embeddings/openai.js +54 -13
  18. package/dist/embeddings/openrouter.d.ts +2 -1
  19. package/dist/embeddings/openrouter.js +52 -23
  20. package/dist/embeddings/rate-limit.d.ts +16 -0
  21. package/dist/embeddings/rate-limit.js +131 -0
  22. package/dist/failure.d.ts +82 -0
  23. package/dist/failure.js +323 -0
  24. package/dist/index.d.ts +13 -2
  25. package/dist/index.js +13 -2
  26. package/dist/inspect/client.d.ts +1 -1
  27. package/dist/inspect/client.js +42 -26
  28. package/dist/kernel.d.ts +1 -1
  29. package/dist/kernel.js +40 -23
  30. package/dist/memory/files.d.ts +24 -0
  31. package/dist/memory/files.js +85 -0
  32. package/dist/memory/graph.d.ts +56 -0
  33. package/dist/memory/graph.js +159 -0
  34. package/dist/memory/index.d.ts +93 -0
  35. package/dist/memory/index.js +357 -0
  36. package/dist/memory/instructions.d.ts +15 -0
  37. package/dist/memory/instructions.js +103 -0
  38. package/dist/memory/page.d.ts +8 -0
  39. package/dist/memory/page.js +797 -0
  40. package/dist/memory/recall.d.ts +35 -0
  41. package/dist/memory/recall.js +161 -0
  42. package/dist/memory/render.d.ts +10 -0
  43. package/dist/memory/render.js +228 -0
  44. package/dist/memory/report.d.ts +57 -0
  45. package/dist/memory/report.js +113 -0
  46. package/dist/memory/store.d.ts +49 -0
  47. package/dist/memory/store.js +226 -0
  48. package/dist/memory/tools.d.ts +11 -0
  49. package/dist/memory/tools.js +310 -0
  50. package/dist/memory/types.d.ts +192 -0
  51. package/dist/memory/types.js +105 -0
  52. package/dist/memory/vectors.d.ts +29 -0
  53. package/dist/memory/vectors.js +152 -0
  54. package/dist/models/anthropic.d.ts +2 -1
  55. package/dist/models/anthropic.js +11 -4
  56. package/dist/models/factory.d.ts +15 -0
  57. package/dist/models/factory.js +49 -10
  58. package/dist/models/gemini.d.ts +2 -1
  59. package/dist/models/gemini.js +8 -3
  60. package/dist/models/openai-chat.d.ts +2 -1
  61. package/dist/models/openai-chat.js +10 -5
  62. package/dist/models/openai-responses.d.ts +2 -1
  63. package/dist/models/openai-responses.js +22 -7
  64. package/dist/models/openrouter.d.ts +2 -1
  65. package/dist/models/openrouter.js +14 -6
  66. package/dist/project/config.d.ts +55 -3
  67. package/dist/project/config.js +76 -1
  68. package/dist/project/index.d.ts +2 -2
  69. package/dist/project/index.js +1 -1
  70. package/dist/project/load.d.ts +16 -2
  71. package/dist/project/load.js +67 -1
  72. package/dist/runner.d.ts +4 -2
  73. package/dist/runner.js +29 -30
  74. package/dist/services.d.ts +23 -6
  75. package/dist/services.js +25 -15
  76. package/dist/tools/workspace.js +195 -17
  77. package/dist/trajectory.d.ts +14 -12
  78. package/dist/types.d.ts +5 -5
  79. package/dist/types.js +3 -3
  80. package/package.json +2 -5
  81. package/dist/memory-stores/factory.d.ts +0 -15
  82. package/dist/memory-stores/factory.js +0 -29
  83. package/dist/memory-stores/file.d.ts +0 -37
  84. package/dist/memory-stores/file.js +0 -174
  85. package/dist/memory-stores/in-memory.d.ts +0 -17
  86. package/dist/memory-stores/in-memory.js +0 -80
  87. package/dist/memory-stores/index.d.ts +0 -4
  88. package/dist/memory-stores/index.js +0 -5
  89. package/dist/memory.d.ts +0 -115
  90. package/dist/memory.js +0 -213
@@ -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
- if (heading) {
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
- for (let i = from; i < lines.length; i++) {
562
- if (lines[i] === heading || lines[i].trim() === heading.trim()) {
563
- return i;
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
- throw new PatchError(`${file}: no line matching '@@ ${heading}' after the previous chunk`);
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
- cursor = at + 1;
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
- const first = chunk.before[0] ?? '';
601
- throw new PatchError(`${file}: the context of the chunk at patch line ${chunk.at} is not in the file` +
602
- (first ? ` — looked for: ${first.trim()}` : ''));
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
- out.splice(found.index, chunk.before.length, ...chunk.after);
606
- cursor = found.index + chunk.after.length;
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
- truncated: cut || !whole || to < total || from > 1 ? true : undefined,
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
- const patched = patchLines(toLines(body), op.chunks, ws.show(at));
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 = body === '' || body.endsWith('\n');
1108
- const content = patched.lines.length === 0 ? '' : patched.lines.join('\n') + (trailing ? '\n' : '');
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 (await statOrNull(to)) {
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) {
@@ -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
- hits: {
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: 'write' | 'update' | 'delete';
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
- recordId: string;
75
- revision: number;
76
- before?: Payload;
77
- after?: Payload;
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 MEMORY_WRITE_TOOL = "memory_write";
188
- export declare const MEMORY_UPDATE_TOOL = "memory_update";
189
- export declare const MEMORY_DELETE_TOOL = "memory_delete";
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 MEMORY_WRITE_TOOL = 'memory_write';
133
- export const MEMORY_UPDATE_TOOL = 'memory_update';
134
- export const MEMORY_DELETE_TOOL = 'memory_delete';
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.10",
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