@zenera/cli 1.1.23 → 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 +88 -2
- package/dist/ask.d.ts +51 -0
- package/dist/ask.js +166 -0
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +83 -0
- package/dist/batch.js +413 -0
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/command.d.ts +6 -0
- package/dist/commands/inspect.d.ts +57 -0
- package/dist/commands/inspect.js +539 -91
- package/dist/commands/list.js +128 -19
- package/dist/commands/memory.js +461 -32
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +110 -14
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +175 -36
- package/dist/commands/sandbox.js +148 -15
- package/dist/engine.d.ts +24 -0
- package/dist/engine.js +61 -8
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +20 -3
- package/dist/main.js +5 -2
- 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 +42 -0
- package/dist/podman.js +198 -5
- package/dist/progress.d.ts +33 -0
- package/dist/progress.js +402 -0
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/request.d.ts +45 -0
- package/dist/request.js +300 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +20 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.d.ts +26 -1
- package/dist/session.js +46 -32
- package/dist/term.d.ts +5 -0
- package/dist/term.js +28 -3
- package/dist/trace.d.ts +84 -0
- package/dist/trace.js +737 -0
- 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/dist/validate.js +25 -3
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +41 -107
- 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 +744 -0
- 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 +18 -4
- package/templates/editor/.github/skills/zen-cli/references/check.md +7 -5
- 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 +300 -21
- package/templates/editor/.github/skills/zen-cli/references/memory.md +121 -28
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +6 -2
- package/templates/editor/.github/skills/zen-cli/references/projects.md +12 -5
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/run.md +201 -4
- 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 +1791 -0
- 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 +431 -0
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +78 -29
- package/templates/editor/.github/skills/zen-memory-warmup/SKILL.md +363 -531
- 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 +25 -2
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +334 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/references/loadtest.md +181 -0
- package/templates/editor/.github/skills/zen-sandbox-capacity/scripts/preflight_sandbox.sh +457 -0
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/agents.schema.json +1 -1
- package/templates/editor/.vscode/settings.json +2 -0
- package/templates/project/agents/files-instructions.md +35 -13
- package/templates/project/agents/fork-instructions.md +27 -0
- package/templates/project/agents/memory-instructions.md +106 -34
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +5 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
package/dist/commands/sandbox.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { readProjectConfig } from '@zenera/neo';
|
|
1
|
+
import { readProjectConfig, sandboxLimits, } from '@zenera/neo';
|
|
2
2
|
import { parse } from "../args.js";
|
|
3
3
|
import { resolveBuild } from "../image.js";
|
|
4
|
-
import { engineDisk, ensurePodmanReady, ownedContainers, podmanStatus, removeContainers, } from "../podman.js";
|
|
4
|
+
import { engineDisk, ensurePodmanReady, idleContainers, ownedContainers, podmanStatus, removeContainers, sessionOwners, } from "../podman.js";
|
|
5
5
|
import { current as currentProject, dirSize, isProjectDir, open as openProject, Registry, sessionIds, } from "../projects.js";
|
|
6
|
-
import { ago, bold, bytes, dim, green, json, note, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
6
|
+
import { ago, bold, bytes, count, dim, green, json, note, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
7
7
|
const USAGE = 'zen sandbox [status|up|pull|clean|disk] [options]';
|
|
8
8
|
/** Enough to see the pattern; the rest are a number. */
|
|
9
9
|
const LISTED = 6;
|
|
10
10
|
/** Under the labels, which is where the eye already is. */
|
|
11
11
|
const INDENT = ' '.repeat(11);
|
|
12
|
+
/** `agents.yaml` states memory in MiB; `bytes` prints bytes. */
|
|
13
|
+
const MIB = 1024 * 1024;
|
|
14
|
+
/** Below this many free locks, `status` says so: a batch can spend that in minutes. */
|
|
15
|
+
const FEW_LOCKS = 256;
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
// The container engine, on its own
|
|
14
18
|
//
|
|
@@ -31,6 +35,8 @@ export const sandbox = {
|
|
|
31
35
|
'',
|
|
32
36
|
' --project <name|dir> Which project the image and containers belong to.',
|
|
33
37
|
' --image <ref> Use this image instead of the project\u2019s.',
|
|
38
|
+
' --idle With clean: only what no live run is using — stopped ones,',
|
|
39
|
+
' and running ones whose run was killed before it cleaned up.',
|
|
34
40
|
'',
|
|
35
41
|
'Without --project this is the project you are standing in, and standing',
|
|
36
42
|
'nowhere is an answer: `status` then reports the engine and every container',
|
|
@@ -43,6 +49,8 @@ export const sandbox = {
|
|
|
43
49
|
const { values, positionals } = parse(ctx.args, {
|
|
44
50
|
project: { type: 'string' },
|
|
45
51
|
image: { type: 'string' },
|
|
52
|
+
stopped: { type: 'boolean' },
|
|
53
|
+
idle: { type: 'boolean' },
|
|
46
54
|
}, USAGE);
|
|
47
55
|
const what = positionals[0] ?? 'status';
|
|
48
56
|
if (!['status', 'up', 'pull', 'clean', 'disk'].includes(what)) {
|
|
@@ -68,7 +76,7 @@ export const sandbox = {
|
|
|
68
76
|
}
|
|
69
77
|
return up(image, build, true, ctx.json, true);
|
|
70
78
|
case 'clean':
|
|
71
|
-
return clean(ctx.json);
|
|
79
|
+
return clean(ctx.json, Boolean(values.idle || values.stopped));
|
|
72
80
|
case 'disk':
|
|
73
81
|
return disk(ctx.json);
|
|
74
82
|
}
|
|
@@ -97,6 +105,7 @@ async function projectSandbox(cwd, values) {
|
|
|
97
105
|
name: found.name,
|
|
98
106
|
image: build?.tag ?? config.sandbox?.image,
|
|
99
107
|
build,
|
|
108
|
+
limits: widest(config),
|
|
100
109
|
};
|
|
101
110
|
}
|
|
102
111
|
catch {
|
|
@@ -113,12 +122,16 @@ async function status(project, image, asJson) {
|
|
|
113
122
|
// a report about one project must not list another's — or the faker's,
|
|
114
123
|
// which wears the same label and belongs to no project at all.
|
|
115
124
|
const containers = project ? ofProject(project.dir, all) : all;
|
|
125
|
+
const idle = all.length > 0 ? idleContainers(all, await sessionOwners()) : [];
|
|
116
126
|
if (asJson) {
|
|
117
127
|
json({
|
|
118
128
|
...found,
|
|
119
129
|
project: project?.name ?? null,
|
|
120
130
|
dockerfile: build?.dockerfile ?? null,
|
|
131
|
+
limits: project?.limits ?? null,
|
|
132
|
+
fits: fits(found.capacity?.memory, project?.limits?.memory) ?? null,
|
|
121
133
|
containers,
|
|
134
|
+
idle: idle.map((c) => c.name),
|
|
122
135
|
});
|
|
123
136
|
return;
|
|
124
137
|
}
|
|
@@ -128,7 +141,17 @@ async function status(project, image, asJson) {
|
|
|
128
141
|
const state = found.machine.starting ? yellow('starting') : mark(found.machine.running);
|
|
129
142
|
write(`${bold('machine')} ${found.machine.name} ${state}`);
|
|
130
143
|
}
|
|
144
|
+
if (found.connection) {
|
|
145
|
+
// A concurrency limit wearing a connection string: `ssh://` opens one
|
|
146
|
+
// connection per call and sshd refuses them past ten, which stays
|
|
147
|
+
// invisible until a batch is wide enough to lose tasks at random.
|
|
148
|
+
const how = found.connection.shared
|
|
149
|
+
? dim('· one shared connection, no limit on parallel calls')
|
|
150
|
+
: yellow('· a new ssh connection per call — about 10 at a time');
|
|
151
|
+
write(`${bold('connection')} ${dim(found.connection.uri)} ${how}`);
|
|
152
|
+
}
|
|
131
153
|
write(`${bold('responds')} ${mark(found.ready)}`);
|
|
154
|
+
writeAll(limitLines(found, project?.limits));
|
|
132
155
|
if (project) {
|
|
133
156
|
write(`${bold('project')} ${project.name} ${dim(project.dir)}`);
|
|
134
157
|
}
|
|
@@ -138,12 +161,69 @@ async function status(project, image, asJson) {
|
|
|
138
161
|
if (build) {
|
|
139
162
|
write(`${bold('dockerfile')} ${dim(build.dockerfile)}`);
|
|
140
163
|
}
|
|
141
|
-
writeAll(
|
|
164
|
+
writeAll(lockLines(found.freeLocks, idle.length));
|
|
165
|
+
writeAll(containerLines(containers, idle, project?.name, all.length));
|
|
142
166
|
if (!found.installed || !found.ready) {
|
|
143
167
|
note('');
|
|
144
168
|
note(dim('run `zen sandbox up` to fix what can be fixed.'));
|
|
145
169
|
}
|
|
146
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* The hungriest container this project can start. An agent may override the
|
|
173
|
+
* project's `sandbox:` block, and it is the largest of them that decides
|
|
174
|
+
* whether a batch fits — reporting the project's own figure would understate
|
|
175
|
+
* the machine by however much an agent asked for on top.
|
|
176
|
+
*
|
|
177
|
+
* The overrides are merged onto the project's block rather than read alone,
|
|
178
|
+
* because that is what `SandboxPool.for` does when it builds the container: an
|
|
179
|
+
* agent that names only `memory:` still runs with the project's `cpus:`.
|
|
180
|
+
*/
|
|
181
|
+
function widest(config) {
|
|
182
|
+
const base = config.sandbox ?? {};
|
|
183
|
+
return config.agents
|
|
184
|
+
.map((a) => sandboxLimits(a.sandbox ? { ...base, ...a.sandbox } : base))
|
|
185
|
+
.reduce((most, one) => (one.memory > most.memory ? one : most), sandboxLimits(base));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* How many containers of this size the engine could hold at once.
|
|
189
|
+
*
|
|
190
|
+
* Nothing enforces it — `--memory` caps one container and the kernel kills
|
|
191
|
+
* whatever overruns the sum, so this is what the machine can honour, not a
|
|
192
|
+
* gate anything checks. It is worth printing because neither figure gives it
|
|
193
|
+
* alone: off Linux the memory is the *machine's*, which has nothing to do with
|
|
194
|
+
* the host, so a few containers at the 4096 MiB default can be past it while
|
|
195
|
+
* the laptop sits nearly empty.
|
|
196
|
+
*/
|
|
197
|
+
function fits(capacity, container) {
|
|
198
|
+
if (!capacity || !container) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
return Math.max(0, Math.floor(capacity / container));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Two ceilings, in the order they bite: what the engine has, and what one
|
|
205
|
+
* container is permitted to take of it.
|
|
206
|
+
*/
|
|
207
|
+
function limitLines(found, limits) {
|
|
208
|
+
const lines = [];
|
|
209
|
+
const where = found.machine ? 'machine' : 'host';
|
|
210
|
+
if (found.capacity) {
|
|
211
|
+
const { memory, swap, cpus } = found.capacity;
|
|
212
|
+
const swapped = swap > 0 ? `${bytes(swap * MIB)} swap` : 'no swap';
|
|
213
|
+
lines.push(`${bold('capacity')} ${bytes(memory * MIB)} ${dim(`· ${swapped} · ${cpus} cpus · the ${where}'s, not the host's`)}`);
|
|
214
|
+
}
|
|
215
|
+
if (limits) {
|
|
216
|
+
const room = fits(found.capacity?.memory, limits.memory);
|
|
217
|
+
const how = limits.declared ? limits.hardening : `${limits.hardening} defaults`;
|
|
218
|
+
const tail = room === undefined
|
|
219
|
+
? how
|
|
220
|
+
: room > 0
|
|
221
|
+
? `· ${how} · the ${where} has room for ${room}`
|
|
222
|
+
: `· ${how} · more than the ${where} has`;
|
|
223
|
+
lines.push(`${bold('container')} ${bytes(limits.memory * MIB)} ${dim(`· ${limits.cpus} cpus ${tail}`)}`);
|
|
224
|
+
}
|
|
225
|
+
return lines;
|
|
226
|
+
}
|
|
147
227
|
/**
|
|
148
228
|
* The containers this project's sessions made. A container carries the session
|
|
149
229
|
* id that made it in a label, and a session id is a directory name under the
|
|
@@ -154,6 +234,22 @@ function ofProject(dir, containers) {
|
|
|
154
234
|
const sessions = new Set(sessionIds(dir));
|
|
155
235
|
return containers.filter((c) => c.key !== undefined && sessions.has(c.key));
|
|
156
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Every container the engine holds takes one of its locks, stopped or not, and
|
|
239
|
+
* at zero nothing can be created — for any project. Said only when it is close.
|
|
240
|
+
*/
|
|
241
|
+
function lockLines(free, idle) {
|
|
242
|
+
if (free === undefined || free >= FEW_LOCKS) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const left = free === 0 ? red('none free') : yellow(`${free} free`);
|
|
246
|
+
return [
|
|
247
|
+
`${bold('locks')} ${left} ${dim('· one per container, stopped ones included')}`,
|
|
248
|
+
...(idle > 0
|
|
249
|
+
? [`${INDENT}${dim(`${idle} no live run is using — zen sandbox clean --idle`)}`]
|
|
250
|
+
: []),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
157
253
|
/**
|
|
158
254
|
* One per line rather than one long line, because there is normally more than
|
|
159
255
|
* one and the interesting part — how old, and whether anything is still up —
|
|
@@ -164,21 +260,31 @@ function ofProject(dir, containers) {
|
|
|
164
260
|
* stopped ones behind. `scope` names the project these belong to, and saying
|
|
165
261
|
* so is half the answer — the other half is that there are more elsewhere.
|
|
166
262
|
*/
|
|
167
|
-
function containerLines(containers, scope) {
|
|
263
|
+
function containerLines(containers, idle, scope, total = containers.length) {
|
|
168
264
|
const tail = scope
|
|
169
265
|
? `one per session in ${scope}, kept by \`persist: true\` — all of them: zen sandbox disk`
|
|
170
266
|
: 'one per session, kept by `persist: true` — see: zen sandbox disk';
|
|
267
|
+
const elsewhere = total > containers.length ? ` · ${total} on the machine` : '';
|
|
171
268
|
if (containers.length === 0) {
|
|
172
|
-
return [`${bold('containers')} ${dim(scope ? `none in ${scope}` : 'none')}`];
|
|
269
|
+
return [`${bold('containers')} ${dim(scope ? `none in ${scope}${elsewhere}` : 'none')}`];
|
|
173
270
|
}
|
|
271
|
+
// Running, but the run that started it is gone: nothing will ever stop it.
|
|
272
|
+
const orphans = new Set(idle.filter((c) => c.state === 'running').map((c) => c.name));
|
|
174
273
|
const running = containers.filter((c) => c.state === 'running').length;
|
|
175
|
-
const
|
|
274
|
+
const lost = containers.filter((c) => orphans.has(c.name)).length;
|
|
275
|
+
const head = `${bold('containers')} ${containers.length} ${dim((running ? `· ${running} running` : '· none running') +
|
|
276
|
+
(lost ? `, ${lost} of them orphaned` : '') +
|
|
277
|
+
elsewhere)}`;
|
|
176
278
|
const rows = containers
|
|
177
279
|
.slice(0, LISTED)
|
|
178
280
|
.map((c) => [
|
|
179
281
|
INDENT.slice(2),
|
|
180
282
|
c.name,
|
|
181
|
-
|
|
283
|
+
orphans.has(c.name)
|
|
284
|
+
? yellow('orphaned')
|
|
285
|
+
: c.state === 'running'
|
|
286
|
+
? green('running')
|
|
287
|
+
: dim(c.state),
|
|
182
288
|
dim(ago(c.createdAt)),
|
|
183
289
|
]);
|
|
184
290
|
const rest = containers.length - LISTED;
|
|
@@ -187,6 +293,9 @@ function containerLines(containers, scope) {
|
|
|
187
293
|
...table(rows),
|
|
188
294
|
...(rest > 0 ? [`${INDENT}${dim(`+${rest} more`)}`] : []),
|
|
189
295
|
`${INDENT}${dim(tail)}`,
|
|
296
|
+
...(lost
|
|
297
|
+
? [`${INDENT}${dim('orphaned: its run was killed — zen sandbox clean --idle')}`]
|
|
298
|
+
: []),
|
|
190
299
|
];
|
|
191
300
|
}
|
|
192
301
|
async function up(image, build, yes, asJson, rebuild = false) {
|
|
@@ -197,8 +306,9 @@ async function up(image, build, yes, asJson, rebuild = false) {
|
|
|
197
306
|
}
|
|
198
307
|
write(`${green('ready')}${image ? ` ${dim(image)}` : ''}`);
|
|
199
308
|
}
|
|
200
|
-
async function clean(asJson) {
|
|
201
|
-
const
|
|
309
|
+
async function clean(asJson, idle) {
|
|
310
|
+
const owned = await ownedContainers(undefined, undefined, { sizes: true });
|
|
311
|
+
const containers = idle ? idleContainers(owned, await sessionOwners()) : owned;
|
|
202
312
|
const names = containers.map((c) => c.name);
|
|
203
313
|
const freed = containers.reduce((n, c) => n + (c.size ?? 0), 0);
|
|
204
314
|
await removeContainers(names);
|
|
@@ -236,10 +346,30 @@ async function disk(asJson) {
|
|
|
236
346
|
write('');
|
|
237
347
|
}
|
|
238
348
|
writeAll(projectRows(projects, loose));
|
|
239
|
-
|
|
349
|
+
const idle = idleContainers(containers, await sessionOwners());
|
|
350
|
+
const live = containers.length - idle.length;
|
|
351
|
+
const sessions = projects.reduce((n, p) => n + p.files, 0);
|
|
352
|
+
const hints = [
|
|
353
|
+
...(idle.length > 0
|
|
354
|
+
? [`zen sandbox clean --idle ${count(idle.length, 'container')} no live run is using`]
|
|
355
|
+
: []),
|
|
356
|
+
...(live > 0
|
|
357
|
+
? [`zen sandbox clean also the ${live} in use, cutting off whatever runs there`]
|
|
358
|
+
: []),
|
|
359
|
+
...(usage && usage.images.reclaimable > 0
|
|
360
|
+
? [`podman image prune -a ${bytes(usage.images.reclaimable)} of unused images`]
|
|
361
|
+
: []),
|
|
362
|
+
// The ON DISK column is the bulk of the report and no command above touches it.
|
|
363
|
+
...(sessions > 0
|
|
364
|
+
? [
|
|
365
|
+
`${bytes(sessions)} ON DISK is project directories on this host, not the engine: ` +
|
|
366
|
+
'removing containers frees none of it',
|
|
367
|
+
]
|
|
368
|
+
: []),
|
|
369
|
+
];
|
|
370
|
+
if (hints.length > 0) {
|
|
240
371
|
write('');
|
|
241
|
-
|
|
242
|
-
write(dim(`podman image prune -a ${bytes(usage.images.reclaimable)} of unused images`));
|
|
372
|
+
writeAll(hints.map((h) => dim(h)));
|
|
243
373
|
}
|
|
244
374
|
}
|
|
245
375
|
function engineRows(usage) {
|
|
@@ -311,13 +441,16 @@ function projectRows(projects, loose) {
|
|
|
311
441
|
// Containers whose session directory is gone, and faker's, which are
|
|
312
442
|
// labelled the same way and belong to no project at all.
|
|
313
443
|
const size = loose.reduce((n, c) => n + (c.size ?? 0), 0);
|
|
444
|
+
const fakers = loose.filter((c) => c.key?.startsWith('faker-')).length;
|
|
314
445
|
rows.push([
|
|
315
446
|
dim('(unclaimed)'),
|
|
316
447
|
dim('—'),
|
|
317
448
|
dim('—'),
|
|
318
449
|
dim(String(loose.length)),
|
|
319
450
|
dim(bytes(size)),
|
|
320
|
-
dim(
|
|
451
|
+
dim(fakers === loose.length
|
|
452
|
+
? `zen faker ${fakers === 1 ? 'server' : 'servers'}, no project`
|
|
453
|
+
: 'no session owns these'),
|
|
321
454
|
]);
|
|
322
455
|
}
|
|
323
456
|
const total = (pick) => projects.reduce((n, p) => n + pick(p), 0);
|
package/dist/engine.d.ts
CHANGED
|
@@ -12,10 +12,23 @@ export interface EngineOptions {
|
|
|
12
12
|
image?: string;
|
|
13
13
|
/** another directory to remember into — `--memory` */
|
|
14
14
|
memoryDir?: string;
|
|
15
|
+
/** recall only: nothing is written and several runs may share one graph */
|
|
16
|
+
memoryReadOnly?: boolean;
|
|
15
17
|
/** whether credentials reach the sandbox — `--no-keys` sets this false */
|
|
16
18
|
keys?: boolean;
|
|
17
19
|
/** answer the sandbox's install question without asking — `--yes` */
|
|
18
20
|
yes?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Hold back the warnings about the project rather than the run. A batch
|
|
23
|
+
* opens one project a hundred times, and a hundred copies of the same
|
|
24
|
+
* complaint is a worse report than one.
|
|
25
|
+
*/
|
|
26
|
+
quiet?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The session will never be resumed, so its containers are removed on close
|
|
29
|
+
* whatever `persist:` says. A stopped one would only hold an engine lock.
|
|
30
|
+
*/
|
|
31
|
+
disposable?: boolean;
|
|
19
32
|
}
|
|
20
33
|
export interface Engine {
|
|
21
34
|
project: AgentProject;
|
|
@@ -27,6 +40,8 @@ export interface Engine {
|
|
|
27
40
|
/** whether the agent was denied every writing tool */
|
|
28
41
|
readOnly: boolean;
|
|
29
42
|
session: SessionPaths;
|
|
43
|
+
/** the graph this session remembers into, when any agent has one */
|
|
44
|
+
memory?: string;
|
|
30
45
|
/** the session's accumulated state, when it has one */
|
|
31
46
|
state?: AgentState;
|
|
32
47
|
/** the containers this session may start; present whether or not it does */
|
|
@@ -56,7 +71,16 @@ export interface RunOutcome {
|
|
|
56
71
|
durationMs: number;
|
|
57
72
|
/** where the report landed, when one could be rendered */
|
|
58
73
|
report?: string;
|
|
74
|
+
/** where the Mermaid graph landed, when one could be emitted */
|
|
75
|
+
graph?: string;
|
|
59
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* What one finished run looks like to a program: `zen run --json` prints it,
|
|
79
|
+
* and a batch writes one beside every item. The same function on purpose — a
|
|
80
|
+
* batch whose items were shaped differently would make every consumer of a
|
|
81
|
+
* single run a special case.
|
|
82
|
+
*/
|
|
83
|
+
export declare function envelope(engine: Engine, outcome: RunOutcome): Record<string, unknown>;
|
|
60
84
|
/**
|
|
61
85
|
* One turn. The session's state decides whether this starts a run or continues
|
|
62
86
|
* one — which is the whole of what `resume` used to be, and why it is not a
|
package/dist/engine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentRunner, FilePayloadStore, SANDBOX_MOUNT, SKILLS_MOUNT, Workspace, assertState, buildRunReport, exaTools, lastText, loadProject, readProjectConfig, renderReportHtml, sandboxTools, turns,
|
|
1
|
+
import { AgentRunner, FilePayloadStore, SANDBOX_MOUNT, SKILLS_MOUNT, Workspace, assertState, buildRunReport, exaTools, fileTools, lastText, loadProject, memoryDir, readProjectConfig, renderReportHtml, sandboxTools, turns, } from '@zenera/neo';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { writeFile } from 'node:fs/promises';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
@@ -8,8 +8,9 @@ import { readJson, writeJson } from "./home.js";
|
|
|
8
8
|
import { KeyStore, assertUsable } from "./keys.js";
|
|
9
9
|
import { projectMounts } from "./projects.js";
|
|
10
10
|
import { buildSandbox, preflight, teardown, usesSandbox } from "./sandbox.js";
|
|
11
|
-
import { acquire, createRun, readSessionMeta, writeSessionMeta, } from "./session.js";
|
|
11
|
+
import { acquire, createRun, readSessionMeta, writeRunMeta, writeSessionMeta, } from "./session.js";
|
|
12
12
|
import { CliError, EXIT, invalidError, warn } from "./term.js";
|
|
13
|
+
import { traceMermaid } from "./trace.js";
|
|
13
14
|
/**
|
|
14
15
|
* Wires the library to a session directory. Every store is file-backed and
|
|
15
16
|
* rooted inside `.data/`, so a session is self-contained: copy the directory
|
|
@@ -27,7 +28,9 @@ export async function open(opts) {
|
|
|
27
28
|
// build: one SDK's words about one model, when the useful answer is which
|
|
28
29
|
// of the project's models are reachable and which are not.
|
|
29
30
|
for (const issue of auditModels(opts.project.dir, keys)) {
|
|
30
|
-
|
|
31
|
+
if (!opts.quiet) {
|
|
32
|
+
warn(describeIssue(issue));
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
35
|
const meta = await readSessionMeta(opts.session);
|
|
33
36
|
const workspace = resolve(meta.workspace);
|
|
@@ -40,13 +43,15 @@ export async function open(opts) {
|
|
|
40
43
|
let sandbox;
|
|
41
44
|
let project;
|
|
42
45
|
let files;
|
|
46
|
+
let memory;
|
|
43
47
|
try {
|
|
44
48
|
const { root, config } = readProjectConfig(opts.project.dir);
|
|
45
49
|
// Naming a directory declares the graph, but it cannot bind anyone to
|
|
46
50
|
// it — so say when the run is pointed at a memory no agent will touch.
|
|
47
|
-
if (opts.memoryDir && !config.agents.some((a) => a.memory)) {
|
|
51
|
+
if (opts.memoryDir && !opts.quiet && !config.agents.some((a) => a.memory)) {
|
|
48
52
|
warn('--memory names a directory no agent uses — none has `memory: true`');
|
|
49
53
|
}
|
|
54
|
+
memory = memoryDir(root, config, opts.memoryDir);
|
|
50
55
|
// Assets and the skill catalog are mounted for both, under one name.
|
|
51
56
|
const mounts = projectMounts(root, config, opts.memoryDir);
|
|
52
57
|
sandbox = buildSandbox({
|
|
@@ -59,6 +64,7 @@ export async function open(opts) {
|
|
|
59
64
|
keys: opts.keys,
|
|
60
65
|
env: env?.names,
|
|
61
66
|
mounts,
|
|
67
|
+
...(opts.disposable ? { persist: false } : {}),
|
|
62
68
|
});
|
|
63
69
|
const workspaceOptions = {
|
|
64
70
|
root: workspace,
|
|
@@ -74,7 +80,7 @@ export async function open(opts) {
|
|
|
74
80
|
// Both groups are pointed at one directory, so both are told the
|
|
75
81
|
// one name for it: whatever `run_command` prints a path as,
|
|
76
82
|
// `read_file` accepts.
|
|
77
|
-
...
|
|
83
|
+
...fileTools(workspaceOptions),
|
|
78
84
|
...sandboxTools(sandbox.pool),
|
|
79
85
|
// Registered whether or not a key exists: the credential is
|
|
80
86
|
// read when a tool is called, so a project that names `exa:*`
|
|
@@ -84,6 +90,7 @@ export async function open(opts) {
|
|
|
84
90
|
],
|
|
85
91
|
skillsAt: SKILLS_MOUNT,
|
|
86
92
|
memoryDir: opts.memoryDir,
|
|
93
|
+
memoryReadOnly: opts.memoryReadOnly,
|
|
87
94
|
payloads,
|
|
88
95
|
resolveFile: (path) => files.within(path),
|
|
89
96
|
});
|
|
@@ -118,6 +125,7 @@ export async function open(opts) {
|
|
|
118
125
|
workspaceMount: sandbox.spec.workdir ?? SANDBOX_MOUNT,
|
|
119
126
|
readOnly: Boolean(opts.readOnly),
|
|
120
127
|
session: opts.session,
|
|
128
|
+
memory,
|
|
121
129
|
state,
|
|
122
130
|
sandbox,
|
|
123
131
|
lock,
|
|
@@ -151,6 +159,33 @@ export function mounts(engine) {
|
|
|
151
159
|
})),
|
|
152
160
|
];
|
|
153
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* What one finished run looks like to a program: `zen run --json` prints it,
|
|
164
|
+
* and a batch writes one beside every item. The same function on purpose — a
|
|
165
|
+
* batch whose items were shaped differently would make every consumer of a
|
|
166
|
+
* single run a special case.
|
|
167
|
+
*/
|
|
168
|
+
export function envelope(engine, outcome) {
|
|
169
|
+
return {
|
|
170
|
+
session: { id: engine.session.id, dir: engine.session.dir },
|
|
171
|
+
run: {
|
|
172
|
+
id: outcome.run.id,
|
|
173
|
+
dir: outcome.run.dir,
|
|
174
|
+
input: outcome.run.input,
|
|
175
|
+
output: outcome.run.output,
|
|
176
|
+
state: outcome.run.state,
|
|
177
|
+
meta: outcome.run.meta,
|
|
178
|
+
...(outcome.report ? { report: outcome.report } : {}),
|
|
179
|
+
...(outcome.graph ? { graph: outcome.graph } : {}),
|
|
180
|
+
},
|
|
181
|
+
mounts: mounts(engine),
|
|
182
|
+
agent: outcome.result.agent,
|
|
183
|
+
stopReason: outcome.result.stopReason,
|
|
184
|
+
durationMs: outcome.durationMs,
|
|
185
|
+
usage: outcome.result.usage,
|
|
186
|
+
output: outcome.text,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
154
189
|
/**
|
|
155
190
|
* One turn. The session's state decides whether this starts a run or continues
|
|
156
191
|
* one — which is the whole of what `resume` used to be, and why it is not a
|
|
@@ -205,7 +240,24 @@ async function record(engine, input, result, startedAt, durationMs) {
|
|
|
205
240
|
catch {
|
|
206
241
|
// A report is a convenience. Losing it must not lose the run.
|
|
207
242
|
}
|
|
208
|
-
|
|
243
|
+
// The same trajectory for the other reader. `report.html` is for a person;
|
|
244
|
+
// this is what `zen inspect graph` prints, written down while the state is
|
|
245
|
+
// already in hand — a batch of a hundred items is otherwise a hundred more
|
|
246
|
+
// commands to run before anything can be read.
|
|
247
|
+
let graph;
|
|
248
|
+
try {
|
|
249
|
+
await writeFile(run.graph, traceMermaid(result.state, {
|
|
250
|
+
runId: run.id,
|
|
251
|
+
dir: run.dir,
|
|
252
|
+
workspace: engine.workspace,
|
|
253
|
+
memory: engine.memory,
|
|
254
|
+
}), 'utf8');
|
|
255
|
+
graph = run.graph;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Same rule as the report: a view of the run is not the run.
|
|
259
|
+
}
|
|
260
|
+
writeRunMeta(run, {
|
|
209
261
|
version: 1,
|
|
210
262
|
id: run.id,
|
|
211
263
|
session: session.id,
|
|
@@ -217,9 +269,10 @@ async function record(engine, input, result, startedAt, durationMs) {
|
|
|
217
269
|
turns: turns(result.state),
|
|
218
270
|
usage: result.usage,
|
|
219
271
|
workspace: engine.workspace,
|
|
272
|
+
memory: engine.memory,
|
|
220
273
|
error: result.state.error,
|
|
221
|
-
}
|
|
222
|
-
return { run, result, text, durationMs, report };
|
|
274
|
+
});
|
|
275
|
+
return { run, result, text, durationMs, report, graph };
|
|
223
276
|
}
|
|
224
277
|
function asText(input) {
|
|
225
278
|
if (typeof input === 'string') {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type AgentEvent } from '@zenera/neo';
|
|
2
|
+
export type EventBody = {
|
|
3
|
+
type: 'llm';
|
|
4
|
+
model: string;
|
|
5
|
+
in: number;
|
|
6
|
+
cached: number;
|
|
7
|
+
out: number;
|
|
8
|
+
reasoning: number;
|
|
9
|
+
ms?: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'tool';
|
|
12
|
+
phase: 'start';
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
subject: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'tool';
|
|
18
|
+
phase: 'end';
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
ms?: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'say';
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
export type EventLine = EventBody & {
|
|
28
|
+
t: string;
|
|
29
|
+
};
|
|
30
|
+
/** A line that cannot be written is dropped: watching a run must not break it. */
|
|
31
|
+
export declare function eventLog(path: string): (body: EventBody) => void;
|
|
32
|
+
/** For `zen run`: the runtime's own events, folded to the shared lines. */
|
|
33
|
+
export declare function agentEventLog(path: string): (event: AgentEvent) => void;
|
|
34
|
+
/** One line of copilot's JSONL, as much of it as is read here. */
|
|
35
|
+
export interface CopilotEvent {
|
|
36
|
+
type: string;
|
|
37
|
+
data?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/** A model call read off copilot's span file - see `meta/tokens.ts`. */
|
|
40
|
+
export interface CopilotCall {
|
|
41
|
+
model: string;
|
|
42
|
+
usage: {
|
|
43
|
+
inputTokens: number;
|
|
44
|
+
cachedInputTokens: number;
|
|
45
|
+
outputTokens: number;
|
|
46
|
+
reasoningTokens: number;
|
|
47
|
+
};
|
|
48
|
+
durationMs: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* For `zen meta run`: copilot's events and spans, folded to the same lines.
|
|
52
|
+
* Its JSONL has no token counts, so `llm` lines come from the spans instead,
|
|
53
|
+
* about a second after each call.
|
|
54
|
+
*/
|
|
55
|
+
export declare function copilotEventLog(path: string): {
|
|
56
|
+
event: (event: CopilotEvent) => void;
|
|
57
|
+
call: (call: CopilotCall) => void;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=eventlog.d.ts.map
|