@zenera/neo 1.1.17 → 1.1.18

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 CHANGED
@@ -166,7 +166,7 @@ of surfacing three turns into a run as a confused model.
166
166
  [docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md) ·
167
167
  [docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
168
168
  · and
169
- [`examples/`](https://github.com/andreyryabov/ZeneraNeo/tree/main/examples) for
169
+ [`examples/sdk/`](https://github.com/andreyryabov/ZeneraNeo/tree/main/examples/sdk) for
170
170
  eight worked demos, from a single agent to a project loaded entirely from a
171
171
  folder.
172
172
 
@@ -51,7 +51,7 @@ export class MemoryStore {
51
51
  const b = opts.embedding;
52
52
  if (a.model !== b.model || a.dimensions !== b.dimensions) {
53
53
  throw new MemoryError(`this memory was embedded with ${a.model} (${a.dimensions}d), ` +
54
- `but ${b.model} (${b.dimensions}d) is configured`, 're-embed it with `zen memory reembed`, or restore the original model');
54
+ `but ${b.model} (${b.dimensions}d) is configured`, 'restore the original model, or start a new memory');
55
55
  }
56
56
  }
57
57
  const graph = manifest
@@ -79,7 +79,7 @@ export class MemoryStore {
79
79
  if (held) {
80
80
  if (held.model !== embedding.model || held.dimensions !== embedding.dimensions) {
81
81
  throw new MemoryError(`this memory was embedded with ${held.model} (${held.dimensions}d), ` +
82
- `but ${embedding.model} (${embedding.dimensions}d) is configured`, 're-embed it with `zen memory reembed`, or restore the original model');
82
+ `but ${embedding.model} (${embedding.dimensions}d) is configured`, 'restore the original model, or start a new memory');
83
83
  }
84
84
  return;
85
85
  }
@@ -56,6 +56,13 @@ export interface ProjectOptions<TCtx = unknown> {
56
56
  */
57
57
  registry?: ModelRegistry;
58
58
  memory?: MemoryIndex;
59
+ /**
60
+ * Another directory to keep the memory graph in, relative to the project
61
+ * root when it is not absolute. Whoever sets it must mount that directory
62
+ * as `/memory` too, or the agent reads its remembered files from one graph
63
+ * and writes to another.
64
+ */
65
+ memoryDir?: string;
59
66
  /** turns an agent-visible path into a host path, so files can be remembered */
60
67
  resolveFile?: (path: string) => string;
61
68
  payloads?: PayloadStore;
@@ -166,8 +173,13 @@ export declare function projectRegistry<TCtx>(config: ProjectConfig, opts?: Proj
166
173
  * with the front end, which has to mount the same directory into the sandbox:
167
174
  * two answers to this question would mean `/memory` pointing somewhere the
168
175
  * graph is not.
176
+ *
177
+ * `override` is the host pointing the run at another graph — `zen run
178
+ * --memory`. It answers for a project that declares no memory at all, because
179
+ * naming a directory is the declaration; the agents bound to it are still the
180
+ * ones that said `memory: true`.
169
181
  */
170
- export declare function memoryDir(root: string, config: ProjectConfig): string | undefined;
182
+ export declare function memoryDir(root: string, config: ProjectConfig, override?: string): string | undefined;
171
183
  /**
172
184
  * Where each skill directory appears in the agent's namespace.
173
185
  *
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { basename, join, relative, sep } from 'node:path';
2
+ import { basename, join, relative, resolve, sep } from 'node:path';
3
3
  import { Agent, AgentRegistry } from "../agent.js";
4
4
  import { RunStream } from "../events.js";
5
5
  import { MemoryIndex } from "../memory/index.js";
@@ -74,7 +74,7 @@ export async function loadProject(dir, opts = {}) {
74
74
  // The default is resolved here rather than on first use, so a broken
75
75
  // `embedding:` fails at load like a broken `model:` does.
76
76
  embedders(config.embedding, 'embedding');
77
- const memory = opts.memory ?? (await openMemory(root, config, embedders));
77
+ const memory = opts.memory ?? (await openMemory(root, config, embedders, opts.memoryDir));
78
78
  return new AgentProject({
79
79
  root,
80
80
  source,
@@ -392,8 +392,16 @@ function memoryFor(spec) {
392
392
  * with the front end, which has to mount the same directory into the sandbox:
393
393
  * two answers to this question would mean `/memory` pointing somewhere the
394
394
  * graph is not.
395
+ *
396
+ * `override` is the host pointing the run at another graph — `zen run
397
+ * --memory`. It answers for a project that declares no memory at all, because
398
+ * naming a directory is the declaration; the agents bound to it are still the
399
+ * ones that said `memory: true`.
395
400
  */
396
- export function memoryDir(root, config) {
401
+ export function memoryDir(root, config, override) {
402
+ if (override) {
403
+ return resolve(root, override);
404
+ }
397
405
  if (!config.memory && !config.agents.some((a) => a.memory)) {
398
406
  return undefined;
399
407
  }
@@ -405,8 +413,8 @@ export function memoryDir(root, config) {
405
413
  * to it still counts: it is how a project declares the graph `zen memory`
406
414
  * inspects between runs.
407
415
  */
408
- async function openMemory(root, config, embedders) {
409
- const dir = memoryDir(root, config);
416
+ async function openMemory(root, config, embedders, override) {
417
+ const dir = memoryDir(root, config, override);
410
418
  if (!dir) {
411
419
  return undefined;
412
420
  }
package/dist/prompt.d.ts CHANGED
@@ -58,6 +58,8 @@ export interface ComposedPrompt {
58
58
  /** the editable contributors, in the order they were rendered */
59
59
  sources: PromptSource[];
60
60
  }
61
+ /** `&`, `<` and `"` are the three that would end the attribute or the tag. */
62
+ export declare function attribute(value: string): string;
61
63
  /**
62
64
  * Renders the authored parts, then the kernel's derived text, into one string —
63
65
  * and reports which documents went into it.
package/dist/prompt.js CHANGED
@@ -22,7 +22,7 @@ export function promptFile(path, section, src) {
22
22
  }
23
23
  const SEPARATOR = '\n\n';
24
24
  /** `&`, `<` and `"` are the three that would end the attribute or the tag. */
25
- function attribute(value) {
25
+ export function attribute(value) {
26
26
  return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
27
27
  }
28
28
  /**
@@ -1,5 +1,5 @@
1
1
  import { readFile, readdir } from 'node:fs/promises';
2
- import { basename, join, resolve } from 'node:path';
2
+ import { basename, join, relative, resolve } from 'node:path';
3
3
  import { selectTools } from "../types.js";
4
4
  // ---------------------------------------------------------------------------
5
5
  // Filesystem-backed skill provider
@@ -83,6 +83,7 @@ export class FileSkillProvider {
83
83
  ...entry.summary,
84
84
  content: entry.content,
85
85
  file: entry.file,
86
+ src: entry.src,
86
87
  ...(tools.length ? { tools } : {}),
87
88
  ...(entry.at ? { path: entry.at } : {}),
88
89
  };
@@ -122,19 +123,20 @@ export class FileSkillProvider {
122
123
  // Only a folder skill has files of its own to point at, and
123
124
  // only when the host said where the directory can be reached.
124
125
  const at = folder && dir.at ? `${dir.at}/${basename(folder)}` : undefined;
125
- const entry = parse(raw, base, file, folder, at);
126
+ const entry = parse(raw, base, file, relative(dir.path, file), folder, at);
126
127
  index.set(entry.summary.name, entry);
127
128
  }
128
129
  }
129
130
  return index;
130
131
  }
131
132
  }
132
- function parse(raw, base, file, folder, at) {
133
+ function parse(raw, base, file, src, folder, at) {
133
134
  const { data, body } = frontmatter(raw);
134
135
  const tags = toList(data.tags);
135
136
  const tools = toList(data.tools);
136
137
  return {
137
138
  file,
139
+ src,
138
140
  folder,
139
141
  ...(at ? { at } : {}),
140
142
  content: body,
package/dist/skills.d.ts CHANGED
@@ -33,6 +33,13 @@ export interface Skill extends SkillSummary {
33
33
  path?: string;
34
34
  /** absolute path of the source file, if known (set by file-backed providers) */
35
35
  file?: string;
36
+ /**
37
+ * The source document *as the model is told it*, written onto the block as
38
+ * `src`. Deliberately not `file`: that one is absolute, so it would put a
39
+ * home directory in the prompt and give two machines two different cache
40
+ * prefixes for the same catalog.
41
+ */
42
+ src?: string;
36
43
  }
37
44
  export interface SkillProvider {
38
45
  readonly id: string;
package/dist/skills.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { hash } from "./payload.js";
2
+ import { attribute } from "./prompt.js";
2
3
  import { projected } from "./trajectory.js";
3
4
  import { SKILL_LOAD_TOOL, SKILL_SEARCH_TOOL, tool, withEffects, } from "./types.js";
4
5
  export function skillContentHash(s) {
@@ -7,15 +8,21 @@ export function skillContentHash(s) {
7
8
  export function renderSkills(skills) {
8
9
  return skills
9
10
  .map((s) => {
10
- const head = `## Skill: ${s.name}${s.version ? ` (${s.version})` : ''}`;
11
+ const attrs = [
12
+ ` name="${attribute(s.name)}"`,
13
+ s.version ? ` version="${attribute(s.version)}"` : '',
14
+ // Same contract as a prompt block's `src`: the document to edit,
15
+ // under a name that is the same on every machine.
16
+ s.src ? ` src="${attribute(s.src)}"` : '',
17
+ ].join('');
11
18
  // One line, and only when the files are reachable. Without it the
12
19
  // model has the instructions and no way to act on the half of them
13
20
  // that names a file.
14
21
  const where = s.path
15
- ? `\nThis skill's files are at ${s.path}, read-only. Paths written in it are ` +
16
- 'relative to that directory.\n'
22
+ ? `This skill's files are at ${s.path}, read-only. Paths written in it are ` +
23
+ 'relative to that directory.\n\n'
17
24
  : '';
18
- return `${head}${where}\n${s.content}`;
25
+ return `<skill${attrs}>\n${where}${s.content}\n</skill>`;
19
26
  })
20
27
  .join('\n\n');
21
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/neo",
3
- "version": "1.1.17",
3
+ "version": "1.1.18",
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",