@yaag/cli 0.7.0 → 0.8.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 +31 -0
- package/assets/types/runtime/agent/define-agent.d.ts +7 -0
- package/assets/types/runtime/agent/spawn-extensions.d.ts +39 -0
- package/assets/types/runtime/agent/spawn-request.d.ts +1 -0
- package/assets/types/runtime/agent/spawn.d.ts +3 -0
- package/assets/types/runtime/cassette/cassette-schema.d.ts +1 -0
- package/assets/types/runtime/cassette/cassette.d.ts +5 -0
- package/assets/types/runtime/config/config-file.d.ts +22 -0
- package/assets/types/runtime/config/config-issues.d.ts +12 -0
- package/assets/types/runtime/config/config-paths.d.ts +22 -0
- package/assets/types/runtime/config/config-schema.d.ts +17 -0
- package/assets/types/runtime/config/effective-config.d.ts +42 -0
- package/assets/types/runtime/config/index.d.ts +8 -0
- package/assets/types/runtime/errors.d.ts +1 -1
- package/assets/types/runtime/extension/extension-paths.d.ts +10 -2
- package/assets/types/runtime/index.d.ts +2 -0
- package/assets/types/runtime/run/run.d.ts +7 -0
- package/assets/types/runtime/transport/transport.d.ts +5 -0
- package/assets/types/runtime/types.d.ts +5 -0
- package/package.json +3 -3
- package/src/argv.ts +23 -4
- package/src/cli.ts +9 -0
- package/src/config/index.ts +8 -0
- package/src/config/program-directory.ts +34 -0
- package/src/config/run-config.ts +54 -0
- package/src/terminal/run-invocation.ts +4 -1
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @yaag/cli
|
|
2
|
+
|
|
3
|
+
The `yaag` CLI. It runs an Orchestration Program on Bun, describes one, and
|
|
4
|
+
prepares a workspace for authoring.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun add @yaag/cli
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The CLI needs Bun. Install Bun with `curl -fsSL https://bun.sh/install | bash`.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
- `yaag run <program.ts>` — run an Orchestration Program. `--args <json>`,
|
|
17
|
+
`--record <file>`, `--resume <file>`, `--replay <file>`, `--quiet`.
|
|
18
|
+
- `yaag describe <program.ts>` — report the name, description, and args schema.
|
|
19
|
+
- `yaag setup-workspace [dir]` — create `.yaag/` with the editor types.
|
|
20
|
+
|
|
21
|
+
`yaag` with no valid command prints its usage text and exits with code 2.
|
|
22
|
+
|
|
23
|
+
## Docs
|
|
24
|
+
|
|
25
|
+
The full doc set ships with `@yaag/extension`:
|
|
26
|
+
|
|
27
|
+
- [`getting-started.md`](../../packages/extension/docs/getting-started.md)
|
|
28
|
+
- [`authoring.md`](../../packages/extension/docs/authoring.md)
|
|
29
|
+
- [`examples.md`](../../packages/extension/docs/examples.md)
|
|
30
|
+
- [`cli.md`](../../packages/extension/docs/cli.md)
|
|
31
|
+
- [`troubleshooting.md`](../../packages/extension/docs/troubleshooting.md)
|
|
@@ -33,6 +33,13 @@ export interface AgentConfig {
|
|
|
33
33
|
readonly skills?: readonly string[];
|
|
34
34
|
/** Deny-list of skill names, applied after `skills`. */
|
|
35
35
|
readonly disallowedSkills?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Load the Effective Config's `agents.extensions` for Agents of this
|
|
38
|
+
* definition. Default true. A spawn's own `SpawnOptions.configExtensions`
|
|
39
|
+
* wins over this value; spawn *overrides* stay topology-only, so the opt-out
|
|
40
|
+
* is not one of them (ADR-0019, ADR-0040).
|
|
41
|
+
*/
|
|
42
|
+
readonly configExtensions?: boolean;
|
|
36
43
|
/** Ask options applied to every Ask on this Agent unless overridden per call. */
|
|
37
44
|
readonly askDefaults?: AskOptions;
|
|
38
45
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ConfigExtension } from "../config/index.ts";
|
|
2
|
+
/** Everything one spawn needs to turn declarations into `-e` arguments. */
|
|
3
|
+
export interface SpawnExtensionRequest {
|
|
4
|
+
/** Effective Config entries in merge order: global → project → run (spec rule 3). */
|
|
5
|
+
readonly configExtensions: readonly ConfigExtension[];
|
|
6
|
+
/** `SpawnOptions.extensions`, resolved against the program file as before. */
|
|
7
|
+
readonly declared?: readonly string[] | undefined;
|
|
8
|
+
/** False when the spawn or its definition opted out (spec rule 7). */
|
|
9
|
+
readonly useConfigExtensions: boolean;
|
|
10
|
+
/** Orchestration Program source path, the base of a relative spawn declaration. */
|
|
11
|
+
readonly programFile?: string | undefined;
|
|
12
|
+
/** Agent working directory, the project install root for `npm:`/`git:`. */
|
|
13
|
+
readonly projectRoot: string;
|
|
14
|
+
}
|
|
15
|
+
/** The two projections of one Agent's extensions: what launches, and what identifies. */
|
|
16
|
+
export interface SpawnExtensions {
|
|
17
|
+
/** Absolute launch-ready `-e` arguments, deduplicated, in final order. */
|
|
18
|
+
readonly resolvedPaths: readonly string[];
|
|
19
|
+
/** The surviving declarations behind them, same order, unresolved (ADR-0040). */
|
|
20
|
+
readonly declared: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the extensions of one Agent: config-sourced first, spawn-declared
|
|
24
|
+
* last, deduplicated by resolved path with the first occurrence keeping its
|
|
25
|
+
* place. Returns undefined when nothing is declared at all, so an
|
|
26
|
+
* extension-free spawn keeps its request shape.
|
|
27
|
+
*
|
|
28
|
+
* The two bases are deliberately different: a relative *path* from a config
|
|
29
|
+
* file resolves against that file's directory, while an `npm:`/`git:`
|
|
30
|
+
* specifier still installs from the Agent working directory (`projectRoot`).
|
|
31
|
+
* An uninstalled specifier passes through verbatim and dedups by its literal
|
|
32
|
+
* string.
|
|
33
|
+
*
|
|
34
|
+
* A declaration joins `declared` only when it contributed at least one new
|
|
35
|
+
* resolved path, so the identity list describes what actually ran. Two
|
|
36
|
+
* different declarations that resolve to the same path therefore keep only the
|
|
37
|
+
* first, which is machine-dependent by nature of the resolution itself.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveSpawnExtensions(request: SpawnExtensionRequest): Promise<SpawnExtensions | undefined>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConfigExtension } from "../config/index.ts";
|
|
1
2
|
import type { EventSink } from "../events.ts";
|
|
2
3
|
import type { RunContext } from "../run/index.ts";
|
|
3
4
|
import type { TransportFactory } from "../transport/index.ts";
|
|
@@ -9,6 +10,8 @@ export interface SpawnDependencies {
|
|
|
9
10
|
readonly emit: EventSink;
|
|
10
11
|
readonly sessionDir: string | undefined;
|
|
11
12
|
readonly programFile: string | undefined;
|
|
13
|
+
/** Effective Config entries, in merge order; empty when the Run has no config. */
|
|
14
|
+
readonly configExtensions: readonly ConfigExtension[];
|
|
12
15
|
}
|
|
13
16
|
/** A RunContext spawn function that can synchronously stop accepting new Agents. */
|
|
14
17
|
export interface SpawnGate {
|
|
@@ -30,6 +30,7 @@ export declare const CassetteSchema: Type.TObject<{
|
|
|
30
30
|
name: Type.TString;
|
|
31
31
|
cwd: Type.TString;
|
|
32
32
|
worktree: Type.TOptional<Type.TLiteral<true>>;
|
|
33
|
+
declaredExtensions: Type.TOptional<Type.TArray<Type.TString>>;
|
|
33
34
|
}>;
|
|
34
35
|
model: Type.TString;
|
|
35
36
|
sessionFile: Type.TOptional<Type.TString>;
|
|
@@ -67,6 +67,11 @@ export interface CassetteSpawn {
|
|
|
67
67
|
readonly disallowedSkills?: readonly string[];
|
|
68
68
|
/** Deterministic request, present only when the Agent requested a worktree. */
|
|
69
69
|
readonly worktree?: true;
|
|
70
|
+
/**
|
|
71
|
+
* Declared extensions in declaration order, unresolved so a Cassette matches
|
|
72
|
+
* across machines (ADR-0040). Absent when the Agent ran no extension.
|
|
73
|
+
*/
|
|
74
|
+
readonly declaredExtensions?: readonly string[];
|
|
70
75
|
}
|
|
71
76
|
/** The frames attributed to one Ask marker. */
|
|
72
77
|
export interface CassetteAsk {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type YaagConfigDocument } from "./config-schema.ts";
|
|
2
|
+
/** One config file that contributed to the Effective Config. */
|
|
3
|
+
export interface ConfigLayer {
|
|
4
|
+
/** The file this layer was read from. */
|
|
5
|
+
readonly file: string;
|
|
6
|
+
/** The file's own directory: relative paths inside it resolve here (spec rule 4). */
|
|
7
|
+
readonly directory: string;
|
|
8
|
+
readonly document: YaagConfigDocument;
|
|
9
|
+
}
|
|
10
|
+
/** Whether an absent file is an empty layer or a Run-start failure. */
|
|
11
|
+
export interface ReadConfigOptions {
|
|
12
|
+
/** True for the explicit Run Config: a missing file is then an error. */
|
|
13
|
+
readonly required: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads, parses, and validates one config layer.
|
|
17
|
+
*
|
|
18
|
+
* A missing file at a discovered location yields `undefined` (an empty layer);
|
|
19
|
+
* a missing explicit Run Config fails. Any other errno — `EACCES`, say — always
|
|
20
|
+
* throws: a config yaag cannot read is not an empty config.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readConfigLayer(path: string, options: ReadConfigOptions): Promise<ConfigLayer | undefined>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TLocalizedValidationError } from "typebox/error";
|
|
2
|
+
/**
|
|
3
|
+
* Turns TypeBox errors into config diagnostics that name the offending key
|
|
4
|
+
* (spec rule 2).
|
|
5
|
+
*
|
|
6
|
+
* A strict object reports an unknown key twice: once at the key itself with the
|
|
7
|
+
* unhelpful `"schema is false"`, and once at the owning object carrying the key
|
|
8
|
+
* name. Neither alone reads well, so the pair collapses into one line. Every
|
|
9
|
+
* other keyword delegates to the shared formatter, which stays untouched
|
|
10
|
+
* because args validation and ADR-0032 steering text depend on its wording.
|
|
11
|
+
*/
|
|
12
|
+
export declare function describeConfigIssues(value: unknown, errors: Iterable<TLocalizedValidationError>): readonly string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The part of the environment this module reads. It is structural on purpose:
|
|
3
|
+
* the vendored declaration tree compiles with no ambient Node globals.
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigEnvironment {
|
|
6
|
+
readonly [name: string]: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/** Every config layer lives in a file with this name. */
|
|
9
|
+
export declare const CONFIG_FILE_NAME = "config.json";
|
|
10
|
+
/** The Project Config directory inside a Program Directory. */
|
|
11
|
+
export declare const PROJECT_CONFIG_DIR = ".yaag";
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the Global Config path: `YAAG_CONFIG_DIR`, then an absolute
|
|
14
|
+
* `XDG_CONFIG_HOME`, then `$HOME/.config`. A relative `XDG_CONFIG_HOME` is
|
|
15
|
+
* ignored (XDG spec), exactly as in `resolveCheckpointDirectory`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveGlobalConfigPath(env?: ConfigEnvironment): string;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the Project Config path under a Program Directory. The Program
|
|
20
|
+
* Directory is an input: this module never walks upward looking for `.yaag/`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function projectConfigPath(programDirectory: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
/**
|
|
3
|
+
* The config document of one layer (spec rule 1): plain JSON, namespaced as
|
|
4
|
+
* `{ "agents": { "extensions": ["..."] } }`.
|
|
5
|
+
*
|
|
6
|
+
* Every node sets `additionalProperties: false` on purpose (spec rule 2) — the
|
|
7
|
+
* opposite choice from `cassette-schema.ts`, which tolerates unknown fields so
|
|
8
|
+
* a newer artifact still loads. A config file is hand-written, so an unknown
|
|
9
|
+
* key is a typo the author wants named, not forward compatibility.
|
|
10
|
+
*/
|
|
11
|
+
export declare const YaagConfigSchema: Type.TObject<{
|
|
12
|
+
agents: Type.TOptional<Type.TObject<{
|
|
13
|
+
extensions: Type.TOptional<Type.TArray<Type.TString>>;
|
|
14
|
+
}>>;
|
|
15
|
+
}>;
|
|
16
|
+
/** The validated shape of one config file. */
|
|
17
|
+
export type YaagConfigDocument = Static<typeof YaagConfigSchema>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ConfigEnvironment } from "./config-paths.ts";
|
|
2
|
+
/** Which of the three layers a config value came from. */
|
|
3
|
+
export type ConfigLayerName = "global" | "project" | "run";
|
|
4
|
+
/** One extension declaration, kept exactly as written, with its resolution base. */
|
|
5
|
+
export interface ConfigExtension {
|
|
6
|
+
/** The declaration as written in the config file; never resolved here. */
|
|
7
|
+
readonly declaration: string;
|
|
8
|
+
/** The declaring config file's own directory (spec rule 4). */
|
|
9
|
+
readonly baseDirectory: string;
|
|
10
|
+
readonly layer: ConfigLayerName;
|
|
11
|
+
/** The declaring file, for diagnostics. */
|
|
12
|
+
readonly file: string;
|
|
13
|
+
}
|
|
14
|
+
/** The merged configuration one Run reads at start. */
|
|
15
|
+
export interface EffectiveConfig {
|
|
16
|
+
/** Extension declarations, concatenated global → project → run (spec rule 3). */
|
|
17
|
+
readonly extensions: readonly ConfigExtension[];
|
|
18
|
+
/** Config files that contributed, in merge order. */
|
|
19
|
+
readonly files: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/** What to discover and read. */
|
|
22
|
+
export interface EffectiveConfigRequest {
|
|
23
|
+
/** Environment for Global Config discovery; defaults to `process.env`. */
|
|
24
|
+
readonly env?: ConfigEnvironment;
|
|
25
|
+
/** Program Directory of the Run; absent means no Project Config layer. */
|
|
26
|
+
readonly programDirectory?: string;
|
|
27
|
+
/** Explicit Run Config path; a missing file here is an error. */
|
|
28
|
+
readonly configPath?: string;
|
|
29
|
+
/** Suppresses the discovered layers only; an explicit `configPath` survives (spec rule 8). */
|
|
30
|
+
readonly noConfig?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** The Effective Config of a Run that found no config at all. */
|
|
33
|
+
export declare const EMPTY_EFFECTIVE_CONFIG: EffectiveConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Locates, reads, validates, and merges the three config layers.
|
|
36
|
+
*
|
|
37
|
+
* The result is frozen: the Effective Config is read once at Run start, so a
|
|
38
|
+
* config edit mid-Run changes nothing until the next Run (spec rule 6). Nothing
|
|
39
|
+
* is resolved, existence-checked, or deduplicated here — dedup happens by
|
|
40
|
+
* resolved path once the spawn knows its base directories (ADR-0040).
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadEffectiveConfig(request?: EffectiveConfigRequest): Promise<EffectiveConfig>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the `config/` module: the Global, Project, and Run Config
|
|
3
|
+
* layers and the frozen Effective Config a Run reads at start (ADR-0040).
|
|
4
|
+
* Files inside this directory import each other directly.
|
|
5
|
+
*/
|
|
6
|
+
export { CONFIG_FILE_NAME, type ConfigEnvironment, PROJECT_CONFIG_DIR, projectConfigPath, resolveGlobalConfigPath, } from "./config-paths.ts";
|
|
7
|
+
export type { YaagConfigDocument } from "./config-schema.ts";
|
|
8
|
+
export { type ConfigExtension, type ConfigLayerName, type EffectiveConfig, type EffectiveConfigRequest, EMPTY_EFFECTIVE_CONFIG, loadEffectiveConfig, } from "./effective-config.ts";
|
|
@@ -30,7 +30,7 @@ export interface ModelResolutionOutcome {
|
|
|
30
30
|
readonly modelErrors: readonly ModelError[];
|
|
31
31
|
}
|
|
32
32
|
/** Why an Ask, spawn, or Run failed. Programs may branch on this; most won't. */
|
|
33
|
-
export type YaagErrorCode = "AGENT_FAILED" | "AGENT_DIED" | "AGENT_BUSY" | "ASK_TIMEOUT" | "ASK_LIMIT" | "ASK_STALLED" | "ASK_INVALID_OUTPUT" | "ARGS_INVALID" | "OPTIONS_CONFLICT" | "REPLAY_DIVERGED" | "RESUME_REFUSED" | "RUN_CLOSED" | "RUN_STOPPED" | "WORKTREE_REFUSED" | "MODEL_RESOLUTION_FAILED" | "SPAWN_FAILED";
|
|
33
|
+
export type YaagErrorCode = "AGENT_FAILED" | "AGENT_DIED" | "AGENT_BUSY" | "ASK_TIMEOUT" | "ASK_LIMIT" | "ASK_STALLED" | "ASK_INVALID_OUTPUT" | "ARGS_INVALID" | "CONFIG_INVALID" | "OPTIONS_CONFLICT" | "REPLAY_DIVERGED" | "RESUME_REFUSED" | "RUN_CLOSED" | "RUN_STOPPED" | "WORKTREE_REFUSED" | "MODEL_RESOLUTION_FAILED" | "SPAWN_FAILED";
|
|
34
34
|
/** The single error class of the runtime (ADR-0003). */
|
|
35
35
|
export declare class YaagError extends Error {
|
|
36
36
|
readonly code: YaagErrorCode;
|
|
@@ -3,14 +3,22 @@ import { type ExtensionInstallRoots } from "./extension-package.ts";
|
|
|
3
3
|
export interface ExtensionResolutionOptions extends ExtensionInstallRoots {
|
|
4
4
|
/** Orchestration Program source path, required by relative declarations. */
|
|
5
5
|
readonly programFile?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Directory a relative declaration resolves against, and which wins over
|
|
8
|
+
* `programFile`: a config-sourced entry resolves from its own config file's
|
|
9
|
+
* directory (ADR-0040, spec rule 4).
|
|
10
|
+
*/
|
|
11
|
+
readonly baseDirectory?: string;
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
14
|
* Resolves declared extensions into launch-ready `pi -e` arguments before transport opening.
|
|
9
15
|
*
|
|
10
|
-
* Filesystem declarations are resolved against
|
|
16
|
+
* Filesystem declarations are resolved against `baseDirectory` when given, and against
|
|
17
|
+
* the Orchestration Program directory otherwise, and are
|
|
11
18
|
* checked for existence. `npm:`/`git:` specifiers expand to the entry points of the
|
|
12
19
|
* already-installed package (project install shadows the user install); an uninstalled
|
|
13
20
|
* specifier passes through verbatim so pi temp-installs it. Results retain declaration
|
|
14
|
-
* order. Rejects missing paths and a relative declaration
|
|
21
|
+
* order. Rejects missing paths, and a relative declaration that has neither
|
|
22
|
+
* `baseDirectory` nor `programFile`.
|
|
15
23
|
*/
|
|
16
24
|
export declare function resolveExtensionPaths(declarations: readonly string[], options?: ExtensionResolutionOptions): Promise<readonly string[]>;
|
|
@@ -2,6 +2,8 @@ export type { AgentConfig, AgentDefinition } from "./agent/index.ts";
|
|
|
2
2
|
export { agentDefinitionConfig, defineAgent, isAgentDefinition } from "./agent/index.ts";
|
|
3
3
|
export type { Cassette, CassetteAgent, CassetteArtifact, CassetteAsk, CassetteGit, CassetteRun, CassetteSink, CassetteSpawn, } from "./cassette/index.ts";
|
|
4
4
|
export { assertReplayable, CASSETTE_VERSION, loadCassette, recordingTransport, replayTransport, resumeTransport, } from "./cassette/index.ts";
|
|
5
|
+
export type { ConfigEnvironment, ConfigExtension, ConfigLayerName, EffectiveConfig, EffectiveConfigRequest, } from "./config/index.ts";
|
|
6
|
+
export { EMPTY_EFFECTIVE_CONFIG, loadEffectiveConfig, PROJECT_CONFIG_DIR, } from "./config/index.ts";
|
|
5
7
|
export type { AskInvalidOutputOutcome, AskLimitKind, AskLimitOutcome, AskStalledOutcome, ModelResolutionOutcome, YaagErrorCode, } from "./errors.ts";
|
|
6
8
|
export { isYaagError, YaagError } from "./errors.ts";
|
|
7
9
|
export type { AgentActivity, AskOutputChannel, EventSink, LifecycleEvent, LifecycleEventBody, NodeState, NodeUsage, StampedEventSink, } from "./events.ts";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Cassette, type CassetteSink } from "../cassette/index.ts";
|
|
2
|
+
import { type EffectiveConfig } from "../config/index.ts";
|
|
2
3
|
import type { StampedEventSink } from "../events.ts";
|
|
3
4
|
import type { TransportFactory } from "../transport/index.ts";
|
|
4
5
|
import { type SkillProbeFactory } from "../transport/index.ts";
|
|
@@ -33,6 +34,12 @@ export interface RunOptions {
|
|
|
33
34
|
readonly signal?: AbortSignal;
|
|
34
35
|
/** Overrides the default checkpoint directory a stopped Run publishes into (ADR-0021). */
|
|
35
36
|
readonly checkpointDir?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Effective Config for this Run, read once by the launcher (ADR-0040). The
|
|
39
|
+
* Run never loads it itself: omission runs config-free, so no Run picks up a
|
|
40
|
+
* config it was not given, and every test stays hermetic.
|
|
41
|
+
*/
|
|
42
|
+
readonly config?: EffectiveConfig;
|
|
36
43
|
}
|
|
37
44
|
/**
|
|
38
45
|
* Executes one Orchestration Program and resolves with its return value.
|
|
@@ -157,6 +157,11 @@ export interface OpenOptions {
|
|
|
157
157
|
readonly resolvedSkillPaths?: readonly string[];
|
|
158
158
|
/** Absolute launch-ready extension paths resolved above the seam, emitted as repeated `-e` flags. */
|
|
159
159
|
readonly resolvedExtensionPaths?: readonly string[];
|
|
160
|
+
/**
|
|
161
|
+
* Declared extensions in final concatenation order, unresolved (ADR-0040).
|
|
162
|
+
* Spawn identity only: it never becomes argv, and it is absent when empty.
|
|
163
|
+
*/
|
|
164
|
+
readonly declaredExtensions?: readonly string[];
|
|
160
165
|
/** Session storage directory. Used by the e2e suite to stay out of ~/.pi (ticket 06). */
|
|
161
166
|
readonly sessionDir?: string;
|
|
162
167
|
/** Resumes an existing pi session, translated to `--session <path>`. */
|
|
@@ -36,6 +36,11 @@ export interface SpawnOptions {
|
|
|
36
36
|
* resolve from the Orchestration Program file; each becomes `pi -e <path>`.
|
|
37
37
|
*/
|
|
38
38
|
readonly extensions?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Load the Effective Config's `agents.extensions` for this Agent. Default
|
|
41
|
+
* true; false spawns with only the extensions this call declares (ADR-0040).
|
|
42
|
+
*/
|
|
43
|
+
readonly configExtensions?: boolean;
|
|
39
44
|
/**
|
|
40
45
|
* Tool allowlist layered over the ADR-0026 baseline; omission is tool-free when hermetic.
|
|
41
46
|
* `disallowedTools` applies last. A non-empty surviving allowlist is verified after startup;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
24
|
-
"@yaag/runtime": "0.
|
|
25
|
-
"@yaag/tui": "0.
|
|
24
|
+
"@yaag/runtime": "0.8.0",
|
|
25
|
+
"@yaag/tui": "0.8.0",
|
|
26
26
|
"typebox": "1.3.7"
|
|
27
27
|
}
|
|
28
28
|
}
|
package/src/argv.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
|
|
7
7
|
export const USAGE = `usage:
|
|
8
|
-
yaag run <program.ts> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>]
|
|
9
|
-
yaag run --eval <source> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>]
|
|
10
|
-
yaag run --eval-fd <n> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>]
|
|
8
|
+
yaag run <program.ts> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
9
|
+
yaag run --eval <source> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
10
|
+
yaag run --eval-fd <n> [--quiet] [--args <json>] [--record <file>] [--replay <file>] [--resume <file>] [--events-fd <n>] [--config <file>] [--no-config]
|
|
11
11
|
yaag describe <program.ts>
|
|
12
12
|
yaag setup-workspace [dir]
|
|
13
13
|
|
|
@@ -20,6 +20,12 @@ must be a file.
|
|
|
20
20
|
close that descriptor. It keeps the source out of the process argument list,
|
|
21
21
|
where every local user can read it. The yaag extension always uses it.
|
|
22
22
|
|
|
23
|
+
--config <file> reads one more config file for this Run. Give it one time only.
|
|
24
|
+
yaag reads it after the global config and after the project config.
|
|
25
|
+
|
|
26
|
+
--no-config tells yaag to ignore the global config and the project config. It
|
|
27
|
+
does not ignore --config.
|
|
28
|
+
|
|
23
29
|
A --resume or --replay Run also needs the program: give the program file,
|
|
24
30
|
--eval <source>, or --eval-fd <n>. A Cassette holds the history of a Run, and never the program
|
|
25
31
|
to run.
|
|
@@ -44,6 +50,8 @@ export type ParsedArgv =
|
|
|
44
50
|
readonly record?: string;
|
|
45
51
|
readonly replay?: string;
|
|
46
52
|
readonly resume?: string;
|
|
53
|
+
readonly config?: string;
|
|
54
|
+
readonly noConfig?: boolean;
|
|
47
55
|
}
|
|
48
56
|
| { readonly ok: true; readonly command: "describe"; readonly file: string }
|
|
49
57
|
| { readonly ok: true; readonly command: "setup-workspace"; readonly dir: string }
|
|
@@ -72,12 +80,16 @@ function parseRun(tokens: readonly string[]): ParsedArgv {
|
|
|
72
80
|
let record: string | undefined;
|
|
73
81
|
let replay: string | undefined;
|
|
74
82
|
let resume: string | undefined;
|
|
83
|
+
let config: string | undefined;
|
|
84
|
+
let noConfig = false;
|
|
75
85
|
let quiet = false;
|
|
76
86
|
|
|
77
87
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
78
88
|
const token = tokens[index] ?? "";
|
|
79
89
|
if (token === "--quiet") {
|
|
80
90
|
quiet = true;
|
|
91
|
+
} else if (token === "--no-config") {
|
|
92
|
+
noConfig = true;
|
|
81
93
|
} else if (
|
|
82
94
|
token === "--args" ||
|
|
83
95
|
token === "--eval" ||
|
|
@@ -85,7 +97,8 @@ function parseRun(tokens: readonly string[]): ParsedArgv {
|
|
|
85
97
|
token === "--events-fd" ||
|
|
86
98
|
token === "--record" ||
|
|
87
99
|
token === "--replay" ||
|
|
88
|
-
token === "--resume"
|
|
100
|
+
token === "--resume" ||
|
|
101
|
+
token === "--config"
|
|
89
102
|
) {
|
|
90
103
|
const value = tokens[index + 1];
|
|
91
104
|
if (value === undefined) return failure(`${token} needs a value\n${USAGE}`);
|
|
@@ -108,6 +121,10 @@ function parseRun(tokens: readonly string[]): ParsedArgv {
|
|
|
108
121
|
record = value;
|
|
109
122
|
} else if (token === "--replay") {
|
|
110
123
|
replay = value;
|
|
124
|
+
} else if (token === "--config") {
|
|
125
|
+
// One Run Config only: two of them would leave the merge order unstated.
|
|
126
|
+
if (config !== undefined) return failure(`--config may be given once\n${USAGE}`);
|
|
127
|
+
config = value;
|
|
111
128
|
} else {
|
|
112
129
|
resume = value;
|
|
113
130
|
}
|
|
@@ -134,6 +151,8 @@ function parseRun(tokens: readonly string[]): ParsedArgv {
|
|
|
134
151
|
...(record === undefined ? {} : { record }),
|
|
135
152
|
...(replay === undefined ? {} : { replay }),
|
|
136
153
|
...(resume === undefined ? {} : { resume }),
|
|
154
|
+
...(config === undefined ? {} : { config }),
|
|
155
|
+
...(noConfig ? { noConfig: true } : {}),
|
|
137
156
|
};
|
|
138
157
|
}
|
|
139
158
|
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type StampedEventSink,
|
|
11
11
|
} from "@yaag/runtime";
|
|
12
12
|
import { parseArgv } from "./argv.ts";
|
|
13
|
+
import { loadRunConfig } from "./config/index.ts";
|
|
13
14
|
import { loadProgram, loadRunProgram, registerRuntimeAlias } from "./program/index.ts";
|
|
14
15
|
import { setupWorkspace } from "./setup-workspace.ts";
|
|
15
16
|
import {
|
|
@@ -47,6 +48,13 @@ export async function main(argv: readonly string[]): Promise<number> {
|
|
|
47
48
|
await loadCassette(parsed.resume);
|
|
48
49
|
}
|
|
49
50
|
if (parsed.command === "describe") return describe(await loadProgram(resolve(parsed.file)));
|
|
51
|
+
// Before the program import: a bad config must fail the Run at start, and
|
|
52
|
+
// never after an arbitrary program module ran its top level (ADR-0040).
|
|
53
|
+
const config = await loadRunConfig({
|
|
54
|
+
programFile: parsed.program.kind === "file" ? resolve(parsed.program.file) : undefined,
|
|
55
|
+
...(parsed.config === undefined ? {} : { configPath: parsed.config }),
|
|
56
|
+
...(parsed.noConfig === true ? { noConfig: true } : {}),
|
|
57
|
+
});
|
|
50
58
|
const { program, programFile, programSource } = await loadRunProgram(parsed.program);
|
|
51
59
|
return await run(program, {
|
|
52
60
|
programFile,
|
|
@@ -56,6 +64,7 @@ export async function main(argv: readonly string[]): Promise<number> {
|
|
|
56
64
|
record: parsed.record,
|
|
57
65
|
replay: parsed.replay,
|
|
58
66
|
resume: parsed.resume,
|
|
67
|
+
config,
|
|
59
68
|
quiet: parsed.quiet,
|
|
60
69
|
});
|
|
61
70
|
} catch (error) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the CLI's `config/` module: Program Directory discovery and
|
|
3
|
+
* the launcher's Effective Config read (ADR-0040).
|
|
4
|
+
* Files inside this directory import each other directly.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { findProgramDirectory } from "./program-directory.ts";
|
|
8
|
+
export { loadRunConfig, type RunConfigRequest } from "./run-config.ts";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Program Directory discovery for the launcher.
|
|
3
|
+
*
|
|
4
|
+
* The runtime's `config-paths.ts` never walks upward: it takes the Program
|
|
5
|
+
* Directory as an input. The CLI is the launcher, so the walk lives here
|
|
6
|
+
* (ADR-0040).
|
|
7
|
+
*/
|
|
8
|
+
import { stat } from "node:fs/promises";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { PROJECT_CONFIG_DIR } from "@yaag/runtime";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The nearest directory at or above `startDirectory` that holds a `.yaag`
|
|
14
|
+
* directory, or `undefined` when the walk reaches the filesystem root.
|
|
15
|
+
*
|
|
16
|
+
* A `.yaag` file is not a marker: only a directory can hold a config file.
|
|
17
|
+
*/
|
|
18
|
+
export async function findProgramDirectory(startDirectory: string): Promise<string | undefined> {
|
|
19
|
+
let directory = resolve(startDirectory);
|
|
20
|
+
for (;;) {
|
|
21
|
+
if (await hasMarker(directory)) return directory;
|
|
22
|
+
const parent = dirname(directory);
|
|
23
|
+
if (parent === directory) return undefined;
|
|
24
|
+
directory = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function hasMarker(directory: string): Promise<boolean> {
|
|
29
|
+
try {
|
|
30
|
+
return (await stat(join(directory, PROJECT_CONFIG_DIR))).isDirectory();
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The launcher's config read: it resolves the Program Directory of one Run and
|
|
3
|
+
* loads the Effective Config from the three layers (ADR-0040).
|
|
4
|
+
*
|
|
5
|
+
* `RunOptions.config` is read once, here, at Run start. Nothing else in the
|
|
6
|
+
* process reads a config file.
|
|
7
|
+
*/
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
9
|
+
import { type ConfigEnvironment, type EffectiveConfig, loadEffectiveConfig } from "@yaag/runtime";
|
|
10
|
+
import { findProgramDirectory } from "./program-directory.ts";
|
|
11
|
+
|
|
12
|
+
/** What one Run needs to know to find its config layers. */
|
|
13
|
+
export interface RunConfigRequest {
|
|
14
|
+
/** Absolute path of the program file; `undefined` for an Inline Program. */
|
|
15
|
+
readonly programFile: string | undefined;
|
|
16
|
+
/** Where an Inline Program starts its search; defaults to `process.cwd()`. */
|
|
17
|
+
readonly cwd?: string;
|
|
18
|
+
/** The `--config` value, absolute or relative to `cwd`. */
|
|
19
|
+
readonly configPath?: string;
|
|
20
|
+
/** `--no-config`: drops the Global and the Project layer only (spec rule 8). */
|
|
21
|
+
readonly noConfig?: boolean;
|
|
22
|
+
/** Environment for Global Config discovery; defaults to `process.env`. */
|
|
23
|
+
readonly env?: ConfigEnvironment;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Loads the Effective Config of one Run.
|
|
28
|
+
*
|
|
29
|
+
* The Program Directory search starts at the program file's directory, or at
|
|
30
|
+
* `cwd` for an Inline Program. `--no-config` skips the search: no discovered
|
|
31
|
+
* layer can contribute, so nothing must be looked for.
|
|
32
|
+
*
|
|
33
|
+
* Throws `YaagError("CONFIG_INVALID", …)` when a config file is missing at the
|
|
34
|
+
* explicit `--config` path, or when any layer is unreadable or invalid.
|
|
35
|
+
*/
|
|
36
|
+
export async function loadRunConfig(request: RunConfigRequest): Promise<EffectiveConfig> {
|
|
37
|
+
const cwd = request.cwd ?? process.cwd();
|
|
38
|
+
const configPath =
|
|
39
|
+
request.configPath === undefined ? undefined : resolve(cwd, request.configPath);
|
|
40
|
+
// `--no-config` discovers nothing, so the upward walk is skipped: it would
|
|
41
|
+
// stat directory after directory for a layer that cannot contribute.
|
|
42
|
+
const programDirectory =
|
|
43
|
+
request.noConfig === true
|
|
44
|
+
? undefined
|
|
45
|
+
: await findProgramDirectory(
|
|
46
|
+
request.programFile === undefined ? cwd : dirname(request.programFile),
|
|
47
|
+
);
|
|
48
|
+
return await loadEffectiveConfig({
|
|
49
|
+
...(request.env === undefined ? {} : { env: request.env }),
|
|
50
|
+
...(request.noConfig === true ? { noConfig: true } : {}),
|
|
51
|
+
...(programDirectory === undefined ? {} : { programDirectory }),
|
|
52
|
+
...(configPath === undefined ? {} : { configPath }),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Both presentation paths — the plain lines and the alt-screen tree — compose
|
|
6
6
|
* it, so they cannot drift on artifact flags or on the result format.
|
|
7
7
|
*/
|
|
8
|
-
import type { RunOptions, StampedEventSink } from "@yaag/runtime";
|
|
8
|
+
import type { EffectiveConfig, RunOptions, StampedEventSink } from "@yaag/runtime";
|
|
9
9
|
|
|
10
10
|
/** Everything `yaag run` parsed for one Run. */
|
|
11
11
|
export interface RunFlags {
|
|
@@ -18,6 +18,8 @@ export interface RunFlags {
|
|
|
18
18
|
readonly record: string | undefined;
|
|
19
19
|
readonly replay: string | undefined;
|
|
20
20
|
readonly resume: string | undefined;
|
|
21
|
+
/** The Effective Config the launcher read at Run start (ADR-0040). */
|
|
22
|
+
readonly config: EffectiveConfig | undefined;
|
|
21
23
|
readonly quiet: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -35,6 +37,7 @@ export function executeOptions(
|
|
|
35
37
|
...(flags.record === undefined ? {} : { record: flags.record }),
|
|
36
38
|
...(flags.replay === undefined ? {} : { replay: flags.replay }),
|
|
37
39
|
...(flags.resume === undefined ? {} : { resume: flags.resume }),
|
|
40
|
+
...(flags.config === undefined ? {} : { config: flags.config }),
|
|
38
41
|
signal,
|
|
39
42
|
};
|
|
40
43
|
}
|