@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/sandbox.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface SandboxInputs {
|
|
|
39
39
|
* promise it does not keep.
|
|
40
40
|
*/
|
|
41
41
|
env?: readonly string[];
|
|
42
|
+
/** `false` overrides every `persist:` in the config, the agents' included */
|
|
43
|
+
persist?: boolean;
|
|
42
44
|
}
|
|
43
45
|
export declare function buildSandbox(opts: SandboxInputs): SandboxSetup;
|
|
44
46
|
/**
|
package/dist/sandbox.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
4
|
import { resolveBuild } from "./image.js";
|
|
5
5
|
import { credentials } from "./keys.js";
|
|
6
|
-
import { ensurePodmanReady } from "./podman.js";
|
|
6
|
+
import { assertLocks, ensurePodmanReady } from "./podman.js";
|
|
7
7
|
import { warn } from "./term.js";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// The sandbox, as this CLI wires it
|
|
@@ -71,6 +71,7 @@ export function buildSandbox(opts) {
|
|
|
71
71
|
...(opts.config.sandbox ?? {}),
|
|
72
72
|
...((opts.image ?? build?.tag) ? { image: opts.image ?? build?.tag } : {}),
|
|
73
73
|
...(opts.keys === false ? { keys: false } : {}),
|
|
74
|
+
...(opts.persist === false ? { persist: false } : {}),
|
|
74
75
|
};
|
|
75
76
|
const home = join(opts.session.data, 'sandbox', 'home');
|
|
76
77
|
const mounts = [{ host: home, at: HOME }, ...(opts.mounts ?? [])];
|
|
@@ -81,18 +82,22 @@ export function buildSandbox(opts) {
|
|
|
81
82
|
// Skills and assets are mounted read-only, and a python script run from a
|
|
82
83
|
// read-only directory fails on writing its own `__pycache__` — a confusing
|
|
83
84
|
// error about a file nobody asked for.
|
|
84
|
-
|
|
85
|
+
// `ZENERA_AGENT` tells a `zen` run by `run_command` that no person is there.
|
|
86
|
+
const extra = { HOME, PYTHONDONTWRITEBYTECODE: '1', ZENERA_AGENT: '1', ...(keys?.env ?? {}) };
|
|
85
87
|
const spec = toSpec(base, extra, secretsOf(keys?.secrets, opts.env));
|
|
86
88
|
const agents = {};
|
|
87
89
|
for (const agent of opts.config.agents) {
|
|
88
90
|
if (agent.sandbox) {
|
|
89
91
|
const merged = merge(base, agent.sandbox);
|
|
92
|
+
if (opts.persist === false) {
|
|
93
|
+
merged.persist = false;
|
|
94
|
+
}
|
|
90
95
|
// An agent that opts out gets neither the variables nor the mount,
|
|
91
96
|
// but the mount is on the pool and cannot be taken back per agent —
|
|
92
97
|
// so the variable is what actually decides, and without it the file
|
|
93
98
|
// sitting there is not a credential anything will look for.
|
|
94
99
|
const own = merged.keys === false ? undefined : keys;
|
|
95
|
-
agents[agent.name] = toSpec(merged, { HOME, PYTHONDONTWRITEBYTECODE: '1', ...(own?.env ?? {}) }, own && secretsOf(own.secrets, opts.env));
|
|
100
|
+
agents[agent.name] = toSpec(merged, { HOME, PYTHONDONTWRITEBYTECODE: '1', ZENERA_AGENT: '1', ...(own?.env ?? {}) }, own && secretsOf(own.secrets, opts.env));
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
const pool = new SandboxPool({
|
|
@@ -201,6 +206,8 @@ export async function preflight(setup, yes) {
|
|
|
201
206
|
memory: setup.spec.memory,
|
|
202
207
|
yes,
|
|
203
208
|
});
|
|
209
|
+
// A resumed session restarts its own stopped container, which needs no lock.
|
|
210
|
+
await assertLocks(1, { reuses: setup.pool.for().name });
|
|
204
211
|
READY.add(setup.image);
|
|
205
212
|
}
|
|
206
213
|
/** Best-effort teardown: losing a container must never lose a run. */
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const TEMPLATES: string;
|
|
1
2
|
/**
|
|
2
3
|
* The project files that are ours rather than the project's.
|
|
3
4
|
*
|
|
@@ -19,18 +20,24 @@ export declare const SHARED_RULES: readonly string[];
|
|
|
19
20
|
* scaffold wrote, rather than a second definition of "this version".
|
|
20
21
|
*/
|
|
21
22
|
export declare function ownVersion(): string;
|
|
23
|
+
type Vars = Record<string, string>;
|
|
24
|
+
export interface CopyOptions {
|
|
25
|
+
/** values for the `{{...}}` in any template under this tree */
|
|
26
|
+
vars?: Vars;
|
|
27
|
+
/** leave a file that is already there alone rather than replacing it */
|
|
28
|
+
keep?: boolean;
|
|
29
|
+
}
|
|
22
30
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* none. Returns the relative paths written.
|
|
31
|
+
* Copies one template directory into `dir` at `rel`, depth first, sorted so the
|
|
32
|
+
* list it returns is the same on every machine. Only a `.tmpl` is read as text;
|
|
33
|
+
* everything else is copied byte for byte. A file already holding those bytes
|
|
34
|
+
* is left alone and not listed.
|
|
28
35
|
*/
|
|
29
|
-
export declare function
|
|
36
|
+
export declare function copyTree(from: string, dir: string, rel: string, opts: CopyOptions): string[];
|
|
30
37
|
/**
|
|
31
38
|
* Replaces every file in a project that nobody is meant to be maintaining:
|
|
32
39
|
* `SHARED_RULES` in the project tree, and the editor tree whole. Returns the
|
|
33
|
-
* relative paths
|
|
40
|
+
* relative paths that actually changed, project files first.
|
|
34
41
|
*
|
|
35
42
|
* This is the other half of `keep: true`. A scaffold never overwrites, which is
|
|
36
43
|
* right for the files a project goes on to make its own and wrong for the ones
|
|
@@ -83,4 +90,5 @@ export interface Scaffolded {
|
|
|
83
90
|
* the first set under twice as many lines about the second.
|
|
84
91
|
*/
|
|
85
92
|
export declare function scaffold(opts: ScaffoldOptions): Scaffolded;
|
|
93
|
+
export {};
|
|
86
94
|
//# sourceMappingURL=scaffold.d.ts.map
|
package/dist/scaffold.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { chmodSync,
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { dirname, join, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { editorFiles } from "./meta/editor.js";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// Scaffolding
|
|
6
7
|
//
|
|
@@ -18,7 +19,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
18
19
|
//
|
|
19
20
|
// The trees are copied whole and nothing enumerates them, so adding a file to
|
|
20
21
|
// a new project is adding a file to `templates/project/` and nothing else.
|
|
21
|
-
// `MEMORY_RULES` is the single exception, and it is named below.
|
|
22
22
|
//
|
|
23
23
|
// What is there is deliberately close to empty: a template full of
|
|
24
24
|
// commented-out options is a template nobody reads and everybody deletes. The
|
|
@@ -30,7 +30,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
30
30
|
// standing next to the files that implement it, which is the shape every
|
|
31
31
|
// change to the project is then made in.
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
|
-
const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
|
|
33
|
+
export const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
|
|
34
34
|
/** The suffix on a file with `{{...}}` in it, dropped when the file lands. */
|
|
35
35
|
const TEMPLATE = '.tmpl';
|
|
36
36
|
/** How to use file tools: a house rules file, landing under `agents/`. */
|
|
@@ -54,8 +54,6 @@ const FORK_RULES = join('agents', 'fork-instructions.md');
|
|
|
54
54
|
* own beside them.
|
|
55
55
|
*/
|
|
56
56
|
export const SHARED_RULES = [FILE_RULES, FORK_RULES, MEMORY_RULES, TOOL_RULES];
|
|
57
|
-
/** The same bytes, kept in the `zen-memory` skill to restore or diff against. */
|
|
58
|
-
const MEMORY_REFERENCE = join('.github', 'skills', 'zen-memory', 'references', 'memory-instructions.md');
|
|
59
57
|
/**
|
|
60
58
|
* This `zen`'s own version, which the scaffold pins the sandbox's tools to.
|
|
61
59
|
* The publishable packages move in lockstep, so one number covers them all.
|
|
@@ -144,12 +142,22 @@ function target(name) {
|
|
|
144
142
|
}
|
|
145
143
|
return DOTFILES[name] ?? name;
|
|
146
144
|
}
|
|
145
|
+
/** Writes `body` to `path` unless those are already its bytes; true if it wrote. */
|
|
146
|
+
function writeIfChanged(path, body) {
|
|
147
|
+
const bytes = typeof body === 'string' ? Buffer.from(body) : body;
|
|
148
|
+
if (existsSync(path) && readFileSync(path).equals(bytes)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
writeFileSync(path, bytes);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
147
154
|
/**
|
|
148
155
|
* Copies one template directory into `dir` at `rel`, depth first, sorted so the
|
|
149
156
|
* list it returns is the same on every machine. Only a `.tmpl` is read as text;
|
|
150
|
-
* everything else is copied byte for byte.
|
|
157
|
+
* everything else is copied byte for byte. A file already holding those bytes
|
|
158
|
+
* is left alone and not listed.
|
|
151
159
|
*/
|
|
152
|
-
function copyTree(from, dir, rel, opts) {
|
|
160
|
+
export function copyTree(from, dir, rel, opts) {
|
|
153
161
|
const written = [];
|
|
154
162
|
mkdirSync(join(dir, rel), { recursive: true });
|
|
155
163
|
const entries = readdirSync(from, { withFileTypes: true });
|
|
@@ -167,73 +175,21 @@ function copyTree(from, dir, rel, opts) {
|
|
|
167
175
|
const body = entry.name.endsWith(TEMPLATE)
|
|
168
176
|
? render(readFileSync(source, 'utf8'), opts.vars ?? {})
|
|
169
177
|
: readFileSync(source);
|
|
170
|
-
|
|
178
|
+
const changed = writeIfChanged(join(dir, child), body);
|
|
171
179
|
// The bytes are copied, not the file, so a script arrives unrunnable.
|
|
172
|
-
if (child.endsWith('.sh')) {
|
|
180
|
+
if (child.endsWith('.sh') || child.endsWith('.mjs')) {
|
|
173
181
|
chmodSync(join(dir, child), 0o755);
|
|
174
182
|
}
|
|
175
|
-
|
|
183
|
+
if (changed) {
|
|
184
|
+
written.push(child);
|
|
185
|
+
}
|
|
176
186
|
}
|
|
177
187
|
return written;
|
|
178
188
|
}
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// Telling the editor which instructions are not for it
|
|
181
|
-
//
|
|
182
|
-
// The project's house rules are `agents/instructions.md`, deliberately not
|
|
183
|
-
// `AGENTS.md`: every coding assistant now reads that name out of an open
|
|
184
|
-
// folder and feeds it to itself as always-on instructions, and `zen open`
|
|
185
|
-
// opens exactly this directory. A name nobody else claims means the two are
|
|
186
|
-
// never confused, and `chat.useAgentsMdFile` no longer has to be switched off
|
|
187
|
-
// to keep them apart.
|
|
188
|
-
//
|
|
189
|
-
// They sit under `agents/` because there is rarely only one of them. Anything
|
|
190
|
-
// named `agents/<topic>-instructions.md` is read too, in filename order, so a
|
|
191
|
-
// subject that is true for every agent but is about one capability — memory,
|
|
192
|
-
// say — gets its own document instead of another section in a file that keeps
|
|
193
|
-
// growing.
|
|
194
|
-
//
|
|
195
|
-
// `chat.useNestedAgentsMdFiles` is still written. It is already false by
|
|
196
|
-
// default, but it is opt-in globally, and this is a directory the agent itself
|
|
197
|
-
// writes into — someone who turned it on would otherwise have the editor pick
|
|
198
|
-
// up whatever `AGENTS.md` a run happened to leave behind. It is a *restricted*
|
|
199
|
-
// setting, so it applies only in a trusted workspace; that is the right way
|
|
200
|
-
// round, since an untrusted folder is not one to run agents in either.
|
|
201
|
-
//
|
|
202
|
-
// `agents/instructions.md` addresses the *project's* agents. The editor's
|
|
203
|
-
// assistant still needs a brief of its own, and what it needs to know is how
|
|
204
|
-
// this kind of project is put together — the file formats, how a prompt is
|
|
205
|
-
// written, when to add a skill rather than an agent. That is what the
|
|
206
|
-
// `.github/` tree is: the standing brief, plus the prompt files and skills the
|
|
207
|
-
// editor picks up from the same place.
|
|
208
|
-
// ---------------------------------------------------------------------------
|
|
209
|
-
/**
|
|
210
|
-
* Writes the editor's files under `dir`, replacing what is there. They are
|
|
211
|
-
* ours: they say how the editor is to treat a directory the agents write into,
|
|
212
|
-
* and they describe the file formats of the version of `zen` in hand, so the
|
|
213
|
-
* current answer is the only one worth having and a stale one is worse than
|
|
214
|
-
* none. Returns the relative paths written.
|
|
215
|
-
*/
|
|
216
|
-
export function editorFiles(dir) {
|
|
217
|
-
const written = copyTree(join(TEMPLATES, 'editor'), dir, '', {});
|
|
218
|
-
// The one file that belongs to both trees. It is a project file first —
|
|
219
|
-
// house rules reaching every agent that can see the graph — and it is
|
|
220
|
-
// written once and edited from then on, like every other project file. But
|
|
221
|
-
// a project whose copy drifted, or that deleted it and turned memory back
|
|
222
|
-
// on later, needs somewhere to get the current text from, and the editor
|
|
223
|
-
// tree is replaced on every `init` and `open`. Copying the same bytes into
|
|
224
|
-
// the skill's references is what makes `diff` between the two mean
|
|
225
|
-
// something, and what stops a second copy of these rules existing in the
|
|
226
|
-
// repository to fall out of step with the first.
|
|
227
|
-
const reference = join(dir, MEMORY_REFERENCE);
|
|
228
|
-
mkdirSync(dirname(reference), { recursive: true });
|
|
229
|
-
copyFileSync(join(TEMPLATES, 'project', MEMORY_RULES), reference);
|
|
230
|
-
written.push(MEMORY_REFERENCE);
|
|
231
|
-
return written;
|
|
232
|
-
}
|
|
233
189
|
/**
|
|
234
190
|
* Replaces every file in a project that nobody is meant to be maintaining:
|
|
235
191
|
* `SHARED_RULES` in the project tree, and the editor tree whole. Returns the
|
|
236
|
-
* relative paths
|
|
192
|
+
* relative paths that actually changed, project files first.
|
|
237
193
|
*
|
|
238
194
|
* This is the other half of `keep: true`. A scaffold never overwrites, which is
|
|
239
195
|
* right for the files a project goes on to make its own and wrong for the ones
|
|
@@ -245,8 +201,9 @@ export function refreshShared(dir) {
|
|
|
245
201
|
for (const rel of SHARED_RULES) {
|
|
246
202
|
const path = join(dir, rel);
|
|
247
203
|
mkdirSync(dirname(path), { recursive: true });
|
|
248
|
-
|
|
249
|
-
|
|
204
|
+
if (writeIfChanged(path, readFileSync(join(TEMPLATES, 'project', rel)))) {
|
|
205
|
+
written.push(rel);
|
|
206
|
+
}
|
|
250
207
|
}
|
|
251
208
|
written.push(...editorFiles(dir));
|
|
252
209
|
return written;
|
package/dist/session.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { claimLock, ownLock } from '@zenera/neo';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { readJson, writeJson } from "./home.js";
|
|
5
5
|
import { isStamp, stamp, stampInstant } from "./ids.js";
|
|
6
|
-
import {
|
|
6
|
+
import { isBusy, runIds, sessionIds, sessionsDir } from "./projects.js";
|
|
7
7
|
import { CliError, EXIT, invalidError, usageError } from "./term.js";
|
|
8
8
|
export function sessionPaths(projectDir, id) {
|
|
9
9
|
if (!isStamp(id)) {
|
|
@@ -85,24 +85,7 @@ export function requireSession(projectDir, id) {
|
|
|
85
85
|
return p;
|
|
86
86
|
}
|
|
87
87
|
export function acquire(p) {
|
|
88
|
-
|
|
89
|
-
const body = `${JSON.stringify(lock, null, 2)}\n`;
|
|
90
|
-
try {
|
|
91
|
-
// 'wx' fails when the file exists — the create and the check are one
|
|
92
|
-
// operation, so two `zen run`s racing cannot both win.
|
|
93
|
-
writeFileSync(p.lock, body, { flag: 'wx' });
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
if (err.code !== 'EEXIST') {
|
|
97
|
-
throw err;
|
|
98
|
-
}
|
|
99
|
-
const held = current(p);
|
|
100
|
-
if (held && alive(held.pid) && held.host === hostname()) {
|
|
101
|
-
throw new CliError(`session ${p.id} is already running (pid ${held.pid})`, EXIT.failed, 'wait for it, or start another with --new');
|
|
102
|
-
}
|
|
103
|
-
// Stale, or from another machine's run that cannot be verified here.
|
|
104
|
-
writeFileSync(p.lock, body);
|
|
105
|
-
}
|
|
88
|
+
claimLock(p.lock, ownLock(), (held) => new CliError(`session ${p.id} is already running (pid ${held.pid})`, EXIT.failed, 'wait for it, or start another with --new'), 2);
|
|
106
89
|
let released = false;
|
|
107
90
|
const release = () => {
|
|
108
91
|
if (released) {
|
|
@@ -113,14 +96,6 @@ export function acquire(p) {
|
|
|
113
96
|
};
|
|
114
97
|
return { release };
|
|
115
98
|
}
|
|
116
|
-
function current(p) {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(readFileSync(p.lock, 'utf8'));
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
99
|
export function runPaths(session, id) {
|
|
125
100
|
if (!isStamp(id)) {
|
|
126
101
|
throw usageError(`"${id}" is not a run id`);
|
package/dist/term.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export declare function warn(message: string): void;
|
|
|
73
73
|
/** What this run calls itself, in the process table and on the terminal's tab. */
|
|
74
74
|
export declare function title(text: string): void;
|
|
75
75
|
export declare function fail(message: string, hint?: string): void;
|
|
76
|
+
/** An agent's terminal can be a real pty (VS Code Copilot's is), so TTY checks alone miss it. */
|
|
77
|
+
export declare function drivenByAgent(): boolean;
|
|
76
78
|
export declare function isInteractive(): boolean;
|
|
77
79
|
export declare function deferBanner(print: () => void): void;
|
|
78
80
|
export declare function ask(question: string, fallback?: string): Promise<string>;
|
package/dist/term.js
CHANGED
|
@@ -230,8 +230,16 @@ export function fail(message, hint) {
|
|
|
230
230
|
// a stdin that will never answer. A CLI that hangs in CI is worse than one that
|
|
231
231
|
// fails in CI, because only one of the two tells you which flag you forgot.
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
233
|
+
/** An agent's terminal can be a real pty (VS Code Copilot's is), so TTY checks alone miss it. */
|
|
234
|
+
export function drivenByAgent() {
|
|
235
|
+
const own = process.env['ZENERA_AGENT'];
|
|
236
|
+
if (own !== undefined) {
|
|
237
|
+
return own !== '' && own !== '0';
|
|
238
|
+
}
|
|
239
|
+
return Boolean(process.env['AI_AGENT'] || process.env['COPILOT_AGENT']);
|
|
240
|
+
}
|
|
233
241
|
export function isInteractive() {
|
|
234
|
-
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
242
|
+
return Boolean(process.stdin.isTTY && process.stderr.isTTY) && !drivenByAgent();
|
|
235
243
|
}
|
|
236
244
|
// A banner is for whoever is sitting there, and the only proof that anyone is
|
|
237
245
|
// sitting there is that we are about to ask them something. Commands whose
|
|
@@ -244,7 +252,8 @@ export function deferBanner(print) {
|
|
|
244
252
|
}
|
|
245
253
|
function requireTty(what, flag) {
|
|
246
254
|
if (!isInteractive()) {
|
|
247
|
-
|
|
255
|
+
const why = drivenByAgent() ? 'when an agent is running zen' : 'without a terminal';
|
|
256
|
+
throw usageError(`cannot ask for ${what} ${why}`, `pass ${flag} instead`);
|
|
248
257
|
}
|
|
249
258
|
const banner = pending;
|
|
250
259
|
pending = undefined;
|
package/dist/trace.d.ts
CHANGED
|
@@ -81,4 +81,9 @@ export interface NodeDetail {
|
|
|
81
81
|
*/
|
|
82
82
|
export declare function parseNodeIds(words: readonly string[], known: Map<string, unknown>): string[];
|
|
83
83
|
export declare function nodeDetail(trace: Trace, ids: readonly string[], payloads: PayloadResolver): Promise<NodeDetail[]>;
|
|
84
|
+
/**
|
|
85
|
+
* The two questions a recorded request is usually opened for, answered without
|
|
86
|
+
* printing it: which tools the call was offered, and what conversation it saw.
|
|
87
|
+
*/
|
|
88
|
+
export declare function requestParts(json: string): NodePart[];
|
|
84
89
|
//# sourceMappingURL=trace.d.ts.map
|
package/dist/trace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { coveredIds, totalUsage, } from '@zenera/neo';
|
|
1
|
+
import { contentToText, coveredIds, totalUsage, } from '@zenera/neo';
|
|
2
2
|
import { format } from "./narrate.js";
|
|
3
3
|
import { describeCall, summarise } from "./tui/wrap.js";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
@@ -387,6 +387,53 @@ function tally(entries) {
|
|
|
387
387
|
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
388
388
|
.map(([name, n]) => `${safe(name, 40)} x${n}`);
|
|
389
389
|
}
|
|
390
|
+
const WRITES = new Set(['write_file', 'apply_patch', 'copy_file', 'move_file']);
|
|
391
|
+
/**
|
|
392
|
+
* What the run did with memory, zeros included. The `tools` row only lists
|
|
393
|
+
* calls that happened, and a commit that never happened is the commonest
|
|
394
|
+
* memory finding there is: absent from a list, a reader does not see it.
|
|
395
|
+
*/
|
|
396
|
+
function memoryRows(entries, bound) {
|
|
397
|
+
let recalls = 0;
|
|
398
|
+
let recalled = 0;
|
|
399
|
+
let commits = 0;
|
|
400
|
+
let committed = 0;
|
|
401
|
+
let files = 0;
|
|
402
|
+
let written = 0;
|
|
403
|
+
const calls = new Map();
|
|
404
|
+
for (const { node: n } of entries) {
|
|
405
|
+
if (n.type === 'memory_recall') {
|
|
406
|
+
recalls++;
|
|
407
|
+
recalled += n.nodes.length;
|
|
408
|
+
}
|
|
409
|
+
else if (n.type === 'memory_op' && n.op === 'commit') {
|
|
410
|
+
commits++;
|
|
411
|
+
committed += n.nodes.length;
|
|
412
|
+
files += n.files;
|
|
413
|
+
}
|
|
414
|
+
else if (n.type === 'tool_call') {
|
|
415
|
+
calls.set(n.name, (calls.get(n.name) ?? 0) + 1);
|
|
416
|
+
if (WRITES.has(n.name)) {
|
|
417
|
+
written++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const used = (name) => calls.get(name) ?? 0;
|
|
422
|
+
const reads = used('memory_search') + used('memory_grep') + used('memory_load');
|
|
423
|
+
if (!bound && !recalls && !reads && !commits) {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
const label = bound ? '' : 'memory';
|
|
427
|
+
const lines = [
|
|
428
|
+
row(label, `recall ${recalls} (${recalled} nodes) · search ${used('memory_search')} · ` +
|
|
429
|
+
`grep ${used('memory_grep')} · load ${used('memory_load')} · ` +
|
|
430
|
+
`commit ${commits} (${committed} nodes, ${files} files)`),
|
|
431
|
+
];
|
|
432
|
+
if (!commits && written) {
|
|
433
|
+
lines.push(row('', `nothing committed after ${written} workspace file writes`));
|
|
434
|
+
}
|
|
435
|
+
return lines;
|
|
436
|
+
}
|
|
390
437
|
function elapsed(entries) {
|
|
391
438
|
const stamps = entries.map((e) => e.endMs).filter((t) => t !== null);
|
|
392
439
|
return stamps.length < 2 ? '' : span(Math.max(...stamps) - Math.min(...stamps));
|
|
@@ -441,6 +488,7 @@ function header(state, trace, opts) {
|
|
|
441
488
|
...(opts.dir ? [row('dir', safe(opts.dir, 200))] : []),
|
|
442
489
|
...(opts.workspace ? [row('workspace', safe(opts.workspace, 200))] : []),
|
|
443
490
|
...(opts.memory ? [row('memory', safe(opts.memory, 200))] : []),
|
|
491
|
+
...memoryRows(entries, Boolean(opts.memory)),
|
|
444
492
|
row('agent', `${safe(state.agentName, 40)} · started as ${safe(state.spec.startAgent, 40)}`),
|
|
445
493
|
row('phase', state.error ? `${state.phase} — ${safe(state.error, 200)}` : state.phase),
|
|
446
494
|
row('nodes', `${entries.length} · ${kinds.get('llm_call') ?? 0} llm · ` +
|
|
@@ -473,10 +521,13 @@ function header(state, trace, opts) {
|
|
|
473
521
|
if (hidden) {
|
|
474
522
|
lines.push(row('compacted', `${hidden} nodes are marked "hidden by" a later summary`), row('', 'they still ran; the model simply stopped seeing them'));
|
|
475
523
|
}
|
|
476
|
-
lines.push(
|
|
524
|
+
lines.push(
|
|
525
|
+
// Never a bare `%%`: Mermaid only strips a comment line with something
|
|
526
|
+
// after the marker, and leaves the rest to the parser, which rejects it.
|
|
527
|
+
'%% ---', row('reading', 'nN is a node id · t+ counts from the start of the turn'), row('', 'dotted edges are fork/join and calls answered out of order'), row('', 'nodes are declared in run order; every edge is in one block below'), row('detail', `zen inspect node n1 n2 n5..n9${where}`),
|
|
477
528
|
// Named here because this is where the question forms. A reader who has
|
|
478
529
|
// just found the node they distrust will not go looking for a verb.
|
|
479
|
-
row('why', `zen inspect ask n5 "why did you do that?"${where}`), '%%');
|
|
530
|
+
row('why', `zen inspect ask n5 "why did you do that?"${where}`), '%% ---');
|
|
480
531
|
return lines;
|
|
481
532
|
}
|
|
482
533
|
export function traceMermaid(state, opts = {}) {
|
|
@@ -668,12 +719,61 @@ export async function nodeDetail(trace, ids, payloads) {
|
|
|
668
719
|
ts: e.node.ts,
|
|
669
720
|
label: labelOf(e.node, {}),
|
|
670
721
|
facts: factsOf(e.node),
|
|
671
|
-
parts: (named.get(e) ?? []).
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
722
|
+
parts: (named.get(e) ?? []).flatMap((part) => {
|
|
723
|
+
const text = part.literal ?? blobs.get(part.payload?.sha256 ?? '') ?? '(unresolved)';
|
|
724
|
+
return part.name === 'request'
|
|
725
|
+
? [{ name: part.name, text }, ...requestParts(text)]
|
|
726
|
+
: [{ name: part.name, text }];
|
|
727
|
+
}),
|
|
675
728
|
}));
|
|
676
729
|
}
|
|
730
|
+
/** Longest a message or a tool result runs in `messages` before it is clipped. */
|
|
731
|
+
const MESSAGE_CLIP = 400;
|
|
732
|
+
const clip = (s, n = MESSAGE_CLIP) => {
|
|
733
|
+
const flat = s.replace(/\s+/g, ' ').trim();
|
|
734
|
+
return flat.length <= n ? flat : `${flat.slice(0, n)} … (+${flat.length - n} chars)`;
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* The two questions a recorded request is usually opened for, answered without
|
|
738
|
+
* printing it: which tools the call was offered, and what conversation it saw.
|
|
739
|
+
*/
|
|
740
|
+
export function requestParts(json) {
|
|
741
|
+
let req;
|
|
742
|
+
try {
|
|
743
|
+
req = JSON.parse(json);
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
const out = [];
|
|
749
|
+
if (Array.isArray(req.messages)) {
|
|
750
|
+
const lines = req.messages.map((m) => {
|
|
751
|
+
switch (m.role) {
|
|
752
|
+
case 'system':
|
|
753
|
+
return `[system] ${clip(m.content, 200)}`;
|
|
754
|
+
case 'user':
|
|
755
|
+
return `[user] ${clip(contentToText(m.content))}`;
|
|
756
|
+
case 'assistant':
|
|
757
|
+
return [
|
|
758
|
+
`[assistant${m.agent ? ` @${m.agent}` : ''}] ${clip(m.content) || '(no text)'}`,
|
|
759
|
+
...(m.toolCalls ?? []).map((c) => ` -> ${c.name} ${clip(c.args, 300)} #${c.id}`),
|
|
760
|
+
].join('\n');
|
|
761
|
+
case 'tool':
|
|
762
|
+
return `[tool ${m.name} #${m.callId}${m.isError ? ' ERROR' : ''}] ${clip(m.content)}`;
|
|
763
|
+
default:
|
|
764
|
+
return `[${m.role ?? '?'}]`;
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
out.push({ name: 'messages', text: lines.join('\n') });
|
|
768
|
+
}
|
|
769
|
+
if (Array.isArray(req.tools)) {
|
|
770
|
+
const names = req.tools
|
|
771
|
+
.map((t) => t?.name)
|
|
772
|
+
.filter((n) => typeof n === 'string');
|
|
773
|
+
out.push({ name: 'tools', text: names.join('\n') });
|
|
774
|
+
}
|
|
775
|
+
return out;
|
|
776
|
+
}
|
|
677
777
|
const GONE = '(the blob is not in this session’s store; only the preview survives)';
|
|
678
778
|
async function resolve(payloads, p) {
|
|
679
779
|
try {
|
package/dist/tui/app.js
CHANGED
|
@@ -9,6 +9,7 @@ import { History } from "../history.js";
|
|
|
9
9
|
import { format } from "../narrate.js";
|
|
10
10
|
import { display } from "../session.js";
|
|
11
11
|
import { CliError } from "../term.js";
|
|
12
|
+
import { hostUrls, hyperlink, hyperlinks, linkBlock, mountsOf } from "./links.js";
|
|
12
13
|
import { resolveTheme, THEMES } from "./theme.js";
|
|
13
14
|
import { ACTIVITY_ROWS, answerWidth, blocksOf, BOX_CHROME, boxWidth, BRANCH_ROWS, branchRows, budgetOf, CHROME_ROWS, clip, describeCall, gistOf, gridOf, summarise, THINKING_ROWS, TIME_COL, unmarked, windowOf, } from "./wrap.js";
|
|
14
15
|
const BANNER = { key: 'banner' };
|
|
@@ -30,6 +31,9 @@ const FRAME_MS = 100;
|
|
|
30
31
|
// A context rather than props only because every part of the view wants it.
|
|
31
32
|
const ThemeContext = React.createContext(THEMES.dark);
|
|
32
33
|
const useTheme = () => useContext(ThemeContext);
|
|
34
|
+
const LinksContext = React.createContext(undefined);
|
|
35
|
+
/** Without hyperlinks the only way to reach the file is to print where it is. */
|
|
36
|
+
const shownText = (text, links) => links && !links.osc8 ? hostUrls(text, links.mounts) : text;
|
|
33
37
|
function Row({ line }) {
|
|
34
38
|
const theme = useTheme();
|
|
35
39
|
const style = theme.line[line.kind];
|
|
@@ -87,7 +91,10 @@ function Call({ line }) {
|
|
|
87
91
|
*/
|
|
88
92
|
function Spans({ spans }) {
|
|
89
93
|
const theme = useTheme();
|
|
90
|
-
|
|
94
|
+
const links = useContext(LinksContext);
|
|
95
|
+
return (_jsx(_Fragment, { children: spans.map((s, i) => (_jsx(Text, { bold: s.bold === true, italic: s.italic === true, underline: s.href !== undefined && s.href !== '', ...(s.code ? { color: theme.code.color } : {}), children: links?.osc8 && s.href?.startsWith('file:')
|
|
96
|
+
? hyperlink(s.href, s.text)
|
|
97
|
+
: s.text }, i))) }));
|
|
91
98
|
}
|
|
92
99
|
/**
|
|
93
100
|
* One block of an answer.
|
|
@@ -128,11 +135,13 @@ function BlockView({ block, gap, width, }) {
|
|
|
128
135
|
* answer and boxing each of them turns the transcript into a stack of frames;
|
|
129
136
|
* the box means *this is the answer*, and it only means that if it is rare.
|
|
130
137
|
*/
|
|
131
|
-
function Answer({ text, boxed }) {
|
|
138
|
+
function Answer({ text: said, boxed }) {
|
|
132
139
|
const theme = useTheme();
|
|
140
|
+
const links = useContext(LinksContext);
|
|
133
141
|
const { stdout } = useStdout();
|
|
142
|
+
const text = shownText(said, links);
|
|
134
143
|
const width = boxWidth(text, stdout?.columns ?? 80);
|
|
135
|
-
const blocks = blocksOf(text);
|
|
144
|
+
const blocks = blocksOf(text).map((b) => (links?.osc8 ? linkBlock(b, links.mounts) : b));
|
|
136
145
|
const inner = width - (boxed ? 4 : GUTTER);
|
|
137
146
|
const body = blocks.map((block, i) => (
|
|
138
147
|
// Items of one list are one thing; a blank row between them is two
|
|
@@ -145,6 +154,10 @@ function Answer({ text, boxed }) {
|
|
|
145
154
|
function App({ engine, options, theme }) {
|
|
146
155
|
const { exit } = useApp();
|
|
147
156
|
const { stdout } = useStdout();
|
|
157
|
+
const [links] = useState(() => ({
|
|
158
|
+
mounts: mountsOf(engine.files),
|
|
159
|
+
osc8: hyperlinks(),
|
|
160
|
+
}));
|
|
148
161
|
const [lines, setLines] = useState([]);
|
|
149
162
|
const [live, setLive] = useState('');
|
|
150
163
|
const [thinking, setThinking] = useState('');
|
|
@@ -651,7 +664,7 @@ function App({ engine, options, theme }) {
|
|
|
651
664
|
recall(1);
|
|
652
665
|
}
|
|
653
666
|
});
|
|
654
|
-
return (_jsx(ThemeContext.Provider, { value: theme, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: [BANNER, ...lines], children: (item) => isBanner(item) ? (_jsx(Header, { engine: engine, readOnly: options.readOnly, started: options.started }, item.key)) : (_jsx(Row, { line: item }, item.key)) }), _jsxs(Box, { flexDirection: "column", justifyContent: "flex-end", height: busy ? grown.current : undefined, overflow: "hidden", children: [_jsx(Branches, { boxes: fitted.boxes, spin: spin, frame: frame, columns: columns }), streaming && budget.thinking ? (_jsx(Reasoning, { text: thinking, columns: columns, frame: frame, live: musing })) : null, _jsx(Activity, { rows: fitted.trunk, hidden: fitted.hidden, frame: frame }), live ? _jsx(Streaming, { text: live, columns: columns, rows: textRows }) : null] }), _jsx(Footer, { agent: agent, busy: busy, spin: spin, frame: frame, running: mine, forked: branches.current.size, step: step, model: model, stats: stats, inflight: inflight, reasoning: musing && streaming }), busy ? null : (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: "\u203A " }), _jsx(TextInput, { defaultValue: draft, placeholder: "Ask something\u2026 (\u2191 for history, /help for commands)", onChange: remember, onSubmit: submit }, generation)] }))] }) }));
|
|
667
|
+
return (_jsx(ThemeContext.Provider, { value: theme, children: _jsx(LinksContext.Provider, { value: links, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: [BANNER, ...lines], children: (item) => isBanner(item) ? (_jsx(Header, { engine: engine, readOnly: options.readOnly, started: options.started }, item.key)) : (_jsx(Row, { line: item }, item.key)) }), _jsxs(Box, { flexDirection: "column", justifyContent: "flex-end", height: busy ? grown.current : undefined, overflow: "hidden", children: [_jsx(Branches, { boxes: fitted.boxes, spin: spin, frame: frame, columns: columns }), streaming && budget.thinking ? (_jsx(Reasoning, { text: thinking, columns: columns, frame: frame, live: musing })) : null, _jsx(Activity, { rows: fitted.trunk, hidden: fitted.hidden, frame: frame }), live ? _jsx(Streaming, { text: live, columns: columns, rows: textRows }) : null] }), _jsx(Footer, { agent: agent, busy: busy, spin: spin, frame: frame, running: mine, forked: branches.current.size, step: step, model: model, stats: stats, inflight: inflight, reasoning: musing && streaming }), busy ? null : (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: "\u203A " }), _jsx(TextInput, { defaultValue: draft, placeholder: "Ask something\u2026 (\u2191 for history, /help for commands)", onChange: remember, onSubmit: submit }, generation)] }))] }) }) }));
|
|
655
668
|
}
|
|
656
669
|
function Header({ engine, readOnly, started, }) {
|
|
657
670
|
const theme = useTheme();
|
|
@@ -952,7 +965,8 @@ function Reasoning({ text, columns, frame, live, }) {
|
|
|
952
965
|
const gist = gistOf(text, columns - GUTTER - 2);
|
|
953
966
|
return (_jsx(Box, { height: 1, paddingLeft: GUTTER, overflow: "hidden", children: _jsx(Text, { wrap: "truncate-end", children: live ? (_jsx(Shimmer, { text: gist, frame: frame, color: theme.thinking.color })) : (_jsx(Text, { color: theme.thinking.color, children: gist })) }) }));
|
|
954
967
|
}
|
|
955
|
-
function Streaming({ text, columns, rows }) {
|
|
968
|
+
function Streaming({ text: said, columns, rows }) {
|
|
969
|
+
const text = shownText(said, useContext(LinksContext));
|
|
956
970
|
// The same width the finished answer will take, so landing it reflows
|
|
957
971
|
// nothing: what is on screen is what stays there.
|
|
958
972
|
//
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Workspace } from '@zenera/neo';
|
|
2
|
+
import type { Block, Span } from './wrap.ts';
|
|
3
|
+
export interface Mounts {
|
|
4
|
+
/** every name a tree is reached by, longest first */
|
|
5
|
+
names: readonly string[];
|
|
6
|
+
/** the host path an agent path names, or nothing when it is in no tree */
|
|
7
|
+
resolve(path: string): string | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Exactly the trees the file tools reach, under the same containment rule. */
|
|
10
|
+
export declare function mountsOf(files: Workspace): Mounts;
|
|
11
|
+
/** The host `file:` url a mounted path names, or nothing. */
|
|
12
|
+
export declare function mountUrl(token: string, mounts: Mounts): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* The same spans with the same text, mounted paths carrying a host `href`.
|
|
15
|
+
*
|
|
16
|
+
* Only `href` changes, so every width measured from `span.text` still holds.
|
|
17
|
+
*/
|
|
18
|
+
export declare function linkSpans(spans: readonly Span[], mounts: Mounts): Span[];
|
|
19
|
+
/** A block whose prose and cells link their mounted paths; a fence is left alone. */
|
|
20
|
+
export declare function linkBlock(block: Block, mounts: Mounts): Block;
|
|
21
|
+
/** For a terminal with no hyperlinks: every `file:///<mount>/…` url, rewritten to the host. */
|
|
22
|
+
export declare function hostUrls(text: string, mounts: Mounts): string;
|
|
23
|
+
/** `text` as an OSC 8 hyperlink to `href`. */
|
|
24
|
+
export declare function hyperlink(href: string, text: string): string;
|
|
25
|
+
/** Whether this terminal turns OSC 8 into a link; an unknown one is assumed not to. */
|
|
26
|
+
export declare function hyperlinks(env?: NodeJS.ProcessEnv): boolean;
|
|
27
|
+
//# sourceMappingURL=links.d.ts.map
|