@yaag/extension 0.3.0 → 0.4.0
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 -3
- package/package.json +4 -4
- package/src/cli-child.ts +34 -4
- package/src/eval-pipe.ts +25 -0
- package/src/event-reader.ts +8 -0
- package/src/fake-extension-ui.ts +6 -12
- package/src/fake-run-start.ts +25 -0
- package/src/resume-source.ts +77 -0
- package/src/run-argv.ts +16 -11
- package/src/run-call-render.ts +58 -0
- package/src/run-program-param.ts +137 -23
- package/src/run-record.ts +109 -30
- package/src/run-registry.ts +16 -0
- package/src/run-store.ts +22 -5
- package/src/run-summary-parse.ts +265 -0
- package/src/run-tool-test-support.ts +2 -61
- package/src/run-tool.ts +42 -19
- package/src/run-tree-host.ts +5 -4
- package/src/spawn-run.ts +10 -3
- package/src/status-tool.ts +32 -9
- package/src/test-tui-context.ts +4 -14
- package/src/yaag-command.ts +1 -1
package/src/run-tool.ts
CHANGED
|
@@ -12,9 +12,16 @@ import { RunTreeStore } from "./run-trees.ts";
|
|
|
12
12
|
export type { RunDetails } from "./run-details.ts";
|
|
13
13
|
|
|
14
14
|
import { type ProcessIdentity, readProcessStart } from "./process-liveness.ts";
|
|
15
|
+
import { resolveProgramParams } from "./resume-source.ts";
|
|
15
16
|
import type { ProgramTarget } from "./run-argv.ts";
|
|
16
17
|
import { mintRunId } from "./run-id.ts";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
confirmProgramTarget,
|
|
20
|
+
programExpansion,
|
|
21
|
+
programLabel,
|
|
22
|
+
programTarget,
|
|
23
|
+
} from "./run-program-param.ts";
|
|
24
|
+
import type { RunLaunch } from "./run-record.ts";
|
|
18
25
|
import type { LiveRun, RunRegistry, RunSettlement } from "./run-registry.ts";
|
|
19
26
|
import { errorText, failure, observedSettlement } from "./run-settlement.ts";
|
|
20
27
|
import { type RunHandle, type RunOutcome, type StartRunOptions, startRun } from "./spawn-run.ts";
|
|
@@ -44,7 +51,7 @@ const parameters = Type.Object({
|
|
|
44
51
|
resume: Type.Optional(
|
|
45
52
|
Type.String({
|
|
46
53
|
description:
|
|
47
|
-
"Resume from this Cassette: matching Asks replay free, then the Run continues live",
|
|
54
|
+
"Resume from this Cassette: matching Asks replay free, then the Run continues live. For an inline Run, give resume with no file and no script, and yaag reuses the stored source",
|
|
48
55
|
}),
|
|
49
56
|
),
|
|
50
57
|
});
|
|
@@ -84,14 +91,19 @@ const DESCRIPTION = [
|
|
|
84
91
|
"background Runs may overlap and each is stopped with `yaag_stop({ id })`.",
|
|
85
92
|
"",
|
|
86
93
|
"`script` runs a program that you give as source text. It needs no file.",
|
|
87
|
-
"Give `file` or `script
|
|
88
|
-
"fails before yaag starts the Run.",
|
|
94
|
+
"Give `file` or `script` to start a new Run. Do not give both. A call with",
|
|
95
|
+
"both, or a new Run with neither, fails before yaag starts the Run. A call that",
|
|
96
|
+
"gives `resume` alone is the one exception: it repeats a stored inline Run.",
|
|
89
97
|
"A `script` program has the same shape as a file program. It default-exports",
|
|
90
98
|
"`defineRun(...)`.",
|
|
91
99
|
'A `script` program can import "@yaag/runtime" and "typebox" only. Make a file',
|
|
92
100
|
"program if the program needs other modules.",
|
|
101
|
+
"A `script` program must be 65536 bytes (64 KiB) or smaller. Write a larger",
|
|
102
|
+
"program to a file and give `file`.",
|
|
93
103
|
"`args`, `background`, `record` and `resume` work the same way for `script`.",
|
|
94
|
-
"
|
|
104
|
+
"To resume an inline Run, give `resume` and give no `file` and no `script`.",
|
|
105
|
+
"yaag reads the program source from its own Run record.",
|
|
106
|
+
"Give `script` again only when you want to change the program.",
|
|
95
107
|
"`yaag_describe` reads a file only.",
|
|
96
108
|
"",
|
|
97
109
|
"Set `record` to write the Run's Cassette artifact. If a recorded Run fails,",
|
|
@@ -104,9 +116,13 @@ const DESCRIPTION = [
|
|
|
104
116
|
* The `yaag_run` tool: runs one Orchestration Program through the Bun CLI and
|
|
105
117
|
* returns its value (ADR-0005).
|
|
106
118
|
*
|
|
107
|
-
* `execute` throws on any failure
|
|
108
|
-
*
|
|
109
|
-
*
|
|
119
|
+
* `execute` throws on any failure, which is how pi marks a tool result as an
|
|
120
|
+
* error. It refuses the call before any child starts when the parameters are
|
|
121
|
+
* not one program: both `file` and `script`, a new Run with neither, a blank
|
|
122
|
+
* or oversize `script`, or a `resume` alone whose Run record holds no Inline
|
|
123
|
+
* Program source (ADR-0033). It then throws for a missing Bun, a program file
|
|
124
|
+
* that is not there, or a Run that failed. On a Run failure the message
|
|
125
|
+
* carries the tail of the CLI's own error output.
|
|
110
126
|
*/
|
|
111
127
|
export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof parameters, RunDetails> {
|
|
112
128
|
const { bun, cli, registry, sendMessage } = deps;
|
|
@@ -121,10 +137,11 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
121
137
|
renderCall(params, theme, context) {
|
|
122
138
|
const text =
|
|
123
139
|
context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
124
|
-
|
|
140
|
+
const head =
|
|
125
141
|
theme.fg("toolTitle", theme.bold("yaag_run")) +
|
|
126
|
-
|
|
127
|
-
);
|
|
142
|
+
theme.fg("muted", `(${programLabel(params)})`);
|
|
143
|
+
const body = context.expanded ? programExpansion(params) : "";
|
|
144
|
+
text.setText(body === "" ? head : `${head}\n${theme.fg("muted", body)}`);
|
|
128
145
|
return text;
|
|
129
146
|
},
|
|
130
147
|
renderResult(result, _options, _theme, context) {
|
|
@@ -138,10 +155,15 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
138
155
|
return tree;
|
|
139
156
|
},
|
|
140
157
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
158
|
+
// Shape only, before the Bun check: an exactly-one-of violation, an empty
|
|
159
|
+
// or oversize script is refused identically with or without Bun. The file
|
|
160
|
+
// probe comes after, so a Host Session with no Bun is told to install Bun
|
|
161
|
+
// instead of being told its path is wrong (ADR-0033).
|
|
162
|
+
// A resume with no file and no script takes its source from the Run
|
|
163
|
+
// record, before the shape check, so the shape it produces is valid.
|
|
164
|
+
const target = programTarget(await resolveProgramParams(params, registry));
|
|
144
165
|
if (bun === null) throw new Error(statusReport(null, cli));
|
|
166
|
+
const program = await confirmProgramTarget(target);
|
|
145
167
|
|
|
146
168
|
const id = mintRunId();
|
|
147
169
|
const background = params.background === true;
|
|
@@ -188,7 +210,7 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
188
210
|
}
|
|
189
211
|
|
|
190
212
|
/**
|
|
191
|
-
* Cassette paths
|
|
213
|
+
* Cassette paths are `resolve`d here; the CLI owns every rule about them —
|
|
192
214
|
* combinations, validation, refusals — and its own message surfaces on failure.
|
|
193
215
|
*/
|
|
194
216
|
function cassetteOptions(params: { readonly record?: string; readonly resume?: string }): {
|
|
@@ -256,14 +278,15 @@ function registeredRun(options: {
|
|
|
256
278
|
outcome: handle.outcome,
|
|
257
279
|
summary: initialSummary(),
|
|
258
280
|
};
|
|
259
|
-
const
|
|
260
|
-
...(options.program.kind === "file"
|
|
261
|
-
? { file: options.program.file }
|
|
262
|
-
: { script: options.program.source }),
|
|
281
|
+
const context = {
|
|
263
282
|
...(options.args === undefined ? {} : { args: options.args }),
|
|
264
283
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
265
284
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
266
285
|
};
|
|
286
|
+
const launch: RunLaunch =
|
|
287
|
+
options.program.kind === "file"
|
|
288
|
+
? { kind: "file", file: options.program.file, ...context }
|
|
289
|
+
: { kind: "inline", script: options.program.source, ...context };
|
|
267
290
|
options.registry.add(run, launch, processIdentity(handle.pid));
|
|
268
291
|
return run;
|
|
269
292
|
}
|
package/src/run-tree-host.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { NamedKeybindings, RunTreeViewHost, TreeState } from "@yaag/tui";
|
|
|
13
13
|
|
|
14
14
|
/** The pi services one open view holds. */
|
|
15
15
|
export interface RunTreeHostOptions {
|
|
16
|
-
readonly ui: Pick<ExtensionUIContext, "notify"
|
|
16
|
+
readonly ui: Pick<ExtensionUIContext, "notify">;
|
|
17
17
|
readonly tui: TUI;
|
|
18
18
|
readonly keybindings: NamedKeybindings;
|
|
19
19
|
/** The Run's projection; the host only reads it. */
|
|
@@ -31,10 +31,11 @@ export function createRunTreeHost(options: RunTreeHostOptions): ReadOnlyRunTreeH
|
|
|
31
31
|
agentInfo: (agent) => state.summary.agents[agent],
|
|
32
32
|
readSession: (agent) => readSession(state.summary.agents[agent]),
|
|
33
33
|
sessionPath: (agent) => state.summary.agents[agent]?.sessionFile ?? null,
|
|
34
|
+
// No capability here opens a pi dialog: `ui.editor`, `select`, `input`, and
|
|
35
|
+
// `confirm` all clear the editor container, which removes the non-overlay
|
|
36
|
+
// `ctx.ui.custom()` view and stops pi's input loop (ADR-0034). Every drill
|
|
37
|
+
// layer draws itself instead.
|
|
34
38
|
copyPath: (text) => copyToClipboard(text),
|
|
35
|
-
openEditor: async (title, body) => {
|
|
36
|
-
await ui.editor(title, body);
|
|
37
|
-
},
|
|
38
39
|
notify: (message, level) => ui.notify(message, level),
|
|
39
40
|
requestRender: () => tui.requestRender(),
|
|
40
41
|
rows: () => Math.max(1, tui.terminal.rows - 1),
|
package/src/spawn-run.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface RunOutcome {
|
|
|
18
18
|
export interface StartRunOptions {
|
|
19
19
|
readonly bun: string;
|
|
20
20
|
readonly cli: string;
|
|
21
|
-
/** A program file, or an Inline Program
|
|
21
|
+
/** A program file, or an Inline Program whose source travels on fd 4 as `--eval-fd` (ADR-0033). */
|
|
22
22
|
readonly program: ProgramTarget;
|
|
23
23
|
/** Opaque JSON, forwarded untouched: the program validates it (ADR-0010). */
|
|
24
24
|
readonly args?: string;
|
|
@@ -48,7 +48,8 @@ export interface RunHandle {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Starts one Orchestration Program as a child `bun` process without waiting for
|
|
51
|
-
* it. Descriptor 3 remains exclusive to Run Lifecycle Events (ADR-0016)
|
|
51
|
+
* it. Descriptor 3 remains exclusive to Run Lifecycle Events out (ADR-0016),
|
|
52
|
+
* and descriptor 4 carries the Inline Program source in (ADR-0033).
|
|
52
53
|
*
|
|
53
54
|
* Never throws for a failed Run — the exit code and error tail are part of its
|
|
54
55
|
* outcome; callers choose how that failure reaches the Host Session.
|
|
@@ -60,7 +61,13 @@ export function startRun(options: StartRunOptions): RunHandle {
|
|
|
60
61
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
61
62
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
62
63
|
});
|
|
63
|
-
const child = startCliChild({
|
|
64
|
+
const child = startCliChild({
|
|
65
|
+
bun: options.bun,
|
|
66
|
+
cli: options.cli,
|
|
67
|
+
argv,
|
|
68
|
+
events: true,
|
|
69
|
+
...(options.program.kind === "inline" ? { evalSource: options.program.source } : {}),
|
|
70
|
+
});
|
|
64
71
|
let summary: RunSummary = initialSummary();
|
|
65
72
|
let sequence = 0;
|
|
66
73
|
const outcome = Promise.all([
|
package/src/status-tool.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentToolResult, ToolDefinition } from "@earendil-works/pi-coding-
|
|
|
2
2
|
import type { RunSummary } from "@yaag/runtime";
|
|
3
3
|
import { renderSnapshot, TreeState } from "@yaag/tui";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
|
-
import type {
|
|
5
|
+
import type { RunRecord } from "./run-record.ts";
|
|
6
6
|
import type { RegisteredRun, RestoredRun, RunRegistry } from "./run-registry.ts";
|
|
7
7
|
import { toUsage } from "./usage.ts";
|
|
8
8
|
|
|
@@ -35,6 +35,9 @@ const DESCRIPTION = [
|
|
|
35
35
|
"",
|
|
36
36
|
"Without an id, lists this session's Runs plus every orphaned or interrupted",
|
|
37
37
|
"Run left by an earlier session.",
|
|
38
|
+
"",
|
|
39
|
+
"An interrupted inline Run resumes with `resume` alone: yaag reads the program",
|
|
40
|
+
"source from its own Run record.",
|
|
38
41
|
].join("\n");
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -114,20 +117,40 @@ function restoredDetails(run: RestoredRun): SnapshotDetails {
|
|
|
114
117
|
record.state === "orphaned" && record.process !== null
|
|
115
118
|
? ` Its process ${record.process.pid} is still running; stop it with yaag_stop.`
|
|
116
119
|
: "";
|
|
117
|
-
const detail = `${alive}${resumeHint(record
|
|
120
|
+
const detail = `${alive}${resumeHint(record)}`;
|
|
118
121
|
const result = record.outcome?.kind === "fulfilled" ? `\n${record.outcome.result}` : "";
|
|
119
122
|
return { id: record.id, summary: run.summary, result: `${state}${detail}${result}` };
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
/**
|
|
123
|
-
* Names the Checkpoint an unowned Run left behind (ADR-0031)
|
|
124
|
-
*
|
|
125
|
-
* one, for a dead Run and for a still-running
|
|
126
|
+
* Names the Checkpoint an unowned Run left behind (ADR-0031), and the call that
|
|
127
|
+
* resumes it. The Run republishes the Checkpoint at each Ask boundary, so it
|
|
128
|
+
* exists whenever the Run reached one, for a dead Run and for a still-running
|
|
129
|
+
* orphan alike.
|
|
130
|
+
*
|
|
131
|
+
* The hint names the Checkpoint path and never the stored program source: the
|
|
132
|
+
* path is enough, because yaag reads the source back from the Run record
|
|
133
|
+
* (ADR-0033). A whole source here would flood the transcript and repeat a
|
|
134
|
+
* private text the record already holds.
|
|
126
135
|
*/
|
|
127
|
-
function resumeHint(
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
136
|
+
function resumeHint(record: RunRecord): string {
|
|
137
|
+
const artifact = record.summary.artifact;
|
|
138
|
+
if (artifact === null || artifact === undefined) return "";
|
|
139
|
+
if (record.state !== "interrupted" && record.state !== "orphaned") return "";
|
|
140
|
+
// Each path becomes a JSON string literal: a path can hold a quote, a
|
|
141
|
+
// backslash, or a newline, and the hint must stay a call the model can copy.
|
|
142
|
+
const resume = JSON.stringify(artifact);
|
|
143
|
+
const { launch } = record;
|
|
144
|
+
switch (launch.kind) {
|
|
145
|
+
case "inline":
|
|
146
|
+
return ` Resume it with yaag_run({ resume: ${resume} }) and no file and no script; yaag reuses the program source it stored.`;
|
|
147
|
+
case "file":
|
|
148
|
+
return ` Resume it with yaag_run({ file: ${JSON.stringify(launch.file)}, resume: ${resume} }).`;
|
|
149
|
+
default: {
|
|
150
|
+
const never: never = launch;
|
|
151
|
+
return never;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
131
154
|
}
|
|
132
155
|
|
|
133
156
|
function snapshot(
|
package/src/test-tui-context.ts
CHANGED
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
* covers the rpc/json path, where every `ui` access must throw.
|
|
8
8
|
*/
|
|
9
9
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import {
|
|
11
|
-
FakeExtensionUi,
|
|
12
|
-
type Notice,
|
|
13
|
-
type OpenedComponent,
|
|
14
|
-
type OpenedEditor,
|
|
15
|
-
} from "./fake-extension-ui.ts";
|
|
10
|
+
import { FakeExtensionUi, type Notice, type OpenedComponent } from "./fake-extension-ui.ts";
|
|
16
11
|
|
|
17
12
|
export type { OpenedComponent } from "./fake-extension-ui.ts";
|
|
18
13
|
|
|
@@ -22,9 +17,9 @@ export interface TestTuiContextOptions {
|
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
/**
|
|
25
|
-
* A tui context whose UI is `FakeExtensionUi`: `custom
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* A tui context whose UI is `FakeExtensionUi`: `custom` and `notify` work, and
|
|
21
|
+
* every other capability throws on access, so an accidental dependency fails
|
|
22
|
+
* loudly. `ui.editor` throws with the rest (ADR-0034).
|
|
28
23
|
*/
|
|
29
24
|
export class TestTuiContext implements ExtensionContext {
|
|
30
25
|
readonly mode: ExtensionContext["mode"] = "tui";
|
|
@@ -53,11 +48,6 @@ export class TestTuiContext implements ExtensionContext {
|
|
|
53
48
|
return this.ui.notices;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
/** Editors the view opened. */
|
|
57
|
-
get edits(): readonly OpenedEditor[] {
|
|
58
|
-
return this.ui.edits;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
51
|
/** The latest inline widget frame per key. */
|
|
62
52
|
get widgets(): ReadonlyMap<string, string[] | undefined> {
|
|
63
53
|
return this.ui.widgets;
|
package/src/yaag-command.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface YaagCommandOptions {
|
|
|
33
33
|
/** Everything `/yaag` needs from its pi context, and nothing more. */
|
|
34
34
|
export interface YaagContext {
|
|
35
35
|
readonly mode: ExtensionContext["mode"];
|
|
36
|
-
readonly ui: Pick<ExtensionUIContext, "notify" | "select" | "custom"
|
|
36
|
+
readonly ui: Pick<ExtensionUIContext, "notify" | "select" | "custom">;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Builds the `/yaag` command bound to this session's registry and projections. */
|