@zenera/cli 1.1.24 → 1.1.25
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 +53 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/commands/inspect.d.ts +47 -1
- package/dist/commands/inspect.js +167 -18
- package/dist/commands/memory.js +128 -29
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +96 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +76 -14
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +1 -0
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +15 -1
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +338 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +125 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +41 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.js +53 -2
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +31 -102
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +47 -9
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1449 -658
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +60 -25
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +18 -14
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +7 -3
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/memory-instructions.md +31 -32
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +2 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
package/dist/projects.d.ts
CHANGED
|
@@ -87,10 +87,5 @@ export declare function lastUsedAt(projectDir: string): string | undefined;
|
|
|
87
87
|
export declare function dirSize(dir: string): number;
|
|
88
88
|
/** True when a session's lock names a process that is still alive. */
|
|
89
89
|
export declare function isBusy(sessionDir: string): boolean;
|
|
90
|
-
/**
|
|
91
|
-
* `kill(pid, 0)` sends no signal and only asks whether the process exists.
|
|
92
|
-
* EPERM means it exists and belongs to someone else, which still counts.
|
|
93
|
-
*/
|
|
94
|
-
export declare function alive(pid: number): boolean;
|
|
95
90
|
export declare function projectName(dir: string): string;
|
|
96
91
|
//# sourceMappingURL=projects.d.ts.map
|
package/dist/projects.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ASSETS_MOUNT, assetsDir, FILES_DIR, MEMORY_MOUNT, memoryDir, skillDirs, skillMounts, SKILLS_MOUNT, } from '@zenera/neo';
|
|
1
|
+
import { ASSETS_MOUNT, assetsDir, FILES_DIR, MEMORY_MOUNT, memoryDir, pidAlive, skillDirs, skillMounts, SKILLS_MOUNT, } from '@zenera/neo';
|
|
2
2
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { paths, readJson, writeJson } from "./home.js";
|
|
@@ -290,26 +290,13 @@ export function isBusy(sessionDir) {
|
|
|
290
290
|
const path = join(sessionDir, '.lock');
|
|
291
291
|
try {
|
|
292
292
|
const { pid } = JSON.parse(readFileSync(path, 'utf8'));
|
|
293
|
-
return typeof pid === 'number' &&
|
|
293
|
+
return typeof pid === 'number' && pidAlive(pid);
|
|
294
294
|
}
|
|
295
295
|
catch {
|
|
296
296
|
// Absent, unreadable or malformed: nothing is holding it.
|
|
297
297
|
return false;
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
|
-
/**
|
|
301
|
-
* `kill(pid, 0)` sends no signal and only asks whether the process exists.
|
|
302
|
-
* EPERM means it exists and belongs to someone else, which still counts.
|
|
303
|
-
*/
|
|
304
|
-
export function alive(pid) {
|
|
305
|
-
try {
|
|
306
|
-
process.kill(pid, 0);
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
catch (err) {
|
|
310
|
-
return err.code === 'EPERM';
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
300
|
export function projectName(dir) {
|
|
314
301
|
return basename(resolve(dir));
|
|
315
302
|
}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface SandboxInputs {
|
|
|
39
39
|
* promise it does not keep.
|
|
40
40
|
*/
|
|
41
41
|
env?: readonly string[];
|
|
42
|
+
/** `false` overrides every `persist:` in the config, the agents' included */
|
|
43
|
+
persist?: boolean;
|
|
42
44
|
}
|
|
43
45
|
export declare function buildSandbox(opts: SandboxInputs): SandboxSetup;
|
|
44
46
|
/**
|
package/dist/sandbox.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
4
|
import { resolveBuild } from "./image.js";
|
|
5
5
|
import { credentials } from "./keys.js";
|
|
6
|
-
import { ensurePodmanReady } from "./podman.js";
|
|
6
|
+
import { assertLocks, ensurePodmanReady } from "./podman.js";
|
|
7
7
|
import { warn } from "./term.js";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// The sandbox, as this CLI wires it
|
|
@@ -71,6 +71,7 @@ export function buildSandbox(opts) {
|
|
|
71
71
|
...(opts.config.sandbox ?? {}),
|
|
72
72
|
...((opts.image ?? build?.tag) ? { image: opts.image ?? build?.tag } : {}),
|
|
73
73
|
...(opts.keys === false ? { keys: false } : {}),
|
|
74
|
+
...(opts.persist === false ? { persist: false } : {}),
|
|
74
75
|
};
|
|
75
76
|
const home = join(opts.session.data, 'sandbox', 'home');
|
|
76
77
|
const mounts = [{ host: home, at: HOME }, ...(opts.mounts ?? [])];
|
|
@@ -81,18 +82,22 @@ export function buildSandbox(opts) {
|
|
|
81
82
|
// Skills and assets are mounted read-only, and a python script run from a
|
|
82
83
|
// read-only directory fails on writing its own `__pycache__` — a confusing
|
|
83
84
|
// error about a file nobody asked for.
|
|
84
|
-
|
|
85
|
+
// `ZENERA_AGENT` tells a `zen` run by `run_command` that no person is there.
|
|
86
|
+
const extra = { HOME, PYTHONDONTWRITEBYTECODE: '1', ZENERA_AGENT: '1', ...(keys?.env ?? {}) };
|
|
85
87
|
const spec = toSpec(base, extra, secretsOf(keys?.secrets, opts.env));
|
|
86
88
|
const agents = {};
|
|
87
89
|
for (const agent of opts.config.agents) {
|
|
88
90
|
if (agent.sandbox) {
|
|
89
91
|
const merged = merge(base, agent.sandbox);
|
|
92
|
+
if (opts.persist === false) {
|
|
93
|
+
merged.persist = false;
|
|
94
|
+
}
|
|
90
95
|
// An agent that opts out gets neither the variables nor the mount,
|
|
91
96
|
// but the mount is on the pool and cannot be taken back per agent —
|
|
92
97
|
// so the variable is what actually decides, and without it the file
|
|
93
98
|
// sitting there is not a credential anything will look for.
|
|
94
99
|
const own = merged.keys === false ? undefined : keys;
|
|
95
|
-
agents[agent.name] = toSpec(merged, { HOME, PYTHONDONTWRITEBYTECODE: '1', ...(own?.env ?? {}) }, own && secretsOf(own.secrets, opts.env));
|
|
100
|
+
agents[agent.name] = toSpec(merged, { HOME, PYTHONDONTWRITEBYTECODE: '1', ZENERA_AGENT: '1', ...(own?.env ?? {}) }, own && secretsOf(own.secrets, opts.env));
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
const pool = new SandboxPool({
|
|
@@ -201,6 +206,8 @@ export async function preflight(setup, yes) {
|
|
|
201
206
|
memory: setup.spec.memory,
|
|
202
207
|
yes,
|
|
203
208
|
});
|
|
209
|
+
// A resumed session restarts its own stopped container, which needs no lock.
|
|
210
|
+
await assertLocks(1, { reuses: setup.pool.for().name });
|
|
204
211
|
READY.add(setup.image);
|
|
205
212
|
}
|
|
206
213
|
/** Best-effort teardown: losing a container must never lose a run. */
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare const TEMPLATES: string;
|
|
2
|
+
/** How to use the memory graph: a house rules file, landing under `agents/`. */
|
|
3
|
+
export declare const MEMORY_RULES: string;
|
|
1
4
|
/**
|
|
2
5
|
* The project files that are ours rather than the project's.
|
|
3
6
|
*
|
|
@@ -19,14 +22,19 @@ export declare const SHARED_RULES: readonly string[];
|
|
|
19
22
|
* scaffold wrote, rather than a second definition of "this version".
|
|
20
23
|
*/
|
|
21
24
|
export declare function ownVersion(): string;
|
|
25
|
+
type Vars = Record<string, string>;
|
|
26
|
+
export interface CopyOptions {
|
|
27
|
+
/** values for the `{{...}}` in any template under this tree */
|
|
28
|
+
vars?: Vars;
|
|
29
|
+
/** leave a file that is already there alone rather than replacing it */
|
|
30
|
+
keep?: boolean;
|
|
31
|
+
}
|
|
22
32
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* current answer is the only one worth having and a stale one is worse than
|
|
27
|
-
* none. Returns the relative paths written.
|
|
33
|
+
* Copies one template directory into `dir` at `rel`, depth first, sorted so the
|
|
34
|
+
* list it returns is the same on every machine. Only a `.tmpl` is read as text;
|
|
35
|
+
* everything else is copied byte for byte.
|
|
28
36
|
*/
|
|
29
|
-
export declare function
|
|
37
|
+
export declare function copyTree(from: string, dir: string, rel: string, opts: CopyOptions): string[];
|
|
30
38
|
/**
|
|
31
39
|
* Replaces every file in a project that nobody is meant to be maintaining:
|
|
32
40
|
* `SHARED_RULES` in the project tree, and the editor tree whole. Returns the
|
|
@@ -83,4 +91,5 @@ export interface Scaffolded {
|
|
|
83
91
|
* the first set under twice as many lines about the second.
|
|
84
92
|
*/
|
|
85
93
|
export declare function scaffold(opts: ScaffoldOptions): Scaffolded;
|
|
94
|
+
export {};
|
|
86
95
|
//# sourceMappingURL=scaffold.d.ts.map
|
package/dist/scaffold.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { dirname, join, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { editorFiles } from "./meta/editor.js";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// Scaffolding
|
|
6
7
|
//
|
|
@@ -30,13 +31,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
30
31
|
// standing next to the files that implement it, which is the shape every
|
|
31
32
|
// change to the project is then made in.
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
|
-
const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
|
|
34
|
+
export const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
|
|
34
35
|
/** The suffix on a file with `{{...}}` in it, dropped when the file lands. */
|
|
35
36
|
const TEMPLATE = '.tmpl';
|
|
36
37
|
/** How to use file tools: a house rules file, landing under `agents/`. */
|
|
37
38
|
const FILE_RULES = join('agents', 'files-instructions.md');
|
|
38
39
|
/** How to use the memory graph: a house rules file, landing under `agents/`. */
|
|
39
|
-
const MEMORY_RULES = join('agents', 'memory-instructions.md');
|
|
40
|
+
export const MEMORY_RULES = join('agents', 'memory-instructions.md');
|
|
40
41
|
/** How tools are called: the same, and equally not the project's to maintain. */
|
|
41
42
|
const TOOL_RULES = join('agents', 'tools-instructions.md');
|
|
42
43
|
/** How forking behaves: the same again, reaching only agents that declare `fork:`. */
|
|
@@ -54,8 +55,6 @@ const FORK_RULES = join('agents', 'fork-instructions.md');
|
|
|
54
55
|
* own beside them.
|
|
55
56
|
*/
|
|
56
57
|
export const SHARED_RULES = [FILE_RULES, FORK_RULES, MEMORY_RULES, TOOL_RULES];
|
|
57
|
-
/** The same bytes, kept in the `zen-memory` skill to restore or diff against. */
|
|
58
|
-
const MEMORY_REFERENCE = join('.github', 'skills', 'zen-memory', 'references', 'memory-instructions.md');
|
|
59
58
|
/**
|
|
60
59
|
* This `zen`'s own version, which the scaffold pins the sandbox's tools to.
|
|
61
60
|
* The publishable packages move in lockstep, so one number covers them all.
|
|
@@ -149,7 +148,7 @@ function target(name) {
|
|
|
149
148
|
* list it returns is the same on every machine. Only a `.tmpl` is read as text;
|
|
150
149
|
* everything else is copied byte for byte.
|
|
151
150
|
*/
|
|
152
|
-
function copyTree(from, dir, rel, opts) {
|
|
151
|
+
export function copyTree(from, dir, rel, opts) {
|
|
153
152
|
const written = [];
|
|
154
153
|
mkdirSync(join(dir, rel), { recursive: true });
|
|
155
154
|
const entries = readdirSync(from, { withFileTypes: true });
|
|
@@ -169,67 +168,13 @@ function copyTree(from, dir, rel, opts) {
|
|
|
169
168
|
: readFileSync(source);
|
|
170
169
|
writeFileSync(join(dir, child), body);
|
|
171
170
|
// The bytes are copied, not the file, so a script arrives unrunnable.
|
|
172
|
-
if (child.endsWith('.sh')) {
|
|
171
|
+
if (child.endsWith('.sh') || child.endsWith('.mjs')) {
|
|
173
172
|
chmodSync(join(dir, child), 0o755);
|
|
174
173
|
}
|
|
175
174
|
written.push(child);
|
|
176
175
|
}
|
|
177
176
|
return written;
|
|
178
177
|
}
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// Telling the editor which instructions are not for it
|
|
181
|
-
//
|
|
182
|
-
// The project's house rules are `agents/instructions.md`, deliberately not
|
|
183
|
-
// `AGENTS.md`: every coding assistant now reads that name out of an open
|
|
184
|
-
// folder and feeds it to itself as always-on instructions, and `zen open`
|
|
185
|
-
// opens exactly this directory. A name nobody else claims means the two are
|
|
186
|
-
// never confused, and `chat.useAgentsMdFile` no longer has to be switched off
|
|
187
|
-
// to keep them apart.
|
|
188
|
-
//
|
|
189
|
-
// They sit under `agents/` because there is rarely only one of them. Anything
|
|
190
|
-
// named `agents/<topic>-instructions.md` is read too, in filename order, so a
|
|
191
|
-
// subject that is true for every agent but is about one capability — memory,
|
|
192
|
-
// say — gets its own document instead of another section in a file that keeps
|
|
193
|
-
// growing.
|
|
194
|
-
//
|
|
195
|
-
// `chat.useNestedAgentsMdFiles` is still written. It is already false by
|
|
196
|
-
// default, but it is opt-in globally, and this is a directory the agent itself
|
|
197
|
-
// writes into — someone who turned it on would otherwise have the editor pick
|
|
198
|
-
// up whatever `AGENTS.md` a run happened to leave behind. It is a *restricted*
|
|
199
|
-
// setting, so it applies only in a trusted workspace; that is the right way
|
|
200
|
-
// round, since an untrusted folder is not one to run agents in either.
|
|
201
|
-
//
|
|
202
|
-
// `agents/instructions.md` addresses the *project's* agents. The editor's
|
|
203
|
-
// assistant still needs a brief of its own, and what it needs to know is how
|
|
204
|
-
// this kind of project is put together — the file formats, how a prompt is
|
|
205
|
-
// written, when to add a skill rather than an agent. That is what the
|
|
206
|
-
// `.github/` tree is: the standing brief, plus the prompt files and skills the
|
|
207
|
-
// editor picks up from the same place.
|
|
208
|
-
// ---------------------------------------------------------------------------
|
|
209
|
-
/**
|
|
210
|
-
* Writes the editor's files under `dir`, replacing what is there. They are
|
|
211
|
-
* ours: they say how the editor is to treat a directory the agents write into,
|
|
212
|
-
* and they describe the file formats of the version of `zen` in hand, so the
|
|
213
|
-
* current answer is the only one worth having and a stale one is worse than
|
|
214
|
-
* none. Returns the relative paths written.
|
|
215
|
-
*/
|
|
216
|
-
export function editorFiles(dir) {
|
|
217
|
-
const written = copyTree(join(TEMPLATES, 'editor'), dir, '', {});
|
|
218
|
-
// The one file that belongs to both trees. It is a project file first —
|
|
219
|
-
// house rules reaching every agent that can see the graph — and it is
|
|
220
|
-
// written once and edited from then on, like every other project file. But
|
|
221
|
-
// a project whose copy drifted, or that deleted it and turned memory back
|
|
222
|
-
// on later, needs somewhere to get the current text from, and the editor
|
|
223
|
-
// tree is replaced on every `init` and `open`. Copying the same bytes into
|
|
224
|
-
// the skill's references is what makes `diff` between the two mean
|
|
225
|
-
// something, and what stops a second copy of these rules existing in the
|
|
226
|
-
// repository to fall out of step with the first.
|
|
227
|
-
const reference = join(dir, MEMORY_REFERENCE);
|
|
228
|
-
mkdirSync(dirname(reference), { recursive: true });
|
|
229
|
-
copyFileSync(join(TEMPLATES, 'project', MEMORY_RULES), reference);
|
|
230
|
-
written.push(MEMORY_REFERENCE);
|
|
231
|
-
return written;
|
|
232
|
-
}
|
|
233
178
|
/**
|
|
234
179
|
* Replaces every file in a project that nobody is meant to be maintaining:
|
|
235
180
|
* `SHARED_RULES` in the project tree, and the editor tree whole. Returns the
|
package/dist/session.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { claimLock, ownLock } from '@zenera/neo';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { readJson, writeJson } from "./home.js";
|
|
5
5
|
import { isStamp, stamp, stampInstant } from "./ids.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isBusy, runIds, sessionIds, sessionsDir } from "./projects.js";
|
|
7
7
|
import { CliError, EXIT, invalidError, usageError } from "./term.js";
|
|
8
8
|
export function sessionPaths(projectDir, id) {
|
|
9
9
|
if (!isStamp(id)) {
|
|
@@ -85,24 +85,7 @@ export function requireSession(projectDir, id) {
|
|
|
85
85
|
return p;
|
|
86
86
|
}
|
|
87
87
|
export function acquire(p) {
|
|
88
|
-
|
|
89
|
-
const body = `${JSON.stringify(lock, null, 2)}\n`;
|
|
90
|
-
try {
|
|
91
|
-
// 'wx' fails when the file exists — the create and the check are one
|
|
92
|
-
// operation, so two `zen run`s racing cannot both win.
|
|
93
|
-
writeFileSync(p.lock, body, { flag: 'wx' });
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
if (err.code !== 'EEXIST') {
|
|
97
|
-
throw err;
|
|
98
|
-
}
|
|
99
|
-
const held = current(p);
|
|
100
|
-
if (held && alive(held.pid) && held.host === hostname()) {
|
|
101
|
-
throw new CliError(`session ${p.id} is already running (pid ${held.pid})`, EXIT.failed, 'wait for it, or start another with --new');
|
|
102
|
-
}
|
|
103
|
-
// Stale, or from another machine's run that cannot be verified here.
|
|
104
|
-
writeFileSync(p.lock, body);
|
|
105
|
-
}
|
|
88
|
+
claimLock(p.lock, ownLock(), (held) => new CliError(`session ${p.id} is already running (pid ${held.pid})`, EXIT.failed, 'wait for it, or start another with --new'), 2);
|
|
106
89
|
let released = false;
|
|
107
90
|
const release = () => {
|
|
108
91
|
if (released) {
|
|
@@ -113,14 +96,6 @@ export function acquire(p) {
|
|
|
113
96
|
};
|
|
114
97
|
return { release };
|
|
115
98
|
}
|
|
116
|
-
function current(p) {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(readFileSync(p.lock, 'utf8'));
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
99
|
export function runPaths(session, id) {
|
|
125
100
|
if (!isStamp(id)) {
|
|
126
101
|
throw usageError(`"${id}" is not a run id`);
|
package/dist/term.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export declare function warn(message: string): void;
|
|
|
73
73
|
/** What this run calls itself, in the process table and on the terminal's tab. */
|
|
74
74
|
export declare function title(text: string): void;
|
|
75
75
|
export declare function fail(message: string, hint?: string): void;
|
|
76
|
+
/** An agent's terminal can be a real pty (VS Code Copilot's is), so TTY checks alone miss it. */
|
|
77
|
+
export declare function drivenByAgent(): boolean;
|
|
76
78
|
export declare function isInteractive(): boolean;
|
|
77
79
|
export declare function deferBanner(print: () => void): void;
|
|
78
80
|
export declare function ask(question: string, fallback?: string): Promise<string>;
|
package/dist/term.js
CHANGED
|
@@ -230,8 +230,16 @@ export function fail(message, hint) {
|
|
|
230
230
|
// a stdin that will never answer. A CLI that hangs in CI is worse than one that
|
|
231
231
|
// fails in CI, because only one of the two tells you which flag you forgot.
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
233
|
+
/** An agent's terminal can be a real pty (VS Code Copilot's is), so TTY checks alone miss it. */
|
|
234
|
+
export function drivenByAgent() {
|
|
235
|
+
const own = process.env['ZENERA_AGENT'];
|
|
236
|
+
if (own !== undefined) {
|
|
237
|
+
return own !== '' && own !== '0';
|
|
238
|
+
}
|
|
239
|
+
return Boolean(process.env['AI_AGENT'] || process.env['COPILOT_AGENT']);
|
|
240
|
+
}
|
|
233
241
|
export function isInteractive() {
|
|
234
|
-
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
242
|
+
return Boolean(process.stdin.isTTY && process.stderr.isTTY) && !drivenByAgent();
|
|
235
243
|
}
|
|
236
244
|
// A banner is for whoever is sitting there, and the only proof that anyone is
|
|
237
245
|
// sitting there is that we are about to ask them something. Commands whose
|
|
@@ -244,7 +252,8 @@ export function deferBanner(print) {
|
|
|
244
252
|
}
|
|
245
253
|
function requireTty(what, flag) {
|
|
246
254
|
if (!isInteractive()) {
|
|
247
|
-
|
|
255
|
+
const why = drivenByAgent() ? 'when an agent is running zen' : 'without a terminal';
|
|
256
|
+
throw usageError(`cannot ask for ${what} ${why}`, `pass ${flag} instead`);
|
|
248
257
|
}
|
|
249
258
|
const banner = pending;
|
|
250
259
|
pending = undefined;
|
package/dist/trace.js
CHANGED
|
@@ -387,6 +387,53 @@ function tally(entries) {
|
|
|
387
387
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
388
388
|
.map(([name, n]) => `${safe(name, 40)} x${n}`);
|
|
389
389
|
}
|
|
390
|
+
const WRITES = new Set(['write_file', 'apply_patch', 'copy_file', 'move_file']);
|
|
391
|
+
/**
|
|
392
|
+
* What the run did with memory, zeros included. The `tools` row only lists
|
|
393
|
+
* calls that happened, and a commit that never happened is the commonest
|
|
394
|
+
* memory finding there is: absent from a list, a reader does not see it.
|
|
395
|
+
*/
|
|
396
|
+
function memoryRows(entries, bound) {
|
|
397
|
+
let recalls = 0;
|
|
398
|
+
let recalled = 0;
|
|
399
|
+
let commits = 0;
|
|
400
|
+
let committed = 0;
|
|
401
|
+
let files = 0;
|
|
402
|
+
let written = 0;
|
|
403
|
+
const calls = new Map();
|
|
404
|
+
for (const { node: n } of entries) {
|
|
405
|
+
if (n.type === 'memory_recall') {
|
|
406
|
+
recalls++;
|
|
407
|
+
recalled += n.nodes.length;
|
|
408
|
+
}
|
|
409
|
+
else if (n.type === 'memory_op' && n.op === 'commit') {
|
|
410
|
+
commits++;
|
|
411
|
+
committed += n.nodes.length;
|
|
412
|
+
files += n.files;
|
|
413
|
+
}
|
|
414
|
+
else if (n.type === 'tool_call') {
|
|
415
|
+
calls.set(n.name, (calls.get(n.name) ?? 0) + 1);
|
|
416
|
+
if (WRITES.has(n.name)) {
|
|
417
|
+
written++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const used = (name) => calls.get(name) ?? 0;
|
|
422
|
+
const reads = used('memory_search') + used('memory_grep') + used('memory_load');
|
|
423
|
+
if (!bound && !recalls && !reads && !commits) {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
const label = bound ? '' : 'memory';
|
|
427
|
+
const lines = [
|
|
428
|
+
row(label, `recall ${recalls} (${recalled} nodes) · search ${used('memory_search')} · ` +
|
|
429
|
+
`grep ${used('memory_grep')} · load ${used('memory_load')} · ` +
|
|
430
|
+
`commit ${commits} (${committed} nodes, ${files} files)`),
|
|
431
|
+
];
|
|
432
|
+
if (!commits && written) {
|
|
433
|
+
lines.push(row('', `nothing committed after ${written} workspace file writes`));
|
|
434
|
+
}
|
|
435
|
+
return lines;
|
|
436
|
+
}
|
|
390
437
|
function elapsed(entries) {
|
|
391
438
|
const stamps = entries.map((e) => e.endMs).filter((t) => t !== null);
|
|
392
439
|
return stamps.length < 2 ? '' : span(Math.max(...stamps) - Math.min(...stamps));
|
|
@@ -441,6 +488,7 @@ function header(state, trace, opts) {
|
|
|
441
488
|
...(opts.dir ? [row('dir', safe(opts.dir, 200))] : []),
|
|
442
489
|
...(opts.workspace ? [row('workspace', safe(opts.workspace, 200))] : []),
|
|
443
490
|
...(opts.memory ? [row('memory', safe(opts.memory, 200))] : []),
|
|
491
|
+
...memoryRows(entries, Boolean(opts.memory)),
|
|
444
492
|
row('agent', `${safe(state.agentName, 40)} · started as ${safe(state.spec.startAgent, 40)}`),
|
|
445
493
|
row('phase', state.error ? `${state.phase} — ${safe(state.error, 200)}` : state.phase),
|
|
446
494
|
row('nodes', `${entries.length} · ${kinds.get('llm_call') ?? 0} llm · ` +
|
|
@@ -473,10 +521,13 @@ function header(state, trace, opts) {
|
|
|
473
521
|
if (hidden) {
|
|
474
522
|
lines.push(row('compacted', `${hidden} nodes are marked "hidden by" a later summary`), row('', 'they still ran; the model simply stopped seeing them'));
|
|
475
523
|
}
|
|
476
|
-
lines.push(
|
|
524
|
+
lines.push(
|
|
525
|
+
// Never a bare `%%`: Mermaid only strips a comment line with something
|
|
526
|
+
// after the marker, and leaves the rest to the parser, which rejects it.
|
|
527
|
+
'%% ---', row('reading', 'nN is a node id · t+ counts from the start of the turn'), row('', 'dotted edges are fork/join and calls answered out of order'), row('', 'nodes are declared in run order; every edge is in one block below'), row('detail', `zen inspect node n1 n2 n5..n9${where}`),
|
|
477
528
|
// Named here because this is where the question forms. A reader who has
|
|
478
529
|
// just found the node they distrust will not go looking for a verb.
|
|
479
|
-
row('why', `zen inspect ask n5 "why did you do that?"${where}`), '%%');
|
|
530
|
+
row('why', `zen inspect ask n5 "why did you do that?"${where}`), '%% ---');
|
|
480
531
|
return lines;
|
|
481
532
|
}
|
|
482
533
|
export function traceMermaid(state, opts = {}) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Catalog, type CatalogEntry } from '../catalog.ts';
|
|
2
|
+
import type { Provider } from '../keys.ts';
|
|
3
|
+
export interface ProviderRow {
|
|
4
|
+
provider: Provider;
|
|
5
|
+
/** where its credential came from — `environment`, `keyring` — or '' for none */
|
|
6
|
+
credential: string;
|
|
7
|
+
/** what the cached listing holds, read without asking anyone */
|
|
8
|
+
models: number;
|
|
9
|
+
origin: Catalog['origin'];
|
|
10
|
+
fetchedAt: string;
|
|
11
|
+
}
|
|
12
|
+
/** What asking a model one question found out. */
|
|
13
|
+
export interface Verdict {
|
|
14
|
+
state: 'live' | 'dead' | 'blocked' | 'unknown';
|
|
15
|
+
ms?: number;
|
|
16
|
+
detail?: string;
|
|
17
|
+
fix?: string;
|
|
18
|
+
dimensions?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface BrowseOptions {
|
|
21
|
+
providers: readonly ProviderRow[];
|
|
22
|
+
/** opens straight into this provider's listing */
|
|
23
|
+
initial?: Provider;
|
|
24
|
+
theme?: string;
|
|
25
|
+
load(provider: Provider, refresh: boolean): Promise<Catalog>;
|
|
26
|
+
test(entry: CatalogEntry): Promise<Verdict>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Runs the browser until the person picks a model or leaves. Resolves to the
|
|
30
|
+
* picked entry, or undefined when nothing was picked.
|
|
31
|
+
*/
|
|
32
|
+
export declare function browse(options: BrowseOptions): Promise<CatalogEntry | undefined>;
|
|
33
|
+
//# sourceMappingURL=models.d.ts.map
|