@zenera/cli 1.1.24 → 1.1.26
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 +65 -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 +31 -1
- package/dist/catalog.js +140 -7
- package/dist/commands/index.js +2 -0
- package/dist/commands/inspect.d.ts +52 -2
- package/dist/commands/inspect.js +220 -31
- package/dist/commands/memory.js +156 -32
- package/dist/commands/meta.js +333 -24
- package/dist/commands/models.js +97 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +148 -16
- package/dist/commands/view.d.ts +8 -0
- package/dist/commands/view.js +114 -0
- package/dist/engine.d.ts +8 -1
- package/dist/engine.js +7 -0
- package/dist/eventlog.d.ts +64 -0
- package/dist/eventlog.js +170 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +35 -3
- package/dist/main.js +0 -0
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +43 -0
- 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/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 +516 -0
- package/dist/meta/finetune/cost.d.ts +46 -0
- package/dist/meta/finetune/cost.js +105 -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 +127 -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 +57 -0
- package/dist/meta/finetune/feedback.js +104 -0
- package/dist/meta/finetune/guard.d.ts +29 -0
- package/dist/meta/finetune/guard.js +104 -0
- package/dist/meta/finetune/improvements.d.ts +98 -0
- package/dist/meta/finetune/improvements.js +501 -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 +84 -0
- package/dist/meta/finetune/live.js +272 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +163 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/recheck.d.ts +58 -0
- package/dist/meta/finetune/recheck.js +311 -0
- package/dist/meta/finetune/report.d.ts +40 -0
- package/dist/meta/finetune/report.js +1397 -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 +47 -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 +43 -0
- package/dist/meta/finetune/train.js +425 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +316 -0
- package/dist/meta/finetune/tuning.d.ts +193 -0
- package/dist/meta/finetune/tuning.js +308 -0
- package/dist/meta/finetune/usage.d.ts +66 -0
- package/dist/meta/finetune/usage.js +260 -0
- package/dist/meta/finetune/view.d.ts +9 -0
- package/dist/meta/finetune/view.js +27 -0
- package/dist/meta/host.d.ts +15 -0
- package/dist/meta/host.js +21 -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/meta.d.ts +71 -1
- package/dist/meta.js +303 -6
- 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/resources.d.ts +215 -0
- package/dist/resources.js +856 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -7
- package/dist/scaffold.js +24 -67
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.d.ts +5 -0
- package/dist/trace.js +107 -7
- 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 +35 -0
- package/dist/tui/models.js +447 -0
- package/dist/tui/sandbox.d.ts +10 -0
- package/dist/tui/sandbox.js +131 -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 +4 -2
- package/templates/editor/.github/copilot-instructions.md +60 -145
- package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -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 +5 -4
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -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 +137 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
- 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/projects.md +4 -6
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
- 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 +1492 -660
- 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 +74 -26
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
- package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
- 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/fork-instructions.md +78 -28
- package/templates/project/agents/memory-instructions.md +222 -494
- package/templates/project/agents/tools-instructions.md +14 -4
- package/templates/project/gitignore +2 -0
- 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/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
package/dist/commands/meta.js
CHANGED
|
@@ -1,16 +1,60 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readProjectConfig } from '@zenera/neo';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
7
|
import { parse } from "../args.js";
|
|
3
8
|
import { loadProjectEnv } from "../env.js";
|
|
9
|
+
import { copilotEventLog } from "../eventlog.js";
|
|
4
10
|
import { ensureHome } from "../home.js";
|
|
5
11
|
import { assertOwner, KeyStore, PROVIDERS } from "../keys.js";
|
|
6
|
-
import { probeModel } from "../liveness.js";
|
|
7
|
-
import { answerBox, chooseModel, defaultRef, launch, listPrompts, loadPrompt, locate, masked, misspelledProvider, MODEL_ENV, openLog, ORDER, PROMPT_DIR, providersWarning, readMeta, RECOMMENDED, SOURCE_LABELS,
|
|
12
|
+
import { probeChat, probeModel } from "../liveness.js";
|
|
13
|
+
import { answerBox, chooseModel, DATASET_DETAILS, DATASET_SUMMARY, DATASET_USAGE, datasetProjectFlag, DEFAULT_RESUMES, defaultRef, editorFiles, FINETUNE_DETAILS, FINETUNE_SUMMARY, FINETUNE_USAGE, finetuneProjectFlag, lastSession, launch, listPrompts, loadPrompt, locate, masked, misspelledProvider, MODEL_ENV, openLog, ORDER, PROMPT_DIR, providersWarning, readMeta, RECOMMENDED, recordSession, RESUME_PROMPT, resumeDelayMs, runDataset, runFinetune, sameModel, sessionModel, sessionTotals, SOURCE_LABELS, startVertexRelay, tailSpans, Tally, terminalSink, transient, wire, writeMeta, } from "../meta/index.js";
|
|
14
|
+
import { splitRef } from "../modelref.js";
|
|
15
|
+
import { duration } from "../narrate.js";
|
|
8
16
|
import * as Projects from "../projects.js";
|
|
9
17
|
import { project as resolveProject } from "../resolve.js";
|
|
10
|
-
import { editorFiles } from "../scaffold.js";
|
|
11
18
|
import { bold, choose, CliError, credentialError, cyan, dim, EXIT, green, isInteractive, json, note, progress, readStdin, table, usageError, warn, write, writeAll, } from "../term.js";
|
|
19
|
+
import { appendUsage, LEDGER_ENV, ledgerPath, META_PROMPT_ENV, META_SESSION_ENV, } from "../usage.js";
|
|
12
20
|
const USAGE = 'zen meta run [project] [prompt] [options]';
|
|
13
|
-
const VERBS = new Set(['model', 'run', 'prompts']);
|
|
21
|
+
const VERBS = new Set(['model', 'run', 'prompts', 'resume', 'dataset', 'finetune']);
|
|
22
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
|
+
/** A copilot session id, or the 7+ hex prefix of one it also accepts. */
|
|
24
|
+
const SESSION_ID = /^[0-9a-f]{7,}(?:-[0-9a-f]+)*$/i;
|
|
25
|
+
/** Verbs that own their flags and their help page. */
|
|
26
|
+
const SUBCOMMANDS = {
|
|
27
|
+
dataset: {
|
|
28
|
+
run: runDataset,
|
|
29
|
+
projectFlag: datasetProjectFlag,
|
|
30
|
+
page: { usage: DATASET_USAGE, summary: DATASET_SUMMARY, details: DATASET_DETAILS },
|
|
31
|
+
},
|
|
32
|
+
finetune: {
|
|
33
|
+
run: runFinetune,
|
|
34
|
+
projectFlag: finetuneProjectFlag,
|
|
35
|
+
page: { usage: FINETUNE_USAGE, summary: FINETUNE_SUMMARY, details: FINETUNE_DETAILS },
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/** `zen meta dataset ...` or `zen meta acme dataset ...`: which one, and where its own args start. */
|
|
39
|
+
function subcommand(args) {
|
|
40
|
+
for (const name of Object.keys(SUBCOMMANDS)) {
|
|
41
|
+
if (args[0] === name) {
|
|
42
|
+
return { name, at: 0 };
|
|
43
|
+
}
|
|
44
|
+
if (args[1] === name && !args[0]?.startsWith('-')) {
|
|
45
|
+
return { name, at: 1 };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
function page(p) {
|
|
51
|
+
write(bold(p.usage));
|
|
52
|
+
write(`\n ${p.summary}`);
|
|
53
|
+
write('');
|
|
54
|
+
for (const line of p.details) {
|
|
55
|
+
write(line ? ` ${line}` : '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
14
58
|
export const meta = {
|
|
15
59
|
summary: 'Run the meta agent over this project, on your own keys.',
|
|
16
60
|
usage: USAGE,
|
|
@@ -21,9 +65,13 @@ export const meta = {
|
|
|
21
65
|
' zen meta run [project] /<name> [words] run a stored prompt',
|
|
22
66
|
' zen meta run [project] pick one of its stored prompts',
|
|
23
67
|
' zen meta prompts [project] list the stored prompts',
|
|
68
|
+
' zen meta resume [project] [session] carry on a run that stopped',
|
|
24
69
|
' zen meta model [ref] show or set the model it uses',
|
|
70
|
+
' zen meta dataset <verb> the cases it tunes against',
|
|
71
|
+
' zen meta finetune <verb> tune the project on those cases',
|
|
25
72
|
'',
|
|
26
73
|
'The project may come before the verb instead: `zen meta acme run`.',
|
|
74
|
+
'`zen meta dataset --help` and `zen meta finetune --help` have their own pages.',
|
|
27
75
|
'',
|
|
28
76
|
'Arguments:',
|
|
29
77
|
' [project] Name of a project. Default: the one you are in.',
|
|
@@ -35,14 +83,21 @@ export const meta = {
|
|
|
35
83
|
' --project <name|dir> Which project to work in. The agent is rooted there.',
|
|
36
84
|
' --model <ref> Model for this run, e.g. vertex/gemini-3.8-flash.',
|
|
37
85
|
' --provider <name> Force the key to use. Default: the model ref says.',
|
|
86
|
+
' --prompt-file <file> Append this file to the prompt, after any words.',
|
|
38
87
|
' --agent <name> A .github/agents/<name>.agent.md.',
|
|
39
88
|
' --effort <level> none, minimal, low, medium, high, xhigh or max.',
|
|
40
89
|
' --allow-tool <list> Only these: read, shell(git:*), write(README.md).',
|
|
41
90
|
' --ask Have it ask before each tool. Default: it does not.',
|
|
42
91
|
' --add-dir <dir> Another directory it may touch. Repeatable.',
|
|
43
92
|
' --resume <id> Continue a copilot session. --continue takes the last.',
|
|
93
|
+
' --session-id <uuid> Start a new session under this id. Default: a random one.',
|
|
94
|
+
` --retries <n> Resume after a rate limit or outage. Default ${DEFAULT_RESUMES}, 0 = off.`,
|
|
44
95
|
' --share <file> Write the transcript to a markdown file.',
|
|
96
|
+
' --events <file> Append what the agent does to this file as it does it:',
|
|
97
|
+
' one JSON line per model call, tool call and message.',
|
|
45
98
|
' --dry-run Print what would run, secrets masked, and stop.',
|
|
99
|
+
' --no-refresh Leave the editor files as they are (parallel runs).',
|
|
100
|
+
' --json Print one object: answer, answerFile, sessionId, exitCode, tokens.',
|
|
46
101
|
'',
|
|
47
102
|
'It always uses your own keys — `zen key add` — and never a coding-agent',
|
|
48
103
|
'subscription. The answer goes to stdout and the progress to stderr, so',
|
|
@@ -53,6 +108,13 @@ export const meta = {
|
|
|
53
108
|
'watching to answer. Narrow it with --allow-tool, or restore the asking',
|
|
54
109
|
'with --ask.',
|
|
55
110
|
'',
|
|
111
|
+
'`zen meta resume` picks up the last session the project ran - or the one',
|
|
112
|
+
'named - where it stopped. A run that ends any way but success prints it.',
|
|
113
|
+
'',
|
|
114
|
+
'Its tokens are counted as it runs and summed when it ends. In a project with',
|
|
115
|
+
'a finetune/, every call - and every zen run and inspect ask it makes - is',
|
|
116
|
+
'appended to finetune/usage/ledger.jsonl, and finetune/USAGE.md is rewritten.',
|
|
117
|
+
'',
|
|
56
118
|
'Nothing it needs is put on a command line: every credential reaches it',
|
|
57
119
|
'through the environment, where other processes cannot read it.',
|
|
58
120
|
'',
|
|
@@ -75,22 +137,43 @@ export const meta = {
|
|
|
75
137
|
' zen meta prompts',
|
|
76
138
|
' zen meta run /project-review',
|
|
77
139
|
' zen meta run acme /spec-sync-project agents/triage.md',
|
|
140
|
+
' zen meta run acme /analyze --json',
|
|
141
|
+
' zen meta resume acme',
|
|
78
142
|
' git diff | zen meta run "what broke?" --allow-tool read',
|
|
79
143
|
' zen meta model vertex/gemini-3.8-flash',
|
|
80
144
|
' zen meta model --pick',
|
|
81
145
|
' zen meta run --dry-run "hello"',
|
|
82
146
|
],
|
|
147
|
+
help: (ctx) => {
|
|
148
|
+
const sub = subcommand(ctx.args);
|
|
149
|
+
page(sub ? SUBCOMMANDS[sub.name].page : { ...meta, details: meta.details ?? [] });
|
|
150
|
+
},
|
|
83
151
|
run: async (ctx) => {
|
|
152
|
+
// These own their flags, so they are routed before the meta agent's are parsed.
|
|
153
|
+
const sub = subcommand(ctx.args);
|
|
154
|
+
if (sub) {
|
|
155
|
+
const { run, projectFlag } = SUBCOMMANDS[sub.name];
|
|
156
|
+
const args = ctx.args.slice(sub.at + 1);
|
|
157
|
+
const found = await resolveProject({
|
|
158
|
+
cwd: ctx.cwd,
|
|
159
|
+
project: projectFlag(args) ?? (sub.at === 1 ? ctx.args[0] : undefined),
|
|
160
|
+
});
|
|
161
|
+
return await run({ args, json: ctx.json, cwd: ctx.cwd }, found);
|
|
162
|
+
}
|
|
84
163
|
const { values, positionals } = parse(ctx.args, {
|
|
85
164
|
project: { type: 'string' },
|
|
86
165
|
provider: { type: 'string' },
|
|
87
166
|
model: { type: 'string' },
|
|
88
167
|
prompt: { type: 'string', short: 'p' },
|
|
168
|
+
'prompt-file': { type: 'string' },
|
|
89
169
|
agent: { type: 'string' },
|
|
90
170
|
effort: { type: 'string' },
|
|
91
171
|
share: { type: 'string' },
|
|
172
|
+
events: { type: 'string' },
|
|
92
173
|
resume: { type: 'string' },
|
|
93
174
|
continue: { type: 'boolean' },
|
|
175
|
+
'session-id': { type: 'string' },
|
|
176
|
+
retries: { type: 'string' },
|
|
94
177
|
'allow-all': { type: 'boolean' },
|
|
95
178
|
'allow-tool': { type: 'string' },
|
|
96
179
|
ask: { type: 'boolean' },
|
|
@@ -100,6 +183,7 @@ export const meta = {
|
|
|
100
183
|
pick: { type: 'boolean' },
|
|
101
184
|
clear: { type: 'boolean' },
|
|
102
185
|
force: { type: 'boolean' },
|
|
186
|
+
'no-refresh': { type: 'boolean' },
|
|
103
187
|
}, USAGE);
|
|
104
188
|
// `zen meta acme run` is typed at least as often as `zen meta run acme`,
|
|
105
189
|
// and read literally it asks the agent the one-word question "run" —
|
|
@@ -126,6 +210,9 @@ export const meta = {
|
|
|
126
210
|
if (verb === 'prompts') {
|
|
127
211
|
return await prompts(ctx, values, args);
|
|
128
212
|
}
|
|
213
|
+
if (verb === 'resume') {
|
|
214
|
+
return await resumeRun(ctx, values, args);
|
|
215
|
+
}
|
|
129
216
|
throw usageError(first === undefined ? 'nothing to run' : 'a prompt goes through `run`', `try: zen meta run ${positionals.map(requote).join(' ') || '"what does this project do?"'}`);
|
|
130
217
|
},
|
|
131
218
|
};
|
|
@@ -158,7 +245,9 @@ async function where(ctx, flag, positionals) {
|
|
|
158
245
|
function refresh(project) {
|
|
159
246
|
try {
|
|
160
247
|
const written = editorFiles(project.dir);
|
|
161
|
-
|
|
248
|
+
if (written.length) {
|
|
249
|
+
note(dim(`refreshed ${written.length} editor files in ${project.name}`));
|
|
250
|
+
}
|
|
162
251
|
}
|
|
163
252
|
catch (err) {
|
|
164
253
|
warn(`could not refresh the editor files — ${err.message}`);
|
|
@@ -191,11 +280,13 @@ async function stored(ctx, values, args) {
|
|
|
191
280
|
rest = args;
|
|
192
281
|
}
|
|
193
282
|
const { project } = await where(ctx, values.project, head);
|
|
194
|
-
refresh
|
|
283
|
+
if (!values['no-refresh']) {
|
|
284
|
+
refresh(project);
|
|
285
|
+
}
|
|
195
286
|
if (name) {
|
|
196
287
|
return await runPrompt(ctx, values, project, name, rest);
|
|
197
288
|
}
|
|
198
|
-
const question = (values.prompt ?? rest.join(' ')).trim() || (await readStdin());
|
|
289
|
+
const question = withFile(values, (values.prompt ?? rest.join(' ')).trim()) || (await readStdin());
|
|
199
290
|
if (question) {
|
|
200
291
|
return await go(ctx, values, project, question);
|
|
201
292
|
}
|
|
@@ -207,8 +298,23 @@ async function runPrompt(ctx, values, project, name, extra) {
|
|
|
207
298
|
if (found.description) {
|
|
208
299
|
note(dim(found.description));
|
|
209
300
|
}
|
|
210
|
-
const prompt = extra.length > 0 ? `${found.body}\n\n${extra.join(' ')}` : found.body;
|
|
211
|
-
await go(ctx, values, project, prompt);
|
|
301
|
+
const prompt = withFile(values, extra.length > 0 ? `${found.body}\n\n${extra.join(' ')}` : found.body);
|
|
302
|
+
await go(ctx, values, project, prompt, name.replace(/^\//, ''));
|
|
303
|
+
}
|
|
304
|
+
/** The prompt with `--prompt-file` after it: text too long, or too awkward to quote, for a word. */
|
|
305
|
+
function withFile(values, prompt) {
|
|
306
|
+
const file = values['prompt-file'];
|
|
307
|
+
if (!file) {
|
|
308
|
+
return prompt;
|
|
309
|
+
}
|
|
310
|
+
let text;
|
|
311
|
+
try {
|
|
312
|
+
text = readFileSync(resolve(file), 'utf8').trim();
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
throw usageError(`no prompt file at ${file}`, 'name a file that exists');
|
|
316
|
+
}
|
|
317
|
+
return [prompt, text].filter(Boolean).join('\n\n');
|
|
212
318
|
}
|
|
213
319
|
async function pickPrompt(ctx, project) {
|
|
214
320
|
const names = await listPrompts(project.dir);
|
|
@@ -243,9 +349,40 @@ async function prompts(ctx, values, args) {
|
|
|
243
349
|
note(dim(`run one: zen meta run /${found[0].name}`));
|
|
244
350
|
}
|
|
245
351
|
// ---------------------------------------------------------------------------
|
|
352
|
+
// zen meta resume [project] [session id] [words...]
|
|
353
|
+
//
|
|
354
|
+
// A long run stops for reasons nobody chose - a refused request, a closed
|
|
355
|
+
// laptop, Ctrl-C. Copilot keeps the session; this hands it back, by default the
|
|
356
|
+
// last one the project ran, with the words (or a plain "continue") as the turn.
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
async function resumeRun(ctx, values, args) {
|
|
359
|
+
const { project, rest } = await where(ctx, values.project, args);
|
|
360
|
+
const named = rest[0] !== undefined && SESSION_ID.test(rest[0]) ? rest[0] : undefined;
|
|
361
|
+
const words = named ? rest.slice(1) : rest;
|
|
362
|
+
const session = named ?? values.resume ?? lastSession(project.dir);
|
|
363
|
+
if (!session) {
|
|
364
|
+
throw usageError(`no meta session recorded in ${project.name}`, `name one: zen meta resume ${project.name} <session id>`);
|
|
365
|
+
}
|
|
366
|
+
note(dim(`resuming session ${session}`));
|
|
367
|
+
if (!values['no-refresh']) {
|
|
368
|
+
refresh(project);
|
|
369
|
+
}
|
|
370
|
+
const message = withFile(values, (values.prompt ?? words.join(' ')).trim()) || RESUME_PROMPT;
|
|
371
|
+
await go(ctx, { ...values, resume: session, continue: false }, project, message);
|
|
372
|
+
}
|
|
373
|
+
// ---------------------------------------------------------------------------
|
|
246
374
|
// The run itself
|
|
247
375
|
// ---------------------------------------------------------------------------
|
|
248
|
-
async function go(ctx, values, project, prompt) {
|
|
376
|
+
async function go(ctx, values, project, prompt, promptName) {
|
|
377
|
+
const fresh = values['session-id'];
|
|
378
|
+
if (fresh !== undefined) {
|
|
379
|
+
if (values.resume || values.continue) {
|
|
380
|
+
throw usageError('--session-id starts a new session', 'drop --resume / --continue');
|
|
381
|
+
}
|
|
382
|
+
if (!UUID.test(fresh)) {
|
|
383
|
+
throw usageError(`--session-id must be a UUID, not ${fresh}`, 'try: uuidgen');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
249
386
|
const shell = process.env[MODEL_ENV];
|
|
250
387
|
loadProjectEnv(project.dir);
|
|
251
388
|
ensureHome();
|
|
@@ -280,7 +417,19 @@ async function go(ctx, values, project, prompt) {
|
|
|
280
417
|
}
|
|
281
418
|
const wiring = wire(store, entry, id);
|
|
282
419
|
const binary = locate();
|
|
283
|
-
|
|
420
|
+
// Copilot cannot carry a session to another model: the resume fails on the first call.
|
|
421
|
+
const ran = values.resume ? sessionModel(values.resume) : undefined;
|
|
422
|
+
if (values.resume && ran && !sameModel(ran, wiring.model)) {
|
|
423
|
+
if (prompt === RESUME_PROMPT) {
|
|
424
|
+
throw usageError(`session ${values.resume} ran on ${ran}, and the meta model is now ${wiring.model} - copilot cannot carry a session to another model`, `switch back for it: zen meta model ${owner}/${ran.replace(/^.*\//, '')} - or ask again, which starts a new session`);
|
|
425
|
+
}
|
|
426
|
+
warn(`session ${values.resume} ran on ${ran}; the meta model is now ${wiring.model}, so this starts a new session`);
|
|
427
|
+
values = { ...values, resume: undefined };
|
|
428
|
+
}
|
|
429
|
+
// Named up front, so a run killed before copilot reports its id can still be resumed.
|
|
430
|
+
let session = values.resume ?? (values.continue ? undefined : (fresh ?? randomUUID()));
|
|
431
|
+
const args = argv(values, project.dir, prompt, wiring.secret, session);
|
|
432
|
+
const retries = count(values.retries, '--retries') ?? DEFAULT_RESUMES;
|
|
284
433
|
if (values['dry-run']) {
|
|
285
434
|
const lines = masked(wiring.env, wiring.secret);
|
|
286
435
|
if (ctx.json) {
|
|
@@ -310,33 +459,112 @@ async function go(ctx, values, project, prompt) {
|
|
|
310
459
|
note(dim('set another: zen meta model <provider>/<id>'));
|
|
311
460
|
}
|
|
312
461
|
note(cyan(`${owner} · ${wiring.model} · ${project.name}`));
|
|
313
|
-
const log = openLog(project.dir);
|
|
462
|
+
const log = openLog(project.dir, new Date(), session);
|
|
314
463
|
note(`log ${log.path}`);
|
|
315
464
|
const elided = args.map((a, i) => (args[i - 1] === '-p' ? '<prompt>' : a));
|
|
316
465
|
log.line(`zen meta ${new Date().toISOString()}`);
|
|
317
466
|
log.line(`project ${project.name} ${project.dir}`);
|
|
318
467
|
log.line(`model ${chosen.ref} from ${SOURCE_LABELS[chosen.from]}`);
|
|
319
468
|
log.line(`command ${[binary.command, ...binary.args, ...elided].join(' ')}`);
|
|
469
|
+
if (session) {
|
|
470
|
+
recordSession(project.dir, session);
|
|
471
|
+
log.line(`session ${session}`);
|
|
472
|
+
}
|
|
473
|
+
const relay = wiring.relay ? await startVertexRelay(wiring.relay) : undefined;
|
|
474
|
+
if (relay) {
|
|
475
|
+
wiring.env.COPILOT_PROVIDER_BASE_URL = relay.url;
|
|
476
|
+
log.line(`relay ${relay.url} -> vertex rawPredict`);
|
|
477
|
+
}
|
|
320
478
|
log.line('');
|
|
321
479
|
log.line('--- prompt ---');
|
|
322
480
|
log.line(prompt);
|
|
323
481
|
log.line('');
|
|
324
482
|
log.line('--- run ---');
|
|
483
|
+
const resumeHint = () => session ? `resume it: zen meta resume ${project.name} ${session}` : undefined;
|
|
484
|
+
// Every zen command the agent runs inherits these, so a batch it starts or
|
|
485
|
+
// an ask it makes lands in the same ledger, tagged with this session.
|
|
486
|
+
const spans = log.path.replace(/\.log$/, '.otel.jsonl');
|
|
487
|
+
const ledger = ledgerPath(project.dir);
|
|
488
|
+
const env = {
|
|
489
|
+
...wiring.env,
|
|
490
|
+
COPILOT_OTEL_FILE_EXPORTER_PATH: spans,
|
|
491
|
+
...(ledger ? { [LEDGER_ENV]: ledger } : {}),
|
|
492
|
+
...(session ? { [META_SESSION_ENV]: session } : {}),
|
|
493
|
+
...(promptName ? { [META_PROMPT_ENV]: promptName } : {}),
|
|
494
|
+
};
|
|
495
|
+
const tally = new Tally();
|
|
496
|
+
const record = values.events ? copilotEventLog(resolve(ctx.cwd, values.events)) : undefined;
|
|
497
|
+
const tail = tailSpans(spans, (call) => {
|
|
498
|
+
tally.add(call);
|
|
499
|
+
record?.call(call);
|
|
500
|
+
appendUsage(project.dir, {
|
|
501
|
+
kind: 'meta.call',
|
|
502
|
+
ts: new Date(Date.parse(call.startedAt) + call.durationMs).toISOString(),
|
|
503
|
+
...call,
|
|
504
|
+
session: call.session ?? session,
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
const reporter = usageReporter(project.dir);
|
|
508
|
+
const tokens = () => {
|
|
509
|
+
tail.stop();
|
|
510
|
+
return tally.toString();
|
|
511
|
+
};
|
|
325
512
|
// A run that dies still has to leave a readable file behind.
|
|
326
513
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
514
|
+
let resumes = 0;
|
|
515
|
+
const run = async (argList) => {
|
|
516
|
+
const launchedAt = new Date();
|
|
517
|
+
const outcome = await launch({
|
|
518
|
+
binary,
|
|
519
|
+
args: argList,
|
|
520
|
+
env,
|
|
521
|
+
cwd: project.dir,
|
|
522
|
+
log,
|
|
523
|
+
sink: ctx.json ? terminalSink : undefined,
|
|
524
|
+
onEvent: record?.event,
|
|
525
|
+
status: () => (tally.calls > 0 ? tally.toString() : ''),
|
|
526
|
+
});
|
|
527
|
+
if (outcome.sessionId && outcome.sessionId !== session) {
|
|
528
|
+
session = outcome.sessionId;
|
|
529
|
+
recordSession(project.dir, session);
|
|
530
|
+
}
|
|
531
|
+
const models = session ? sessionTotals(session) : undefined;
|
|
532
|
+
if (session && models) {
|
|
533
|
+
appendUsage(project.dir, {
|
|
534
|
+
kind: 'meta.session',
|
|
535
|
+
ts: new Date().toISOString(),
|
|
536
|
+
session,
|
|
537
|
+
models,
|
|
538
|
+
exitCode: outcome.exitCode,
|
|
539
|
+
resumes,
|
|
540
|
+
startedAt: launchedAt.toISOString(),
|
|
541
|
+
durationMs: Date.now() - launchedAt.getTime(),
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return outcome;
|
|
545
|
+
};
|
|
546
|
+
let outcome = await run(args);
|
|
547
|
+
for (let reason = transient(outcome); reason && resumes < retries;) {
|
|
548
|
+
const wait = resumeDelayMs(resumes);
|
|
549
|
+
resumes += 1;
|
|
550
|
+
const said = `${reason}: resuming in ${duration(wait)} (${resumes}/${retries})`;
|
|
551
|
+
warn(said);
|
|
552
|
+
log.line('');
|
|
553
|
+
log.line(`--- resume ${resumes}: ${said} ---`);
|
|
554
|
+
await pause(wait, resumeHint());
|
|
555
|
+
const again = { ...values, resume: session, continue: false };
|
|
556
|
+
outcome = await run(argv(again, project.dir, RESUME_PROMPT, wiring.secret));
|
|
557
|
+
reason = transient(outcome);
|
|
558
|
+
}
|
|
334
559
|
log.line('');
|
|
335
560
|
log.line('--- answer ---');
|
|
336
561
|
log.line(outcome.answer);
|
|
337
562
|
if (outcome.answer) {
|
|
338
563
|
log.saveAnswer(outcome.answer);
|
|
339
564
|
}
|
|
565
|
+
const spent = tokens();
|
|
566
|
+
log.line('');
|
|
567
|
+
log.line(`tokens ${spent}`);
|
|
340
568
|
if (ctx.json) {
|
|
341
569
|
json({
|
|
342
570
|
project: project.dir,
|
|
@@ -344,7 +572,9 @@ async function go(ctx, values, project, prompt) {
|
|
|
344
572
|
model: chosen.ref,
|
|
345
573
|
sessionId: outcome.sessionId,
|
|
346
574
|
exitCode: outcome.exitCode,
|
|
575
|
+
resumes,
|
|
347
576
|
usage: outcome.usage,
|
|
577
|
+
tokens: { calls: tally.calls, ...tally.usage },
|
|
348
578
|
answer: outcome.answer,
|
|
349
579
|
answerFile: outcome.answer ? log.answerPath : undefined,
|
|
350
580
|
});
|
|
@@ -355,16 +585,82 @@ async function go(ctx, values, project, prompt) {
|
|
|
355
585
|
// have read enough to want it in an editor.
|
|
356
586
|
note(cyan(log.answerPath));
|
|
357
587
|
}
|
|
588
|
+
if (!ctx.json && tally.calls > 0) {
|
|
589
|
+
note(dim(`tokens ${spent}`));
|
|
590
|
+
}
|
|
358
591
|
if (outcome.exitCode !== 0) {
|
|
359
|
-
throw new CliError(`the meta agent exited ${outcome.exitCode}`, EXIT.failed,
|
|
592
|
+
throw new CliError(`the meta agent exited ${outcome.exitCode}`, EXIT.failed, resumeHint() ??
|
|
593
|
+
(binary.from === 'npx' ? 'install it: npm i -g @github/copilot' : undefined));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
const hint = resumeHint();
|
|
598
|
+
if (hint && !(err instanceof CliError)) {
|
|
599
|
+
note(dim(hint));
|
|
360
600
|
}
|
|
601
|
+
throw err;
|
|
361
602
|
}
|
|
362
603
|
finally {
|
|
604
|
+
tail.stop();
|
|
605
|
+
reporter.stop();
|
|
606
|
+
await relay?.close();
|
|
363
607
|
log.close();
|
|
364
608
|
}
|
|
365
609
|
}
|
|
610
|
+
/** How often a running meta agent rewrites the fine-tuning's usage report. */
|
|
611
|
+
const REPORT_EVERY_MS = 5 * 60_000;
|
|
612
|
+
/**
|
|
613
|
+
* Rewrites `finetune/USAGE.md` on a timer and once more at the end, when the
|
|
614
|
+
* project is being tuned. Detached and silent: the report is a by-product, and
|
|
615
|
+
* neither its time nor its failure belongs to the run.
|
|
616
|
+
*/
|
|
617
|
+
function usageReporter(dir) {
|
|
618
|
+
const script = join(dir, '.github/skills/zen-finetune/scripts/usage.mjs');
|
|
619
|
+
if (!existsSync(join(dir, 'finetune')) || !existsSync(script)) {
|
|
620
|
+
return { stop: () => undefined };
|
|
621
|
+
}
|
|
622
|
+
let running = false;
|
|
623
|
+
const refresh = () => {
|
|
624
|
+
if (running) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
running = true;
|
|
628
|
+
const child = spawn(process.execPath, [script, '-q'], {
|
|
629
|
+
cwd: dir,
|
|
630
|
+
stdio: 'ignore',
|
|
631
|
+
detached: true,
|
|
632
|
+
});
|
|
633
|
+
child.on('exit', () => (running = false));
|
|
634
|
+
child.on('error', () => (running = false));
|
|
635
|
+
child.unref();
|
|
636
|
+
};
|
|
637
|
+
const timer = setInterval(refresh, REPORT_EVERY_MS);
|
|
638
|
+
timer.unref();
|
|
639
|
+
return {
|
|
640
|
+
stop: () => {
|
|
641
|
+
clearInterval(timer);
|
|
642
|
+
running = false;
|
|
643
|
+
refresh();
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/** The wait before a resume, cut short by Ctrl-C - which still says how to resume. */
|
|
648
|
+
async function pause(ms, hint) {
|
|
649
|
+
const stop = new AbortController();
|
|
650
|
+
const onInt = () => stop.abort();
|
|
651
|
+
process.once('SIGINT', onInt);
|
|
652
|
+
try {
|
|
653
|
+
await sleep(ms, undefined, { signal: stop.signal });
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
throw new CliError('interrupted', EXIT.failed, hint);
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
process.off('SIGINT', onInt);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
366
662
|
/** Copilot's own flags, assembled once. Nothing secret goes on this line. */
|
|
367
|
-
function argv(values, dir, prompt, secret) {
|
|
663
|
+
function argv(values, dir, prompt, secret, fresh) {
|
|
368
664
|
const args = ['-C', dir, '-p', prompt, '--output-format', 'json', '--no-color'];
|
|
369
665
|
// A prompt written for an editor expects to read, write and run things, and
|
|
370
666
|
// there is nobody at a `-p` run to answer the question. Naming tools is the
|
|
@@ -390,9 +686,12 @@ function argv(values, dir, prompt, secret) {
|
|
|
390
686
|
if (values.resume) {
|
|
391
687
|
args.push('--resume', values.resume);
|
|
392
688
|
}
|
|
393
|
-
if (values.continue) {
|
|
689
|
+
else if (values.continue) {
|
|
394
690
|
args.push('--continue');
|
|
395
691
|
}
|
|
692
|
+
else if (fresh) {
|
|
693
|
+
args.push('--session-id', fresh);
|
|
694
|
+
}
|
|
396
695
|
if (secret.length > 0) {
|
|
397
696
|
args.push('--secret-env-vars', secret.join(','));
|
|
398
697
|
}
|
|
@@ -417,6 +716,16 @@ function projectModel(dir) {
|
|
|
417
716
|
return undefined;
|
|
418
717
|
}
|
|
419
718
|
}
|
|
719
|
+
function count(text, flag) {
|
|
720
|
+
if (text === undefined) {
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
const value = Number(text);
|
|
724
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
725
|
+
throw usageError(`${flag} takes a whole number, 0 or more`, `got "${text}"`);
|
|
726
|
+
}
|
|
727
|
+
return value;
|
|
728
|
+
}
|
|
420
729
|
function asProvider(name) {
|
|
421
730
|
const owner = assertOwner(name);
|
|
422
731
|
if (owner === 'exa') {
|
|
@@ -534,7 +843,7 @@ async function vet(ctx, ref) {
|
|
|
534
843
|
// The colon form, so a refusal suggests `zen models ls <provider>`.
|
|
535
844
|
ref: `${owner}:${id}`,
|
|
536
845
|
kind: 'model',
|
|
537
|
-
model:
|
|
846
|
+
model: probeChat(owner, id),
|
|
538
847
|
});
|
|
539
848
|
bar?.done();
|
|
540
849
|
if (probe.check.state !== 'live') {
|