@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.
Files changed (43) hide show
  1. package/assets/types/runtime/agent.d.ts +4 -2
  2. package/assets/types/runtime/ask-contract-identity.d.ts +11 -2
  3. package/assets/types/runtime/ask-exchange-events.d.ts +11 -1
  4. package/assets/types/runtime/ask-exchange-options.d.ts +7 -2
  5. package/assets/types/runtime/ask-hash.d.ts +4 -3
  6. package/assets/types/runtime/ask-limit.d.ts +2 -0
  7. package/assets/types/runtime/ask-output-steering.d.ts +0 -2
  8. package/assets/types/runtime/ask-output.d.ts +16 -4
  9. package/assets/types/runtime/ask-turn.d.ts +5 -0
  10. package/assets/types/runtime/cassette-loader.d.ts +10 -0
  11. package/assets/types/runtime/cassette-schema.d.ts +9 -1
  12. package/assets/types/runtime/cassette.d.ts +4 -2
  13. package/assets/types/runtime/checkpoint-flush.d.ts +31 -0
  14. package/assets/types/runtime/connection.d.ts +12 -0
  15. package/assets/types/runtime/define-agent.d.ts +10 -5
  16. package/assets/types/runtime/errors.d.ts +7 -1
  17. package/assets/types/runtime/events.d.ts +24 -2
  18. package/assets/types/runtime/fake-transport.d.ts +10 -0
  19. package/assets/types/runtime/frame-queue.d.ts +2 -0
  20. package/assets/types/runtime/index.d.ts +3 -2
  21. package/assets/types/runtime/model-resolution.d.ts +45 -0
  22. package/assets/types/runtime/model-suffix.d.ts +16 -0
  23. package/assets/types/runtime/pi-state.d.ts +16 -0
  24. package/assets/types/runtime/report-result-extension.d.ts +44 -0
  25. package/assets/types/runtime/report-result-output.d.ts +37 -0
  26. package/assets/types/runtime/report-result-steering.d.ts +35 -0
  27. package/assets/types/runtime/report-result.d.ts +66 -0
  28. package/assets/types/runtime/run-checkpoint.d.ts +35 -13
  29. package/assets/types/runtime/run.d.ts +3 -2
  30. package/assets/types/runtime/stall-watchdog.d.ts +70 -0
  31. package/assets/types/runtime/summary-agent.d.ts +7 -1
  32. package/assets/types/runtime/summary.d.ts +10 -0
  33. package/assets/types/runtime/thinking-level.d.ts +11 -0
  34. package/assets/types/runtime/transport.d.ts +41 -4
  35. package/assets/types/runtime/types.d.ts +49 -14
  36. package/package.json +3 -3
  37. package/src/cli-tree-host.ts +3 -3
  38. package/src/cli.ts +19 -10
  39. package/src/interactive-run.ts +10 -8
  40. package/src/output-channel.ts +35 -0
  41. package/src/presenter.ts +2 -1
  42. package/src/runtime-alias.ts +6 -8
  43. 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.write(`${parsed.error}\n`);
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.write(`${(await setupWorkspace(resolve(parsed.dir))).join("\n")}\n`);
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
- if (parsed.command === "run" && (parsed.replay !== undefined || parsed.resume !== undefined)) {
38
- await loadCassette(parsed.replay ?? parsed.resume ?? "");
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
- process.stderr.write(`[yaag] ${error instanceof Error ? error.message : String(error)}\n`);
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
- process.stdout.write(
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.write(`${output}\n`);
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) writeSync(fd, `${JSON.stringify({ v: 1, ...event })}\n`);
140
+ if (fd !== undefined) writeChannelFd(fd, `${JSON.stringify({ v: 1, ...event })}\n`);
132
141
  present(event);
133
142
  };
134
143
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * The `yaag run` path that opens the alt-screen Run tree (spec §4).
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, detach, fatal render, signal — is
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-`q` path, a confirmed stop, a detach, and a render throw. A
66
- * detach closes the alt-screen and keeps awaiting the Run on the plain lines,
67
- * so the process never exits with a Run still running. A fatal render error is
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 `q` (spec §1).
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.write(text),
150
- writeStderr: (text) => process.stderr.write(text),
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.write(`${rendered}\n`);
28
+ if (rendered !== null) writeChannel(process.stderr, `${rendered}\n`);
28
29
  },
29
30
  dispose: () => {},
30
31
  };
@@ -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 (spec §4).
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 dismisses or detaches, or a render throws. */
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
- * A detach closes the alt-screen while the Run is still live, so this step is
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 a detach closes the alt-screen and
102
- // the caller keeps awaiting the Run on the plain line presenter.
103
- detach: () => resolveExit({ kind: "detached" }),
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