@yaag/extension 0.2.1 → 0.3.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 +6 -1
- package/package.json +4 -4
- package/src/run-argv.ts +34 -0
- package/src/run-program-param.ts +64 -0
- package/src/run-record.ts +12 -2
- package/src/run-tool-test-support.ts +56 -2
- package/src/run-tool.ts +35 -14
- package/src/spawn-run.ts +11 -5
package/README.md
CHANGED
|
@@ -35,12 +35,17 @@ For a throwaway session instead: `pi -e ./packages/extension`.
|
|
|
35
35
|
|
|
36
36
|
| Parameter | Type | Meaning |
|
|
37
37
|
|---|---|---|
|
|
38
|
-
| `file` | `string
|
|
38
|
+
| `file` | `string?` | Path to the Orchestration Program file |
|
|
39
|
+
| `script` | `string?` | Orchestration Program source text (Inline Program) |
|
|
39
40
|
| `args` | `string?` | The program's arguments, as a JSON object string |
|
|
40
41
|
| `background` | `boolean?` | Start the Run in the background and return its Run id |
|
|
41
42
|
| `record` | `string?` | Write this Run's Cassette artifact to this path |
|
|
42
43
|
| `resume` | `string?` | Replay a matching Cassette prefix, then continue live |
|
|
43
44
|
|
|
45
|
+
Give `file` or `script`, and not both; a violation is a parameter error raised
|
|
46
|
+
before any process starts. A `script` program can import `@yaag/runtime` and
|
|
47
|
+
`typebox` only (ADR-0033).
|
|
48
|
+
|
|
44
49
|
Blocking by default: the call returns when the Run ends, and its content is the
|
|
45
50
|
Run's return value. With `background: true` it returns at once with a short Run
|
|
46
51
|
id (`r1`, `r2`, …) and the result arrives later as a follow-up message that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
27
|
-
"@yaag/cli": "0.
|
|
28
|
-
"@yaag/runtime": "0.
|
|
29
|
-
"@yaag/tui": "0.
|
|
27
|
+
"@yaag/cli": "0.3.0",
|
|
28
|
+
"@yaag/runtime": "0.3.0",
|
|
29
|
+
"@yaag/tui": "0.3.0",
|
|
30
30
|
"nanoid": "^6.0.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
package/src/run-argv.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** What a Run runs: a program file, or Inline Program source text (ADR-0033). */
|
|
2
|
+
export type ProgramTarget =
|
|
3
|
+
| { readonly kind: "file"; readonly file: string }
|
|
4
|
+
| { readonly kind: "inline"; readonly source: string };
|
|
5
|
+
|
|
6
|
+
/** The parts of a Run the CLI argv carries. */
|
|
7
|
+
export interface RunArgvOptions {
|
|
8
|
+
readonly program: ProgramTarget;
|
|
9
|
+
/** Opaque JSON, forwarded untouched: the program validates it (ADR-0010). */
|
|
10
|
+
readonly args?: string;
|
|
11
|
+
/** Cassette path to write, forwarded as `--record` (ADR-0013). */
|
|
12
|
+
readonly record?: string;
|
|
13
|
+
/** Cassette path to resume from, forwarded as `--resume` (ADR-0014). */
|
|
14
|
+
readonly resume?: string;
|
|
15
|
+
/** The descriptor Run Lifecycle Events travel on; 3 by default (ADR-0016). */
|
|
16
|
+
readonly eventsFd?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds the CLI argv for one Run.
|
|
21
|
+
*
|
|
22
|
+
* An Inline Program travels as one `--eval` argument holding the source text
|
|
23
|
+
* verbatim (ADR-0033); this function never quotes, escapes or validates it —
|
|
24
|
+
* the CLI owns the closed import contract.
|
|
25
|
+
*/
|
|
26
|
+
export function runArgv(options: RunArgvOptions): readonly string[] {
|
|
27
|
+
const { program } = options;
|
|
28
|
+
const argv = program.kind === "file" ? ["run", program.file] : ["run", "--eval", program.source];
|
|
29
|
+
argv.push("--events-fd", String(options.eventsFd ?? 3));
|
|
30
|
+
if (options.args !== undefined) argv.push("--args", options.args);
|
|
31
|
+
if (options.record !== undefined) argv.push("--record", options.record);
|
|
32
|
+
if (options.resume !== undefined) argv.push("--resume", options.resume);
|
|
33
|
+
return argv;
|
|
34
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { sanitizeTerminalLine } from "@yaag/tui";
|
|
4
|
+
import type { ProgramTarget } from "./run-argv.ts";
|
|
5
|
+
|
|
6
|
+
/** The `yaag_run` parameters that name the program; exactly one is given. */
|
|
7
|
+
export interface ProgramParams {
|
|
8
|
+
readonly file?: string;
|
|
9
|
+
readonly script?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const LABEL_LIMIT = 60;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the program the caller named to a {@link ProgramTarget}.
|
|
16
|
+
*
|
|
17
|
+
* `file` and `script` are exactly-one-of (ADR-0033): a violation throws a
|
|
18
|
+
* parameter error, so the refusal happens before any process starts. A `script`
|
|
19
|
+
* touches no filesystem; the CLI writes and owns its temp module.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveProgramTarget(params: ProgramParams): Promise<ProgramTarget> {
|
|
22
|
+
const { file, script } = params;
|
|
23
|
+
if (file !== undefined && script !== undefined) {
|
|
24
|
+
throw new Error("yaag_run: give file or script, and not both");
|
|
25
|
+
}
|
|
26
|
+
if (script !== undefined) {
|
|
27
|
+
if (script.trim() === "") throw new Error("yaag_run: script is empty");
|
|
28
|
+
return { kind: "inline", source: script };
|
|
29
|
+
}
|
|
30
|
+
if (file === undefined) throw new Error("yaag_run: give file or script");
|
|
31
|
+
const resolved = resolve(file);
|
|
32
|
+
try {
|
|
33
|
+
await access(resolved);
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error(`yaag_run: no such Orchestration Program: ${resolved}`);
|
|
36
|
+
}
|
|
37
|
+
return { kind: "file", file: resolved };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A one-line label for the call render. Total by design: the render runs before
|
|
42
|
+
* `execute` validates, so an invalid parameter pair must still produce a label.
|
|
43
|
+
*
|
|
44
|
+
* An Inline Program shows its declared name, else its first line — never a temp
|
|
45
|
+
* path, which exists only inside the CLI.
|
|
46
|
+
*/
|
|
47
|
+
export function programLabel(params: ProgramParams): string {
|
|
48
|
+
const { file, script } = params;
|
|
49
|
+
if (file !== undefined && script !== undefined) return "(invalid call)";
|
|
50
|
+
if (script !== undefined) return truncate(inlineLabel(script));
|
|
51
|
+
if (file !== undefined) return file;
|
|
52
|
+
return "(invalid call)";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function inlineLabel(source: string): string {
|
|
56
|
+
const declared = /name:\s*["'`]([^"'`]+)/.exec(source);
|
|
57
|
+
const line = declared?.[1] ?? source.split("\n").find((one) => one.trim() !== "") ?? "";
|
|
58
|
+
return sanitizeTerminalLine(line).replace(/\s+/g, " ").trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function truncate(label: string): string {
|
|
62
|
+
if (label === "") return "(empty script)";
|
|
63
|
+
return label.length <= LABEL_LIMIT ? label : `${label.slice(0, LABEL_LIMIT)}…`;
|
|
64
|
+
}
|
package/src/run-record.ts
CHANGED
|
@@ -18,7 +18,14 @@ export type PersistedOutcome =
|
|
|
18
18
|
|
|
19
19
|
/** What starting a Run said about it; enough to describe it in a later session. */
|
|
20
20
|
export interface RunLaunch {
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Exactly one of `file` and `script` is present: `file` for a program file,
|
|
23
|
+
* `script` for an Inline Program (ADR-0033). The pair stays a tolerant
|
|
24
|
+
* optional pair rather than a union, because a record written by an older
|
|
25
|
+
* yaag must still narrow.
|
|
26
|
+
*/
|
|
27
|
+
readonly file?: string;
|
|
28
|
+
readonly script?: string;
|
|
22
29
|
readonly args?: string;
|
|
23
30
|
readonly record?: string;
|
|
24
31
|
readonly resume?: string;
|
|
@@ -82,7 +89,10 @@ function isState(value: unknown): value is PersistedRunState {
|
|
|
82
89
|
|
|
83
90
|
function isLaunch(value: unknown): value is RunLaunch {
|
|
84
91
|
return (
|
|
85
|
-
typeof value === "object" &&
|
|
92
|
+
typeof value === "object" &&
|
|
93
|
+
value !== null &&
|
|
94
|
+
(("file" in value && typeof value.file === "string") ||
|
|
95
|
+
("script" in value && typeof value.script === "string"))
|
|
86
96
|
);
|
|
87
97
|
}
|
|
88
98
|
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
4
|
+
import { initialSummary } from "@yaag/runtime";
|
|
3
5
|
import type { BackgroundStatus } from "./background-status.ts";
|
|
6
|
+
import { fakeTheme } from "./fake-theme.ts";
|
|
4
7
|
import { resolveBun } from "./resolve-bun.ts";
|
|
5
8
|
import { resolveCliEntry } from "./resolve-cli.ts";
|
|
6
9
|
import type { RunDetails } from "./run-details.ts";
|
|
7
10
|
import { RunRegistry } from "./run-registry.ts";
|
|
8
11
|
import { createRunTool, type SendMessage } from "./run-tool.ts";
|
|
9
12
|
import type { RunTreeStore } from "./run-trees.ts";
|
|
10
|
-
import type { RunHandle } from "./spawn-run.ts";
|
|
13
|
+
import type { RunHandle, StartRunOptions } from "./spawn-run.ts";
|
|
11
14
|
import { createStopTool, type StopDetails } from "./stop-tool.ts";
|
|
12
15
|
import { TestExtensionContext } from "./test-extension-context.ts";
|
|
13
16
|
|
|
@@ -19,9 +22,60 @@ const ctx = new TestExtensionContext(dirname(cli));
|
|
|
19
22
|
export const fixture = (name: string): string => join(dirname(cli), "fixtures", `${name}.ts`);
|
|
20
23
|
|
|
21
24
|
export interface RunParams {
|
|
22
|
-
readonly file
|
|
25
|
+
readonly file?: string;
|
|
26
|
+
readonly script?: string;
|
|
23
27
|
readonly args?: string;
|
|
24
28
|
readonly background?: boolean;
|
|
29
|
+
readonly record?: string;
|
|
30
|
+
readonly resume?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Renders the tool's call line, stripped of styling, as the transcript shows it. */
|
|
34
|
+
export function renderCallLabel(params: RunParams): string {
|
|
35
|
+
const tool = createRunTool({
|
|
36
|
+
bun,
|
|
37
|
+
cli,
|
|
38
|
+
registry: new RunRegistry(),
|
|
39
|
+
sendMessage: () => undefined,
|
|
40
|
+
});
|
|
41
|
+
const component = tool.renderCall?.(params, fakeTheme(), {
|
|
42
|
+
args: params,
|
|
43
|
+
toolCallId: "call-1",
|
|
44
|
+
invalidate: () => {},
|
|
45
|
+
lastComponent: undefined,
|
|
46
|
+
state: undefined,
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
executionStarted: false,
|
|
49
|
+
argsComplete: true,
|
|
50
|
+
isPartial: false,
|
|
51
|
+
expanded: false,
|
|
52
|
+
showImages: false,
|
|
53
|
+
isError: false,
|
|
54
|
+
});
|
|
55
|
+
return stripTerminalSequences(component?.render(80)[0] ?? "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A start seam that never spawns: it captures its options and ends the Run at once. */
|
|
59
|
+
export function fakeStart(stdout = "{}"): {
|
|
60
|
+
readonly start: (options: StartRunOptions) => RunHandle;
|
|
61
|
+
readonly seen: StartRunOptions[];
|
|
62
|
+
} {
|
|
63
|
+
const seen: StartRunOptions[] = [];
|
|
64
|
+
return {
|
|
65
|
+
seen,
|
|
66
|
+
start: (options) => {
|
|
67
|
+
seen.push(options);
|
|
68
|
+
return {
|
|
69
|
+
stop: () => {},
|
|
70
|
+
outcome: Promise.resolve({
|
|
71
|
+
code: 0,
|
|
72
|
+
stdout,
|
|
73
|
+
stderr: "",
|
|
74
|
+
summary: initialSummary(),
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
25
79
|
}
|
|
26
80
|
|
|
27
81
|
export interface Sent {
|
package/src/run-tool.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
2
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
@@ -13,14 +12,26 @@ import { RunTreeStore } from "./run-trees.ts";
|
|
|
13
12
|
export type { RunDetails } from "./run-details.ts";
|
|
14
13
|
|
|
15
14
|
import { type ProcessIdentity, readProcessStart } from "./process-liveness.ts";
|
|
15
|
+
import type { ProgramTarget } from "./run-argv.ts";
|
|
16
16
|
import { mintRunId } from "./run-id.ts";
|
|
17
|
+
import { programLabel, resolveProgramTarget } from "./run-program-param.ts";
|
|
17
18
|
import type { LiveRun, RunRegistry, RunSettlement } from "./run-registry.ts";
|
|
18
19
|
import { errorText, failure, observedSettlement } from "./run-settlement.ts";
|
|
19
20
|
import { type RunHandle, type RunOutcome, type StartRunOptions, startRun } from "./spawn-run.ts";
|
|
20
21
|
import { statusReport } from "./status.ts";
|
|
21
22
|
|
|
22
23
|
const parameters = Type.Object({
|
|
23
|
-
file: Type.
|
|
24
|
+
file: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description: "Path to the Orchestration Program file; use script instead for inline source",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
script: Type.Optional(
|
|
30
|
+
Type.String({
|
|
31
|
+
description:
|
|
32
|
+
'The Orchestration Program source text; it can import "@yaag/runtime" and "typebox" only',
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
24
35
|
args: Type.Optional(
|
|
25
36
|
Type.String({ description: "The program's arguments, as a JSON object string" }),
|
|
26
37
|
),
|
|
@@ -72,6 +83,17 @@ const DESCRIPTION = [
|
|
|
72
83
|
"later message. Every Run gets an id and can be inspected with `yaag_status`;",
|
|
73
84
|
"background Runs may overlap and each is stopped with `yaag_stop({ id })`.",
|
|
74
85
|
"",
|
|
86
|
+
"`script` runs a program that you give as source text. It needs no file.",
|
|
87
|
+
"Give `file` or `script`. Do not give both. A call with both, or with neither,",
|
|
88
|
+
"fails before yaag starts the Run.",
|
|
89
|
+
"A `script` program has the same shape as a file program. It default-exports",
|
|
90
|
+
"`defineRun(...)`.",
|
|
91
|
+
'A `script` program can import "@yaag/runtime" and "typebox" only. Make a file',
|
|
92
|
+
"program if the program needs other modules.",
|
|
93
|
+
"`args`, `background`, `record` and `resume` work the same way for `script`.",
|
|
94
|
+
"Give the same script when you resume a recorded `script` Run.",
|
|
95
|
+
"`yaag_describe` reads a file only.",
|
|
96
|
+
"",
|
|
75
97
|
"Set `record` to write the Run's Cassette artifact. If a recorded Run fails,",
|
|
76
98
|
"pass its artifact as `resume` on the retry: Asks that already succeeded",
|
|
77
99
|
"replay instantly and free, and the Run goes live where it diverges. Record",
|
|
@@ -100,7 +122,8 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
100
122
|
const text =
|
|
101
123
|
context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
102
124
|
text.setText(
|
|
103
|
-
theme.fg("toolTitle", theme.bold("yaag_run")) +
|
|
125
|
+
theme.fg("toolTitle", theme.bold("yaag_run")) +
|
|
126
|
+
theme.fg("muted", `(${programLabel(params)})`),
|
|
104
127
|
);
|
|
105
128
|
return text;
|
|
106
129
|
},
|
|
@@ -115,15 +138,11 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
115
138
|
return tree;
|
|
116
139
|
},
|
|
117
140
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
141
|
+
// Parameter validation precedes the Bun check, so an exactly-one-of
|
|
142
|
+
// violation is refused identically with or without Bun (ADR-0033).
|
|
143
|
+
const program = await resolveProgramTarget(params);
|
|
118
144
|
if (bun === null) throw new Error(statusReport(null, cli));
|
|
119
145
|
|
|
120
|
-
const file = resolve(params.file);
|
|
121
|
-
try {
|
|
122
|
-
await access(file);
|
|
123
|
-
} catch {
|
|
124
|
-
throw new Error(`yaag_run: no such Orchestration Program: ${file}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
146
|
const id = mintRunId();
|
|
128
147
|
const background = params.background === true;
|
|
129
148
|
const inline = inlineAvailable(ctx, background);
|
|
@@ -133,7 +152,7 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
133
152
|
const run = registeredRun({
|
|
134
153
|
bun,
|
|
135
154
|
cli,
|
|
136
|
-
|
|
155
|
+
program,
|
|
137
156
|
args: params.args,
|
|
138
157
|
...cassetteOptions(params),
|
|
139
158
|
id,
|
|
@@ -207,7 +226,7 @@ async function announce(
|
|
|
207
226
|
function registeredRun(options: {
|
|
208
227
|
readonly bun: string;
|
|
209
228
|
readonly cli: string;
|
|
210
|
-
readonly
|
|
229
|
+
readonly program: ProgramTarget;
|
|
211
230
|
readonly args?: string;
|
|
212
231
|
readonly record?: string;
|
|
213
232
|
readonly resume?: string;
|
|
@@ -221,7 +240,7 @@ function registeredRun(options: {
|
|
|
221
240
|
const handle = options.start({
|
|
222
241
|
bun: options.bun,
|
|
223
242
|
cli: options.cli,
|
|
224
|
-
|
|
243
|
+
program: options.program,
|
|
225
244
|
args: options.args,
|
|
226
245
|
record: options.record,
|
|
227
246
|
resume: options.resume,
|
|
@@ -238,7 +257,9 @@ function registeredRun(options: {
|
|
|
238
257
|
summary: initialSummary(),
|
|
239
258
|
};
|
|
240
259
|
const launch = {
|
|
241
|
-
|
|
260
|
+
...(options.program.kind === "file"
|
|
261
|
+
? { file: options.program.file }
|
|
262
|
+
: { script: options.program.source }),
|
|
242
263
|
...(options.args === undefined ? {} : { args: options.args }),
|
|
243
264
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
244
265
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
package/src/spawn-run.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { applyEvent, initialSummary, type LifecycleEvent, type RunSummary } from "@yaag/runtime";
|
|
2
2
|
import { startCliChild } from "./cli-child.ts";
|
|
3
3
|
import { readEvents } from "./event-reader.ts";
|
|
4
|
+
import { type ProgramTarget, runArgv } from "./run-argv.ts";
|
|
5
|
+
|
|
6
|
+
export type { ProgramTarget } from "./run-argv.ts";
|
|
4
7
|
|
|
5
8
|
/** Everything one Run of the CLI produced, once the child has exited. */
|
|
6
9
|
export interface RunOutcome {
|
|
@@ -15,7 +18,8 @@ export interface RunOutcome {
|
|
|
15
18
|
export interface StartRunOptions {
|
|
16
19
|
readonly bun: string;
|
|
17
20
|
readonly cli: string;
|
|
18
|
-
|
|
21
|
+
/** A program file, or an Inline Program that travels as `--eval` (ADR-0033). */
|
|
22
|
+
readonly program: ProgramTarget;
|
|
19
23
|
/** Opaque JSON, forwarded untouched: the program validates it (ADR-0010). */
|
|
20
24
|
readonly args?: string;
|
|
21
25
|
/** Cassette path to write, forwarded as `--record` (ADR-0013). */
|
|
@@ -50,10 +54,12 @@ export interface RunHandle {
|
|
|
50
54
|
* outcome; callers choose how that failure reaches the Host Session.
|
|
51
55
|
*/
|
|
52
56
|
export function startRun(options: StartRunOptions): RunHandle {
|
|
53
|
-
const argv =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const argv = runArgv({
|
|
58
|
+
program: options.program,
|
|
59
|
+
...(options.args === undefined ? {} : { args: options.args }),
|
|
60
|
+
...(options.record === undefined ? {} : { record: options.record }),
|
|
61
|
+
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
62
|
+
});
|
|
57
63
|
const child = startCliChild({ bun: options.bun, cli: options.cli, argv, events: true });
|
|
58
64
|
let summary: RunSummary = initialSummary();
|
|
59
65
|
let sequence = 0;
|