@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
@@ -0,0 +1,85 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { basename, extname, join } from 'node:path';
4
+ import { FILES_DIR } from "./store.js";
5
+ import { MemoryError } from "./types.js";
6
+ // ---------------------------------------------------------------------------
7
+ // Remembered files
8
+ //
9
+ // An agent that writes a working script should be able to run it again next
10
+ // week, and a summary of a script is not a script. So the bytes are copied out
11
+ // of the workspace — which is disposable, and may be a container that is about
12
+ // to be discarded — into the memory directory, under the id of the node that
13
+ // describes them. The copy is the point: a reference to a workspace path would
14
+ // dangle the moment the run ends.
15
+ //
16
+ // The tree is mounted read-only at /memory. Nothing writes there through the
17
+ // file tools, so every file under it has a node that explains what it is, and
18
+ // the graph cannot acquire orphans that no recollection can ever surface.
19
+ // ---------------------------------------------------------------------------
20
+ /** Where remembered files appear in the agent's namespace. */
21
+ export const MEMORY_MOUNT = '/memory';
22
+ /**
23
+ * Big enough for any script or config worth re-running, small enough that the
24
+ * memory directory cannot become a place where build outputs go to hide.
25
+ */
26
+ export const MAX_FILE_BYTES = 2 * 1024 * 1024;
27
+ /** Extensions are for `run_command` and syntax hints, not a type system. */
28
+ const MAX_FORMAT_LEN = 16;
29
+ /**
30
+ * Copy `source` into the memory directory under `id` and describe it.
31
+ *
32
+ * The bytes are read once and both hashed and written from that one buffer, so
33
+ * the digest provably belongs to the copy that was kept — re-reading the source
34
+ * to hash it would leave a window for it to change in between.
35
+ */
36
+ export async function rememberFile(dir, source, id, opts = {}) {
37
+ const maxBytes = opts.maxBytes ?? MAX_FILE_BYTES;
38
+ const info = await describe(source);
39
+ if (!info.isFile()) {
40
+ throw new MemoryError(`${source} is not a regular file`, 'only files can be remembered; write a `fact` node describing a directory instead');
41
+ }
42
+ if (info.size > maxBytes) {
43
+ throw new MemoryError(`${source} is ${info.size} bytes, over the ${maxBytes} byte limit for a remembered file`, 'keep the artifact in the workspace and remember a `snippet` of the part that matters');
44
+ }
45
+ const bytes = await readFile(source);
46
+ if (bytes.byteLength > maxBytes) {
47
+ throw new MemoryError(`${source} grew past the ${maxBytes} byte limit while being read`);
48
+ }
49
+ const format = formatOf(source);
50
+ const name = id + (format ? `.${format}` : '');
51
+ await mkdir(join(dir, FILES_DIR), { recursive: true });
52
+ await writeFile(join(dir, FILES_DIR, name), bytes);
53
+ return {
54
+ path: `${MEMORY_MOUNT}/${name}`,
55
+ bytes: bytes.byteLength,
56
+ sha256: createHash('sha256').update(bytes).digest('hex'),
57
+ format,
58
+ };
59
+ }
60
+ /** The host path of a remembered file, for reading it back or deleting it. */
61
+ export function hostPath(dir, file) {
62
+ // Through `basename` so a stored path can never climb out of the tree.
63
+ return join(dir, FILES_DIR, basename(file.path));
64
+ }
65
+ /** Drop the bytes of a forgotten node. Missing is success — the goal is absence. */
66
+ export async function forgetFile(dir, file) {
67
+ await rm(hostPath(dir, file), { force: true });
68
+ }
69
+ async function describe(source) {
70
+ try {
71
+ return await stat(source);
72
+ }
73
+ catch {
74
+ throw new MemoryError(`${source} does not exist`, 'remember a file the run actually wrote, by the path the file tools use');
75
+ }
76
+ }
77
+ /**
78
+ * The extension drives how the file is run and rendered, so it is kept, but it
79
+ * becomes part of a filename this module chooses — hence the character class.
80
+ */
81
+ function formatOf(source) {
82
+ const ext = extname(source).slice(1).toLowerCase();
83
+ return /^[a-z0-9]+$/.test(ext) && ext.length <= MAX_FORMAT_LEN ? ext : '';
84
+ }
85
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,56 @@
1
+ import { MultiDirectedGraph } from 'graphology';
2
+ import { type MemoryEdge, type MemoryEdgeAttrs, type MemoryNode, type Relation } from './types.ts';
3
+ export type Graphology = MultiDirectedGraph<MemoryNode, MemoryEdgeAttrs>;
4
+ export interface NodeDraft {
5
+ id: string;
6
+ kind: string;
7
+ text: string;
8
+ audience: string[];
9
+ metadata?: Record<string, unknown>;
10
+ file?: MemoryNode['file'];
11
+ }
12
+ export interface NodePatch {
13
+ kind?: string;
14
+ text?: string;
15
+ audience?: string[];
16
+ metadata?: Record<string, unknown>;
17
+ file?: MemoryNode['file'];
18
+ /** when given, the update fails unless the stored revision matches */
19
+ expectedRevision?: number;
20
+ }
21
+ /** Deterministic, so the same link committed twice stays one edge. */
22
+ export declare const edgeKey: (source: string, target: string, relation: Relation) => string;
23
+ export declare class MemoryGraph {
24
+ #private;
25
+ static from(serialized: unknown): MemoryGraph;
26
+ export(): ReturnType<Graphology['export']>;
27
+ get order(): number;
28
+ get size(): number;
29
+ has(id: string): boolean;
30
+ get(id: string): MemoryNode | undefined;
31
+ add(draft: NodeDraft, at: string): MemoryNode;
32
+ update(id: string, patch: NodePatch, at: string): MemoryNode;
33
+ /** Reading is what counts as use — see `memory_load`, not `memory_search`. */
34
+ touch(id: string, at: string): MemoryNode | undefined;
35
+ link(source: string, target: string, relation: Relation, at: string): void;
36
+ /** Drops the node and every edge touching it; the file it names is the store's to unlink. */
37
+ forget(id: string): MemoryNode | undefined;
38
+ nodes(sees?: readonly string[]): MemoryNode[];
39
+ edges(sees?: readonly string[]): MemoryEdge[];
40
+ /** Both directions: a memory is as often reached from its artifact as from its request. */
41
+ neighbors(id: string, sees?: readonly string[]): MemoryEdge[];
42
+ /** Edges between nodes already chosen — what makes a materialized subgraph closed. */
43
+ between(ids: Iterable<string>, sees?: readonly string[]): MemoryEdge[];
44
+ /**
45
+ * The nodes some visible node replaces. Recall drops these, which is what
46
+ * makes a correction take effect without erasing what was corrected.
47
+ */
48
+ superseded(sees?: readonly string[]): Set<string>;
49
+ }
50
+ export declare class RevisionMismatch extends Error {
51
+ readonly id: string;
52
+ readonly expected: number;
53
+ readonly actual: number;
54
+ constructor(id: string, expected: number, actual: number);
55
+ }
56
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1,159 @@
1
+ import { MultiDirectedGraph } from 'graphology';
2
+ import { edgeVisible, visible, } from "./types.js";
3
+ /** Deterministic, so the same link committed twice stays one edge. */
4
+ export const edgeKey = (source, target, relation) => `${source}|${relation}|${target}`;
5
+ export class MemoryGraph {
6
+ #graph = new MultiDirectedGraph();
7
+ static from(serialized) {
8
+ const g = new MemoryGraph();
9
+ g.#graph.import(serialized);
10
+ return g;
11
+ }
12
+ export() {
13
+ return this.#graph.export();
14
+ }
15
+ get order() {
16
+ return this.#graph.order;
17
+ }
18
+ get size() {
19
+ return this.#graph.size;
20
+ }
21
+ has(id) {
22
+ return this.#graph.hasNode(id);
23
+ }
24
+ get(id) {
25
+ return this.#graph.hasNode(id) ? this.#graph.getNodeAttributes(id) : undefined;
26
+ }
27
+ add(draft, at) {
28
+ const node = {
29
+ id: draft.id,
30
+ kind: draft.kind,
31
+ text: draft.text,
32
+ audience: draft.audience.length ? [...draft.audience] : ['*'],
33
+ createdAt: at,
34
+ updatedAt: at,
35
+ lastUsedAt: at,
36
+ useCount: 0,
37
+ revision: 1,
38
+ ...(draft.metadata ? { metadata: draft.metadata } : {}),
39
+ ...(draft.file ? { file: draft.file } : {}),
40
+ };
41
+ this.#graph.addNode(node.id, node);
42
+ return node;
43
+ }
44
+ update(id, patch, at) {
45
+ const current = this.#graph.getNodeAttributes(id);
46
+ if (patch.expectedRevision !== undefined && patch.expectedRevision !== current.revision) {
47
+ throw new RevisionMismatch(id, patch.expectedRevision, current.revision);
48
+ }
49
+ const next = {
50
+ ...current,
51
+ ...(patch.kind !== undefined ? { kind: patch.kind } : {}),
52
+ ...(patch.text !== undefined ? { text: patch.text } : {}),
53
+ ...(patch.audience !== undefined ? { audience: [...patch.audience] } : {}),
54
+ ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}),
55
+ ...(patch.file !== undefined ? { file: patch.file } : {}),
56
+ updatedAt: at,
57
+ revision: current.revision + 1,
58
+ };
59
+ this.#graph.replaceNodeAttributes(id, next);
60
+ return next;
61
+ }
62
+ /** Reading is what counts as use — see `memory_load`, not `memory_search`. */
63
+ touch(id, at) {
64
+ if (!this.#graph.hasNode(id)) {
65
+ return undefined;
66
+ }
67
+ const node = this.#graph.getNodeAttributes(id);
68
+ const next = { ...node, lastUsedAt: at, useCount: node.useCount + 1 };
69
+ this.#graph.replaceNodeAttributes(id, next);
70
+ return next;
71
+ }
72
+ link(source, target, relation, at) {
73
+ this.#graph.mergeEdgeWithKey(edgeKey(source, target, relation), source, target, {
74
+ relation,
75
+ createdAt: at,
76
+ });
77
+ }
78
+ /** Drops the node and every edge touching it; the file it names is the store's to unlink. */
79
+ forget(id) {
80
+ const node = this.get(id);
81
+ if (node) {
82
+ this.#graph.dropNode(id);
83
+ }
84
+ return node;
85
+ }
86
+ nodes(sees) {
87
+ const out = [];
88
+ this.#graph.forEachNode((_id, attrs) => {
89
+ if (!sees || visible(attrs, sees)) {
90
+ out.push(attrs);
91
+ }
92
+ });
93
+ return out;
94
+ }
95
+ edges(sees) {
96
+ const out = [];
97
+ this.#graph.forEachEdge((_key, attrs, source, target) => {
98
+ if (sees && !this.#passable(source, target, attrs, sees)) {
99
+ return;
100
+ }
101
+ out.push({ source, target, relation: attrs.relation });
102
+ });
103
+ return out;
104
+ }
105
+ /** Both directions: a memory is as often reached from its artifact as from its request. */
106
+ neighbors(id, sees) {
107
+ if (!this.#graph.hasNode(id)) {
108
+ return [];
109
+ }
110
+ const out = [];
111
+ this.#graph.forEachEdge(id, (_key, attrs, source, target) => {
112
+ if (sees && !this.#passable(source, target, attrs, sees)) {
113
+ return;
114
+ }
115
+ out.push({ source, target, relation: attrs.relation });
116
+ });
117
+ return out;
118
+ }
119
+ /** Edges between nodes already chosen — what makes a materialized subgraph closed. */
120
+ between(ids, sees) {
121
+ const set = new Set(ids);
122
+ return this.edges(sees).filter((e) => set.has(e.source) && set.has(e.target));
123
+ }
124
+ /**
125
+ * The nodes some visible node replaces. Recall drops these, which is what
126
+ * makes a correction take effect without erasing what was corrected.
127
+ */
128
+ superseded(sees) {
129
+ const out = new Set();
130
+ this.#graph.forEachEdge((_key, attrs, source, target) => {
131
+ if (attrs.relation !== 'SUPERSEDES') {
132
+ return;
133
+ }
134
+ if (sees && !this.#passable(source, target, attrs, sees)) {
135
+ return;
136
+ }
137
+ out.add(target);
138
+ });
139
+ return out;
140
+ }
141
+ #passable(source, target, attrs, sees) {
142
+ return (edgeVisible(attrs, sees) &&
143
+ visible(this.#graph.getNodeAttributes(source), sees) &&
144
+ visible(this.#graph.getNodeAttributes(target), sees));
145
+ }
146
+ }
147
+ export class RevisionMismatch extends Error {
148
+ id;
149
+ expected;
150
+ actual;
151
+ constructor(id, expected, actual) {
152
+ super(`memory ${id} has revision ${actual}, not ${expected}`);
153
+ this.name = 'RevisionMismatch';
154
+ this.id = id;
155
+ this.expected = expected;
156
+ this.actual = actual;
157
+ }
158
+ }
159
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1,93 @@
1
+ import type { Embedder } from '../embedding.ts';
2
+ import type { IdClock } from '../ids.ts';
3
+ import type { MemoryGraph } from './graph.ts';
4
+ import type { MemoryStore } from './store.ts';
5
+ import { type MemoryNode, type MemoryQuery, type Recollection } from './types.ts';
6
+ /** Past this a `load` returns the path and lets the agent open it itself. */
7
+ export declare const INLINE_FILE_BYTES: number;
8
+ /**
9
+ * Cosine at or above this is one memory written twice, not two memories. Set
10
+ * high on purpose: merging two things that only resembled each other loses a
11
+ * memory outright, while missing a merge only wastes a node.
12
+ */
13
+ export declare const DUPLICATE_SCORE = 0.97;
14
+ export interface MemoryIndexOptions {
15
+ store: MemoryStore;
16
+ /** absent ranks by term overlap and stores no vectors */
17
+ embedder?: Embedder;
18
+ /** the project's vocabulary, when it extends the built-in one */
19
+ kinds?: readonly string[];
20
+ relations?: readonly string[];
21
+ }
22
+ export interface CommitNode {
23
+ /** names this node within the transaction; for a node being created */
24
+ ref?: string;
25
+ /** an existing node to update; mutually exclusive with `ref` */
26
+ id?: string;
27
+ kind?: string;
28
+ text?: string;
29
+ audience?: string[];
30
+ metadata?: Record<string, unknown>;
31
+ /** absolute host path of a file to copy in, already resolved by the caller */
32
+ file?: {
33
+ source: string;
34
+ };
35
+ expectedRevision?: number;
36
+ }
37
+ export interface CommitEdge {
38
+ from: string;
39
+ to: string;
40
+ relation: string;
41
+ }
42
+ export interface MemoryTransaction {
43
+ nodes?: CommitNode[];
44
+ edges?: CommitEdge[];
45
+ }
46
+ export interface CommitOptions {
47
+ /** audience labels this agent may assign */
48
+ writes: readonly string[];
49
+ /** labels it may read; a duplicate is only a duplicate if it can see it */
50
+ sees: readonly string[];
51
+ clock: IdClock;
52
+ }
53
+ export interface CommitResult {
54
+ /** local ref (or id, for an update) to the id it resolved to */
55
+ ids: Record<string, string>;
56
+ created: number;
57
+ updated: number;
58
+ edges: number;
59
+ files: number;
60
+ /** new nodes that turned out to already exist, and were folded into them */
61
+ merged: number;
62
+ }
63
+ export interface LoadedNode {
64
+ node: MemoryNode;
65
+ /** the file's bytes, when it was small enough to inline */
66
+ content?: string;
67
+ }
68
+ export declare class MemoryIndex {
69
+ #private;
70
+ readonly store: MemoryStore;
71
+ constructor(opts: MemoryIndexOptions);
72
+ get graph(): MemoryGraph;
73
+ get kinds(): readonly string[];
74
+ get relations(): readonly string[];
75
+ /** Read-only: a search returns nodes the model has not read, so nothing is touched. */
76
+ search(query: MemoryQuery, sees: readonly string[]): Promise<Recollection>;
77
+ /**
78
+ * Listed rather than ranked, and deliberately not routed through `recall`:
79
+ * ranking decays by `lastUsedAt`, only `memory_load` bumps that, and a
80
+ * preference is injected rather than loaded — so it would age out of its
81
+ * own list. Ordered so the rendered block is byte-stable between runs,
82
+ * because an unstable one would invalidate the prompt prefix for nothing.
83
+ */
84
+ preferences(sees: readonly string[]): MemoryNode[];
85
+ /**
86
+ * The only path that counts as use — recency decay is meant to track what
87
+ * an agent actually read, and a search hands back context it never opened.
88
+ */
89
+ load(ids: readonly string[], sees: readonly string[], clock: IdClock): Promise<LoadedNode[]>;
90
+ commit(tx: MemoryTransaction, opts: CommitOptions): Promise<CommitResult>;
91
+ forget(ids: readonly string[], sees: readonly string[]): Promise<MemoryNode[]>;
92
+ }
93
+ //# sourceMappingURL=index.d.ts.map