@zenera/cli 1.1.25 → 1.1.27
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 +13 -1
- package/dist/catalog.d.ts +20 -0
- package/dist/catalog.js +60 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/inspect.d.ts +5 -1
- package/dist/commands/inspect.js +54 -14
- package/dist/commands/memory.js +28 -3
- package/dist/commands/meta.js +24 -5
- package/dist/commands/models.js +2 -1
- package/dist/commands/sandbox.js +73 -3
- package/dist/commands/view.d.ts +8 -0
- package/dist/commands/view.js +114 -0
- package/dist/engine.d.ts +3 -1
- package/dist/engine.js +6 -0
- package/dist/eventlog.d.ts +5 -0
- package/dist/eventlog.js +25 -1
- package/dist/liveness.js +20 -2
- package/dist/main.js +0 -0
- package/dist/meta/editor.d.ts +1 -1
- package/dist/meta/editor.js +4 -21
- package/dist/meta/finetune/analyze-brief.d.ts +26 -0
- package/dist/meta/finetune/analyze-brief.js +131 -0
- package/dist/meta/finetune/apply-brief.d.ts +23 -0
- package/dist/meta/finetune/apply-brief.js +247 -0
- package/dist/meta/finetune/brief.d.ts +45 -0
- package/dist/meta/finetune/brief.js +158 -0
- package/dist/meta/finetune/command.d.ts +1 -1
- package/dist/meta/finetune/command.js +224 -46
- package/dist/meta/finetune/cost.d.ts +46 -0
- package/dist/meta/finetune/cost.js +105 -0
- package/dist/meta/finetune/dataset/types.d.ts +3 -1
- package/dist/meta/finetune/feedback.d.ts +14 -2
- package/dist/meta/finetune/feedback.js +22 -3
- package/dist/meta/finetune/guard.d.ts +29 -0
- package/dist/meta/finetune/guard.js +104 -0
- package/dist/meta/finetune/improvements.d.ts +50 -3
- package/dist/meta/finetune/improvements.js +290 -33
- package/dist/meta/finetune/live.d.ts +17 -7
- package/dist/meta/finetune/live.js +56 -10
- package/dist/meta/finetune/loop.js +59 -10
- package/dist/meta/finetune/recheck.d.ts +58 -0
- package/dist/meta/finetune/recheck.js +311 -0
- package/dist/meta/finetune/report.d.ts +9 -2
- package/dist/meta/finetune/report.js +551 -438
- package/dist/meta/finetune/sampler.js +8 -2
- package/dist/meta/finetune/train.d.ts +6 -2
- package/dist/meta/finetune/train.js +55 -21
- package/dist/meta/finetune/tui.d.ts +3 -3
- package/dist/meta/finetune/tui.js +37 -12
- package/dist/meta/finetune/tuning.d.ts +27 -1
- package/dist/meta/finetune/tuning.js +21 -2
- package/dist/meta/finetune/usage.d.ts +4 -0
- package/dist/meta/finetune/usage.js +35 -0
- package/dist/meta/finetune/view.d.ts +9 -0
- package/dist/meta/finetune/view.js +27 -0
- package/dist/meta/host.d.ts +2 -1
- package/dist/meta/host.js +2 -1
- package/dist/meta.d.ts +291 -0
- package/dist/meta.js +981 -0
- package/dist/resources.d.ts +215 -0
- package/dist/resources.js +856 -0
- package/dist/scaffold.d.ts +3 -4
- package/dist/scaffold.js +21 -9
- package/dist/trace.d.ts +5 -0
- package/dist/trace.js +54 -5
- package/dist/tui/app.js +19 -5
- package/dist/tui/links.d.ts +27 -0
- package/dist/tui/links.js +142 -0
- package/dist/tui/models.d.ts +3 -1
- package/dist/tui/models.js +65 -7
- package/dist/tui/sandbox.d.ts +10 -0
- package/dist/tui/sandbox.js +131 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +29 -43
- package/templates/editor/.github/prompts/analyze.prompt.md +3 -1
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +221 -42
- package/templates/editor/.github/prompts/project-review.prompt.md +1 -1
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +162 -57
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +74 -14
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +16 -2
- package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +25 -13
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +74 -33
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +14 -1
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +4 -3
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
- package/templates/editor/.github/skills/zen-memory/SKILL.md +64 -39
- package/templates/editor/.github/skills/zen-review/SKILL.md +6 -31
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +1 -2
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +2 -1
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
- package/templates/project/agents/fork-instructions.md +78 -28
- package/templates/project/agents/memory-instructions.md +222 -493
- package/templates/project/agents/tools-instructions.md +7 -0
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
|
@@ -29,13 +29,19 @@ export function resultOf(t, c) {
|
|
|
29
29
|
return r;
|
|
30
30
|
}
|
|
31
31
|
export function nextCase(t) {
|
|
32
|
+
if (t.more !== undefined && t.tried.size >= t.more) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
32
35
|
return selection(t).find((c) => {
|
|
33
36
|
if (t.claimed.has(c.id)) {
|
|
34
37
|
return false;
|
|
35
38
|
}
|
|
36
39
|
const r = resultOf(t, c);
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
if (r) {
|
|
41
|
+
// Once per start: a sandbox that is down would only fail it again at once.
|
|
42
|
+
return r.state === 'failed' && !t.tried.has(c.id);
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
39
45
|
});
|
|
40
46
|
}
|
|
41
47
|
//# sourceMappingURL=sampler.js.map
|
|
@@ -24,14 +24,18 @@ export interface Run {
|
|
|
24
24
|
tokens?: ByModel;
|
|
25
25
|
metrics?: RunMetrics;
|
|
26
26
|
durationMs?: number;
|
|
27
|
+
/** skills an apply made for this case that the run never loaded */
|
|
28
|
+
missedSkills?: string[];
|
|
27
29
|
}
|
|
28
30
|
export declare class VoidRun extends StepError {
|
|
29
31
|
}
|
|
30
32
|
/** The container engine failed: every run after this one would fail the same way. */
|
|
31
33
|
export declare class SandboxDown extends StepError {
|
|
32
34
|
}
|
|
33
|
-
/**
|
|
34
|
-
export declare function
|
|
35
|
+
/** One `zen run`. Writes run.json, or says why the run was void. */
|
|
36
|
+
export declare function runOnce(t: Tuning, c: Case, dir: string, memory?: string, expect?: string[]): Promise<string | undefined>;
|
|
37
|
+
/** The meta agent's answer is the human report: kept as analysis.md beside the feedback. A retry keeps its own envelope, so its tokens are counted too. */
|
|
38
|
+
export declare function keepAnalysis(dir: string, stdout: string, replace?: boolean): void;
|
|
35
39
|
export declare function markCompleted(t: Tuning, c: Case): void;
|
|
36
40
|
export declare function markDifficult(t: Tuning, c: Case, phase: Phase, reason: string): void;
|
|
37
41
|
/** Something outside the prose broke. The case keeps its tries and runs again at the next start. */
|
|
@@ -4,11 +4,12 @@ import { hostname } from 'node:os';
|
|
|
4
4
|
import { basename, dirname, join } from 'node:path';
|
|
5
5
|
import { EXIT } from "../host.js";
|
|
6
6
|
import { sessionKept } from "../tokens.js";
|
|
7
|
+
import { writeAnalyzeBrief } from "./analyze-brief.js";
|
|
7
8
|
import { batchInput } from "./dataset/export.js";
|
|
8
9
|
import { DatasetStore } from "./dataset/store.js";
|
|
9
10
|
import { errorIn, sizeOf, StepError } from "./failure.js";
|
|
10
|
-
import { readFeedback } from "./feedback.js";
|
|
11
|
-
import { caseTokens, writeFeedback } from "./report.js";
|
|
11
|
+
import { feedbackId, forDataset, forProse, lead, readFeedback, } from "./feedback.js";
|
|
12
|
+
import { attemptsOf, caseTokens, rubricScore, writeFeedback } from "./report.js";
|
|
12
13
|
import { readJson } from "./tuning.js";
|
|
13
14
|
import { envelopesIn, runMetrics, spent, tokensOf, } from "./usage.js";
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
@@ -27,8 +28,15 @@ export async function train(t, s, c) {
|
|
|
27
28
|
passed = run;
|
|
28
29
|
break;
|
|
29
30
|
}
|
|
31
|
+
const rubricOnly = datasetOnly(feedback);
|
|
32
|
+
if (rubricOnly) {
|
|
33
|
+
return markDifficult(t, c, 'nomem', rubricOnly);
|
|
34
|
+
}
|
|
30
35
|
if (attempt < t.config.tries) {
|
|
31
|
-
await t.improvements.apply(feedback, s);
|
|
36
|
+
const { structural } = await t.improvements.apply(feedback, s);
|
|
37
|
+
if (structural) {
|
|
38
|
+
return markDifficult(t, c, 'nomem', `structural: ${structural}`);
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
if (!passed) {
|
|
@@ -42,8 +50,15 @@ export async function train(t, s, c) {
|
|
|
42
50
|
t.memories.submit(c, attempt, run);
|
|
43
51
|
break;
|
|
44
52
|
}
|
|
53
|
+
const rubricOnly = datasetOnly(feedback);
|
|
54
|
+
if (rubricOnly) {
|
|
55
|
+
return markDifficult(t, c, 'mem', rubricOnly);
|
|
56
|
+
}
|
|
45
57
|
if (attempt < t.config.memTries) {
|
|
46
|
-
await t.improvements.apply(feedback, s);
|
|
58
|
+
const { structural } = await t.improvements.apply(feedback, s);
|
|
59
|
+
if (structural) {
|
|
60
|
+
return markDifficult(t, c, 'mem', `structural: ${structural}`);
|
|
61
|
+
}
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
if (!cheaper) {
|
|
@@ -51,6 +66,14 @@ export async function train(t, s, c) {
|
|
|
51
66
|
}
|
|
52
67
|
markCompleted(t, c);
|
|
53
68
|
}
|
|
69
|
+
/** Not done, and every finding is about the case itself: another try on the same prose learns nothing. */
|
|
70
|
+
function datasetOnly(f) {
|
|
71
|
+
const dataset = f.improvements.filter(forDataset);
|
|
72
|
+
if (dataset.length === 0 || forProse(f).length > 0) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return `specification: ${dataset.map((i) => lead(i.change)).join('; ')}`;
|
|
76
|
+
}
|
|
54
77
|
/** A run its analysis finds void is set aside and run again: it says nothing about the prose. */
|
|
55
78
|
async function tryOnce(t, s, c, phase, attempt, memory) {
|
|
56
79
|
for (let voids = 1;; voids++) {
|
|
@@ -97,8 +120,12 @@ async function zenRun(t, s, c, phase, attempt, memory) {
|
|
|
97
120
|
t.at(s, phase, attempt, 'run');
|
|
98
121
|
t.improvements.runs++;
|
|
99
122
|
const began = Date.now();
|
|
123
|
+
// The skills the apply after the last try made for this case: this run should load them.
|
|
124
|
+
const expect = attempt > 1
|
|
125
|
+
? t.improvements.skillsFor(feedbackId(c.id, phase, attempt - 1, t.round(c)))
|
|
126
|
+
: [];
|
|
100
127
|
try {
|
|
101
|
-
const why = await t.journal.step({ kind: 'run', dir }, () => runOnce(t, c, dir, memory));
|
|
128
|
+
const why = await t.journal.step({ kind: 'run', dir }, () => runOnce(t, c, dir, memory, expect));
|
|
102
129
|
const ran = why ? undefined : readJson(file);
|
|
103
130
|
t.ended(s, {
|
|
104
131
|
what: why ? 'void' : 'ran',
|
|
@@ -116,7 +143,7 @@ async function zenRun(t, s, c, phase, attempt, memory) {
|
|
|
116
143
|
return readJson(file);
|
|
117
144
|
}
|
|
118
145
|
/** One `zen run`. Writes run.json, or says why the run was void. */
|
|
119
|
-
async function runOnce(t, c, dir, memory) {
|
|
146
|
+
export async function runOnce(t, c, dir, memory, expect = []) {
|
|
120
147
|
mkdirSync(dir, { recursive: true });
|
|
121
148
|
const mem = join(dir, 'memory');
|
|
122
149
|
rmSync(mem, { recursive: true, force: true });
|
|
@@ -180,6 +207,7 @@ async function runOnce(t, c, dir, memory) {
|
|
|
180
207
|
if (metrics?.sandbox) {
|
|
181
208
|
throw new SandboxDown(metrics.sandbox, join(runDir, 'graph.mmd'));
|
|
182
209
|
}
|
|
210
|
+
const missed = expect.filter((name) => !metrics?.skills.includes(name));
|
|
183
211
|
const run = {
|
|
184
212
|
dir: runDir,
|
|
185
213
|
memory: mem,
|
|
@@ -192,6 +220,7 @@ async function runOnce(t, c, dir, memory) {
|
|
|
192
220
|
...(meta?.usage ? { usage: meta.usage } : {}),
|
|
193
221
|
...(metrics ? { tokens: tokensOf(metrics), metrics } : {}),
|
|
194
222
|
...(meta?.durationMs !== undefined ? { durationMs: meta.durationMs } : {}),
|
|
223
|
+
...(missed.length > 0 ? { missedSkills: missed } : {}),
|
|
195
224
|
};
|
|
196
225
|
writeFileSync(join(dir, 'run.json'), `${JSON.stringify(run, null, 2)}\n`);
|
|
197
226
|
return undefined;
|
|
@@ -199,11 +228,7 @@ async function runOnce(t, c, dir, memory) {
|
|
|
199
228
|
// ---------------------------------------------------------------------------
|
|
200
229
|
// zen meta run /analyze
|
|
201
230
|
// ---------------------------------------------------------------------------
|
|
202
|
-
/**
|
|
203
|
-
export function sessionOf(t, c) {
|
|
204
|
-
const file = join(t.caseDir(c), 'session');
|
|
205
|
-
return existsSync(file) ? readFileSync(file, 'utf8').trim() || undefined : undefined;
|
|
206
|
-
}
|
|
231
|
+
/** One analyze session per try, kept in its folder: the brief carries the earlier tries. */
|
|
207
232
|
async function analyze(t, s, c, phase, attempt, run) {
|
|
208
233
|
const dir = t.attemptDir(c, phase, attempt);
|
|
209
234
|
const file = join(dir, 'feedback.json');
|
|
@@ -225,7 +250,7 @@ async function analyze(t, s, c, phase, attempt, run) {
|
|
|
225
250
|
attempt,
|
|
226
251
|
result: feedback.verdict === 'void'
|
|
227
252
|
? `void: ${feedback.infra}`
|
|
228
|
-
: `${feedback.verdict}${graded.length ? ` ${
|
|
253
|
+
: `${feedback.verdict}${graded.length ? ` ${rubricScore(feedback)}` : ''}${feedback.done ? ', done' : ''}`,
|
|
229
254
|
took: Date.now() - began,
|
|
230
255
|
tokens: spent(envelopesIn(dir, 'analyze')),
|
|
231
256
|
});
|
|
@@ -241,12 +266,18 @@ async function analyze(t, s, c, phase, attempt, run) {
|
|
|
241
266
|
async function analyzeOnce(t, s, c, phase, attempt, run, file) {
|
|
242
267
|
const dir = dirname(file);
|
|
243
268
|
const known = t.known(c, phase, attempt);
|
|
244
|
-
let session =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
269
|
+
let session = randomUUID();
|
|
270
|
+
writeFileSync(join(dir, 'session'), `${session}\n`);
|
|
271
|
+
const all = attemptsOf(t, c);
|
|
272
|
+
const at = all.findIndex((a) => a.phase === phase && a.attempt === attempt);
|
|
273
|
+
const brief = await writeAnalyzeBrief(t, dir, {
|
|
274
|
+
run,
|
|
275
|
+
case: c,
|
|
276
|
+
phase,
|
|
277
|
+
attempt,
|
|
278
|
+
earlier: at >= 0 ? all.slice(0, at) : all,
|
|
279
|
+
...(run.missedSkills?.length ? { missed: run.missedSkills } : {}),
|
|
280
|
+
});
|
|
250
281
|
const hash = t.system.hash();
|
|
251
282
|
const applies = t.improvements.started;
|
|
252
283
|
const events = join(dir, 'analyze.events.jsonl');
|
|
@@ -264,6 +295,8 @@ async function analyzeOnce(t, s, c, phase, attempt, run, file) {
|
|
|
264
295
|
const from = sizeOf(log);
|
|
265
296
|
const res = await t.zen([
|
|
266
297
|
...continuing(),
|
|
298
|
+
'--prompt-file',
|
|
299
|
+
brief,
|
|
267
300
|
'/analyze',
|
|
268
301
|
run.dir,
|
|
269
302
|
`case=${c.id}`,
|
|
@@ -271,13 +304,14 @@ async function analyzeOnce(t, s, c, phase, attempt, run, file) {
|
|
|
271
304
|
`phase=${phase}`,
|
|
272
305
|
`attempt=${attempt}`,
|
|
273
306
|
`feedback=${file}`,
|
|
307
|
+
...(run.missedSkills?.length ? [`missed-skills=${run.missedSkills.join(',')}`] : []),
|
|
274
308
|
], log);
|
|
275
309
|
keepAnalysis(dir, res.stdout);
|
|
276
310
|
// `zen meta run` starts a new session when the meta model changed since this one; follow it.
|
|
277
311
|
const started = sessionIn(res.stdout);
|
|
278
312
|
if (started && started !== session) {
|
|
279
313
|
session = started;
|
|
280
|
-
writeFileSync(join(
|
|
314
|
+
writeFileSync(join(dir, 'session'), `${session}\n`);
|
|
281
315
|
}
|
|
282
316
|
const rubricIds = c.rubric.map((r) => r.id);
|
|
283
317
|
let read = readFeedback(file, known, rubricIds);
|
|
@@ -319,7 +353,7 @@ function sessionIn(stdout) {
|
|
|
319
353
|
}
|
|
320
354
|
}
|
|
321
355
|
/** The meta agent's answer is the human report: kept as analysis.md beside the feedback. A retry keeps its own envelope, so its tokens are counted too. */
|
|
322
|
-
function keepAnalysis(dir, stdout, replace = true) {
|
|
356
|
+
export function keepAnalysis(dir, stdout, replace = true) {
|
|
323
357
|
let envelope;
|
|
324
358
|
try {
|
|
325
359
|
envelope = JSON.parse(stdout);
|
|
@@ -345,7 +379,7 @@ function note(t, c, f, run, session) {
|
|
|
345
379
|
run: run.dir,
|
|
346
380
|
verdict: f.verdict,
|
|
347
381
|
...(Object.keys(rubric).length > 0 ? { rubric } : {}),
|
|
348
|
-
text: f.verdict === 'void' ? `void: ${f.infra}` : f.summary || f.verdict,
|
|
382
|
+
text: f.verdict === 'void' ? `void: ${f.infra}` : lead(f.summary) || f.verdict,
|
|
349
383
|
by: { session, prompt: 'analyze', host: hostname() },
|
|
350
384
|
});
|
|
351
385
|
}
|
|
@@ -8,9 +8,9 @@ export interface Part {
|
|
|
8
8
|
}
|
|
9
9
|
export type Line = Part[];
|
|
10
10
|
/** The overview: under way, tokens, log - as many log rows as the height leaves. */
|
|
11
|
-
export declare function mainLines(s: Snapshot, selected: number, width: number, height: number, theme: Theme): Line[];
|
|
11
|
+
export declare function mainLines(s: Snapshot, selected: number, width: number, height: number, theme: Theme, url?: string): Line[];
|
|
12
12
|
/** One step's agent, line by line as it works. */
|
|
13
|
-
export declare function followLines(s: Snapshot, key: string, width: number, height: number, theme: Theme): Line[];
|
|
13
|
+
export declare function followLines(s: Snapshot, key: string, width: number, height: number, theme: Theme, url?: string): Line[];
|
|
14
14
|
/** Draws the tuning until the returned function is called. */
|
|
15
|
-
export declare function watch(t: Tuning, interrupt: () => void): Promise<() => void>;
|
|
15
|
+
export declare function watch(t: Tuning, interrupt: () => void, url?: string): Promise<() => void>;
|
|
16
16
|
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, render, Text, useInput, useWindowSize } from 'ink';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
3
4
|
import React, { useEffect, useState } from 'react';
|
|
4
5
|
import { resolveTheme } from "../host.js";
|
|
5
6
|
import { since } from "./report.js";
|
|
@@ -21,16 +22,28 @@ function footer(width, keys, theme) {
|
|
|
21
22
|
[{ text: keys, color: theme.chrome.color }],
|
|
22
23
|
];
|
|
23
24
|
}
|
|
24
|
-
function keysFor(s, following) {
|
|
25
|
+
function keysFor(s, following, url) {
|
|
25
26
|
if (s.state === 'stopping') {
|
|
26
27
|
return 'stopping: the steps under way finish first · ctrl-c again kills them';
|
|
27
28
|
}
|
|
28
29
|
if (s.state !== 'running') {
|
|
29
30
|
return s.state;
|
|
30
31
|
}
|
|
32
|
+
const open = url ? 'o open in browser · ' : '';
|
|
31
33
|
return following
|
|
32
|
-
?
|
|
33
|
-
:
|
|
34
|
+
? `esc back · ${open}s stop · ctrl-c stop, twice kills`
|
|
35
|
+
: `up/down choose · enter follow · ${open}s stop · ctrl-c stop, twice kills`;
|
|
36
|
+
}
|
|
37
|
+
/** The platform opener, without a shell, so the address stays one argument. */
|
|
38
|
+
function openInBrowser(url) {
|
|
39
|
+
const command = process.platform === 'darwin'
|
|
40
|
+
? 'open'
|
|
41
|
+
: process.platform === 'win32'
|
|
42
|
+
? 'explorer'
|
|
43
|
+
: 'xdg-open';
|
|
44
|
+
spawn(command, [url], { stdio: 'ignore', detached: true })
|
|
45
|
+
.on('error', () => { })
|
|
46
|
+
.unref();
|
|
34
47
|
}
|
|
35
48
|
function header(s, theme) {
|
|
36
49
|
const c = s.counts;
|
|
@@ -108,7 +121,7 @@ const logCells = (cells, width) => {
|
|
|
108
121
|
return `${fit(where, 31)} ${fit(result, Math.max(10, width - 75))} ${fit(took, 7)} ${tokens}`;
|
|
109
122
|
};
|
|
110
123
|
/** The overview: under way, tokens, log - as many log rows as the height leaves. */
|
|
111
|
-
export function mainLines(s, selected, width, height, theme) {
|
|
124
|
+
export function mainLines(s, selected, width, height, theme, url) {
|
|
112
125
|
const quiet = theme.chrome.color;
|
|
113
126
|
const out = [...header(s, theme)];
|
|
114
127
|
if (s.now.length > 0) {
|
|
@@ -179,7 +192,7 @@ export function mainLines(s, selected, width, height, theme) {
|
|
|
179
192
|
]);
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
|
-
return [...out, ...footer(width, keysFor(s, false), theme)];
|
|
195
|
+
return [...out, ...footer(width, keysFor(s, false, url), theme)];
|
|
183
196
|
}
|
|
184
197
|
function activity(e, theme) {
|
|
185
198
|
const at = { text: `${clock(e.t)} `, color: theme.chrome.color };
|
|
@@ -204,10 +217,16 @@ function activity(e, theme) {
|
|
|
204
217
|
];
|
|
205
218
|
case 'say':
|
|
206
219
|
return [at, { text: 'says ', color: theme.accent }, { text: e.text }];
|
|
220
|
+
case 'think':
|
|
221
|
+
return [
|
|
222
|
+
at,
|
|
223
|
+
{ text: 'thinks ', color: theme.accent },
|
|
224
|
+
{ text: e.text, color: theme.chrome.color },
|
|
225
|
+
];
|
|
207
226
|
}
|
|
208
227
|
}
|
|
209
228
|
/** One step's agent, line by line as it works. */
|
|
210
|
-
export function followLines(s, key, width, height, theme) {
|
|
229
|
+
export function followLines(s, key, width, height, theme, url) {
|
|
211
230
|
const r = s.now.find((x) => x.key === key);
|
|
212
231
|
const out = [];
|
|
213
232
|
if (!r) {
|
|
@@ -230,9 +249,9 @@ export function followLines(s, key, width, height, theme) {
|
|
|
230
249
|
out.push(activity(e, theme));
|
|
231
250
|
}
|
|
232
251
|
}
|
|
233
|
-
return [...out, ...footer(width, keysFor(s, true), theme)];
|
|
252
|
+
return [...out, ...footer(width, keysFor(s, true, url), theme)];
|
|
234
253
|
}
|
|
235
|
-
function Live({ t, theme, interrupt, }) {
|
|
254
|
+
function Live({ t, theme, interrupt, url, }) {
|
|
236
255
|
const [snap, setSnap] = useState();
|
|
237
256
|
const [selected, setSelected] = useState(0);
|
|
238
257
|
const [follow, setFollow] = useState();
|
|
@@ -250,6 +269,12 @@ function Live({ t, theme, interrupt, }) {
|
|
|
250
269
|
interrupt();
|
|
251
270
|
return;
|
|
252
271
|
}
|
|
272
|
+
if (input === 'o') {
|
|
273
|
+
if (url) {
|
|
274
|
+
openInBrowser(url);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
253
278
|
if (follow) {
|
|
254
279
|
if (key.escape) {
|
|
255
280
|
setFollow(undefined);
|
|
@@ -271,8 +296,8 @@ function Live({ t, theme, interrupt, }) {
|
|
|
271
296
|
}
|
|
272
297
|
const height = Math.max(8, rows - 1);
|
|
273
298
|
const all = follow
|
|
274
|
-
? followLines(snap, follow, columns, height, theme)
|
|
275
|
-
: mainLines(snap, at, columns, height, theme);
|
|
299
|
+
? followLines(snap, follow, columns, height, theme, url)
|
|
300
|
+
: mainLines(snap, at, columns, height, theme, url);
|
|
276
301
|
// Too short a terminal loses rows from the middle, never the keys at the bottom.
|
|
277
302
|
const lines = all.length > height ? [...all.slice(0, height - 2), ...all.slice(-2)] : all;
|
|
278
303
|
return (_jsx(Box, { flexDirection: "column", children: lines.map((parts, i) => (_jsx(Text, { wrap: "truncate-end", children: parts.length === 0
|
|
@@ -280,9 +305,9 @@ function Live({ t, theme, interrupt, }) {
|
|
|
280
305
|
: parts.map((p, j) => (_jsx(Text, { color: p.color, bold: p.bold, children: p.text }, j))) }, i))) }));
|
|
281
306
|
}
|
|
282
307
|
/** Draws the tuning until the returned function is called. */
|
|
283
|
-
export async function watch(t, interrupt) {
|
|
308
|
+
export async function watch(t, interrupt, url) {
|
|
284
309
|
const theme = await resolveTheme();
|
|
285
|
-
const instance = render(_jsx(Live, { t: t, theme: theme, interrupt: interrupt }), {
|
|
310
|
+
const instance = render(_jsx(Live, { t: t, theme: theme, interrupt: interrupt, url: url }), {
|
|
286
311
|
stdout: process.stderr,
|
|
287
312
|
exitOnCtrlC: false,
|
|
288
313
|
});
|
|
@@ -5,6 +5,7 @@ import { Improvements } from './improvements.ts';
|
|
|
5
5
|
import { Journal } from './journal.ts';
|
|
6
6
|
import type { Snapshot } from './live.ts';
|
|
7
7
|
import { Memories } from './memories.ts';
|
|
8
|
+
import { Rechecks } from './recheck.ts';
|
|
8
9
|
import { SystemVersions } from './system.ts';
|
|
9
10
|
import type { ByStage } from './usage.ts';
|
|
10
11
|
export declare const FINETUNE_DIR = "finetune";
|
|
@@ -19,6 +20,16 @@ export interface Config {
|
|
|
19
20
|
/** merge the submitted memories after this many more arrive */
|
|
20
21
|
mergeEvery: number;
|
|
21
22
|
seed: number;
|
|
23
|
+
/** completed cases run again after each apply, to catch what it broke */
|
|
24
|
+
recheck: number;
|
|
25
|
+
/** bytes one apply may add to what the agents always read */
|
|
26
|
+
budget: number;
|
|
27
|
+
/** percent over the start past which an apply may add nothing */
|
|
28
|
+
maxGrowth: number;
|
|
29
|
+
/** bytes one on-demand skill may hold */
|
|
30
|
+
skillCap: number;
|
|
31
|
+
/** skills one agent may see */
|
|
32
|
+
maxSkills: number;
|
|
22
33
|
classes?: string[];
|
|
23
34
|
ids?: string[];
|
|
24
35
|
rubric?: boolean;
|
|
@@ -33,6 +44,11 @@ export interface Worker {
|
|
|
33
44
|
attempt?: number;
|
|
34
45
|
step: Step;
|
|
35
46
|
since: number;
|
|
47
|
+
/** a recheck: the apply whose version it runs on, and the try's folder */
|
|
48
|
+
recheck?: {
|
|
49
|
+
apply: string;
|
|
50
|
+
dir: string;
|
|
51
|
+
};
|
|
36
52
|
}
|
|
37
53
|
/** A case in flight. It holds a worker only while it runs or is analyzed; parked, it holds none. */
|
|
38
54
|
export interface Seat {
|
|
@@ -84,6 +100,7 @@ export declare class Tuning {
|
|
|
84
100
|
readonly system: SystemVersions;
|
|
85
101
|
readonly journal: Journal;
|
|
86
102
|
readonly improvements: Improvements;
|
|
103
|
+
readonly rechecks: Rechecks;
|
|
87
104
|
readonly memories: Memories;
|
|
88
105
|
stopping: boolean;
|
|
89
106
|
finished: boolean;
|
|
@@ -94,6 +111,8 @@ export declare class Tuning {
|
|
|
94
111
|
drained: boolean;
|
|
95
112
|
/** cases started by this process: a failed one is not taken twice in one start */
|
|
96
113
|
readonly tried: Set<string>;
|
|
114
|
+
/** `--more`: cases of any kind this start may take; not kept in loop.json */
|
|
115
|
+
more: number | undefined;
|
|
97
116
|
/** cases failed since the last step that went through */
|
|
98
117
|
failures: number;
|
|
99
118
|
/** what the last status page counted */
|
|
@@ -104,6 +123,8 @@ export declare class Tuning {
|
|
|
104
123
|
} | undefined;
|
|
105
124
|
/** called with every live snapshot */
|
|
106
125
|
readonly watchers: Set<(s: Snapshot) => void>;
|
|
126
|
+
/** the latest live snapshot */
|
|
127
|
+
live: Snapshot | undefined;
|
|
107
128
|
/** the latest step failures, newest first */
|
|
108
129
|
readonly errors: StepFailure[];
|
|
109
130
|
constructor(root: string, config: Config, zen: Zen, tickMs?: number);
|
|
@@ -135,6 +156,10 @@ export declare class Tuning {
|
|
|
135
156
|
id: string;
|
|
136
157
|
rev: number;
|
|
137
158
|
}, phase: Phase, attempt: number): string;
|
|
159
|
+
/** The folder of the try a worker is on: the case's own, or a recheck's. */
|
|
160
|
+
workDir(w: Worker & {
|
|
161
|
+
case: Case;
|
|
162
|
+
}): string;
|
|
138
163
|
/** What the loop knows of a try, which a feedback file is never trusted for. */
|
|
139
164
|
known(c: {
|
|
140
165
|
id: string;
|
|
@@ -159,7 +184,8 @@ export declare class Tuning {
|
|
|
159
184
|
/** A step failed: kept for the live view, and in events.jsonl with its log. */
|
|
160
185
|
error(where: string, message: string, log?: string): void;
|
|
161
186
|
event(e: Omit<Event, 'at'>): void;
|
|
162
|
-
report
|
|
187
|
+
/** `live`: only what moves by the second is read again; the rest comes from the last full report. */
|
|
188
|
+
report(live?: boolean): void;
|
|
163
189
|
}
|
|
164
190
|
/** The real `zen`: this same binary, in its own process group so Ctrl-C reaches only us. */
|
|
165
191
|
export declare function spawnZen(root: string, children: Set<ChildProcess>): Zen;
|
|
@@ -7,6 +7,7 @@ import { forget, remember } from "./children.js";
|
|
|
7
7
|
import { Improvements } from "./improvements.js";
|
|
8
8
|
import { Journal } from "./journal.js";
|
|
9
9
|
import { Memories } from "./memories.js";
|
|
10
|
+
import { Rechecks } from "./recheck.js";
|
|
10
11
|
import { writeStatus } from "./report.js";
|
|
11
12
|
import { SystemVersions } from "./system.js";
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -26,6 +27,11 @@ export const DEFAULTS = {
|
|
|
26
27
|
memTries: 3,
|
|
27
28
|
mergeEvery: 3,
|
|
28
29
|
seed: 1,
|
|
30
|
+
recheck: 3,
|
|
31
|
+
budget: 1500,
|
|
32
|
+
maxGrowth: 50,
|
|
33
|
+
skillCap: 12_000,
|
|
34
|
+
maxSkills: 10,
|
|
29
35
|
};
|
|
30
36
|
export class Stopped extends Error {
|
|
31
37
|
constructor() {
|
|
@@ -48,6 +54,7 @@ export class Tuning {
|
|
|
48
54
|
system;
|
|
49
55
|
journal;
|
|
50
56
|
improvements;
|
|
57
|
+
rechecks;
|
|
51
58
|
memories;
|
|
52
59
|
stopping = false;
|
|
53
60
|
finished = false;
|
|
@@ -58,12 +65,16 @@ export class Tuning {
|
|
|
58
65
|
drained = false;
|
|
59
66
|
/** cases started by this process: a failed one is not taken twice in one start */
|
|
60
67
|
tried = new Set();
|
|
68
|
+
/** `--more`: cases of any kind this start may take; not kept in loop.json */
|
|
69
|
+
more;
|
|
61
70
|
/** cases failed since the last step that went through */
|
|
62
71
|
failures = 0;
|
|
63
72
|
/** what the last status page counted */
|
|
64
73
|
progress;
|
|
65
74
|
/** called with every live snapshot */
|
|
66
75
|
watchers = new Set();
|
|
76
|
+
/** the latest live snapshot */
|
|
77
|
+
live;
|
|
67
78
|
/** the latest step failures, newest first */
|
|
68
79
|
errors = [];
|
|
69
80
|
constructor(root, config, zen, tickMs = 1000) {
|
|
@@ -80,6 +91,7 @@ export class Tuning {
|
|
|
80
91
|
this.system = new SystemVersions(root, join(this.dir, 'systems'));
|
|
81
92
|
this.journal = new Journal(join(this.dir, 'journal.jsonl'));
|
|
82
93
|
this.improvements = new Improvements(this);
|
|
94
|
+
this.rechecks = new Rechecks(this);
|
|
83
95
|
this.memories = new Memories(this);
|
|
84
96
|
}
|
|
85
97
|
get stopFile() {
|
|
@@ -139,6 +151,10 @@ export class Tuning {
|
|
|
139
151
|
attemptDir(c, phase, attempt) {
|
|
140
152
|
return join(this.caseDir(c), `${pad(attempt)}-${phase}`);
|
|
141
153
|
}
|
|
154
|
+
/** The folder of the try a worker is on: the case's own, or a recheck's. */
|
|
155
|
+
workDir(w) {
|
|
156
|
+
return w.recheck?.dir ?? this.attemptDir(w.case, w.phase ?? 'nomem', w.attempt ?? 1);
|
|
157
|
+
}
|
|
142
158
|
/** What the loop knows of a try, which a feedback file is never trusted for. */
|
|
143
159
|
known(c, phase, attempt) {
|
|
144
160
|
const round = this.round(c);
|
|
@@ -163,6 +179,7 @@ export class Tuning {
|
|
|
163
179
|
case: s.case,
|
|
164
180
|
phase: undefined,
|
|
165
181
|
attempt: undefined,
|
|
182
|
+
recheck: undefined,
|
|
166
183
|
step: 'idle',
|
|
167
184
|
since: Date.now(),
|
|
168
185
|
});
|
|
@@ -188,6 +205,7 @@ export class Tuning {
|
|
|
188
205
|
case: undefined,
|
|
189
206
|
phase: undefined,
|
|
190
207
|
attempt: undefined,
|
|
208
|
+
recheck: undefined,
|
|
191
209
|
step: 'idle',
|
|
192
210
|
since: Date.now(),
|
|
193
211
|
});
|
|
@@ -233,9 +251,10 @@ export class Tuning {
|
|
|
233
251
|
this.recent.length = Math.min(this.recent.length, 50);
|
|
234
252
|
this.report();
|
|
235
253
|
}
|
|
236
|
-
report
|
|
254
|
+
/** `live`: only what moves by the second is read again; the rest comes from the last full report. */
|
|
255
|
+
report(live = false) {
|
|
237
256
|
try {
|
|
238
|
-
writeStatus(this);
|
|
257
|
+
writeStatus(this, live);
|
|
239
258
|
}
|
|
240
259
|
catch {
|
|
241
260
|
// A status page that failed to render must not stop the tuning.
|
|
@@ -40,6 +40,8 @@ export interface RunMetrics {
|
|
|
40
40
|
branches: number;
|
|
41
41
|
compactions: number;
|
|
42
42
|
handoffs: number;
|
|
43
|
+
/** skills loaded, preloads included, in the order first loaded */
|
|
44
|
+
skills: string[];
|
|
43
45
|
/** the first error a sandbox tool got from the container engine, not from a command */
|
|
44
46
|
sandbox?: string;
|
|
45
47
|
}
|
|
@@ -57,6 +59,8 @@ export declare function runTokens(runDir: string): ByModel | undefined;
|
|
|
57
59
|
export declare function envelopeTokens(file: string): ByModel | undefined;
|
|
58
60
|
/** Every envelope in `dir` named `<stem>.json` or `<stem>-<n>.json` - retries keep their own. */
|
|
59
61
|
export declare function envelopesIn(dir: string, stem: string): ByModel | undefined;
|
|
62
|
+
/** An apply's own envelopes, and - for applies from before rechecks - its guard's runs: `guard/<attempt>/<case>/<try>/`. */
|
|
63
|
+
export declare function applyTokens(dir: string): ByModel | undefined;
|
|
60
64
|
/** 1234 → 1.2k, 2184341 → 2.2M. */
|
|
61
65
|
export declare function short(n: number): string;
|
|
62
66
|
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -88,6 +88,7 @@ export function runMetrics(runDir) {
|
|
|
88
88
|
branches: 0,
|
|
89
89
|
compactions: 0,
|
|
90
90
|
handoffs: 0,
|
|
91
|
+
skills: [],
|
|
91
92
|
};
|
|
92
93
|
const seen = new Set();
|
|
93
94
|
const count = (o, ms) => {
|
|
@@ -140,6 +141,14 @@ export function runMetrics(runDir) {
|
|
|
140
141
|
case 'handoff':
|
|
141
142
|
m.handoffs++;
|
|
142
143
|
return;
|
|
144
|
+
case 'load_skills':
|
|
145
|
+
for (const s of Array.isArray(o.skills) ? o.skills : []) {
|
|
146
|
+
const name = s?.name;
|
|
147
|
+
if (typeof name === 'string' && !m.skills.includes(name)) {
|
|
148
|
+
m.skills.push(name);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
143
152
|
}
|
|
144
153
|
};
|
|
145
154
|
// A branch lane starts where its parent lane was when the fork began.
|
|
@@ -212,6 +221,32 @@ export function envelopesIn(dir, stem) {
|
|
|
212
221
|
const parts = names.map((n) => envelopeTokens(join(dir, n))).filter((p) => !!p);
|
|
213
222
|
return parts.length > 0 ? mergeModels(...parts) : undefined;
|
|
214
223
|
}
|
|
224
|
+
/** An apply's own envelopes, and - for applies from before rechecks - its guard's runs: `guard/<attempt>/<case>/<try>/`. */
|
|
225
|
+
export function applyTokens(dir) {
|
|
226
|
+
const parts = [envelopesIn(dir, 'apply')];
|
|
227
|
+
const under = (d) => {
|
|
228
|
+
try {
|
|
229
|
+
return readdirSync(d, { withFileTypes: true })
|
|
230
|
+
.filter((e) => e.isDirectory())
|
|
231
|
+
.map((e) => join(d, e.name));
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
for (const tryDir of under(join(dir, 'guard')).flatMap(under).flatMap(under)) {
|
|
238
|
+
parts.push(envelopesIn(tryDir, 'analyze'));
|
|
239
|
+
try {
|
|
240
|
+
const run = JSON.parse(readFileSync(join(tryDir, 'run.json'), 'utf8'));
|
|
241
|
+
parts.push(run.tokens);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// A void run wrote none.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const found = parts.filter((p) => !!p);
|
|
248
|
+
return found.length > 0 ? mergeModels(...found) : undefined;
|
|
249
|
+
}
|
|
215
250
|
/** 1234 → 1.2k, 2184341 → 2.2M. */
|
|
216
251
|
export function short(n) {
|
|
217
252
|
if (n >= 1_000_000) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface Viewing {
|
|
2
|
+
url: string;
|
|
3
|
+
close(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
/** The address another live process serves the tuning on, if one does. */
|
|
6
|
+
export declare function liveView(projectDir: string): string | undefined;
|
|
7
|
+
/** Serves the project, opening at STATUS.md, until `close`. */
|
|
8
|
+
export declare function startView(projectDir: string): Promise<Viewing>;
|
|
9
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { liveHolder, ownLock } from '@zenera/neo';
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { FINETUNE_DIR } from "./tuning.js";
|
|
5
|
+
const lockFile = (projectDir) => join(projectDir, FINETUNE_DIR, 'view.json');
|
|
6
|
+
/** The address another live process serves the tuning on, if one does. */
|
|
7
|
+
export function liveView(projectDir) {
|
|
8
|
+
return liveHolder(lockFile(projectDir))?.url;
|
|
9
|
+
}
|
|
10
|
+
/** Serves the project, opening at STATUS.md, until `close`. */
|
|
11
|
+
export async function startView(projectDir) {
|
|
12
|
+
const { serveView } = await import('@zenera/view');
|
|
13
|
+
const server = await serveView({ root: projectDir, start: `/${FINETUNE_DIR}/STATUS.md` });
|
|
14
|
+
const file = lockFile(projectDir);
|
|
15
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
16
|
+
writeFileSync(file, `${JSON.stringify(ownLock({ url: server.url }), null, 2)}\n`);
|
|
17
|
+
return {
|
|
18
|
+
url: server.url,
|
|
19
|
+
close: async () => {
|
|
20
|
+
if (liveHolder(file)?.pid === process.pid) {
|
|
21
|
+
rmSync(file, { force: true });
|
|
22
|
+
}
|
|
23
|
+
await server.close();
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=view.js.map
|
package/dist/meta/host.d.ts
CHANGED
|
@@ -4,11 +4,12 @@ export { paths, readJson, writeJson } from '../home.ts';
|
|
|
4
4
|
export { envOf, type KeyEntry, type KeyStore, type Provider } from '../keys.ts';
|
|
5
5
|
export { PROVIDER_NAMES, splitRef } from '../modelref.ts';
|
|
6
6
|
export { duration } from '../narrate.ts';
|
|
7
|
-
export { copyTree,
|
|
7
|
+
export { copyTree, SHARED_RULES, TEMPLATES } from '../scaffold.ts';
|
|
8
8
|
export { bold, CliError, confirm, credentialError, cut, cyan, dim, EXIT, green, invalidError, json, note, pad, plain, red, styled, table, usageError, write, writeAll, yellow, } from '../term.ts';
|
|
9
9
|
export { safe } from '../trace.ts';
|
|
10
10
|
export { formatInline, formatMarkdown } from '../tui/markdown.ts';
|
|
11
11
|
export { resolveTheme, type Theme } from '../tui/theme.ts';
|
|
12
12
|
export { boxWidth } from '../tui/wrap.ts';
|
|
13
13
|
export { META_PROMPT_ENV, META_SESSION_ENV } from '../usage.ts';
|
|
14
|
+
export { validateProject, type Report as ProjectReport } from '../validate.ts';
|
|
14
15
|
//# sourceMappingURL=host.d.ts.map
|