@yaag/cli 0.1.4 → 0.2.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/assets/types/runtime/agent.d.ts +4 -2
- package/assets/types/runtime/ask-contract-identity.d.ts +11 -2
- package/assets/types/runtime/ask-exchange-events.d.ts +11 -1
- package/assets/types/runtime/ask-exchange-options.d.ts +7 -2
- package/assets/types/runtime/ask-hash.d.ts +4 -3
- package/assets/types/runtime/ask-limit.d.ts +2 -0
- package/assets/types/runtime/ask-output-steering.d.ts +0 -2
- package/assets/types/runtime/ask-output.d.ts +16 -4
- package/assets/types/runtime/ask-turn.d.ts +5 -0
- package/assets/types/runtime/cassette-loader.d.ts +10 -0
- package/assets/types/runtime/cassette-schema.d.ts +9 -1
- package/assets/types/runtime/cassette.d.ts +4 -2
- package/assets/types/runtime/checkpoint-flush.d.ts +31 -0
- package/assets/types/runtime/connection.d.ts +12 -0
- package/assets/types/runtime/define-agent.d.ts +10 -5
- package/assets/types/runtime/errors.d.ts +7 -1
- package/assets/types/runtime/events.d.ts +24 -2
- package/assets/types/runtime/fake-transport.d.ts +10 -0
- package/assets/types/runtime/frame-queue.d.ts +2 -0
- package/assets/types/runtime/index.d.ts +3 -2
- package/assets/types/runtime/model-resolution.d.ts +45 -0
- package/assets/types/runtime/model-suffix.d.ts +16 -0
- package/assets/types/runtime/pi-state.d.ts +16 -0
- package/assets/types/runtime/report-result-extension.d.ts +44 -0
- package/assets/types/runtime/report-result-output.d.ts +37 -0
- package/assets/types/runtime/report-result-steering.d.ts +35 -0
- package/assets/types/runtime/report-result.d.ts +66 -0
- package/assets/types/runtime/run-checkpoint.d.ts +35 -13
- package/assets/types/runtime/run.d.ts +3 -2
- package/assets/types/runtime/stall-watchdog.d.ts +70 -0
- package/assets/types/runtime/summary-agent.d.ts +7 -1
- package/assets/types/runtime/summary.d.ts +10 -0
- package/assets/types/runtime/thinking-level.d.ts +11 -0
- package/assets/types/runtime/transport.d.ts +41 -4
- package/assets/types/runtime/types.d.ts +49 -14
- package/package.json +3 -3
- package/src/cli-tree-host.ts +3 -3
- package/src/cli.ts +19 -10
- package/src/interactive-run.ts +10 -8
- package/src/output-channel.ts +35 -0
- package/src/presenter.ts +2 -1
- package/src/runtime-alias.ts +6 -8
- package/src/tree-app.ts +6 -9
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { writeSync } from "node:fs";
|
|
3
2
|
import { resolve } from "node:path";
|
|
4
3
|
import {
|
|
4
|
+
assertReplayable,
|
|
5
5
|
executeRun,
|
|
6
6
|
isOrchestrationProgram,
|
|
7
7
|
isYaagError,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { interactiveAvailable } from "./alt-screen.ts";
|
|
14
14
|
import { parseArgv } from "./argv.ts";
|
|
15
15
|
import { runInteractive } from "./interactive-run.ts";
|
|
16
|
+
import { writeChannel, writeChannelFd } from "./output-channel.ts";
|
|
16
17
|
import { createPlainPresenter } from "./presenter.ts";
|
|
17
18
|
import { executeOptions, formatResult, type RunFlags } from "./run-invocation.ts";
|
|
18
19
|
import { registerRuntimeAlias } from "./runtime-alias.ts";
|
|
@@ -24,18 +25,22 @@ registerRuntimeAlias();
|
|
|
24
25
|
export async function main(argv: readonly string[]): Promise<number> {
|
|
25
26
|
const parsed = parseArgv(argv);
|
|
26
27
|
if (!parsed.ok) {
|
|
27
|
-
process.stderr
|
|
28
|
+
writeChannel(process.stderr, `${parsed.error}\n`);
|
|
28
29
|
return 2;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
try {
|
|
32
33
|
if (parsed.command === "setup-workspace") {
|
|
33
|
-
process.stdout
|
|
34
|
+
writeChannel(process.stdout, `${(await setupWorkspace(resolve(parsed.dir))).join("\n")}\n`);
|
|
34
35
|
return 0;
|
|
35
36
|
}
|
|
36
|
-
// A bad artifact must fail before importing an arbitrary program module
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// A bad artifact must fail before importing an arbitrary program module,
|
|
38
|
+
// and an interrupted artifact is a bad artifact for strict replay (ADR-0031).
|
|
39
|
+
if (parsed.command === "run" && parsed.replay !== undefined) {
|
|
40
|
+
assertReplayable(await loadCassette(parsed.replay), parsed.replay);
|
|
41
|
+
}
|
|
42
|
+
if (parsed.command === "run" && parsed.resume !== undefined) {
|
|
43
|
+
await loadCassette(parsed.resume);
|
|
39
44
|
}
|
|
40
45
|
const programFile = resolve(parsed.file);
|
|
41
46
|
const program = await loadProgram(programFile);
|
|
@@ -50,14 +55,18 @@ export async function main(argv: readonly string[]): Promise<number> {
|
|
|
50
55
|
quiet: parsed.quiet,
|
|
51
56
|
});
|
|
52
57
|
} catch (error) {
|
|
53
|
-
|
|
58
|
+
writeChannel(
|
|
59
|
+
process.stderr,
|
|
60
|
+
`[yaag] ${error instanceof Error ? error.message : String(error)}\n`,
|
|
61
|
+
);
|
|
54
62
|
return parsed.command === "run" && isYaagError(error) && error.code === "ARGS_INVALID" ? 2 : 1;
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
function describe(program: OrchestrationProgram): number {
|
|
59
67
|
const definition = programDefinition(program);
|
|
60
|
-
|
|
68
|
+
writeChannel(
|
|
69
|
+
process.stdout,
|
|
61
70
|
`${JSON.stringify({
|
|
62
71
|
name: definition.name ?? null,
|
|
63
72
|
description: definition.description ?? null,
|
|
@@ -110,7 +119,7 @@ async function runPlain(program: OrchestrationProgram, flags: RunFlags): Promise
|
|
|
110
119
|
executeOptions(flags, eventSink(flags.eventsFd, presenter.present), stop.signal),
|
|
111
120
|
);
|
|
112
121
|
const output = formatResult(result);
|
|
113
|
-
if (output !== null) process.stdout
|
|
122
|
+
if (output !== null) writeChannel(process.stdout, `${output}\n`);
|
|
114
123
|
return 0;
|
|
115
124
|
} finally {
|
|
116
125
|
process.off("SIGINT", onSignal);
|
|
@@ -128,7 +137,7 @@ async function runPlain(program: OrchestrationProgram, flags: RunFlags): Promise
|
|
|
128
137
|
*/
|
|
129
138
|
function eventSink(fd: number | undefined, present: StampedEventSink): StampedEventSink {
|
|
130
139
|
return (event) => {
|
|
131
|
-
if (fd !== undefined)
|
|
140
|
+
if (fd !== undefined) writeChannelFd(fd, `${JSON.stringify({ v: 1, ...event })}\n`);
|
|
132
141
|
present(event);
|
|
133
142
|
};
|
|
134
143
|
}
|
package/src/interactive-run.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `yaag run` path that opens the alt-screen Run tree (
|
|
2
|
+
* The `yaag run` path that opens the alt-screen Run tree (architecture §9).
|
|
3
3
|
*
|
|
4
4
|
* `orchestrateInteractiveRun` is process-free: every collaborator arrives as a
|
|
5
|
-
* parameter, so each exit path — settle,
|
|
5
|
+
* parameter, so each exit path — settle, close, fatal render, signal — is
|
|
6
6
|
* driven in tests without a TTY. `runInteractive` is the thin production edge
|
|
7
7
|
* that binds it to the real terminal, the real signals, and the real streams.
|
|
8
8
|
*
|
|
@@ -15,6 +15,7 @@ import type { RunViewResult, TreeState } from "@yaag/tui";
|
|
|
15
15
|
import { createAltScreen } from "./alt-screen.ts";
|
|
16
16
|
import { createFatalSignal, type FatalExit, type FatalSignal } from "./fatal-signal.ts";
|
|
17
17
|
import { finalFrame } from "./final-frame.ts";
|
|
18
|
+
import { writeChannel } from "./output-channel.ts";
|
|
18
19
|
import { createPlainPresenter } from "./presenter.ts";
|
|
19
20
|
import { errorText, executeOptions, formatResult, type RunFlags } from "./run-invocation.ts";
|
|
20
21
|
import { type FinalOutput, startTreeApp, type TreeApp } from "./tree-app.ts";
|
|
@@ -62,9 +63,10 @@ export interface InteractiveRunDeps {
|
|
|
62
63
|
* exit code of the Run.
|
|
63
64
|
*
|
|
64
65
|
* The terminal is restored before this resolves or rejects, on every path: the
|
|
65
|
-
* settle-then-`
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* settle-then-`esc` path, a confirmed stop, an `esc` over a live Run, and a
|
|
67
|
+
* render throw. Closing the view over a live Run closes the alt-screen and
|
|
68
|
+
* keeps awaiting the Run on the plain lines, so the process never exits with a
|
|
69
|
+
* Run still running. A fatal render error is
|
|
68
70
|
* reported only after the terminal is restored and the Run is aborted and
|
|
69
71
|
* reaped, so no Run is abandoned pending. A fatal accepted at any point while
|
|
70
72
|
* the handlers are installed is rethrown, never converted into a normal return.
|
|
@@ -117,7 +119,7 @@ export async function orchestrateInteractiveRun(deps: InteractiveRunDeps): Promi
|
|
|
117
119
|
}
|
|
118
120
|
const settled = await running;
|
|
119
121
|
app.settle(viewResult(settled));
|
|
120
|
-
// The settled view stays interactive until the reader presses `
|
|
122
|
+
// The settled view stays interactive until the reader presses `esc`.
|
|
121
123
|
const exit = await Promise.race([app.exit, signal.wait]);
|
|
122
124
|
let fatal = exit.kind === "fatal" ? exit : signal.taken();
|
|
123
125
|
const closing = app.close();
|
|
@@ -146,8 +148,8 @@ export async function runInteractive(options: InteractiveRunOptions): Promise<nu
|
|
|
146
148
|
startTreeApp({
|
|
147
149
|
tui: createAltScreen(),
|
|
148
150
|
stop,
|
|
149
|
-
writeStdout: (text) => process.stdout
|
|
150
|
-
writeStderr: (text) => process.stderr
|
|
151
|
+
writeStdout: (text) => writeChannel(process.stdout, text),
|
|
152
|
+
writeStderr: (text) => writeChannel(process.stderr, text),
|
|
151
153
|
}),
|
|
152
154
|
startRun: (events, signal) =>
|
|
153
155
|
executeRun(options.program, executeOptions(options.flags, options.events(events), signal)),
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort writes to the Run's output channels (architecture §9, ADR-0031).
|
|
3
|
+
*
|
|
4
|
+
* The Orchestrator is detached from its Host Session. When that session dies,
|
|
5
|
+
* stdout, stderr, and the events descriptor become broken pipes. A write to a
|
|
6
|
+
* broken pipe must not end the Run: it becomes a no-op, the Run keeps going as
|
|
7
|
+
* an Orphaned Run, and `yaag_stop` remains the way to end one.
|
|
8
|
+
*/
|
|
9
|
+
import { writeSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
const silenced = new WeakSet<NodeJS.WriteStream>();
|
|
12
|
+
|
|
13
|
+
/** Writes one chunk to a stream; a broken or failed pipe is ignored. */
|
|
14
|
+
export function writeChannel(stream: NodeJS.WriteStream, text: string): void {
|
|
15
|
+
// A stream reports a broken pipe asynchronously, and an unheard `error`
|
|
16
|
+
// event ends the process. One no-op listener per stream absorbs it.
|
|
17
|
+
if (!silenced.has(stream)) {
|
|
18
|
+
silenced.add(stream);
|
|
19
|
+
stream.on("error", () => {});
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
stream.write(text);
|
|
23
|
+
} catch {
|
|
24
|
+
// The Host Session closed the pipe; the Run continues without an observer.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Writes one whole line to a descriptor; a broken or failed pipe is ignored. */
|
|
29
|
+
export function writeChannelFd(fd: number, text: string): void {
|
|
30
|
+
try {
|
|
31
|
+
writeSync(fd, text);
|
|
32
|
+
} catch {
|
|
33
|
+
// The Host Session closed the descriptor; the Run continues without an observer.
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/presenter.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* gets the interactive alt-screen tree instead (`tree-app.ts`).
|
|
6
6
|
*/
|
|
7
7
|
import type { StampedEventSink } from "@yaag/runtime";
|
|
8
|
+
import { writeChannel } from "./output-channel.ts";
|
|
8
9
|
import { type PlainRenderMode, renderEvent } from "./render.ts";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -24,7 +25,7 @@ export function createPlainPresenter(quiet: boolean): EventPresenter {
|
|
|
24
25
|
return {
|
|
25
26
|
present: (event) => {
|
|
26
27
|
const rendered = renderEvent(event, mode);
|
|
27
|
-
if (rendered !== null) process.stderr
|
|
28
|
+
if (rendered !== null) writeChannel(process.stderr, `${rendered}\n`);
|
|
28
29
|
},
|
|
29
30
|
dispose: () => {},
|
|
30
31
|
};
|
package/src/runtime-alias.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
|
|
3
|
-
const runtimeEntry = fileURLToPath(
|
|
4
|
-
new URL("../../../packages/runtime/src/index.ts", import.meta.url),
|
|
5
|
-
);
|
|
6
|
-
const typeboxEntry = fileURLToPath(
|
|
7
|
-
new URL("../node_modules/typebox/build/index.mjs", import.meta.url),
|
|
8
|
-
);
|
|
9
1
|
const runtimeValues = [
|
|
10
2
|
"CASSETTE_VERSION",
|
|
11
3
|
"loadCassette",
|
|
@@ -39,6 +31,12 @@ let registered = false;
|
|
|
39
31
|
*/
|
|
40
32
|
export function registerRuntimeAlias(): void {
|
|
41
33
|
if (registered) return;
|
|
34
|
+
// Resolved from this CLI package (ADR-0018), so each entry is the same copy
|
|
35
|
+
// the CLI itself imports in the monorepo layout and in the published npm
|
|
36
|
+
// layout, where typebox is hoisted and no packages/ sibling exists. A
|
|
37
|
+
// failure here means a corrupt CLI install, so Bun's ResolveMessage stands.
|
|
38
|
+
const runtimeEntry = Bun.resolveSync("@yaag/runtime", import.meta.dir);
|
|
39
|
+
const typeboxEntry = Bun.resolveSync("typebox", import.meta.dir);
|
|
42
40
|
Bun.plugin({
|
|
43
41
|
name: "yaag-runtime-entry-alias",
|
|
44
42
|
setup(build) {
|
package/src/tree-app.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The standalone CLI's interactive Run view: the same `@yaag/tui` tree the Host
|
|
3
|
-
* Session opens, hosted in an alt-screen pi-tui app (
|
|
3
|
+
* Session opens, hosted in an alt-screen pi-tui app (architecture §9).
|
|
4
4
|
*
|
|
5
5
|
* Every collaborator arrives as a parameter, so the app is driven in tests over
|
|
6
6
|
* a recording terminal; `alt-screen.ts` owns the real `ProcessTerminal`.
|
|
@@ -28,7 +28,6 @@ export interface TreeAppDeps {
|
|
|
28
28
|
/** How the app ended. A fatal exit still restored the terminal. */
|
|
29
29
|
export type TreeAppExit =
|
|
30
30
|
| { readonly kind: "dismissed" }
|
|
31
|
-
| { readonly kind: "detached" }
|
|
32
31
|
| { readonly kind: "fatal"; readonly error: unknown };
|
|
33
32
|
|
|
34
33
|
/** What `finish` writes after the alt-screen closes (architecture §9). */
|
|
@@ -43,7 +42,7 @@ export interface FinalOutput {
|
|
|
43
42
|
export interface TreeApp {
|
|
44
43
|
/** The projection the view reads; the caller renders the final frame from it. */
|
|
45
44
|
readonly state: TreeState;
|
|
46
|
-
/** Resolves when the reader
|
|
45
|
+
/** Resolves when the reader closes the view, or a render throws. */
|
|
47
46
|
readonly exit: Promise<TreeAppExit>;
|
|
48
47
|
/** Folds one Lifecycle Event into the projection and redraws. */
|
|
49
48
|
present(event: LifecycleEvent): void;
|
|
@@ -54,7 +53,7 @@ export interface TreeApp {
|
|
|
54
53
|
/**
|
|
55
54
|
* Drains input and leaves the alt-screen, writing no channel. Once.
|
|
56
55
|
*
|
|
57
|
-
*
|
|
56
|
+
* `esc` closes the alt-screen while the Run is still live, so this step is
|
|
58
57
|
* separate from the final channel emission.
|
|
59
58
|
*/
|
|
60
59
|
close(): Promise<void>;
|
|
@@ -98,13 +97,11 @@ export function startTreeApp(deps: TreeAppDeps): TreeApp {
|
|
|
98
97
|
host: {
|
|
99
98
|
...host,
|
|
100
99
|
stop: deps.stop,
|
|
101
|
-
// A CLI has no background surface, so
|
|
102
|
-
// the caller keeps awaiting the Run on the plain
|
|
103
|
-
|
|
104
|
-
done: (kind) => resolveExit({ kind: kind === "detached" ? "detached" : "dismissed" }),
|
|
100
|
+
// A CLI has no background surface, so closing the view closes the
|
|
101
|
+
// alt-screen and the caller keeps awaiting the Run on the plain lines.
|
|
102
|
+
done: () => resolveExit({ kind: "dismissed" }),
|
|
105
103
|
},
|
|
106
104
|
...(deps.label === undefined ? {} : { label: deps.label }),
|
|
107
|
-
surface: "foreground",
|
|
108
105
|
...(deps.now === undefined ? {} : { now: deps.now }),
|
|
109
106
|
});
|
|
110
107
|
|