@yaag/extension 0.7.0 → 0.8.1
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 +10 -0
- package/docs/authoring.md +115 -0
- package/docs/cli.md +105 -0
- package/docs/configuration.md +87 -0
- package/docs/examples/01-minimal.ts +14 -0
- package/docs/examples/02-args.ts +22 -0
- package/docs/examples/03-fan-out.ts +27 -0
- package/docs/examples/04-controlled-ask.ts +33 -0
- package/docs/examples/05-record-resume.ts +18 -0
- package/docs/examples/config.json +5 -0
- package/docs/examples.md +102 -0
- package/docs/getting-started.md +54 -0
- package/docs/troubleshooting.md +133 -0
- package/package.json +5 -4
- package/src/docs/doc-embed.ts +127 -0
- package/src/docs/docs-root.ts +33 -0
- package/src/docs/index.ts +13 -0
- package/src/process/run-argv.ts +6 -0
- package/src/process/spawn-run.ts +6 -0
- package/src/process/status.ts +4 -1
- package/src/record/index.ts +1 -1
- package/src/record/resume-source.ts +56 -11
- package/src/record/run-record.ts +6 -0
- package/src/record/run-registry.ts +11 -1
- package/src/testing/run-call-render.ts +2 -0
- package/src/tool/run-tool.ts +38 -4
- package/src/tool/system-prompt-append.ts +3 -0
- package/src/view/restored-run-text.ts +5 -2
package/README.md
CHANGED
|
@@ -5,6 +5,16 @@ The yaag pi extension: run Orchestration Programs from a pi session.
|
|
|
5
5
|
It is loaded by pi through jiti, in **Node**, and bridges to the `yaag` CLI,
|
|
6
6
|
which runs on **Bun** (ADR-0005, ADR-0015).
|
|
7
7
|
|
|
8
|
+
## Docs
|
|
9
|
+
|
|
10
|
+
The package ships the user and agent docs in `docs/`:
|
|
11
|
+
|
|
12
|
+
- [`docs/getting-started.md`](docs/getting-started.md) — install, prerequisites, first Run.
|
|
13
|
+
- [`docs/authoring.md`](docs/authoring.md) — how to write an Orchestration Program.
|
|
14
|
+
- [`docs/examples.md`](docs/examples.md) — five example programs, in reading order.
|
|
15
|
+
- [`docs/cli.md`](docs/cli.md) — CLI flags and tool parameters.
|
|
16
|
+
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — the errors you can meet.
|
|
17
|
+
|
|
8
18
|
## Prerequisites
|
|
9
19
|
|
|
10
20
|
**Bun is required.** The extension itself runs in pi's Node process, but every
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Authoring an Orchestration Program
|
|
2
|
+
|
|
3
|
+
A program is a TypeScript module. It default-exports `defineRun(...)`. It can
|
|
4
|
+
import `@yaag/runtime` and `typebox`. A program file can import other modules;
|
|
5
|
+
an inline program cannot.
|
|
6
|
+
|
|
7
|
+
The deep reference is the vendored declaration file:
|
|
8
|
+
`<program dir>/.yaag/types/runtime/index.d.ts`. This page gives the shape and
|
|
9
|
+
the rules. It does not restate the types.
|
|
10
|
+
|
|
11
|
+
## The Run
|
|
12
|
+
|
|
13
|
+
`defineRun` takes a name, an optional description, an optional argument schema,
|
|
14
|
+
and a `run` body. The body gets a context with `args` and `spawn`. What the body
|
|
15
|
+
returns is the result of the Run.
|
|
16
|
+
|
|
17
|
+
<!-- embed: docs/examples/01-minimal.ts -->
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
export default defineRun({
|
|
21
|
+
name: "minimal",
|
|
22
|
+
description: "Asks one Agent for one short answer.",
|
|
23
|
+
async run(ctx) {
|
|
24
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
25
|
+
return await agent.ask(prompt`Write one sentence about the sea. Report only that sentence.`);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Keep the module top level side-effect free. `yaag describe` imports the module
|
|
31
|
+
and executes its top level.
|
|
32
|
+
|
|
33
|
+
## Arguments
|
|
34
|
+
|
|
35
|
+
Declare arguments with a typebox object schema. The Orchestrator validates the
|
|
36
|
+
arguments before it starts any Agent, so a bad call costs nothing. `ctx.args` is
|
|
37
|
+
typed from the schema.
|
|
38
|
+
|
|
39
|
+
<!-- embed: docs/examples/02-args.ts -->
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
args: Type.Object({
|
|
43
|
+
topic: Type.String({ description: "What to write about" }),
|
|
44
|
+
sentences: Type.Optional(Type.Integer({ description: "How many sentences" })),
|
|
45
|
+
}),
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Agents and spawn restrictions
|
|
49
|
+
|
|
50
|
+
`defineAgent` holds policy that does not change: the prompt, the model, the
|
|
51
|
+
thinking level, the tools, and the skills. A spawn holds topology: the name, the
|
|
52
|
+
working directory, and the worktree request. `ctx.spawn(definition, overrides)`
|
|
53
|
+
puts the two together.
|
|
54
|
+
|
|
55
|
+
`tools`, `disallowedTools`, `skills`, and `disallowedSkills` take **names**, not
|
|
56
|
+
paths. `disallowedTools` and `disallowedSkills` apply last. An unknown skill
|
|
57
|
+
name rejects the spawn.
|
|
58
|
+
|
|
59
|
+
<!-- embed: docs/examples/03-fan-out.ts -->
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const reviewer = defineAgent({
|
|
63
|
+
name: "reviewer",
|
|
64
|
+
tools: ["read"],
|
|
65
|
+
disallowedTools: ["yaag_run"],
|
|
66
|
+
skills: [],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Worktrees
|
|
71
|
+
|
|
72
|
+
`worktree: true` puts the Agent in its own git worktree, on a fresh branch. Two
|
|
73
|
+
Agents that write files then cannot collide. The worktree outlives the Run: yaag
|
|
74
|
+
creates it and never removes it. Merge or delete the branch yourself.
|
|
75
|
+
|
|
76
|
+
## Asks and limits
|
|
77
|
+
|
|
78
|
+
`handle.ask(prompt, options)` is the only conversational verb. The soft limits
|
|
79
|
+
are `maxTurns`, `maxToolCalls`, and `maxDurationMs`. A soft limit steers the
|
|
80
|
+
Agent with `wrapUpPrompt`, gives it one more turn, then fails the Ask with
|
|
81
|
+
`ASK_LIMIT`. The Handle stays alive, so the program can ask again.
|
|
82
|
+
|
|
83
|
+
`timeoutMs` is different: it kills the Agent and fails with `ASK_TIMEOUT`. Use
|
|
84
|
+
it as the last resort, not as a budget.
|
|
85
|
+
|
|
86
|
+
`outputSchema` makes the Ask return data against a typebox object schema. A
|
|
87
|
+
result that never validates fails with `ASK_INVALID_OUTPUT`.
|
|
88
|
+
|
|
89
|
+
<!-- embed: docs/examples/04-controlled-ask.ts -->
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const report = await agent.ask(prompt`Read README.md and report your verdict.`, {
|
|
93
|
+
maxTurns: 6,
|
|
94
|
+
maxToolCalls: 12,
|
|
95
|
+
maxDurationMs: 120_000,
|
|
96
|
+
wrapUpPrompt: "Stop the work and report what you have now.",
|
|
97
|
+
outputSchema: Report,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use the `prompt` tag for prompt text. It removes the indentation that a
|
|
102
|
+
template literal keeps.
|
|
103
|
+
|
|
104
|
+
## Asking the user
|
|
105
|
+
|
|
106
|
+
An Agent cannot ask the user today. yaag cancels the dialog request of an Agent,
|
|
107
|
+
so the Agent has to decide by itself. Write prompts that state the decision
|
|
108
|
+
rules. ADR-0022 plans the human-in-the-loop verbs; they are not available yet.
|
|
109
|
+
|
|
110
|
+
## Record and resume
|
|
111
|
+
|
|
112
|
+
`--record <file>` writes a Cassette of every frame. `--resume <file>` runs the
|
|
113
|
+
program again and replays the recorded Asks. A resume needs the program too,
|
|
114
|
+
because a Cassette holds the history of a Run and never the program. See
|
|
115
|
+
[Examples](examples.md#05--record-and-resume).
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# CLI and tool reference
|
|
2
|
+
|
|
3
|
+
The `yaag` CLI and the yaag extension start the same Runs. The CLI is for a
|
|
4
|
+
terminal; the tools are for a pi session.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
<!-- embed: @yaag/cli/src/argv.ts#USAGE -->
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
usage:
|
|
12
|
+
yaag run <program.ts> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
13
|
+
yaag run --eval <source> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
14
|
+
yaag run --eval-fd <n> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
15
|
+
yaag describe <program.ts>
|
|
16
|
+
yaag setup-workspace [dir]
|
|
17
|
+
|
|
18
|
+
--eval runs an Orchestration Program that you give as source text. Give a
|
|
19
|
+
program file or --eval, but do not give both. A program that --eval runs can
|
|
20
|
+
import "@yaag/runtime" and "typebox" only. A program that imports other modules
|
|
21
|
+
must be a file.
|
|
22
|
+
|
|
23
|
+
--eval-fd reads the program source from descriptor <n>, and needs the writer to
|
|
24
|
+
close that descriptor. It keeps the source out of the process argument list,
|
|
25
|
+
where every local user can read it. The yaag extension always uses it.
|
|
26
|
+
|
|
27
|
+
--config <file> reads one more config file for this Run. Give it one time only.
|
|
28
|
+
yaag reads it after the global config and after the project config.
|
|
29
|
+
|
|
30
|
+
--no-config tells yaag to ignore the global config and the project config. It
|
|
31
|
+
does not ignore --config.
|
|
32
|
+
|
|
33
|
+
A --resume or --replay Run also needs the program: give the program file,
|
|
34
|
+
--eval <source>, or --eval-fd <n>. A Cassette holds the history of a Run, and never the program
|
|
35
|
+
to run.
|
|
36
|
+
|
|
37
|
+
Warning: describe imports the module and executes its top level. Keep program module top level side-effect free.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A bad invocation prints this usage text on stderr and exits with code 2. Those
|
|
41
|
+
argv errors have no troubleshooting entry: the usage text is the fix.
|
|
42
|
+
|
|
43
|
+
The `--config` and `--no-config` flags select the config layers of a Run. See
|
|
44
|
+
[Configuration](configuration.md#locations).
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
|
|
48
|
+
### yaag_run
|
|
49
|
+
|
|
50
|
+
Runs an Orchestration Program. It maps to `yaag run`.
|
|
51
|
+
|
|
52
|
+
| field | maps to | notes |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `file` | `yaag run <program.ts>` | Path of the program file. |
|
|
55
|
+
| `script` | `--eval-fd` | Inline source. It can import `@yaag/runtime` and `typebox` only. |
|
|
56
|
+
| `args` | `--args <json>` | A JSON object string. |
|
|
57
|
+
| `background` | none | Extension only. Starts the Run and returns its Run id. |
|
|
58
|
+
| `record` | `--record <file>` | Writes the Cassette here. |
|
|
59
|
+
| `resume` | `--resume <file>` | Replays matching Asks, then continues live. |
|
|
60
|
+
| `config` | `--config <file>` | Reads one more config file for this Run. |
|
|
61
|
+
| `noConfig` | `--no-config` | Ignores the global config and the project config. |
|
|
62
|
+
|
|
63
|
+
### yaag_status
|
|
64
|
+
|
|
65
|
+
Reports the Runs of the session, or one Run.
|
|
66
|
+
|
|
67
|
+
| field | maps to | notes |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `id` | none | Extension only. Omit it for every Run of the session. |
|
|
70
|
+
|
|
71
|
+
### yaag_stop
|
|
72
|
+
|
|
73
|
+
Stops a background Run and reaps its Agents.
|
|
74
|
+
|
|
75
|
+
| field | maps to | notes |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `id` | none | Extension only. The Run id that `yaag_run` returned. |
|
|
78
|
+
|
|
79
|
+
### yaag_describe
|
|
80
|
+
|
|
81
|
+
Reports the name, description, and argument schema of a program. It maps to
|
|
82
|
+
`yaag describe`.
|
|
83
|
+
|
|
84
|
+
| field | maps to | notes |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `file` | `yaag describe <program.ts>` | Describing imports the module and executes its top level. |
|
|
87
|
+
|
|
88
|
+
### yaag_setup_workspace
|
|
89
|
+
|
|
90
|
+
Creates `.yaag/` with the editor types. It maps to `yaag setup-workspace`.
|
|
91
|
+
|
|
92
|
+
| field | maps to | notes |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `dir` | `yaag setup-workspace [dir]` | Defaults to pi's working directory. |
|
|
95
|
+
|
|
96
|
+
## Background Runs
|
|
97
|
+
|
|
98
|
+
Only the extension has background Runs. Call `yaag_run` with `background: true`.
|
|
99
|
+
It returns a Run id. Poll it with `yaag_status`, and end it with `yaag_stop`.
|
|
100
|
+
A Run record outlives the session, so a later session reaches a Run by its id.
|
|
101
|
+
|
|
102
|
+
## One side only
|
|
103
|
+
|
|
104
|
+
- CLI only: `--replay` (strict replay, no model calls) and `--quiet`.
|
|
105
|
+
- Extension only: `yaag_status`, `yaag_stop`, and background Runs.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
yaag reads up to three config files at the start of a Run. Their merged
|
|
4
|
+
content is the configuration of that Run. yaag reads them one time, at Run
|
|
5
|
+
start. A config edit during a Run changes nothing until the next Run.
|
|
6
|
+
|
|
7
|
+
## Locations
|
|
8
|
+
|
|
9
|
+
| Layer | Location |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Global | `$YAAG_CONFIG_DIR/config.json` when `YAAG_CONFIG_DIR` is set. Else `$XDG_CONFIG_HOME/yaag/config.json` when `XDG_CONFIG_HOME` is an absolute path. Else `~/.config/yaag/config.json`. |
|
|
12
|
+
| Project | `.yaag/config.json` in the Program Directory of the Run. |
|
|
13
|
+
| Run | The file that `yaag run --config <file>` or the `config` option of the `yaag_run` tool gives. Give it one time only. |
|
|
14
|
+
|
|
15
|
+
The Program Directory is the nearest directory that contains `.yaag/`. yaag
|
|
16
|
+
searches upward from the directory of the program file. For an Inline Program,
|
|
17
|
+
the search starts in the working directory.
|
|
18
|
+
|
|
19
|
+
A global or project file that does not exist is an empty layer. A `--config`
|
|
20
|
+
file that does not exist stops the Run at the start.
|
|
21
|
+
|
|
22
|
+
## Supported fields
|
|
23
|
+
|
|
24
|
+
A config file is plain JSON. [`examples/config.json`](examples/config.json)
|
|
25
|
+
shows the full surface:
|
|
26
|
+
|
|
27
|
+
<!-- embed: docs/examples/config.json -->
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"agents": {
|
|
32
|
+
"extensions": ["./extensions/team-conventions.ts", "npm:@acme/pi-guardrails"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Field | Type | Effect |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `agents.extensions` | array of strings | Extensions that every Agent of the Run loads, as extra `pi -e` arguments. |
|
|
40
|
+
|
|
41
|
+
An entry is an extension path or an `npm:`/`git:` specifier. A relative path
|
|
42
|
+
resolves against the directory of the config file that declares it. An `npm:`
|
|
43
|
+
or `git:` specifier installs from the Agent working directory.
|
|
44
|
+
|
|
45
|
+
Validation is strict. An unknown key, or a value with a wrong type, stops the
|
|
46
|
+
Run at the start.
|
|
47
|
+
|
|
48
|
+
## How the layers merge
|
|
49
|
+
|
|
50
|
+
The extension lists concatenate in this order: global, then project, then run,
|
|
51
|
+
then the `extensions` of the spawn call itself. yaag removes duplicates by
|
|
52
|
+
resolved path. The first occurrence keeps its place. The merged list loads on
|
|
53
|
+
top of the extensions a spawn call declares — a config never removes one.
|
|
54
|
+
|
|
55
|
+
## Opt out
|
|
56
|
+
|
|
57
|
+
`--no-config` (CLI) or `noConfig: true` (tool) makes the Run ignore the global
|
|
58
|
+
config and the project config. It does not ignore an explicit `--config` file.
|
|
59
|
+
Thus `--no-config --config ./one.json` gives the Run one known config file.
|
|
60
|
+
|
|
61
|
+
`configExtensions: false` on one spawn call or one Agent Definition opts that
|
|
62
|
+
one Agent out. The Agent then loads only the extensions its own call declares:
|
|
63
|
+
|
|
64
|
+
<!-- embed: @yaag/runtime/src/types.ts -->
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
/**
|
|
68
|
+
* Load the Effective Config's `agents.extensions` for this Agent. Default
|
|
69
|
+
* true; false spawns with only the extensions this call declares (ADR-0040).
|
|
70
|
+
*/
|
|
71
|
+
readonly configExtensions?: boolean;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Errors
|
|
75
|
+
|
|
76
|
+
A config file that yaag cannot read or parse, or that fails validation, stops
|
|
77
|
+
the Run at the start with exit code 1. The message names the file. See
|
|
78
|
+
[A config file is missing or invalid](troubleshooting.md#a-config-file-is-missing-or-invalid).
|
|
79
|
+
|
|
80
|
+
## Record and resume
|
|
81
|
+
|
|
82
|
+
The Run record stores the `--config` path, never the file content. A
|
|
83
|
+
`yaag_run` call that gives `resume` alone reads the stored path again, and
|
|
84
|
+
yaag reads every layer again at that moment. An extension list that changed
|
|
85
|
+
since the recording is a Divergence at the first spawn it changes: a strict
|
|
86
|
+
replay stops there, and a resume goes live there. See
|
|
87
|
+
[Replay or resume mismatch](troubleshooting.md#replay-or-resume-mismatch).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The smallest Orchestration Program. It starts one Agent, sends one Ask, and
|
|
3
|
+
* returns the text that the Agent reports.
|
|
4
|
+
*/
|
|
5
|
+
import { defineRun, prompt } from "@yaag/runtime";
|
|
6
|
+
|
|
7
|
+
export default defineRun({
|
|
8
|
+
name: "minimal",
|
|
9
|
+
description: "Asks one Agent for one short answer.",
|
|
10
|
+
async run(ctx) {
|
|
11
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
12
|
+
return await agent.ask(prompt`Write one sentence about the sea. Report only that sentence.`);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Run with declared arguments. `yaag describe` shows the schema. The
|
|
3
|
+
* Orchestrator rejects invalid arguments before it starts an Agent.
|
|
4
|
+
*/
|
|
5
|
+
import { defineRun, prompt } from "@yaag/runtime";
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
|
|
8
|
+
export default defineRun({
|
|
9
|
+
name: "args",
|
|
10
|
+
description: "Writes about a topic, with a sentence budget.",
|
|
11
|
+
args: Type.Object({
|
|
12
|
+
topic: Type.String({ description: "What to write about" }),
|
|
13
|
+
sentences: Type.Optional(Type.Integer({ description: "How many sentences" })),
|
|
14
|
+
}),
|
|
15
|
+
async run(ctx) {
|
|
16
|
+
const sentences = ctx.args.sentences ?? 3;
|
|
17
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
18
|
+
return await agent.ask(
|
|
19
|
+
prompt`Write ${String(sentences)} sentences about ${ctx.args.topic}. Report only the text.`,
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel Agents from one Agent Definition. Each Agent gets the same spawn
|
|
3
|
+
* restrictions: it can read files, it cannot start a nested Run, and it has no
|
|
4
|
+
* skills.
|
|
5
|
+
*/
|
|
6
|
+
import { defineAgent, defineRun, prompt } from "@yaag/runtime";
|
|
7
|
+
|
|
8
|
+
const reviewer = defineAgent({
|
|
9
|
+
name: "reviewer",
|
|
10
|
+
tools: ["read"],
|
|
11
|
+
disallowedTools: ["yaag_run"],
|
|
12
|
+
skills: [],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default defineRun({
|
|
16
|
+
name: "fan-out",
|
|
17
|
+
description: "Reviews one file with three Agents at the same time.",
|
|
18
|
+
async run(ctx) {
|
|
19
|
+
const reviews = await Promise.all(
|
|
20
|
+
["style", "risk", "tests"].map(async (topic) => {
|
|
21
|
+
const agent = await ctx.spawn(reviewer, { name: `reviewer-${topic}` });
|
|
22
|
+
return await agent.ask(prompt`Read README.md. Report the ${topic} problems you find.`);
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
return reviews.join("\n\n");
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Ask with limits and a structured result. The limits steer the Agent first,
|
|
3
|
+
* then fail the Ask with `ASK_LIMIT`. The Agent stays alive, so the program can
|
|
4
|
+
* recover.
|
|
5
|
+
*/
|
|
6
|
+
import { defineRun, isYaagError, prompt } from "@yaag/runtime";
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
|
|
9
|
+
const Report = Type.Object({
|
|
10
|
+
verdict: Type.String(),
|
|
11
|
+
reasons: Type.Array(Type.String()),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default defineRun({
|
|
15
|
+
name: "controlled-ask",
|
|
16
|
+
description: "Asks for a structured report under explicit limits.",
|
|
17
|
+
async run(ctx) {
|
|
18
|
+
const agent = await ctx.spawn({ name: "auditor", tools: ["read"] });
|
|
19
|
+
try {
|
|
20
|
+
const report = await agent.ask(prompt`Read README.md and report your verdict.`, {
|
|
21
|
+
maxTurns: 6,
|
|
22
|
+
maxToolCalls: 12,
|
|
23
|
+
maxDurationMs: 120_000,
|
|
24
|
+
wrapUpPrompt: "Stop the work and report what you have now.",
|
|
25
|
+
outputSchema: Report,
|
|
26
|
+
});
|
|
27
|
+
return `${report.verdict}: ${report.reasons.join("; ")}`;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (isYaagError(error) && error.code === "ASK_LIMIT") return "the audit hit its limit";
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two Asks in sequence. Run it with `--record` to write a Cassette. If the
|
|
3
|
+
* second Ask fails, run it again with `--resume` on that Cassette: the first
|
|
4
|
+
* Ask replays from the recording and costs nothing.
|
|
5
|
+
*/
|
|
6
|
+
import { defineRun, prompt } from "@yaag/runtime";
|
|
7
|
+
|
|
8
|
+
export default defineRun({
|
|
9
|
+
name: "record-resume",
|
|
10
|
+
description: "Drafts a text, then shortens it.",
|
|
11
|
+
async run(ctx) {
|
|
12
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
13
|
+
const draft = await agent.ask(prompt`Write two sentences about rain. Report only the text.`);
|
|
14
|
+
return await agent.ask(
|
|
15
|
+
prompt`Shorten this text to one sentence: ${draft}. Report only that sentence.`,
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
});
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Five programs, in reading order. Each file is in `examples/` next to this page.
|
|
4
|
+
Run one with `yaag run <file>`, or with the `yaag_run` tool.
|
|
5
|
+
|
|
6
|
+
## 01 — one Agent, one Ask
|
|
7
|
+
|
|
8
|
+
The smallest program. It starts one Agent, sends one Ask, and returns the text.
|
|
9
|
+
The return value of `run` is the result of the Run.
|
|
10
|
+
|
|
11
|
+
<!-- embed: docs/examples/01-minimal.ts -->
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
export default defineRun({
|
|
15
|
+
name: "minimal",
|
|
16
|
+
description: "Asks one Agent for one short answer.",
|
|
17
|
+
async run(ctx) {
|
|
18
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
19
|
+
return await agent.ask(prompt`Write one sentence about the sea. Report only that sentence.`);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Full file: [`examples/01-minimal.ts`](examples/01-minimal.ts). Run it with
|
|
25
|
+
`yaag run examples/01-minimal.ts`.
|
|
26
|
+
|
|
27
|
+
## 02 — declared arguments
|
|
28
|
+
|
|
29
|
+
A program declares its arguments with a typebox schema. `yaag describe` shows
|
|
30
|
+
that schema, and the Orchestrator rejects invalid arguments before it starts an
|
|
31
|
+
Agent.
|
|
32
|
+
|
|
33
|
+
<!-- embed: docs/examples/02-args.ts -->
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
args: Type.Object({
|
|
37
|
+
topic: Type.String({ description: "What to write about" }),
|
|
38
|
+
sentences: Type.Optional(Type.Integer({ description: "How many sentences" })),
|
|
39
|
+
}),
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Full file: [`examples/02-args.ts`](examples/02-args.ts). Run it with
|
|
43
|
+
`yaag run examples/02-args.ts --args '{"topic":"tides"}'`.
|
|
44
|
+
|
|
45
|
+
## 03 — parallel Agents with restrictions
|
|
46
|
+
|
|
47
|
+
An Agent Definition holds policy: which tools and which skills the Agent gets.
|
|
48
|
+
A spawn holds topology: the name and the working directory. Three Agents run at
|
|
49
|
+
the same time.
|
|
50
|
+
|
|
51
|
+
<!-- embed: docs/examples/03-fan-out.ts -->
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const reviewer = defineAgent({
|
|
55
|
+
name: "reviewer",
|
|
56
|
+
tools: ["read"],
|
|
57
|
+
disallowedTools: ["yaag_run"],
|
|
58
|
+
skills: [],
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Full file: [`examples/03-fan-out.ts`](examples/03-fan-out.ts). Run it with
|
|
63
|
+
`yaag run examples/03-fan-out.ts`.
|
|
64
|
+
|
|
65
|
+
## 04 — Ask limits and a structured result
|
|
66
|
+
|
|
67
|
+
Soft limits steer the Agent first, then fail the Ask with `ASK_LIMIT`. The
|
|
68
|
+
Agent stays alive, so the program can recover. An `outputSchema` makes the Ask
|
|
69
|
+
return data instead of text.
|
|
70
|
+
|
|
71
|
+
<!-- embed: docs/examples/04-controlled-ask.ts -->
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const report = await agent.ask(prompt`Read README.md and report your verdict.`, {
|
|
75
|
+
maxTurns: 6,
|
|
76
|
+
maxToolCalls: 12,
|
|
77
|
+
maxDurationMs: 120_000,
|
|
78
|
+
wrapUpPrompt: "Stop the work and report what you have now.",
|
|
79
|
+
outputSchema: Report,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Full file: [`examples/04-controlled-ask.ts`](examples/04-controlled-ask.ts). Run
|
|
84
|
+
it with `yaag run examples/04-controlled-ask.ts`.
|
|
85
|
+
|
|
86
|
+
## 05 — record and resume
|
|
87
|
+
|
|
88
|
+
A Cassette records every frame of a Run. `--record <file>` writes it. `--resume
|
|
89
|
+
<file>` runs the program again and replays the recorded Asks, so only the new
|
|
90
|
+
work costs money. A resume needs the program too, because a Cassette never
|
|
91
|
+
holds it.
|
|
92
|
+
|
|
93
|
+
<!-- embed: docs/examples/05-record-resume.ts -->
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
97
|
+
const draft = await agent.ask(prompt`Write two sentences about rain. Report only the text.`);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Full file: [`examples/05-record-resume.ts`](examples/05-record-resume.ts). Run
|
|
101
|
+
it with `yaag run examples/05-record-resume.ts --record run.json`, then resume
|
|
102
|
+
it with `yaag run examples/05-record-resume.ts --resume run.json`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
yaag runs Orchestration Programs: TypeScript modules that drive pi Agents.
|
|
4
|
+
Start a Run from the `yaag` CLI, or from a pi session with the yaag extension.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Install the extension into a pi session:
|
|
9
|
+
|
|
10
|
+
- `pi install @yaag/extension` adds it permanently.
|
|
11
|
+
- `pi -e @yaag/extension` loads it for one session.
|
|
12
|
+
|
|
13
|
+
yaag needs Bun on `PATH`. Install it with
|
|
14
|
+
`curl -fsSL https://bun.sh/install | bash`. If the extension reports that Bun is
|
|
15
|
+
missing, read [Bun is missing](troubleshooting.md#bun-is-missing).
|
|
16
|
+
|
|
17
|
+
## Prepare the workspace
|
|
18
|
+
|
|
19
|
+
Run the `yaag_setup_workspace` tool, or `yaag setup-workspace .` on the command
|
|
20
|
+
line. It creates `.yaag/` in the workspace root. `.yaag/types/` holds the
|
|
21
|
+
`@yaag/runtime` declarations that an editor needs. A folder with `.yaag/` is a
|
|
22
|
+
Program Directory, and the extension tells the model about it.
|
|
23
|
+
|
|
24
|
+
## Write and run the first program
|
|
25
|
+
|
|
26
|
+
Copy [`examples/01-minimal.ts`](examples/01-minimal.ts) into the Program
|
|
27
|
+
Directory:
|
|
28
|
+
|
|
29
|
+
<!-- embed: docs/examples/01-minimal.ts -->
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { defineRun, prompt } from "@yaag/runtime";
|
|
33
|
+
|
|
34
|
+
export default defineRun({
|
|
35
|
+
name: "minimal",
|
|
36
|
+
description: "Asks one Agent for one short answer.",
|
|
37
|
+
async run(ctx) {
|
|
38
|
+
const agent = await ctx.spawn({ name: "writer" });
|
|
39
|
+
return await agent.ask(prompt`Write one sentence about the sea. Report only that sentence.`);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then run `yaag run 01-minimal.ts`, or ask the model to use the `yaag_run` tool.
|
|
45
|
+
`yaag describe 01-minimal.ts` reports the name, the description, and the
|
|
46
|
+
argument schema of the program.
|
|
47
|
+
|
|
48
|
+
## Next
|
|
49
|
+
|
|
50
|
+
- [Authoring](authoring.md) — how to write a program.
|
|
51
|
+
- [Examples](examples.md) — five programs, from minimal to record and resume.
|
|
52
|
+
- [CLI reference](cli.md) — every flag and every tool parameter.
|
|
53
|
+
- [Configuration](configuration.md#locations) — the three config files a Run reads.
|
|
54
|
+
- [Troubleshooting](troubleshooting.md) — the errors you can meet.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
One entry for each failure you can meet. The quote is the message text, or the
|
|
4
|
+
text that yaag matches on. An entry is at most ten lines, blank lines apart.
|
|
5
|
+
|
|
6
|
+
## Bun is missing
|
|
7
|
+
|
|
8
|
+
<!-- quote: src/process/status.ts -->
|
|
9
|
+
|
|
10
|
+
> yaag needs Bun and could not find it on PATH or in ~/.bun/bin.
|
|
11
|
+
|
|
12
|
+
Cause: the extension bridges to the Bun CLI, and Bun is not on `PATH` and not
|
|
13
|
+
in `~/.bun/bin`.
|
|
14
|
+
|
|
15
|
+
Fix: install Bun with `curl -fsSL https://bun.sh/install | bash`, then restart
|
|
16
|
+
pi. The extension resolves Bun once, at load.
|
|
17
|
+
|
|
18
|
+
## No API key for a spawned Agent
|
|
19
|
+
|
|
20
|
+
<!-- quote: @yaag/runtime/src/model/model-failure.ts -->
|
|
21
|
+
|
|
22
|
+
> No API key
|
|
23
|
+
|
|
24
|
+
pi says `No API key found for the selected model.`, or `No API key for
|
|
25
|
+
<provider>/<id>`. yaag reads that text and calls the failure `auth`.
|
|
26
|
+
|
|
27
|
+
Cause: a spawned Agent is hermetic, so it loads no pi extension of the session.
|
|
28
|
+
A model that a provider extension registers is out of reach.
|
|
29
|
+
|
|
30
|
+
Fix: name the provider extension in the `extensions` spawn option, or give the
|
|
31
|
+
Agent a model the machine can reach. A `model` list falls back on `auth`.
|
|
32
|
+
|
|
33
|
+
## failed to publish cassette
|
|
34
|
+
|
|
35
|
+
<!-- quote: @yaag/runtime/src/cassette/cassette-publish.ts -->
|
|
36
|
+
|
|
37
|
+
> failed to publish cassette
|
|
38
|
+
|
|
39
|
+
Cause: yaag could not write the Cassette. Most often the parent directory of
|
|
40
|
+
the `--record` path does not exist, or it is not writable.
|
|
41
|
+
|
|
42
|
+
Fix: create the directory first, then run again. The Run keeps its result; only
|
|
43
|
+
the recording failed.
|
|
44
|
+
|
|
45
|
+
## Replay or resume mismatch
|
|
46
|
+
|
|
47
|
+
<!-- quote: @yaag/runtime/src/cassette/replay-divergence.ts -->
|
|
48
|
+
|
|
49
|
+
> replay diverged for agent
|
|
50
|
+
|
|
51
|
+
Cause: the program changed since the recording. A spawn option, an Agent
|
|
52
|
+
Definition field, or an Ask prompt no longer hashes to the recorded value.
|
|
53
|
+
|
|
54
|
+
Fix: restore the program to the recorded shape, or record again. A resume
|
|
55
|
+
replays only the Asks that still match, and runs the rest live.
|
|
56
|
+
|
|
57
|
+
## --resume or --replay needs the program
|
|
58
|
+
|
|
59
|
+
<!-- quote: @yaag/cli/src/argv.ts -->
|
|
60
|
+
|
|
61
|
+
> needs the program too: give <program.ts>, --eval <source>, or --eval-fd <n>.
|
|
62
|
+
|
|
63
|
+
Cause: a Cassette holds the history of a Run. It never holds the program.
|
|
64
|
+
|
|
65
|
+
Fix: name the program as well: `yaag run program.ts --resume run.json`.
|
|
66
|
+
|
|
67
|
+
## Inline program limits
|
|
68
|
+
|
|
69
|
+
<!-- quote: src/record/run-program-param.ts -->
|
|
70
|
+
|
|
71
|
+
> yaag_run: script is too large
|
|
72
|
+
|
|
73
|
+
Cause: an inline program (`script`, or `--eval`) must be 65536 bytes (64 KiB)
|
|
74
|
+
or smaller, and it can import `@yaag/runtime` and `typebox` only.
|
|
75
|
+
|
|
76
|
+
Fix: write the program to a file and pass `file` or `<program.ts>`. A file may
|
|
77
|
+
import any module.
|
|
78
|
+
|
|
79
|
+
## An Ask failed with a limit or a timeout
|
|
80
|
+
|
|
81
|
+
<!-- quote: @yaag/runtime/src/errors.ts -->
|
|
82
|
+
|
|
83
|
+
> limit reached at
|
|
84
|
+
|
|
85
|
+
Cause: `ASK_LIMIT` is a tripped soft budget (`maxTurns`, `maxToolCalls`,
|
|
86
|
+
`maxDurationMs`). `ASK_TIMEOUT` is `timeoutMs`, and it kills the Agent.
|
|
87
|
+
`ASK_INVALID_OUTPUT` is a result that never satisfied `outputSchema`.
|
|
88
|
+
`ASK_STALLED` is silence past the watchdog budget.
|
|
89
|
+
|
|
90
|
+
Fix: raise the budget, or add a `wrapUpPrompt`. After `ASK_LIMIT` the Handle is
|
|
91
|
+
alive, so the program can ask again.
|
|
92
|
+
|
|
93
|
+
## describe executes the module top level
|
|
94
|
+
|
|
95
|
+
<!-- quote: @yaag/cli/src/argv.ts -->
|
|
96
|
+
|
|
97
|
+
> describe imports the module and executes its top level.
|
|
98
|
+
|
|
99
|
+
Cause: `yaag describe` imports the program to read its declaration.
|
|
100
|
+
|
|
101
|
+
Fix: keep the module top level side-effect free. Put every action inside `run`.
|
|
102
|
+
|
|
103
|
+
## Missing @yaag/runtime editor types
|
|
104
|
+
|
|
105
|
+
The editor says it cannot find the module `@yaag/runtime` in a program file.
|
|
106
|
+
|
|
107
|
+
Cause: the folder is not a Program Directory yet, so `.yaag/types/` is absent.
|
|
108
|
+
|
|
109
|
+
Fix: run the `yaag_setup_workspace` tool, or `yaag setup-workspace .`. It
|
|
110
|
+
reports the written declarations:
|
|
111
|
+
|
|
112
|
+
<!-- quote: @yaag/cli/src/setup-workspace.ts -->
|
|
113
|
+
|
|
114
|
+
> .yaag/types/: written
|
|
115
|
+
|
|
116
|
+
Rerun it after an upgrade to refresh the declarations.
|
|
117
|
+
|
|
118
|
+
## A config file is missing or invalid
|
|
119
|
+
|
|
120
|
+
<!-- quote: @yaag/runtime/src/config/config-file.ts -->
|
|
121
|
+
|
|
122
|
+
> does not exist
|
|
123
|
+
|
|
124
|
+
Cause: yaag reads the global config, the project config, and the `--config`
|
|
125
|
+
file at the start of a Run. A `--config` file that is not there, or any config
|
|
126
|
+
file that yaag cannot read or parse, stops the Run at the start (exit 1).
|
|
127
|
+
|
|
128
|
+
Fix: correct the path or the file. Give `--no-config` to ignore the global
|
|
129
|
+
config and the project config. [Configuration](configuration.md#locations)
|
|
130
|
+
names the three locations and the supported fields.
|
|
131
|
+
|
|
132
|
+
Plain usage errors of the CLI exit with code 2 and print the usage text. See
|
|
133
|
+
[the CLI reference](cli.md#usage).
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
7
|
"files": [
|
|
8
8
|
"src",
|
|
9
|
+
"docs",
|
|
9
10
|
"!src/**/*.test.ts"
|
|
10
11
|
],
|
|
11
12
|
"type": "module",
|
|
@@ -24,9 +25,9 @@
|
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
27
|
-
"@yaag/cli": "0.
|
|
28
|
-
"@yaag/runtime": "0.
|
|
29
|
-
"@yaag/tui": "0.
|
|
28
|
+
"@yaag/cli": "0.8.1",
|
|
29
|
+
"@yaag/runtime": "0.8.1",
|
|
30
|
+
"@yaag/tui": "0.8.1",
|
|
30
31
|
"nanoid": "^6.0.1"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The drift guard behind every doc page: a fenced block must be a verbatim
|
|
3
|
+
* region of a real source file, and every block must name that file with an
|
|
4
|
+
* `<!-- embed: <source> -->` marker.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { docPath } from "./docs-root.ts";
|
|
11
|
+
|
|
12
|
+
/** One fenced block of a page, with the marker that precedes it. */
|
|
13
|
+
export interface DocBlock {
|
|
14
|
+
/** The page the block came from, relative to the docs root. */
|
|
15
|
+
readonly page: string;
|
|
16
|
+
/** The marker payload, e.g. `docs/examples/01-minimal.ts` or `@yaag/cli/src/argv.ts#USAGE`. */
|
|
17
|
+
readonly source: string | null;
|
|
18
|
+
/** The block body, without the fences and without a trailing newline. */
|
|
19
|
+
readonly body: string;
|
|
20
|
+
/** 1-based line of the opening fence, for a self-explaining failure. */
|
|
21
|
+
readonly line: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const MARKER = /^<!--\s*embed:\s*(.+?)\s*-->$/;
|
|
25
|
+
|
|
26
|
+
/** Reads every fenced block of a page, together with its preceding marker. */
|
|
27
|
+
export function readBlocks(page: string): DocBlock[] {
|
|
28
|
+
const lines = readFileSync(docPath(page), "utf8").split("\n");
|
|
29
|
+
const blocks: DocBlock[] = [];
|
|
30
|
+
let marker: string | null = null;
|
|
31
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
32
|
+
const line = lines[index] ?? "";
|
|
33
|
+
const found = MARKER.exec(line.trim());
|
|
34
|
+
if (found?.[1] !== undefined) {
|
|
35
|
+
marker = found[1];
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!line.startsWith("```")) continue;
|
|
39
|
+
const body: string[] = [];
|
|
40
|
+
index += 1;
|
|
41
|
+
const open = index;
|
|
42
|
+
while (index < lines.length && !(lines[index] ?? "").startsWith("```")) {
|
|
43
|
+
body.push(lines[index] ?? "");
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
blocks.push({ page, source: marker, body: body.join("\n"), line: open });
|
|
47
|
+
marker = null;
|
|
48
|
+
}
|
|
49
|
+
return blocks;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Resolves a marker payload to the text it must match. */
|
|
53
|
+
export function resolveSource(spec: string): string {
|
|
54
|
+
const [path, constant] = spec.split("#");
|
|
55
|
+
const text = readFileSync(resolvePath(path ?? ""), "utf8");
|
|
56
|
+
return constant === undefined ? text : exportedString(text, constant, spec);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolvePath(path: string): string {
|
|
60
|
+
if (path.startsWith("docs/")) return docPath(path.slice("docs/".length));
|
|
61
|
+
if (path.startsWith("src/")) return fileURLToPath(new URL(`../../${path}`, import.meta.url));
|
|
62
|
+
const require = createRequire(import.meta.url);
|
|
63
|
+
const packaged = /^(@yaag\/[a-z]+)\/(src\/.+)$/.exec(path);
|
|
64
|
+
if (packaged?.[1] === "@yaag/runtime" && packaged[2] !== undefined) {
|
|
65
|
+
// @yaag/runtime publishes one entry point, so reach its files through it.
|
|
66
|
+
const entry = require.resolve("@yaag/runtime");
|
|
67
|
+
return join(entry.slice(0, entry.lastIndexOf("/src/")), packaged[2]);
|
|
68
|
+
}
|
|
69
|
+
return require.resolve(path);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Reads the body of `export const NAME = \`…\`;` out of a source file. */
|
|
73
|
+
function exportedString(text: string, name: string, spec: string): string {
|
|
74
|
+
const start = text.indexOf(`export const ${name} = \``);
|
|
75
|
+
if (start === -1) throw new Error(`${spec}: no exported template string named ${name}`);
|
|
76
|
+
const from = text.indexOf("`", start) + 1;
|
|
77
|
+
const to = text.indexOf("`;", from);
|
|
78
|
+
if (to === -1) throw new Error(`${spec}: ${name} is not terminated`);
|
|
79
|
+
return text.slice(from, to);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One quoted message of a page, with the source it is guarded against. */
|
|
83
|
+
export interface DocQuote {
|
|
84
|
+
readonly source: string;
|
|
85
|
+
readonly text: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const QUOTE_MARKER = /^<!--\s*quote:\s*(.+?)\s*-->$/;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reads every `> quoted` line of a markdown text with the
|
|
92
|
+
* `<!-- quote: <source> -->` marker that precedes it.
|
|
93
|
+
*
|
|
94
|
+
* One marker guards one quote: the marker is consumed, so a quote can never be
|
|
95
|
+
* compared with the file of the entry above it. Throws when a quote line has no
|
|
96
|
+
* marker of its own.
|
|
97
|
+
*/
|
|
98
|
+
export function readQuotes(markdown: string): DocQuote[] {
|
|
99
|
+
const found: DocQuote[] = [];
|
|
100
|
+
let source: string | null = null;
|
|
101
|
+
for (const line of markdown.split("\n")) {
|
|
102
|
+
const marker = QUOTE_MARKER.exec(line.trim());
|
|
103
|
+
if (marker?.[1] !== undefined) {
|
|
104
|
+
source = marker[1];
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!line.startsWith("> ")) continue;
|
|
108
|
+
if (source === null) throw new Error(`quoted message with no marker: ${line}`);
|
|
109
|
+
found.push({ source, text: line.slice(2).trim() });
|
|
110
|
+
source = null;
|
|
111
|
+
}
|
|
112
|
+
return found;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Fails unless the block body appears line for line inside its source. */
|
|
116
|
+
export function assertVerbatim(block: DocBlock): void {
|
|
117
|
+
if (block.source === null) throw new Error(describe(block, "carries no embed marker"));
|
|
118
|
+
const source = resolveSource(block.source);
|
|
119
|
+
const body = block.body.replace(/\s+$/, "");
|
|
120
|
+
if (source.replace(/\r/g, "").includes(body)) return;
|
|
121
|
+
throw new Error(describe(block, `is not verbatim in ${block.source}`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function describe(block: DocBlock, what: string): string {
|
|
125
|
+
const first = block.body.split("\n")[0] ?? "";
|
|
126
|
+
return `${block.page}:${block.line} (\`${first}\`) ${what}`;
|
|
127
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipped docs folder. The repo path is the tarball path, so one constant
|
|
3
|
+
* serves the system prompt, the error messages, and the drift guards.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
/** Absolute path of the shipped `docs/` folder, with no trailing separator. */
|
|
9
|
+
export const DOCS_ROOT = fileURLToPath(new URL("../../docs", import.meta.url));
|
|
10
|
+
|
|
11
|
+
/** The shipped pages, in reading order. */
|
|
12
|
+
export const DOC_PAGES = [
|
|
13
|
+
"getting-started.md",
|
|
14
|
+
"authoring.md",
|
|
15
|
+
"examples.md",
|
|
16
|
+
"cli.md",
|
|
17
|
+
"configuration.md",
|
|
18
|
+
"troubleshooting.md",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
/** The five shipped example programs, in reading order. */
|
|
22
|
+
export const EXAMPLE_FILES = [
|
|
23
|
+
"01-minimal.ts",
|
|
24
|
+
"02-args.ts",
|
|
25
|
+
"03-fan-out.ts",
|
|
26
|
+
"04-controlled-ask.ts",
|
|
27
|
+
"05-record-resume.ts",
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
/** Absolute path of one shipped doc file, named relative to the docs root. */
|
|
31
|
+
export function docPath(name: string): string {
|
|
32
|
+
return join(DOCS_ROOT, name);
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `docs/` module: where the shipped docs are, and the
|
|
3
|
+
* drift guard that keeps them true.
|
|
4
|
+
*/
|
|
5
|
+
export {
|
|
6
|
+
assertVerbatim,
|
|
7
|
+
type DocBlock,
|
|
8
|
+
type DocQuote,
|
|
9
|
+
readBlocks,
|
|
10
|
+
readQuotes,
|
|
11
|
+
resolveSource,
|
|
12
|
+
} from "./doc-embed.ts";
|
|
13
|
+
export { DOC_PAGES, DOCS_ROOT, docPath, EXAMPLE_FILES } from "./docs-root.ts";
|
package/src/process/run-argv.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface RunArgvOptions {
|
|
|
15
15
|
readonly record?: string;
|
|
16
16
|
/** Cassette path to resume from, forwarded as `--resume` (ADR-0014). */
|
|
17
17
|
readonly resume?: string;
|
|
18
|
+
/** Run Config path, forwarded as `--config` (ADR-0040). */
|
|
19
|
+
readonly config?: string;
|
|
20
|
+
/** Suppresses the discovered config layers, forwarded as `--no-config` (ADR-0040). */
|
|
21
|
+
readonly noConfig?: boolean;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
/**
|
|
@@ -35,5 +39,7 @@ export function runArgv(options: RunArgvOptions): readonly string[] {
|
|
|
35
39
|
...(options.args === undefined ? [] : ["--args", options.args]),
|
|
36
40
|
...(options.record === undefined ? [] : ["--record", options.record]),
|
|
37
41
|
...(options.resume === undefined ? [] : ["--resume", options.resume]),
|
|
42
|
+
...(options.config === undefined ? [] : ["--config", options.config]),
|
|
43
|
+
...(options.noConfig === true ? ["--no-config"] : []),
|
|
38
44
|
];
|
|
39
45
|
}
|
package/src/process/spawn-run.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface StartRunOptions {
|
|
|
26
26
|
readonly record?: string;
|
|
27
27
|
/** Cassette path to resume from, forwarded as `--resume` (ADR-0014). */
|
|
28
28
|
readonly resume?: string;
|
|
29
|
+
/** Run Config path, forwarded as `--config` (ADR-0040). */
|
|
30
|
+
readonly config?: string;
|
|
31
|
+
/** Suppresses the discovered config layers, forwarded as `--no-config` (ADR-0040). */
|
|
32
|
+
readonly noConfig?: boolean;
|
|
29
33
|
/**
|
|
30
34
|
* Called after each event is folded. `sequence` identifies this fd 3 stream
|
|
31
35
|
* occurrence, not an event value or timestamp; it makes renderer observation
|
|
@@ -60,6 +64,8 @@ export function startRun(options: StartRunOptions): RunHandle {
|
|
|
60
64
|
...(options.args === undefined ? {} : { args: options.args }),
|
|
61
65
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
62
66
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
67
|
+
...(options.config === undefined ? {} : { config: options.config }),
|
|
68
|
+
...(options.noConfig === true ? { noConfig: true } : {}),
|
|
63
69
|
});
|
|
64
70
|
const child = startCliChild({
|
|
65
71
|
bun: options.bun,
|
package/src/process/status.ts
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* The one message the extension has to say about itself: either the bridge to
|
|
3
3
|
* the Bun CLI is in place, or exactly what is missing and how to install it.
|
|
4
4
|
*/
|
|
5
|
+
import { docPath } from "../docs/index.ts";
|
|
6
|
+
|
|
5
7
|
export function statusReport(bun: string | null, cli: string): string {
|
|
6
8
|
if (bun === null) {
|
|
7
9
|
return (
|
|
8
10
|
"yaag needs Bun and could not find it on PATH or in ~/.bun/bin. " +
|
|
9
|
-
"Install it with: curl -fsSL https://bun.sh/install | bash"
|
|
11
|
+
"Install it with: curl -fsSL https://bun.sh/install | bash. " +
|
|
12
|
+
`See ${docPath("troubleshooting.md#bun-is-missing")}`
|
|
10
13
|
);
|
|
11
14
|
}
|
|
12
15
|
return `yaag ready — bun: ${bun}, cli: ${cli}`;
|
package/src/record/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Public surface of the `record/` module: the Run record registry, store, restore, and resume.
|
|
3
3
|
* Files inside this directory import each other directly.
|
|
4
4
|
*/
|
|
5
|
-
export { resolveProgramParams } from "./resume-source.ts";
|
|
5
|
+
export { resolveLaunchConfig, resolveProgramParams } from "./resume-source.ts";
|
|
6
6
|
export { mintRunId } from "./run-id.ts";
|
|
7
7
|
export {
|
|
8
8
|
confirmProgramTarget,
|
|
@@ -13,10 +13,12 @@ import type { RunRecord } from "./run-record.ts";
|
|
|
13
13
|
* source it sees is byte-identical to the recorded one.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/** The stored Runs a resume
|
|
17
|
-
export interface
|
|
16
|
+
/** The stored Runs a resume reads back; a structural type, so a test needs no store. */
|
|
17
|
+
export interface ResumeRecordLookup {
|
|
18
18
|
/** The Inline Program source whose Run published `artifact`, or null. */
|
|
19
19
|
inlineSourceFor(artifact: string): Promise<string | null>;
|
|
20
|
+
/** The Run Config path of the Run that published `artifact`, or null. */
|
|
21
|
+
resumeConfigFor(artifact: string): Promise<string | null>;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -32,18 +34,36 @@ export function pickInlineResumeSource(
|
|
|
32
34
|
): string | null {
|
|
33
35
|
let found: string | null = null;
|
|
34
36
|
for (const record of records) {
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
const { script, record: recorded } = launch;
|
|
38
|
-
const published = record.summary.artifact;
|
|
39
|
-
const matches =
|
|
40
|
-
(published !== null && published !== undefined && resolve(published) === artifact) ||
|
|
41
|
-
(recorded !== undefined && resolve(recorded) === artifact);
|
|
42
|
-
if (matches) found = script;
|
|
37
|
+
if (record.launch.kind !== "inline") continue;
|
|
38
|
+
if (publishes(record, artifact)) found = record.launch.script;
|
|
43
39
|
}
|
|
44
40
|
return found;
|
|
45
41
|
}
|
|
46
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The stored Run Config path of the Run that published `artifact`, or null.
|
|
45
|
+
*
|
|
46
|
+
* Pure, and blind to the launch kind: a file Run and an inline Run both carry
|
|
47
|
+
* their Run Config path. The newest match wins, as above.
|
|
48
|
+
*/
|
|
49
|
+
export function pickResumeConfig(records: readonly RunRecord[], artifact: string): string | null {
|
|
50
|
+
let found: string | null = null;
|
|
51
|
+
for (const record of records) {
|
|
52
|
+
if (publishes(record, artifact)) found = record.launch.config ?? null;
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether the Run of this record published, or wrote, the given Checkpoint. */
|
|
58
|
+
function publishes(record: RunRecord, artifact: string): boolean {
|
|
59
|
+
const published = record.summary.artifact;
|
|
60
|
+
const recorded = record.launch.record;
|
|
61
|
+
return (
|
|
62
|
+
(published !== null && published !== undefined && resolve(published) === artifact) ||
|
|
63
|
+
(recorded !== undefined && resolve(recorded) === artifact)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
/** What the tool says when no record holds the source of the given Checkpoint. */
|
|
48
68
|
export function noStoredSourceMessage(artifact: string): string {
|
|
49
69
|
return [
|
|
@@ -66,7 +86,7 @@ export function noStoredSourceMessage(artifact: string): string {
|
|
|
66
86
|
*/
|
|
67
87
|
export async function resolveProgramParams(
|
|
68
88
|
params: ProgramParams,
|
|
69
|
-
lookup:
|
|
89
|
+
lookup: ResumeRecordLookup,
|
|
70
90
|
): Promise<ProgramParams> {
|
|
71
91
|
if (params.file !== undefined || params.script !== undefined) return params;
|
|
72
92
|
if (params.resume === undefined) return params;
|
|
@@ -75,3 +95,28 @@ export async function resolveProgramParams(
|
|
|
75
95
|
if (source === null) throw new Error(noStoredSourceMessage(artifact));
|
|
76
96
|
return { ...params, script: source };
|
|
77
97
|
}
|
|
98
|
+
|
|
99
|
+
/** The launch parameters a resume needs, with the stored Run Config supplied. */
|
|
100
|
+
export interface LaunchParams {
|
|
101
|
+
readonly resume?: string;
|
|
102
|
+
readonly config?: string;
|
|
103
|
+
readonly noConfig?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The Run Config path a resume inherits from its Run record.
|
|
108
|
+
*
|
|
109
|
+
* A call that gives `config`, or `noConfig: true`, states its own layers, so it
|
|
110
|
+
* passes through untouched. `noConfig: false` states nothing: it is the default,
|
|
111
|
+
* and it emits no `--no-config`, so it must not drop the stored path either. A call that gives `resume` alone repeats the layers of the
|
|
112
|
+
* Run it resumes: the path is stored, and every layer is read again now.
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveLaunchConfig<T extends LaunchParams>(
|
|
115
|
+
params: T,
|
|
116
|
+
lookup: ResumeRecordLookup,
|
|
117
|
+
): Promise<T & LaunchParams> {
|
|
118
|
+
if (params.config !== undefined || params.noConfig === true) return params;
|
|
119
|
+
if (params.resume === undefined) return params;
|
|
120
|
+
const config = await lookup.resumeConfigFor(resolve(params.resume));
|
|
121
|
+
return config === null ? params : { ...params, config };
|
|
122
|
+
}
|
package/src/record/run-record.ts
CHANGED
|
@@ -22,6 +22,11 @@ export interface RunLaunchContext {
|
|
|
22
22
|
readonly args?: string;
|
|
23
23
|
readonly record?: string;
|
|
24
24
|
readonly resume?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The Run Config path the launch gave, never its contents: a resume reads
|
|
27
|
+
* every layer again at resume time (ADR-0040).
|
|
28
|
+
*/
|
|
29
|
+
readonly config?: string;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/**
|
|
@@ -144,6 +149,7 @@ function launchContext(stored: Record<string, unknown>): RunLaunchContext {
|
|
|
144
149
|
...(typeof stored.args === "string" ? { args: stored.args } : {}),
|
|
145
150
|
...(typeof stored.record === "string" ? { record: stored.record } : {}),
|
|
146
151
|
...(typeof stored.resume === "string" ? { resume: stored.resume } : {}),
|
|
152
|
+
...(typeof stored.config === "string" ? { config: stored.config } : {}),
|
|
147
153
|
};
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RunSummary, RunOutcome as SummaryOutcome } from "@yaag/runtime";
|
|
2
2
|
import type { ProcessIdentity, RunOutcome } from "../process/index.ts";
|
|
3
|
-
import { pickInlineResumeSource } from "./resume-source.ts";
|
|
3
|
+
import { pickInlineResumeSource, pickResumeConfig } from "./resume-source.ts";
|
|
4
4
|
import { type RunLaunch, type RunRecord, startedRecord } from "./run-record.ts";
|
|
5
5
|
import { restoreRecords } from "./run-restore.ts";
|
|
6
6
|
import { failedSummary, settledRecord } from "./run-settle-record.ts";
|
|
@@ -229,6 +229,16 @@ export class RunRegistry {
|
|
|
229
229
|
return pickInlineResumeSource(await this.#store.load(), artifact);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* The Run Config path a Checkpoint resumes with, read back from the durable
|
|
234
|
+
* records (ADR-0040). Null when no record holds one. Same wait as above.
|
|
235
|
+
*/
|
|
236
|
+
async resumeConfigFor(artifact: string): Promise<string | null> {
|
|
237
|
+
if (this.#store === undefined) return null;
|
|
238
|
+
await this.#store.settled();
|
|
239
|
+
return pickResumeConfig(await this.#store.load(), artifact);
|
|
240
|
+
}
|
|
241
|
+
|
|
232
242
|
/** Reads a Run this session never started back from the store, by id. */
|
|
233
243
|
async recall(id: string): Promise<RunStatus> {
|
|
234
244
|
const known = this.lookup(id);
|
|
@@ -16,6 +16,8 @@ export interface RunParams {
|
|
|
16
16
|
readonly background?: boolean;
|
|
17
17
|
readonly record?: string;
|
|
18
18
|
readonly resume?: string;
|
|
19
|
+
readonly config?: string;
|
|
20
|
+
readonly noConfig?: boolean;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function renderCallComponent(params: RunParams, expanded: boolean) {
|
package/src/tool/run-tool.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
mintRunId,
|
|
36
36
|
observedSettlement,
|
|
37
37
|
programTarget,
|
|
38
|
+
resolveLaunchConfig,
|
|
38
39
|
resolveProgramParams,
|
|
39
40
|
} from "../record/index.ts";
|
|
40
41
|
|
|
@@ -65,6 +66,12 @@ const parameters = Type.Object({
|
|
|
65
66
|
"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",
|
|
66
67
|
}),
|
|
67
68
|
),
|
|
69
|
+
config: Type.Optional(Type.String({ description: "Path to one more config file for this Run" })),
|
|
70
|
+
noConfig: Type.Optional(
|
|
71
|
+
Type.Boolean({
|
|
72
|
+
description: "Ignore the global config and the project config for this Run",
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
68
75
|
});
|
|
69
76
|
|
|
70
77
|
/** pi's own message channel, narrowed to what a background Run needs. */
|
|
@@ -121,6 +128,12 @@ const DESCRIPTION = [
|
|
|
121
128
|
"pass its artifact as `resume` on the retry: Asks that already succeeded",
|
|
122
129
|
"replay instantly and free, and the Run goes live where it diverges. Record",
|
|
123
130
|
"the retry too (to a different path) to keep every attempt resumable.",
|
|
131
|
+
"",
|
|
132
|
+
"`config` gives one more config file to this Run. yaag reads it after the",
|
|
133
|
+
"global config and after the project config. `noConfig: true` tells yaag to",
|
|
134
|
+
"ignore the global config and the project config. It does not ignore `config`,",
|
|
135
|
+
"so `config` with `noConfig: true` gives the Run one known config. A config",
|
|
136
|
+
"file that is not there stops the Run at the start.",
|
|
124
137
|
].join("\n");
|
|
125
138
|
|
|
126
139
|
/**
|
|
@@ -169,6 +182,9 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
169
182
|
// A resume with no file and no script takes its source from the Run
|
|
170
183
|
// record, before the shape check, so the shape it produces is valid.
|
|
171
184
|
const target = programTarget(await resolveProgramParams(params, registry));
|
|
185
|
+
// A resume that states no layers of its own repeats the layers of the Run
|
|
186
|
+
// it resumes; every layer is read again now, at resume time (ADR-0040).
|
|
187
|
+
const launch = await resolveLaunchConfig(params, registry);
|
|
172
188
|
if (bun === null) throw new Error(statusReport(null, cli));
|
|
173
189
|
const program = await confirmProgramTarget(target);
|
|
174
190
|
|
|
@@ -183,7 +199,7 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
183
199
|
cli,
|
|
184
200
|
program,
|
|
185
201
|
args: params.args,
|
|
186
|
-
...
|
|
202
|
+
...launchOptions(launch),
|
|
187
203
|
id,
|
|
188
204
|
registry,
|
|
189
205
|
start,
|
|
@@ -217,16 +233,26 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
217
233
|
}
|
|
218
234
|
|
|
219
235
|
/**
|
|
220
|
-
* Cassette paths are `resolve`d here; the CLI owns every rule about
|
|
221
|
-
* combinations, validation, refusals — and its own message surfaces on
|
|
236
|
+
* Cassette and config paths are `resolve`d here; the CLI owns every rule about
|
|
237
|
+
* them — combinations, validation, refusals — and its own message surfaces on
|
|
238
|
+
* failure.
|
|
222
239
|
*/
|
|
223
|
-
function
|
|
240
|
+
function launchOptions(params: {
|
|
241
|
+
readonly record?: string;
|
|
242
|
+
readonly resume?: string;
|
|
243
|
+
readonly config?: string;
|
|
244
|
+
readonly noConfig?: boolean;
|
|
245
|
+
}): {
|
|
224
246
|
record?: string;
|
|
225
247
|
resume?: string;
|
|
248
|
+
config?: string;
|
|
249
|
+
noConfig?: boolean;
|
|
226
250
|
} {
|
|
227
251
|
return {
|
|
228
252
|
...(params.record === undefined ? {} : { record: resolve(params.record) }),
|
|
229
253
|
...(params.resume === undefined ? {} : { resume: resolve(params.resume) }),
|
|
254
|
+
...(params.config === undefined ? {} : { config: resolve(params.config) }),
|
|
255
|
+
...(params.noConfig === true ? { noConfig: true } : {}),
|
|
230
256
|
};
|
|
231
257
|
}
|
|
232
258
|
|
|
@@ -259,6 +285,8 @@ function registeredRun(options: {
|
|
|
259
285
|
readonly args?: string;
|
|
260
286
|
readonly record?: string;
|
|
261
287
|
readonly resume?: string;
|
|
288
|
+
readonly config?: string;
|
|
289
|
+
readonly noConfig?: boolean;
|
|
262
290
|
readonly id: string;
|
|
263
291
|
readonly registry: RunRegistry;
|
|
264
292
|
readonly start: (options: StartRunOptions) => RunHandle;
|
|
@@ -273,6 +301,8 @@ function registeredRun(options: {
|
|
|
273
301
|
args: options.args,
|
|
274
302
|
record: options.record,
|
|
275
303
|
resume: options.resume,
|
|
304
|
+
config: options.config,
|
|
305
|
+
noConfig: options.noConfig,
|
|
276
306
|
onProgress: (summary, event, sequence) => {
|
|
277
307
|
options.registry.progress(options.id, summary);
|
|
278
308
|
options.store.ingest(options.id, { summary, event, sequence, id: options.id });
|
|
@@ -289,6 +319,10 @@ function registeredRun(options: {
|
|
|
289
319
|
...(options.args === undefined ? {} : { args: options.args }),
|
|
290
320
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
291
321
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
322
|
+
// The record stores the Run Config path, never its contents; a resume
|
|
323
|
+
// re-resolves every layer at resume time (ADR-0040). `noConfig` is not
|
|
324
|
+
// stored: it is a property of one launch, not of the Run's program.
|
|
325
|
+
...(options.config === undefined ? {} : { config: options.config }),
|
|
292
326
|
};
|
|
293
327
|
const launch: RunLaunch =
|
|
294
328
|
options.program.kind === "file"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DOCS_ROOT } from "../docs/index.ts";
|
|
2
|
+
|
|
1
3
|
/** Builds the host-visible yaag block appended to pi's system prompt. */
|
|
2
4
|
export function yaagPromptBlock(directories: readonly string[]): string {
|
|
3
5
|
const list = directories.join(", ");
|
|
@@ -26,6 +28,7 @@ export function yaagPromptBlock(directories: readonly string[]): string {
|
|
|
26
28
|
"",
|
|
27
29
|
"Full authoring surface (defineAgent, args schemas, ask limits, worktrees, model fallback):",
|
|
28
30
|
"read `<program dir>/.yaag/types/runtime/index.d.ts`.",
|
|
31
|
+
`Shipped docs (${DOCS_ROOT}): getting-started.md, authoring.md, examples.md, cli.md, configuration.md, troubleshooting.md.`,
|
|
29
32
|
].join("\n");
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -64,11 +64,14 @@ function resumeHint(record: RunRecord): string {
|
|
|
64
64
|
// backslash, or a newline, and the hint must stay a call the model can copy.
|
|
65
65
|
const resume = JSON.stringify(artifact);
|
|
66
66
|
const { launch } = record;
|
|
67
|
+
// The Run Config path goes with the hint, so a copied call reads the same
|
|
68
|
+
// config layers the stopped Run read (ADR-0040).
|
|
69
|
+
const config = launch.config === undefined ? "" : `, config: ${JSON.stringify(launch.config)}`;
|
|
67
70
|
switch (launch.kind) {
|
|
68
71
|
case "inline":
|
|
69
|
-
return ` Resume it with yaag_run({ resume: ${resume} }) and no file and no script; yaag reuses the program source it stored.`;
|
|
72
|
+
return ` Resume it with yaag_run({ resume: ${resume}${config} }) and no file and no script; yaag reuses the program source it stored.`;
|
|
70
73
|
case "file":
|
|
71
|
-
return ` Resume it with yaag_run({ file: ${JSON.stringify(launch.file)}, resume: ${resume} }).`;
|
|
74
|
+
return ` Resume it with yaag_run({ file: ${JSON.stringify(launch.file)}, resume: ${resume}${config} }).`;
|
|
72
75
|
default: {
|
|
73
76
|
const never: never = launch;
|
|
74
77
|
return never;
|