@yaag/extension 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/package.json +4 -4
- package/src/background-status.ts +94 -0
- package/src/fake-extension-ui.ts +6 -3
- package/src/fake-tui.ts +1 -1
- package/src/index.ts +5 -5
- package/src/run-argv.ts +34 -0
- package/src/run-foreground.ts +107 -60
- package/src/run-program-param.ts +64 -0
- package/src/run-record.ts +12 -2
- package/src/run-settlement.ts +1 -1
- package/src/run-tool-test-support.ts +60 -6
- package/src/run-tool.ts +51 -39
- package/src/run-tree-component.ts +2 -1
- package/src/run-tree-host.ts +4 -5
- package/src/run-trees.ts +4 -4
- package/src/spawn-run.ts +11 -5
- package/src/test-tui-context.ts +7 -1
- package/src/yaag-command.ts +12 -19
- package/src/background-widget.ts +0 -107
- package/src/run-foreground-view.ts +0 -95
package/README.md
CHANGED
|
@@ -35,12 +35,17 @@ For a throwaway session instead: `pi -e ./packages/extension`.
|
|
|
35
35
|
|
|
36
36
|
| Parameter | Type | Meaning |
|
|
37
37
|
|---|---|---|
|
|
38
|
-
| `file` | `string
|
|
38
|
+
| `file` | `string?` | Path to the Orchestration Program file |
|
|
39
|
+
| `script` | `string?` | Orchestration Program source text (Inline Program) |
|
|
39
40
|
| `args` | `string?` | The program's arguments, as a JSON object string |
|
|
40
41
|
| `background` | `boolean?` | Start the Run in the background and return its Run id |
|
|
41
42
|
| `record` | `string?` | Write this Run's Cassette artifact to this path |
|
|
42
43
|
| `resume` | `string?` | Replay a matching Cassette prefix, then continue live |
|
|
43
44
|
|
|
45
|
+
Give `file` or `script`, and not both; a violation is a parameter error raised
|
|
46
|
+
before any process starts. A `script` program can import `@yaag/runtime` and
|
|
47
|
+
`typebox` only (ADR-0033).
|
|
48
|
+
|
|
44
49
|
Blocking by default: the call returns when the Run ends, and its content is the
|
|
45
50
|
Run's return value. With `background: true` it returns at once with a short Run
|
|
46
51
|
id (`r1`, `r2`, …) and the result arrives later as a follow-up message that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
27
|
-
"@yaag/cli": "0.
|
|
28
|
-
"@yaag/runtime": "0.
|
|
29
|
-
"@yaag/tui": "0.
|
|
27
|
+
"@yaag/cli": "0.3.0",
|
|
28
|
+
"@yaag/runtime": "0.3.0",
|
|
29
|
+
"@yaag/tui": "0.3.0",
|
|
30
30
|
"nanoid": "^6.0.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The footer segment for every live background Run (architecture §9).
|
|
3
|
+
*
|
|
4
|
+
* A background Run costs no scrollback and owns no widget: it reports itself in
|
|
5
|
+
* one cooperative footer segment, `yaag: 2 live · $0.42`, which pi lays out
|
|
6
|
+
* beside the segments of every other extension. The segment is cleared as soon
|
|
7
|
+
* as the last background Run ends.
|
|
8
|
+
*/
|
|
9
|
+
import { costText } from "@yaag/tui";
|
|
10
|
+
import type { RunRegistry } from "./run-registry.ts";
|
|
11
|
+
import type { RunTreeStore } from "./run-trees.ts";
|
|
12
|
+
|
|
13
|
+
/** The one footer key this extension owns. */
|
|
14
|
+
export const STATUS_KEY = "yaag";
|
|
15
|
+
|
|
16
|
+
/** The only pi capability the footer segment needs. */
|
|
17
|
+
export interface StatusSurface {
|
|
18
|
+
setStatus(key: string, text: string | undefined): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What the footer segment reads: the Run registry and the projection store. */
|
|
22
|
+
export interface BackgroundStatusOptions {
|
|
23
|
+
readonly registry: RunRegistry;
|
|
24
|
+
readonly store: RunTreeStore;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The footer controller: bind it once, then refresh it on every occurrence. */
|
|
28
|
+
export interface BackgroundStatus {
|
|
29
|
+
/**
|
|
30
|
+
* Binds the pi UI surface, once the Host Session has one, and writes the
|
|
31
|
+
* current text. A `StatusSurface.setStatus()` that throws propagates here.
|
|
32
|
+
*/
|
|
33
|
+
bind(surface: StatusSurface): void;
|
|
34
|
+
/**
|
|
35
|
+
* Rewrites the segment; drops the write before `bind`, and skips unchanged
|
|
36
|
+
* text. A bound `StatusSurface.setStatus()` that throws propagates here.
|
|
37
|
+
*/
|
|
38
|
+
refresh(): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Builds the footer segment over this session's Run registry and projections.
|
|
43
|
+
*
|
|
44
|
+
* An unbound controller drops every refresh, and a Run without a projection is
|
|
45
|
+
* skipped. The controller lives for the whole Host Session, so it never
|
|
46
|
+
* unsubscribes. Construction itself never throws, but `bind()` and `refresh()`
|
|
47
|
+
* call the caller's `StatusSurface.setStatus()` and propagate whatever it
|
|
48
|
+
* throws; the store subscription also propagates it into the `ingest()` that
|
|
49
|
+
* caused the rewrite.
|
|
50
|
+
*/
|
|
51
|
+
export function createBackgroundStatus(options: BackgroundStatusOptions): BackgroundStatus {
|
|
52
|
+
let surface: StatusSurface | undefined;
|
|
53
|
+
let written: string | undefined;
|
|
54
|
+
|
|
55
|
+
const refresh = (): void => {
|
|
56
|
+
if (surface === undefined) return;
|
|
57
|
+
const text = statusText(options);
|
|
58
|
+
if (written === text) return;
|
|
59
|
+
written = text;
|
|
60
|
+
surface.setStatus(STATUS_KEY, text);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
options.store.subscribe(() => refresh());
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
bind(bound: StatusSurface): void {
|
|
67
|
+
surface = bound;
|
|
68
|
+
written = undefined;
|
|
69
|
+
refresh();
|
|
70
|
+
},
|
|
71
|
+
refresh,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The segment text, or undefined when no background Run is live.
|
|
77
|
+
*
|
|
78
|
+
* The cost is the sum over every live background Run, and it carries
|
|
79
|
+
* `+incomplete` when any of those Runs reports its cost as a floor (ADR-0012).
|
|
80
|
+
*/
|
|
81
|
+
function statusText(options: BackgroundStatusOptions): string | undefined {
|
|
82
|
+
let count = 0;
|
|
83
|
+
let cost: number | null = null;
|
|
84
|
+
let incomplete = false;
|
|
85
|
+
for (const run of options.registry.live) {
|
|
86
|
+
if (options.store.kind(run.id) !== "background") continue;
|
|
87
|
+
count += 1;
|
|
88
|
+
const summary = run.summary;
|
|
89
|
+
if (summary.cost !== null) cost = (cost ?? 0) + summary.cost;
|
|
90
|
+
if (summary.incomplete) incomplete = true;
|
|
91
|
+
}
|
|
92
|
+
if (count === 0) return undefined;
|
|
93
|
+
return `${STATUS_KEY}: ${count} live · ${costText(cost, incomplete)}`;
|
|
94
|
+
}
|
package/src/fake-extension-ui.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A fully typed `ExtensionUIContext` for the tui-mode test context.
|
|
3
3
|
*
|
|
4
|
-
* `custom`, `notify`, `editor`, `setWidget`, and a scripted
|
|
4
|
+
* `custom`, `notify`, `editor`, `setWidget`, `setStatus`, and a scripted
|
|
5
|
+
* `select` work;
|
|
5
6
|
* every other capability throws, so a dependency a view is not supposed to
|
|
6
7
|
* have fails loudly. The
|
|
7
8
|
* class implements the interface, so pi's UI surface is checked at compile
|
|
@@ -54,6 +55,8 @@ export class FakeExtensionUi implements ExtensionUIContext {
|
|
|
54
55
|
readonly edits: OpenedEditor[] = [];
|
|
55
56
|
/** The latest frame per widget key; `undefined` means the key was cleared. */
|
|
56
57
|
readonly widgets = new Map<string, string[] | undefined>();
|
|
58
|
+
/** The latest text per footer key; `undefined` means the key was cleared. */
|
|
59
|
+
readonly statuses = new Map<string, string | undefined>();
|
|
57
60
|
/** Answers `select` in order; an empty queue answers `undefined`, i.e. esc. */
|
|
58
61
|
readonly selections: string[] = [];
|
|
59
62
|
/** The component `custom` opened, once a Run opened one. */
|
|
@@ -116,8 +119,8 @@ export class FakeExtensionUi implements ExtensionUIContext {
|
|
|
116
119
|
onTerminalInput(): () => void {
|
|
117
120
|
return unavailable("ui.onTerminalInput");
|
|
118
121
|
}
|
|
119
|
-
setStatus(): void {
|
|
120
|
-
|
|
122
|
+
setStatus(key: string, text: string | undefined): void {
|
|
123
|
+
this.statuses.set(key, text);
|
|
121
124
|
}
|
|
122
125
|
setWorkingMessage(): void {
|
|
123
126
|
unavailable("ui.setWorkingMessage");
|
package/src/fake-tui.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fully typed `TUI` and `Terminal` doubles for the tui-mode test context.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The Run view uses three capabilities only: `terminal.rows`,
|
|
5
5
|
* `terminal.columns`, and `requestRender()`. Every other member of `Terminal`
|
|
6
6
|
* and `TUI` — fields and getters included — throws on access, so a component
|
|
7
7
|
* that starts to depend on the terminal fails loudly in the test instead of
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { createBackgroundStatus } from "./background-status.ts";
|
|
3
3
|
import { createDescribeTool } from "./describe-tool.ts";
|
|
4
4
|
import { findProgramDirectories } from "./program-directories.ts";
|
|
5
5
|
import { resolveBun } from "./resolve-bun.ts";
|
|
@@ -36,12 +36,12 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
36
36
|
// Host Session keeps every Run id addressable (ticket 02).
|
|
37
37
|
const registry = new RunRegistry({ store: new RunStore() });
|
|
38
38
|
await registry.restore();
|
|
39
|
-
// The renderer projections and the
|
|
39
|
+
// The renderer projections and the background footer segment both live for
|
|
40
40
|
// the session, beside the registry, so a background Run keeps a tree.
|
|
41
41
|
const store = new RunTreeStore();
|
|
42
|
-
const
|
|
42
|
+
const status = createBackgroundStatus({ registry, store });
|
|
43
43
|
pi.registerTool(
|
|
44
|
-
createRunTool({ bun, cli, registry, store,
|
|
44
|
+
createRunTool({ bun, cli, registry, store, status, sendMessage: pi.sendMessage.bind(pi) }),
|
|
45
45
|
);
|
|
46
46
|
pi.registerTool(createStatusTool(registry));
|
|
47
47
|
pi.registerMessageRenderer("yaag-run-complete", createRunCompleteRenderer());
|
|
@@ -58,7 +58,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
58
58
|
|
|
59
59
|
pi.on("session_start", (_event, ctx) => {
|
|
60
60
|
if (bun === null) ctx.ui.notify(report, "error");
|
|
61
|
-
if (ctx.hasUI)
|
|
61
|
+
if (ctx.hasUI) status.bind(ctx.ui);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Commands and tools have separate namespaces: this CLI-resolution command
|
package/src/run-argv.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** What a Run runs: a program file, or Inline Program source text (ADR-0033). */
|
|
2
|
+
export type ProgramTarget =
|
|
3
|
+
| { readonly kind: "file"; readonly file: string }
|
|
4
|
+
| { readonly kind: "inline"; readonly source: string };
|
|
5
|
+
|
|
6
|
+
/** The parts of a Run the CLI argv carries. */
|
|
7
|
+
export interface RunArgvOptions {
|
|
8
|
+
readonly program: ProgramTarget;
|
|
9
|
+
/** Opaque JSON, forwarded untouched: the program validates it (ADR-0010). */
|
|
10
|
+
readonly args?: string;
|
|
11
|
+
/** Cassette path to write, forwarded as `--record` (ADR-0013). */
|
|
12
|
+
readonly record?: string;
|
|
13
|
+
/** Cassette path to resume from, forwarded as `--resume` (ADR-0014). */
|
|
14
|
+
readonly resume?: string;
|
|
15
|
+
/** The descriptor Run Lifecycle Events travel on; 3 by default (ADR-0016). */
|
|
16
|
+
readonly eventsFd?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds the CLI argv for one Run.
|
|
21
|
+
*
|
|
22
|
+
* An Inline Program travels as one `--eval` argument holding the source text
|
|
23
|
+
* verbatim (ADR-0033); this function never quotes, escapes or validates it —
|
|
24
|
+
* the CLI owns the closed import contract.
|
|
25
|
+
*/
|
|
26
|
+
export function runArgv(options: RunArgvOptions): readonly string[] {
|
|
27
|
+
const { program } = options;
|
|
28
|
+
const argv = program.kind === "file" ? ["run", program.file] : ["run", "--eval", program.source];
|
|
29
|
+
argv.push("--events-fd", String(options.eventsFd ?? 3));
|
|
30
|
+
if (options.args !== undefined) argv.push("--args", options.args);
|
|
31
|
+
if (options.record !== undefined) argv.push("--record", options.record);
|
|
32
|
+
if (options.resume !== undefined) argv.push("--resume", options.resume);
|
|
33
|
+
return argv;
|
|
34
|
+
}
|
package/src/run-foreground.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The two blocking `yaag_run` paths (
|
|
3
|
-
* client gets, and the
|
|
2
|
+
* The two blocking `yaag_run` paths (architecture §9): the details-only stream every
|
|
3
|
+
* client gets, and the inline frame a tui-mode Host Session also draws.
|
|
4
4
|
*
|
|
5
|
-
* Both return the same model-visible contract, so
|
|
6
|
-
*
|
|
5
|
+
* Both return the same model-visible contract, so drawing the frame changes
|
|
6
|
+
* nothing the model can see, and neither path opens a modal: the Host Session
|
|
7
|
+
* keeps its input loop while the tool call blocks.
|
|
7
8
|
*/
|
|
8
9
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type
|
|
10
|
+
import { renderInlineRun, type TreeState } from "@yaag/tui";
|
|
10
11
|
import type { RunDetails } from "./run-details.ts";
|
|
11
|
-
import { openRunTreeView } from "./run-foreground-view.ts";
|
|
12
12
|
import type { LiveRun, RunRegistry, RunSettlement } from "./run-registry.ts";
|
|
13
|
-
import { failure, observedSettlement, toError
|
|
13
|
+
import { failure, observedSettlement, toError } from "./run-settlement.ts";
|
|
14
14
|
import type { RunTreeStore } from "./run-trees.ts";
|
|
15
15
|
import { toUsage } from "./usage.ts";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The pi widget key one blocking Run draws under.
|
|
19
|
+
*
|
|
20
|
+
* The key carries the Run id, because pi executes sibling tool calls
|
|
21
|
+
* concurrently: two blocking Runs keep two frames, and each one clears only
|
|
22
|
+
* its own.
|
|
23
|
+
*/
|
|
24
|
+
export function foregroundWidgetKey(id: string): string {
|
|
25
|
+
return `yaag-run:${id}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Line budget for the inline frame; pi truncates a widget past 10 lines. */
|
|
29
|
+
const WIDGET_MAX_LINES = 10;
|
|
30
|
+
|
|
31
|
+
/** Width the inline frame is clamped to; pi wraps nothing for a widget. */
|
|
32
|
+
const WIDGET_WIDTH = 100;
|
|
33
|
+
|
|
17
34
|
/** What `execute` returns for a blocking Run. */
|
|
18
35
|
export interface ForegroundResult {
|
|
19
36
|
content: { type: "text"; text: string }[];
|
|
@@ -34,25 +51,23 @@ export interface ForegroundOptions {
|
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
/**
|
|
37
|
-
* `ForegroundOptions` plus what
|
|
54
|
+
* `ForegroundOptions` plus what drawing the inline frame needs.
|
|
38
55
|
*
|
|
39
|
-
* Only valid when `
|
|
40
|
-
*
|
|
56
|
+
* Only valid when `inlineAvailable()` is true; `ctx.ui` is touched on no other
|
|
57
|
+
* path.
|
|
41
58
|
*/
|
|
42
|
-
export interface
|
|
59
|
+
export interface InlineOptions extends ForegroundOptions {
|
|
43
60
|
readonly ctx: ExtensionContext;
|
|
44
|
-
/** Ingested from Run start, so the
|
|
61
|
+
/** Ingested from Run start by the caller, so the frame misses no early Ask. */
|
|
45
62
|
readonly state: TreeState;
|
|
46
|
-
/** Detaching hands the Run to the
|
|
63
|
+
/** Detaching hands the Run to the footer segment. */
|
|
47
64
|
readonly store: RunTreeStore;
|
|
48
65
|
/** Registers the `yaag-run-complete` follow-up; called only on detach. */
|
|
49
66
|
announce(): void;
|
|
50
|
-
/** Receives the redraw handle, so a pushed fd 3 update can reach the view. */
|
|
51
|
-
onOpen?(view: { touch(): void }): void;
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
/** Whether this call may
|
|
55
|
-
export function
|
|
69
|
+
/** Whether this call may draw the inline frame; tui mode only (architecture §9). */
|
|
70
|
+
export function inlineAvailable(ctx: ExtensionContext, background: boolean): boolean {
|
|
56
71
|
return !background && ctx.mode === "tui" && ctx.hasUI;
|
|
57
72
|
}
|
|
58
73
|
|
|
@@ -77,63 +92,95 @@ export async function foregroundResult(options: ForegroundOptions): Promise<Fore
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
/**
|
|
80
|
-
* The
|
|
81
|
-
*
|
|
95
|
+
* The inline path: a read-only frame of the Run tree is drawn beside the chat
|
|
96
|
+
* while the tool call blocks, and the frame is cleared when the call returns.
|
|
82
97
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
98
|
+
* `esc` aborts the tool call, and an abort detaches rather than stops: the Run
|
|
99
|
+
* becomes a background Run, joins the footer segment, and its completion
|
|
100
|
+
* arrives through the `yaag-run-complete` follow-up. Stopping a Run is `/yaag`
|
|
101
|
+
* → `ctrl+q` → Stop Run (ADR-0008).
|
|
87
102
|
*/
|
|
88
|
-
export async function
|
|
89
|
-
options: InteractiveOptions,
|
|
90
|
-
): Promise<ForegroundResult> {
|
|
103
|
+
export async function foregroundInline(options: InlineOptions): Promise<ForegroundResult> {
|
|
91
104
|
const { run, registry, signal, ctx } = options;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
const frame = createFrame(ctx, run.id, options.state);
|
|
106
|
+
let detached = false;
|
|
107
|
+
// Resolved by the detach itself, so the abort needs no second listener and
|
|
108
|
+
// leaves nothing registered on a signal the tool call outlives.
|
|
109
|
+
let onDetached: () => void = () => {};
|
|
110
|
+
const detaching = new Promise<undefined>((resolve) => {
|
|
111
|
+
onDetached = () => resolve(undefined);
|
|
98
112
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
const detach = (): void => {
|
|
114
|
+
if (detached) return;
|
|
115
|
+
detached = true;
|
|
116
|
+
options.store.adopt(run.id);
|
|
117
|
+
options.announce();
|
|
118
|
+
onDetached();
|
|
103
119
|
};
|
|
104
|
-
signal?.addEventListener("abort",
|
|
105
|
-
|
|
120
|
+
signal?.addEventListener("abort", detach, { once: true });
|
|
121
|
+
const unsubscribe = options.store.subscribe((id) => {
|
|
122
|
+
if (id === run.id && !detached) frame.draw();
|
|
123
|
+
});
|
|
124
|
+
frame.draw();
|
|
106
125
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
if (signal?.aborted === true) detach();
|
|
127
|
+
if (detached) return detachedResult(run);
|
|
109
128
|
const settled = observedSettlement(run.id, run.outcome, registry).then(
|
|
110
|
-
(settlement):
|
|
111
|
-
(reason):
|
|
129
|
+
(settlement): RunSettlement => settlement,
|
|
130
|
+
(reason): RunSettlement => ({ kind: "rejected", reason }),
|
|
112
131
|
);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
// The detach resolves this race, so the tool call returns while the Run
|
|
133
|
+
// keeps executing; `settled` stays a handled promise either way.
|
|
134
|
+
const settlement = await Promise.race([settled, detaching]);
|
|
135
|
+
// `detached` is checked beside the settlement: an abort that lands in the
|
|
136
|
+
// same synchronous stretch as the settlement already announced the Run, and
|
|
137
|
+
// returning its value here would report the same Run to the model twice.
|
|
138
|
+
if (settlement === undefined || detached) {
|
|
139
|
+
detach();
|
|
119
140
|
return detachedResult(run);
|
|
120
141
|
}
|
|
121
|
-
|
|
122
|
-
const exit = await view.exit;
|
|
123
|
-
if (exit === "detached") {
|
|
124
|
-
options.announce();
|
|
125
|
-
return detachedResult(run);
|
|
126
|
-
}
|
|
127
|
-
if (signal?.aborted === true) throw new Error("yaag_run: cancelled");
|
|
128
|
-
return finalResult(run.id, await settled.then(({ settlement }) => settlement));
|
|
142
|
+
return finalResult(run.id, settlement);
|
|
129
143
|
} finally {
|
|
130
|
-
signal?.removeEventListener("abort",
|
|
131
|
-
|
|
132
|
-
|
|
144
|
+
signal?.removeEventListener("abort", detach);
|
|
145
|
+
unsubscribe();
|
|
146
|
+
frame.clear();
|
|
133
147
|
}
|
|
134
148
|
}
|
|
135
149
|
|
|
136
|
-
|
|
150
|
+
/** One inline frame: a redraw and a clear, both idempotent after the clear. */
|
|
151
|
+
interface InlineFrame {
|
|
152
|
+
draw(): void;
|
|
153
|
+
clear(): void;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createFrame(ctx: ExtensionContext, id: string, state: TreeState): InlineFrame {
|
|
157
|
+
const key = foregroundWidgetKey(id);
|
|
158
|
+
let cleared = false;
|
|
159
|
+
let drawn: readonly string[] | undefined;
|
|
160
|
+
return {
|
|
161
|
+
draw(): void {
|
|
162
|
+
if (cleared) return;
|
|
163
|
+
const lines = renderInlineRun(state, {
|
|
164
|
+
now: Date.now(),
|
|
165
|
+
width: WIDGET_WIDTH,
|
|
166
|
+
label: id,
|
|
167
|
+
maxLines: WIDGET_MAX_LINES,
|
|
168
|
+
});
|
|
169
|
+
if (drawn !== undefined && sameLines(drawn, lines)) return;
|
|
170
|
+
drawn = lines;
|
|
171
|
+
ctx.ui.setWidget(key, [...lines]);
|
|
172
|
+
},
|
|
173
|
+
clear(): void {
|
|
174
|
+
if (cleared) return;
|
|
175
|
+
cleared = true;
|
|
176
|
+
ctx.ui.setWidget(key, undefined);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function sameLines(left: readonly string[], right: readonly string[]): boolean {
|
|
182
|
+
return left.length === right.length && left.every((line, index) => line === right[index]);
|
|
183
|
+
}
|
|
137
184
|
|
|
138
185
|
function finalResult(id: string, settlement: RunSettlement): ForegroundResult {
|
|
139
186
|
if (settlement.kind === "rejected") throw toError(settlement.reason);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { sanitizeTerminalLine } from "@yaag/tui";
|
|
4
|
+
import type { ProgramTarget } from "./run-argv.ts";
|
|
5
|
+
|
|
6
|
+
/** The `yaag_run` parameters that name the program; exactly one is given. */
|
|
7
|
+
export interface ProgramParams {
|
|
8
|
+
readonly file?: string;
|
|
9
|
+
readonly script?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const LABEL_LIMIT = 60;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the program the caller named to a {@link ProgramTarget}.
|
|
16
|
+
*
|
|
17
|
+
* `file` and `script` are exactly-one-of (ADR-0033): a violation throws a
|
|
18
|
+
* parameter error, so the refusal happens before any process starts. A `script`
|
|
19
|
+
* touches no filesystem; the CLI writes and owns its temp module.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveProgramTarget(params: ProgramParams): Promise<ProgramTarget> {
|
|
22
|
+
const { file, script } = params;
|
|
23
|
+
if (file !== undefined && script !== undefined) {
|
|
24
|
+
throw new Error("yaag_run: give file or script, and not both");
|
|
25
|
+
}
|
|
26
|
+
if (script !== undefined) {
|
|
27
|
+
if (script.trim() === "") throw new Error("yaag_run: script is empty");
|
|
28
|
+
return { kind: "inline", source: script };
|
|
29
|
+
}
|
|
30
|
+
if (file === undefined) throw new Error("yaag_run: give file or script");
|
|
31
|
+
const resolved = resolve(file);
|
|
32
|
+
try {
|
|
33
|
+
await access(resolved);
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error(`yaag_run: no such Orchestration Program: ${resolved}`);
|
|
36
|
+
}
|
|
37
|
+
return { kind: "file", file: resolved };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A one-line label for the call render. Total by design: the render runs before
|
|
42
|
+
* `execute` validates, so an invalid parameter pair must still produce a label.
|
|
43
|
+
*
|
|
44
|
+
* An Inline Program shows its declared name, else its first line — never a temp
|
|
45
|
+
* path, which exists only inside the CLI.
|
|
46
|
+
*/
|
|
47
|
+
export function programLabel(params: ProgramParams): string {
|
|
48
|
+
const { file, script } = params;
|
|
49
|
+
if (file !== undefined && script !== undefined) return "(invalid call)";
|
|
50
|
+
if (script !== undefined) return truncate(inlineLabel(script));
|
|
51
|
+
if (file !== undefined) return file;
|
|
52
|
+
return "(invalid call)";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function inlineLabel(source: string): string {
|
|
56
|
+
const declared = /name:\s*["'`]([^"'`]+)/.exec(source);
|
|
57
|
+
const line = declared?.[1] ?? source.split("\n").find((one) => one.trim() !== "") ?? "";
|
|
58
|
+
return sanitizeTerminalLine(line).replace(/\s+/g, " ").trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function truncate(label: string): string {
|
|
62
|
+
if (label === "") return "(empty script)";
|
|
63
|
+
return label.length <= LABEL_LIMIT ? label : `${label.slice(0, LABEL_LIMIT)}…`;
|
|
64
|
+
}
|
package/src/run-record.ts
CHANGED
|
@@ -18,7 +18,14 @@ export type PersistedOutcome =
|
|
|
18
18
|
|
|
19
19
|
/** What starting a Run said about it; enough to describe it in a later session. */
|
|
20
20
|
export interface RunLaunch {
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Exactly one of `file` and `script` is present: `file` for a program file,
|
|
23
|
+
* `script` for an Inline Program (ADR-0033). The pair stays a tolerant
|
|
24
|
+
* optional pair rather than a union, because a record written by an older
|
|
25
|
+
* yaag must still narrow.
|
|
26
|
+
*/
|
|
27
|
+
readonly file?: string;
|
|
28
|
+
readonly script?: string;
|
|
22
29
|
readonly args?: string;
|
|
23
30
|
readonly record?: string;
|
|
24
31
|
readonly resume?: string;
|
|
@@ -82,7 +89,10 @@ function isState(value: unknown): value is PersistedRunState {
|
|
|
82
89
|
|
|
83
90
|
function isLaunch(value: unknown): value is RunLaunch {
|
|
84
91
|
return (
|
|
85
|
-
typeof value === "object" &&
|
|
92
|
+
typeof value === "object" &&
|
|
93
|
+
value !== null &&
|
|
94
|
+
(("file" in value && typeof value.file === "string") ||
|
|
95
|
+
("script" in value && typeof value.script === "string"))
|
|
86
96
|
);
|
|
87
97
|
}
|
|
88
98
|
|
package/src/run-settlement.ts
CHANGED
|
@@ -26,7 +26,7 @@ export async function observedSettlement(
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* The Result region's content for a settled Run, shared by the
|
|
29
|
+
* The Result region's content for a settled Run, shared by the `/yaag` view
|
|
30
30
|
* and `/yaag`'s settled background view.
|
|
31
31
|
*/
|
|
32
32
|
export function viewResult(settlement: RunSettlement): RunViewResult {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import
|
|
3
|
+
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
4
|
+
import { initialSummary } from "@yaag/runtime";
|
|
5
|
+
import type { BackgroundStatus } from "./background-status.ts";
|
|
6
|
+
import { fakeTheme } from "./fake-theme.ts";
|
|
4
7
|
import { resolveBun } from "./resolve-bun.ts";
|
|
5
8
|
import { resolveCliEntry } from "./resolve-cli.ts";
|
|
6
9
|
import type { RunDetails } from "./run-details.ts";
|
|
7
10
|
import { RunRegistry } from "./run-registry.ts";
|
|
8
11
|
import { createRunTool, type SendMessage } from "./run-tool.ts";
|
|
9
12
|
import type { RunTreeStore } from "./run-trees.ts";
|
|
10
|
-
import type { RunHandle } from "./spawn-run.ts";
|
|
13
|
+
import type { RunHandle, StartRunOptions } from "./spawn-run.ts";
|
|
11
14
|
import { createStopTool, type StopDetails } from "./stop-tool.ts";
|
|
12
15
|
import { TestExtensionContext } from "./test-extension-context.ts";
|
|
13
16
|
|
|
@@ -19,9 +22,60 @@ const ctx = new TestExtensionContext(dirname(cli));
|
|
|
19
22
|
export const fixture = (name: string): string => join(dirname(cli), "fixtures", `${name}.ts`);
|
|
20
23
|
|
|
21
24
|
export interface RunParams {
|
|
22
|
-
readonly file
|
|
25
|
+
readonly file?: string;
|
|
26
|
+
readonly script?: string;
|
|
23
27
|
readonly args?: string;
|
|
24
28
|
readonly background?: boolean;
|
|
29
|
+
readonly record?: string;
|
|
30
|
+
readonly resume?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Renders the tool's call line, stripped of styling, as the transcript shows it. */
|
|
34
|
+
export function renderCallLabel(params: RunParams): string {
|
|
35
|
+
const tool = createRunTool({
|
|
36
|
+
bun,
|
|
37
|
+
cli,
|
|
38
|
+
registry: new RunRegistry(),
|
|
39
|
+
sendMessage: () => undefined,
|
|
40
|
+
});
|
|
41
|
+
const component = tool.renderCall?.(params, fakeTheme(), {
|
|
42
|
+
args: params,
|
|
43
|
+
toolCallId: "call-1",
|
|
44
|
+
invalidate: () => {},
|
|
45
|
+
lastComponent: undefined,
|
|
46
|
+
state: undefined,
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
executionStarted: false,
|
|
49
|
+
argsComplete: true,
|
|
50
|
+
isPartial: false,
|
|
51
|
+
expanded: false,
|
|
52
|
+
showImages: false,
|
|
53
|
+
isError: false,
|
|
54
|
+
});
|
|
55
|
+
return stripTerminalSequences(component?.render(80)[0] ?? "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A start seam that never spawns: it captures its options and ends the Run at once. */
|
|
59
|
+
export function fakeStart(stdout = "{}"): {
|
|
60
|
+
readonly start: (options: StartRunOptions) => RunHandle;
|
|
61
|
+
readonly seen: StartRunOptions[];
|
|
62
|
+
} {
|
|
63
|
+
const seen: StartRunOptions[] = [];
|
|
64
|
+
return {
|
|
65
|
+
seen,
|
|
66
|
+
start: (options) => {
|
|
67
|
+
seen.push(options);
|
|
68
|
+
return {
|
|
69
|
+
stop: () => {},
|
|
70
|
+
outcome: Promise.resolve({
|
|
71
|
+
code: 0,
|
|
72
|
+
stdout,
|
|
73
|
+
stderr: "",
|
|
74
|
+
summary: initialSummary(),
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
25
79
|
}
|
|
26
80
|
|
|
27
81
|
export interface Sent {
|
|
@@ -40,10 +94,10 @@ export async function run(
|
|
|
40
94
|
readonly registry?: RunRegistry;
|
|
41
95
|
readonly sent?: Sent[];
|
|
42
96
|
readonly start?: (options: import("./spawn-run.ts").StartRunOptions) => RunHandle;
|
|
43
|
-
/** A tui-mode context, for the
|
|
97
|
+
/** A tui-mode context, for the inline foreground path. */
|
|
44
98
|
readonly ctx?: ExtensionContext;
|
|
45
99
|
readonly store?: RunTreeStore;
|
|
46
|
-
readonly
|
|
100
|
+
readonly status?: BackgroundStatus;
|
|
47
101
|
} = {},
|
|
48
102
|
): Promise<{
|
|
49
103
|
readonly text: string;
|
|
@@ -56,7 +110,7 @@ export async function run(
|
|
|
56
110
|
registry: opts.registry ?? new RunRegistry(),
|
|
57
111
|
...(opts.start === undefined ? {} : { start: opts.start }),
|
|
58
112
|
...(opts.store === undefined ? {} : { store: opts.store }),
|
|
59
|
-
...(opts.
|
|
113
|
+
...(opts.status === undefined ? {} : { status: opts.status }),
|
|
60
114
|
sendMessage: (message, options) =>
|
|
61
115
|
void opts.sent?.push({
|
|
62
116
|
content: message.content,
|
package/src/run-tool.ts
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
2
|
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
4
|
import { initialSummary } from "@yaag/runtime";
|
|
6
5
|
import { Type } from "typebox";
|
|
7
|
-
import { type
|
|
6
|
+
import { type BackgroundStatus, createBackgroundStatus } from "./background-status.ts";
|
|
8
7
|
import type { RunDetails } from "./run-details.ts";
|
|
9
|
-
import {
|
|
8
|
+
import { foregroundInline, foregroundResult, inlineAvailable } from "./run-foreground.ts";
|
|
10
9
|
import { RunTreeComponent } from "./run-tree-component.ts";
|
|
11
10
|
import { RunTreeStore } from "./run-trees.ts";
|
|
12
11
|
|
|
13
12
|
export type { RunDetails } from "./run-details.ts";
|
|
14
13
|
|
|
15
14
|
import { type ProcessIdentity, readProcessStart } from "./process-liveness.ts";
|
|
15
|
+
import type { ProgramTarget } from "./run-argv.ts";
|
|
16
16
|
import { mintRunId } from "./run-id.ts";
|
|
17
|
+
import { programLabel, resolveProgramTarget } from "./run-program-param.ts";
|
|
17
18
|
import type { LiveRun, RunRegistry, RunSettlement } from "./run-registry.ts";
|
|
18
19
|
import { errorText, failure, observedSettlement } from "./run-settlement.ts";
|
|
19
20
|
import { type RunHandle, type RunOutcome, type StartRunOptions, startRun } from "./spawn-run.ts";
|
|
20
21
|
import { statusReport } from "./status.ts";
|
|
21
22
|
|
|
22
23
|
const parameters = Type.Object({
|
|
23
|
-
file: Type.
|
|
24
|
+
file: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description: "Path to the Orchestration Program file; use script instead for inline source",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
script: Type.Optional(
|
|
30
|
+
Type.String({
|
|
31
|
+
description:
|
|
32
|
+
'The Orchestration Program source text; it can import "@yaag/runtime" and "typebox" only',
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
24
35
|
args: Type.Optional(
|
|
25
36
|
Type.String({ description: "The program's arguments, as a JSON object string" }),
|
|
26
37
|
),
|
|
@@ -50,8 +61,8 @@ export interface RunToolOptions {
|
|
|
50
61
|
readonly start?: (options: StartRunOptions) => RunHandle;
|
|
51
62
|
/** Per-session renderer projections; defaulted so a test may omit it. */
|
|
52
63
|
readonly store?: RunTreeStore;
|
|
53
|
-
/** The
|
|
54
|
-
readonly
|
|
64
|
+
/** The background footer segment; defaulted so a test may omit it. */
|
|
65
|
+
readonly status?: BackgroundStatus;
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
const DESCRIPTION = [
|
|
@@ -72,6 +83,17 @@ const DESCRIPTION = [
|
|
|
72
83
|
"later message. Every Run gets an id and can be inspected with `yaag_status`;",
|
|
73
84
|
"background Runs may overlap and each is stopped with `yaag_stop({ id })`.",
|
|
74
85
|
"",
|
|
86
|
+
"`script` runs a program that you give as source text. It needs no file.",
|
|
87
|
+
"Give `file` or `script`. Do not give both. A call with both, or with neither,",
|
|
88
|
+
"fails before yaag starts the Run.",
|
|
89
|
+
"A `script` program has the same shape as a file program. It default-exports",
|
|
90
|
+
"`defineRun(...)`.",
|
|
91
|
+
'A `script` program can import "@yaag/runtime" and "typebox" only. Make a file',
|
|
92
|
+
"program if the program needs other modules.",
|
|
93
|
+
"`args`, `background`, `record` and `resume` work the same way for `script`.",
|
|
94
|
+
"Give the same script when you resume a recorded `script` Run.",
|
|
95
|
+
"`yaag_describe` reads a file only.",
|
|
96
|
+
"",
|
|
75
97
|
"Set `record` to write the Run's Cassette artifact. If a recorded Run fails,",
|
|
76
98
|
"pass its artifact as `resume` on the retry: Asks that already succeeded",
|
|
77
99
|
"replay instantly and free, and the Run goes live where it diverges. Record",
|
|
@@ -90,7 +112,7 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
90
112
|
const { bun, cli, registry, sendMessage } = deps;
|
|
91
113
|
const start = deps.start ?? startRun;
|
|
92
114
|
const store = deps.store ?? new RunTreeStore();
|
|
93
|
-
const
|
|
115
|
+
const status = deps.status ?? createBackgroundStatus({ registry, store });
|
|
94
116
|
return {
|
|
95
117
|
name: "yaag_run",
|
|
96
118
|
label: "Run",
|
|
@@ -100,7 +122,8 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
100
122
|
const text =
|
|
101
123
|
context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
102
124
|
text.setText(
|
|
103
|
-
theme.fg("toolTitle", theme.bold("yaag_run")) +
|
|
125
|
+
theme.fg("toolTitle", theme.bold("yaag_run")) +
|
|
126
|
+
theme.fg("muted", `(${programLabel(params)})`),
|
|
104
127
|
);
|
|
105
128
|
return text;
|
|
106
129
|
},
|
|
@@ -115,26 +138,21 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
115
138
|
return tree;
|
|
116
139
|
},
|
|
117
140
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
141
|
+
// Parameter validation precedes the Bun check, so an exactly-one-of
|
|
142
|
+
// violation is refused identically with or without Bun (ADR-0033).
|
|
143
|
+
const program = await resolveProgramTarget(params);
|
|
118
144
|
if (bun === null) throw new Error(statusReport(null, cli));
|
|
119
145
|
|
|
120
|
-
const file = resolve(params.file);
|
|
121
|
-
try {
|
|
122
|
-
await access(file);
|
|
123
|
-
} catch {
|
|
124
|
-
throw new Error(`yaag_run: no such Orchestration Program: ${file}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
146
|
const id = mintRunId();
|
|
128
147
|
const background = params.background === true;
|
|
129
|
-
const
|
|
148
|
+
const inline = inlineAvailable(ctx, background);
|
|
130
149
|
// Attached before start(): the Ask ledger folds from events, so a state
|
|
131
|
-
// created when
|
|
150
|
+
// created when the frame is first drawn would miss every early Ask.
|
|
132
151
|
const state = store.attach(id, background ? "background" : "foreground");
|
|
133
|
-
const tree = interactive ? state : undefined;
|
|
134
152
|
const run = registeredRun({
|
|
135
153
|
bun,
|
|
136
154
|
cli,
|
|
137
|
-
|
|
155
|
+
program,
|
|
138
156
|
args: params.args,
|
|
139
157
|
...cassetteOptions(params),
|
|
140
158
|
id,
|
|
@@ -142,33 +160,28 @@ export function createRunTool(deps: RunToolOptions): ToolDefinition<typeof param
|
|
|
142
160
|
start,
|
|
143
161
|
onUpdate,
|
|
144
162
|
store,
|
|
145
|
-
onIngest: () => view?.touch(),
|
|
146
163
|
});
|
|
147
|
-
let view: { touch(): void } | undefined;
|
|
148
164
|
|
|
149
165
|
if (background) {
|
|
150
166
|
// Attached before returning, so a Run that ends immediately still
|
|
151
167
|
// announces itself (ADR-0005).
|
|
152
|
-
void announce(id, run.outcome, registry, sendMessage,
|
|
153
|
-
|
|
168
|
+
void announce(id, run.outcome, registry, sendMessage, status);
|
|
169
|
+
status.refresh();
|
|
154
170
|
return {
|
|
155
171
|
content: [{ type: "text", text: `Run ${id} started in the background.` }],
|
|
156
172
|
details: { summary: run.summary, id },
|
|
157
173
|
};
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
if (
|
|
161
|
-
return await
|
|
176
|
+
if (!inline) return await foregroundResult({ run, registry, signal });
|
|
177
|
+
return await foregroundInline({
|
|
162
178
|
run,
|
|
163
179
|
registry,
|
|
164
180
|
signal,
|
|
165
181
|
ctx,
|
|
166
|
-
state
|
|
182
|
+
state,
|
|
167
183
|
store,
|
|
168
|
-
announce: () => void announce(id, run.outcome, registry, sendMessage,
|
|
169
|
-
onOpen: (opened) => {
|
|
170
|
-
view = opened;
|
|
171
|
-
},
|
|
184
|
+
announce: () => void announce(id, run.outcome, registry, sendMessage, status),
|
|
172
185
|
});
|
|
173
186
|
},
|
|
174
187
|
};
|
|
@@ -197,11 +210,11 @@ async function announce(
|
|
|
197
210
|
outcome: Promise<RunOutcome>,
|
|
198
211
|
registry: RunRegistry,
|
|
199
212
|
sendMessage: SendMessage,
|
|
200
|
-
|
|
213
|
+
status: BackgroundStatus,
|
|
201
214
|
): Promise<void> {
|
|
202
215
|
const settlement = await observedSettlement(id, outcome, registry);
|
|
203
|
-
// The Run left `registry.live`, so
|
|
204
|
-
|
|
216
|
+
// The Run left `registry.live`, so the footer segment must drop it.
|
|
217
|
+
status.refresh();
|
|
205
218
|
const content = completionContent(id, settlement);
|
|
206
219
|
const details = completionDetails(id, settlement, registry);
|
|
207
220
|
await sendMessage<RunDetails>(
|
|
@@ -213,7 +226,7 @@ async function announce(
|
|
|
213
226
|
function registeredRun(options: {
|
|
214
227
|
readonly bun: string;
|
|
215
228
|
readonly cli: string;
|
|
216
|
-
readonly
|
|
229
|
+
readonly program: ProgramTarget;
|
|
217
230
|
readonly args?: string;
|
|
218
231
|
readonly record?: string;
|
|
219
232
|
readonly resume?: string;
|
|
@@ -223,13 +236,11 @@ function registeredRun(options: {
|
|
|
223
236
|
readonly onUpdate?: (partial: { readonly content: []; readonly details: RunDetails }) => void;
|
|
224
237
|
/** The projection store; it ingests each occurrence exactly once. */
|
|
225
238
|
readonly store: RunTreeStore;
|
|
226
|
-
/** Redraw request for the pushed path: fd 3 fold → ingest → requestRender. */
|
|
227
|
-
readonly onIngest?: () => void;
|
|
228
239
|
}): LiveRun {
|
|
229
240
|
const handle = options.start({
|
|
230
241
|
bun: options.bun,
|
|
231
242
|
cli: options.cli,
|
|
232
|
-
|
|
243
|
+
program: options.program,
|
|
233
244
|
args: options.args,
|
|
234
245
|
record: options.record,
|
|
235
246
|
resume: options.resume,
|
|
@@ -237,7 +248,6 @@ function registeredRun(options: {
|
|
|
237
248
|
options.registry.progress(options.id, summary);
|
|
238
249
|
options.store.ingest(options.id, { summary, event, sequence, id: options.id });
|
|
239
250
|
options.onUpdate?.({ content: [], details: { summary, event, sequence, id: options.id } });
|
|
240
|
-
options.onIngest?.();
|
|
241
251
|
},
|
|
242
252
|
});
|
|
243
253
|
const run: LiveRun = {
|
|
@@ -247,7 +257,9 @@ function registeredRun(options: {
|
|
|
247
257
|
summary: initialSummary(),
|
|
248
258
|
};
|
|
249
259
|
const launch = {
|
|
250
|
-
|
|
260
|
+
...(options.program.kind === "file"
|
|
261
|
+
? { file: options.program.file }
|
|
262
|
+
: { script: options.program.source }),
|
|
251
263
|
...(options.args === undefined ? {} : { args: options.args }),
|
|
252
264
|
...(options.record === undefined ? {} : { record: options.record }),
|
|
253
265
|
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* `yaag_run` tool result or its details-only progress frames.
|
|
4
4
|
*
|
|
5
5
|
* It is not interactive — the rpc-mode and details-only path has no input focus
|
|
6
|
-
* (
|
|
6
|
+
* (architecture §9). The inline frame a blocking Run draws is `run-foreground.ts`, and
|
|
7
|
+
* the interactive view is `/yaag` (`yaag-command.ts`).
|
|
7
8
|
*/
|
|
8
9
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
9
10
|
import { renderTree, TreeState } from "@yaag/tui";
|
package/src/run-tree-host.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The read-only half of `RunTreeViewHost`, adapted from a pi context.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* and `done`.
|
|
4
|
+
* `/yaag` composes it, so the property that a Peek observes and never sends is
|
|
5
|
+
* asserted in one place: nothing here can prompt an Agent. The caller adds
|
|
6
|
+
* `stop` and `done`.
|
|
8
7
|
*/
|
|
9
8
|
import { readFile } from "node:fs/promises";
|
|
10
9
|
import { copyToClipboard, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -22,7 +21,7 @@ export interface RunTreeHostOptions {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/** The read-only capabilities every Run view shares. */
|
|
25
|
-
export type ReadOnlyRunTreeHost = Omit<RunTreeViewHost, "stop" | "
|
|
24
|
+
export type ReadOnlyRunTreeHost = Omit<RunTreeViewHost, "stop" | "done">;
|
|
26
25
|
|
|
27
26
|
/** Builds the read-only host; it never writes to an Agent or to the Run. */
|
|
28
27
|
export function createRunTreeHost(options: RunTreeHostOptions): ReadOnlyRunTreeHost {
|
package/src/run-trees.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `RunRegistry` stays the domain record — ids, stop capabilities, outcomes —
|
|
5
5
|
* and this store holds the bounded renderer-only fold beside it, so the inline
|
|
6
|
-
*
|
|
6
|
+
* frame of a blocking Run and `/yaag` read the same state. A
|
|
7
7
|
* `TreeState` is bounded on every axis (node cap, Ask-ledger pruning, 2-line
|
|
8
8
|
* output tails), so keeping one per Run for the session is bounded memory.
|
|
9
9
|
*/
|
|
10
10
|
import { TreeState, type TreeUpdate } from "@yaag/tui";
|
|
11
11
|
|
|
12
|
-
/** Which surface owns a Run: the blocking tool call, or the
|
|
12
|
+
/** Which surface owns a Run: the blocking tool call, or the background. */
|
|
13
13
|
export type RunKind = "foreground" | "background";
|
|
14
14
|
|
|
15
15
|
interface Entry {
|
|
@@ -49,8 +49,8 @@ export class RunTreeStore {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Converts a foreground Run to a background Run after a detach, then
|
|
52
|
-
* notifies subscribers so the
|
|
53
|
-
*
|
|
52
|
+
* notifies subscribers so the footer segment counts the Run at once, without
|
|
53
|
+
* waiting for a later Lifecycle Event.
|
|
54
54
|
*/
|
|
55
55
|
adopt(id: string): void {
|
|
56
56
|
const found = this.#entries.get(id);
|
package/src/spawn-run.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { applyEvent, initialSummary, type LifecycleEvent, type RunSummary } from "@yaag/runtime";
|
|
2
2
|
import { startCliChild } from "./cli-child.ts";
|
|
3
3
|
import { readEvents } from "./event-reader.ts";
|
|
4
|
+
import { type ProgramTarget, runArgv } from "./run-argv.ts";
|
|
5
|
+
|
|
6
|
+
export type { ProgramTarget } from "./run-argv.ts";
|
|
4
7
|
|
|
5
8
|
/** Everything one Run of the CLI produced, once the child has exited. */
|
|
6
9
|
export interface RunOutcome {
|
|
@@ -15,7 +18,8 @@ export interface RunOutcome {
|
|
|
15
18
|
export interface StartRunOptions {
|
|
16
19
|
readonly bun: string;
|
|
17
20
|
readonly cli: string;
|
|
18
|
-
|
|
21
|
+
/** A program file, or an Inline Program that travels as `--eval` (ADR-0033). */
|
|
22
|
+
readonly program: ProgramTarget;
|
|
19
23
|
/** Opaque JSON, forwarded untouched: the program validates it (ADR-0010). */
|
|
20
24
|
readonly args?: string;
|
|
21
25
|
/** Cassette path to write, forwarded as `--record` (ADR-0013). */
|
|
@@ -50,10 +54,12 @@ export interface RunHandle {
|
|
|
50
54
|
* outcome; callers choose how that failure reaches the Host Session.
|
|
51
55
|
*/
|
|
52
56
|
export function startRun(options: StartRunOptions): RunHandle {
|
|
53
|
-
const argv =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const argv = runArgv({
|
|
58
|
+
program: options.program,
|
|
59
|
+
...(options.args === undefined ? {} : { args: options.args }),
|
|
60
|
+
...(options.record === undefined ? {} : { record: options.record }),
|
|
61
|
+
...(options.resume === undefined ? {} : { resume: options.resume }),
|
|
62
|
+
});
|
|
57
63
|
const child = startCliChild({ bun: options.bun, cli: options.cli, argv, events: true });
|
|
58
64
|
let summary: RunSummary = initialSummary();
|
|
59
65
|
let sequence = 0;
|
package/src/test-tui-context.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A tui-mode ExtensionContext double for the
|
|
2
|
+
* A tui-mode ExtensionContext double for the `/yaag` view and the inline
|
|
3
|
+
* frames of a blocking Run.
|
|
3
4
|
*
|
|
4
5
|
* `ui.custom` invokes the factory synchronously against a fake TUI, so a test
|
|
5
6
|
* can feed key bytes and read rendered frames. `TestExtensionContext` still
|
|
@@ -62,6 +63,11 @@ export class TestTuiContext implements ExtensionContext {
|
|
|
62
63
|
return this.ui.widgets;
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/** The latest footer segment text per key. */
|
|
67
|
+
get statuses(): ReadonlyMap<string, string | undefined> {
|
|
68
|
+
return this.ui.statuses;
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
/** Scripted answers for `ui.select`, consumed in order. */
|
|
66
72
|
get selections(): string[] {
|
|
67
73
|
return this.ui.selections;
|
package/src/yaag-command.ts
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `/yaag` command (
|
|
3
|
-
* interactive tree
|
|
4
|
-
* surface.
|
|
2
|
+
* The `/yaag` command (ADR-0008): pick a Run of this session and open the
|
|
3
|
+
* interactive tree over it.
|
|
5
4
|
*
|
|
6
5
|
* It replaces `/yaag-peek`'s Run→Agent→menu picker chain: every surviving
|
|
7
6
|
* action lives in the tree's per-node menu, and the view stays read-only apart
|
|
8
|
-
* from the Run's own reap-ladder stop.
|
|
9
|
-
* live
|
|
10
|
-
*
|
|
7
|
+
* from the Run's own reap-ladder stop. `esc` at tree level closes the view,
|
|
8
|
+
* live or settled, and a live Run keeps executing after that; `ctrl+q` opens
|
|
9
|
+
* the two-way Stop Run prompt.
|
|
11
10
|
*/
|
|
12
11
|
import type {
|
|
13
12
|
ExtensionContext,
|
|
14
13
|
ExtensionUIContext,
|
|
15
14
|
RegisteredCommand,
|
|
16
15
|
} from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import {
|
|
18
|
-
createRunTreeView,
|
|
19
|
-
type RunTreeViewHost,
|
|
20
|
-
type RunViewExit,
|
|
21
|
-
type RunViewResult,
|
|
22
|
-
TreeState,
|
|
23
|
-
} from "@yaag/tui";
|
|
16
|
+
import { createRunTreeView, type RunTreeViewHost, type RunViewResult, TreeState } from "@yaag/tui";
|
|
24
17
|
import { type PickableRun, pickableRuns, runFromLabel, runPickerLabel } from "./run-picker.ts";
|
|
25
18
|
import type { RunRegistry, RunSettlement } from "./run-registry.ts";
|
|
26
19
|
import { observedSettlement, viewResult } from "./run-settlement.ts";
|
|
@@ -102,17 +95,17 @@ async function openRun(
|
|
|
102
95
|
// neither the view nor the renderer.
|
|
103
96
|
let live = true;
|
|
104
97
|
try {
|
|
105
|
-
await ctx.ui.custom<
|
|
98
|
+
await ctx.ui.custom<void>((tui, _theme, keybindings, done) => {
|
|
106
99
|
const host: RunTreeViewHost = {
|
|
107
100
|
...createRunTreeHost({ ui: ctx.ui, tui, keybindings, state }),
|
|
108
101
|
stop: () => {
|
|
109
102
|
if (found.state === "live") found.run.stop();
|
|
110
103
|
},
|
|
111
|
-
// The Run
|
|
112
|
-
|
|
113
|
-
done,
|
|
104
|
+
// The Run keeps executing after the view closes; there is nothing to
|
|
105
|
+
// hand over, because a Run of this session is already addressable.
|
|
106
|
+
done: () => done(),
|
|
114
107
|
};
|
|
115
|
-
const view = createRunTreeView({ state, host, label: run.id
|
|
108
|
+
const view = createRunTreeView({ state, host, label: run.id });
|
|
116
109
|
if (found.state === "finished")
|
|
117
110
|
settle(state, view, found.run.outcome, options.registry, run.id);
|
|
118
111
|
else if (found.state === "live") {
|
|
@@ -120,7 +113,7 @@ async function openRun(
|
|
|
120
113
|
if (id === run.id && live) view.touch();
|
|
121
114
|
});
|
|
122
115
|
// A view opened while live must reach the settled phase itself, or `esc`
|
|
123
|
-
// would keep live behavior after the Run ended (
|
|
116
|
+
// would keep live behavior after the Run ended (ADR-0008).
|
|
124
117
|
const onSettled = (settlement: RunSettlement): void => {
|
|
125
118
|
if (!live) return;
|
|
126
119
|
settle(state, view, settlement, options.registry, run.id);
|
package/src/background-widget.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The single combined inline widget for every live background Run (spec §1).
|
|
3
|
-
*
|
|
4
|
-
* One pi widget key holds every Run, so a Run starting or ending costs no
|
|
5
|
-
* extra scrollback. The frame is a `string[]`, which pi never routes input to,
|
|
6
|
-
* so the widget consumes no key by construction.
|
|
7
|
-
*/
|
|
8
|
-
import { renderRunsWidget, type WidgetRun } from "@yaag/tui";
|
|
9
|
-
import type { RunRegistry } from "./run-registry.ts";
|
|
10
|
-
import type { RunTreeStore } from "./run-trees.ts";
|
|
11
|
-
|
|
12
|
-
/** The one pi widget key; every Run shares it. */
|
|
13
|
-
export const WIDGET_KEY = "yaag-runs";
|
|
14
|
-
|
|
15
|
-
/** The only pi capability the widget needs. */
|
|
16
|
-
export interface WidgetSurface {
|
|
17
|
-
setWidget(key: string, lines: string[] | undefined): void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* What the combined widget needs: the Run registry it reads live Runs from,
|
|
22
|
-
* the projection store it reads their `TreeState` from, and the render budget.
|
|
23
|
-
*/
|
|
24
|
-
export interface BackgroundWidgetOptions {
|
|
25
|
-
readonly registry: RunRegistry;
|
|
26
|
-
readonly store: RunTreeStore;
|
|
27
|
-
readonly now?: () => number;
|
|
28
|
-
readonly width?: number;
|
|
29
|
-
readonly maxLines?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** The widget controller: bind it once, then refresh it on every occurrence. */
|
|
33
|
-
export interface BackgroundWidget {
|
|
34
|
-
/**
|
|
35
|
-
* Binds the pi UI surface, once the Host Session has one, and draws the
|
|
36
|
-
* current frame. A `WidgetSurface.setWidget()` that throws propagates here.
|
|
37
|
-
*/
|
|
38
|
-
bind(surface: WidgetSurface): void;
|
|
39
|
-
/**
|
|
40
|
-
* Redraws the frame; drops the redraw before `bind`, and skips an unchanged
|
|
41
|
-
* frame. A bound `WidgetSurface.setWidget()` that throws propagates here.
|
|
42
|
-
*/
|
|
43
|
-
refresh(): void;
|
|
44
|
-
/**
|
|
45
|
-
* Unsubscribes from the store and clears the widget. A bound
|
|
46
|
-
* `WidgetSurface.setWidget()` that throws propagates here.
|
|
47
|
-
*/
|
|
48
|
-
dispose(): void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Builds the combined widget over this session's Run registry and projections.
|
|
53
|
-
*
|
|
54
|
-
* An unbound controller drops every refresh, and a Run without a projection is
|
|
55
|
-
* skipped. Construction itself never throws, but `bind()`, `refresh()`, and
|
|
56
|
-
* `dispose()` call the caller's `WidgetSurface.setWidget()` and propagate
|
|
57
|
-
* whatever it throws; the store subscription also propagates it into the
|
|
58
|
-
* `ingest()` that caused the redraw.
|
|
59
|
-
*/
|
|
60
|
-
export function createBackgroundWidget(options: BackgroundWidgetOptions): BackgroundWidget {
|
|
61
|
-
const now = options.now ?? Date.now;
|
|
62
|
-
const width = options.width ?? 100;
|
|
63
|
-
let surface: WidgetSurface | undefined;
|
|
64
|
-
let drawn: readonly string[] | undefined;
|
|
65
|
-
|
|
66
|
-
const entries = (): readonly WidgetRun[] => {
|
|
67
|
-
const runs: WidgetRun[] = [];
|
|
68
|
-
for (const run of options.registry.live) {
|
|
69
|
-
if (options.store.kind(run.id) !== "background") continue;
|
|
70
|
-
const state = options.store.get(run.id);
|
|
71
|
-
if (state !== undefined) runs.push({ id: run.id, state });
|
|
72
|
-
}
|
|
73
|
-
return runs;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const refresh = (): void => {
|
|
77
|
-
if (surface === undefined) return;
|
|
78
|
-
const lines = renderRunsWidget(entries(), {
|
|
79
|
-
now: now(),
|
|
80
|
-
width,
|
|
81
|
-
...(options.maxLines === undefined ? {} : { maxLines: options.maxLines }),
|
|
82
|
-
});
|
|
83
|
-
if (drawn !== undefined && sameLines(drawn, lines)) return;
|
|
84
|
-
drawn = lines;
|
|
85
|
-
surface.setWidget(WIDGET_KEY, lines.length === 0 ? undefined : [...lines]);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const unsubscribe = options.store.subscribe(() => refresh());
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
bind(bound: WidgetSurface): void {
|
|
92
|
-
surface = bound;
|
|
93
|
-
drawn = undefined;
|
|
94
|
-
refresh();
|
|
95
|
-
},
|
|
96
|
-
refresh,
|
|
97
|
-
dispose(): void {
|
|
98
|
-
unsubscribe();
|
|
99
|
-
surface?.setWidget(WIDGET_KEY, undefined);
|
|
100
|
-
surface = undefined;
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function sameLines(left: readonly string[], right: readonly string[]): boolean {
|
|
106
|
-
return left.length === right.length && left.every((line, index) => line === right[index]);
|
|
107
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Opens the interactive Run tree for a blocking `yaag_run` in tui mode
|
|
3
|
-
* (spec §4), and adapts the Host Session's capabilities to
|
|
4
|
-
* `RunTreeViewHost`.
|
|
5
|
-
*
|
|
6
|
-
* The host surface is read-only except for the Run's own reap-ladder stop and
|
|
7
|
-
* the detach request: nothing here can prompt an Agent (ADR — a Peek observes,
|
|
8
|
-
* it never sends).
|
|
9
|
-
*/
|
|
10
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
12
|
-
createRunTreeView,
|
|
13
|
-
type RunTreeView,
|
|
14
|
-
type RunTreeViewHost,
|
|
15
|
-
type RunViewExit,
|
|
16
|
-
type TreeState,
|
|
17
|
-
} from "@yaag/tui";
|
|
18
|
-
import { createRunTreeHost } from "./run-tree-host.ts";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* What `openRunTreeView` needs to adapt one Run to the interactive view.
|
|
22
|
-
*
|
|
23
|
-
* The caller must supply a tui-mode `ctx` with UI; `openRunTreeView` throws
|
|
24
|
-
* when `ctx.ui` is absent, because there is no surface to open.
|
|
25
|
-
*/
|
|
26
|
-
export interface OpenRunTreeViewOptions {
|
|
27
|
-
readonly ctx: ExtensionContext;
|
|
28
|
-
/** Created and ingested from Run start, so no early Ask is missed. */
|
|
29
|
-
readonly state: TreeState;
|
|
30
|
-
readonly id: string;
|
|
31
|
-
/** Runs the reap ladder for this Run (ADR-0008). */
|
|
32
|
-
stop(): void;
|
|
33
|
-
/** Converts the Run to a background Run. */
|
|
34
|
-
onDetach(): void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** The opened view: its exit promise, plus the handles the tool drives. */
|
|
38
|
-
export interface OpenedRunTreeView {
|
|
39
|
-
/** Resolves when the reader dismisses or detaches the view. */
|
|
40
|
-
readonly exit: Promise<RunViewExit>;
|
|
41
|
-
/** The Run settled; freeze the tree and show the Result region. */
|
|
42
|
-
settle(result: Parameters<RunTreeView["settle"]>[0]): void;
|
|
43
|
-
/** A pushed fd 3 update landed in the TreeState; redraw. */
|
|
44
|
-
touch(): void;
|
|
45
|
-
/** Closes the view from the host's side, e.g. on an abort. */
|
|
46
|
-
close(exit: RunViewExit): void;
|
|
47
|
-
dispose(): void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Opens the view through `ctx.ui.custom()`.
|
|
52
|
-
*
|
|
53
|
-
* The returned `exit` promise always settles: the reader's `q`/detach resolves
|
|
54
|
-
* it, and `close` resolves it for a host-side abort, so a tool call can never
|
|
55
|
-
* hang on the view.
|
|
56
|
-
*/
|
|
57
|
-
export function openRunTreeView(options: OpenRunTreeViewOptions): OpenedRunTreeView {
|
|
58
|
-
const state = options.state;
|
|
59
|
-
let view: RunTreeView | undefined;
|
|
60
|
-
let finish: ((exit: RunViewExit) => void) | undefined;
|
|
61
|
-
let pendingExit: RunViewExit | undefined;
|
|
62
|
-
let pendingSettle: Parameters<RunTreeView["settle"]>[0] | undefined;
|
|
63
|
-
|
|
64
|
-
const exit = options.ctx.ui.custom<RunViewExit>((tui, _theme, keybindings, done) => {
|
|
65
|
-
finish = done;
|
|
66
|
-
const host: RunTreeViewHost = {
|
|
67
|
-
...createRunTreeHost({ ui: options.ctx.ui, tui, keybindings, state }),
|
|
68
|
-
stop: options.stop,
|
|
69
|
-
detach: options.onDetach,
|
|
70
|
-
done,
|
|
71
|
-
};
|
|
72
|
-
view = createRunTreeView({ state, host, label: options.id, surface: "foreground" });
|
|
73
|
-
if (pendingSettle !== undefined) view.settle(pendingSettle);
|
|
74
|
-
if (pendingExit !== undefined) done(pendingExit);
|
|
75
|
-
return view;
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
exit,
|
|
80
|
-
settle(result): void {
|
|
81
|
-
if (view === undefined) pendingSettle = result;
|
|
82
|
-
else view.settle(result);
|
|
83
|
-
},
|
|
84
|
-
touch(): void {
|
|
85
|
-
view?.touch();
|
|
86
|
-
},
|
|
87
|
-
close(kind): void {
|
|
88
|
-
if (finish === undefined) pendingExit = kind;
|
|
89
|
-
else finish(kind);
|
|
90
|
-
},
|
|
91
|
-
dispose(): void {
|
|
92
|
-
view?.dispose();
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|