@opencode-ai/simulation 0.0.0-dev-17880

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.
@@ -0,0 +1,90 @@
1
+ import type { CliRenderer } from "@opentui/core";
2
+ import { type MockInput, type MockMouse } from "@opentui/core/testing";
3
+ import { Effect } from "effect";
4
+ import { SimulationProtocol } from "../protocol";
5
+ export type Action = SimulationProtocol.Frontend.Action;
6
+ export type Element = SimulationProtocol.Frontend.Element;
7
+ export interface Harness {
8
+ readonly renderer: CliRenderer;
9
+ readonly mockInput: MockInput;
10
+ readonly mockMouse: MockMouse;
11
+ readonly resize: (cols: number, rows: number) => void;
12
+ readonly renderOnce: () => Promise<void>;
13
+ readonly screen: () => string;
14
+ }
15
+ /**
16
+ * Builds the harness the simulation server drives.
17
+ *
18
+ * When the renderer is the headless simulation renderer, its TestRendererSetup
19
+ * provides the supported testing APIs. For the visible terminal renderer the
20
+ * harness falls back to `requestRender` + `idle` and reading the private
21
+ * `currentRenderBuffer`.
22
+ */
23
+ export declare function createHarness(renderer: CliRenderer): Harness;
24
+ export declare function elements(renderer: CliRenderer): Element[];
25
+ export declare function state(harness: Harness): {
26
+ focused: {
27
+ editor: boolean;
28
+ renderable?: number | undefined;
29
+ };
30
+ elements: SimulationProtocol.Frontend.Element[];
31
+ };
32
+ export declare function snapshot(harness: Harness): SimulationProtocol.Frontend.SemanticSnapshot;
33
+ export declare function matches(harness: Pick<Harness, "screen">, text: string): boolean;
34
+ export declare const capture: (harness: Harness) => Effect.Effect<{
35
+ cols: number;
36
+ rows: number;
37
+ cursor: readonly [0, 0];
38
+ lines: {
39
+ spans: {
40
+ text: string;
41
+ fg: [number, number, number, number];
42
+ bg: [number, number, number, number];
43
+ attributes: number;
44
+ width: number;
45
+ }[];
46
+ }[];
47
+ }, import("effect/Cause").UnknownError, never>;
48
+ export declare const execute: (harness: Harness, action: {
49
+ readonly type: "ui.type";
50
+ readonly text: string;
51
+ } | {
52
+ readonly type: "ui.press";
53
+ readonly key: string;
54
+ readonly modifiers?: {
55
+ readonly shift?: boolean | undefined;
56
+ readonly ctrl?: boolean | undefined;
57
+ readonly meta?: boolean | undefined;
58
+ readonly super?: boolean | undefined;
59
+ readonly hyper?: boolean | undefined;
60
+ } | undefined;
61
+ } | {
62
+ readonly type: "ui.enter";
63
+ } | {
64
+ readonly type: "ui.arrow";
65
+ readonly direction: "up" | "down" | "left" | "right";
66
+ } | {
67
+ readonly type: "ui.focus";
68
+ readonly target: number;
69
+ } | {
70
+ readonly type: "ui.click";
71
+ readonly target: number;
72
+ readonly x: number;
73
+ readonly y: number;
74
+ readonly semantic?: {
75
+ readonly id: string;
76
+ readonly element: number;
77
+ readonly instance?: string | undefined;
78
+ } | undefined;
79
+ } | {
80
+ readonly type: "ui.resize";
81
+ readonly cols: number;
82
+ readonly rows: number;
83
+ }) => Effect.Effect<{
84
+ focused: {
85
+ editor: boolean;
86
+ renderable?: number | undefined;
87
+ };
88
+ elements: SimulationProtocol.Frontend.Element[];
89
+ }, Error, never>;
90
+ export * as SimulationActions from "./actions";
@@ -0,0 +1,154 @@
1
+ import {
2
+ createMockKeys,
3
+ createMockMouse,
4
+ KeyCodes
5
+ } from "@opentui/core/testing";
6
+ import { Effect, Schema } from "effect";
7
+ import { SimulationProtocol } from "../protocol";
8
+ import { SimulationRenderer } from "./renderer";
9
+ import { SimulationSemantics } from "./semantics";
10
+ const decoder = new TextDecoder;
11
+ function isKeyCode(key) {
12
+ return Object.hasOwn(KeyCodes, key);
13
+ }
14
+ function keyInput(key) {
15
+ const named = key.toUpperCase();
16
+ return isKeyCode(named) ? named : key;
17
+ }
18
+ function children(renderable) {
19
+ return renderable.getChildren().filter((child) => ("num" in child));
20
+ }
21
+ function all(renderable) {
22
+ return [renderable, ...children(renderable).flatMap(all)];
23
+ }
24
+ function mouseListeners(renderable) {
25
+ const general = Reflect.get(renderable, "_mouseListener"), specific = Reflect.get(renderable, "_mouseListeners");
26
+ return Boolean(general) || specific && typeof specific === "object" && Object.keys(specific).length > 0;
27
+ }
28
+ function hit(renderer, renderable) {
29
+ if (renderable.width <= 0 || renderable.height <= 0)
30
+ return !1;
31
+ const x = Math.floor(renderable.screenX + renderable.width / 2), y = Math.floor(renderable.screenY + renderable.height / 2), target = renderer.hitTest(x, y);
32
+ return all(renderable).some((item) => item.num === target);
33
+ }
34
+ export function createHarness(renderer) {
35
+ const setup = SimulationRenderer.setupFor(renderer);
36
+ return {
37
+ renderer,
38
+ mockInput: setup?.mockInput ?? createMockKeys(renderer),
39
+ mockMouse: setup?.mockMouse ?? createMockMouse(renderer),
40
+ resize: setup?.resize ?? ((cols, rows) => renderer.resize(cols, rows)),
41
+ renderOnce: setup?.renderOnce ?? (async () => {
42
+ renderer.requestRender();
43
+ await renderer.idle();
44
+ }),
45
+ screen: () => decoder.decode(Reflect.get(renderer, "currentRenderBuffer").getRealCharBytes())
46
+ };
47
+ }
48
+ export function elements(renderer) {
49
+ return all(renderer.root).filter((renderable) => renderable.visible && !renderable.isDestroyed).map((renderable) => {
50
+ const clickable = mouseListeners(renderable) && hit(renderer, renderable);
51
+ return {
52
+ id: renderable.id,
53
+ num: renderable.num,
54
+ x: renderable.screenX,
55
+ y: renderable.screenY,
56
+ width: renderable.width,
57
+ height: renderable.height,
58
+ focusable: renderable.focusable,
59
+ focused: renderable.focused,
60
+ clickable,
61
+ editor: renderer.currentFocusedEditor === renderable
62
+ };
63
+ }).filter((element) => element.focusable || element.clickable || element.editor);
64
+ }
65
+ export function state(harness) {
66
+ const renderable = harness.renderer.currentFocusedRenderable?.num;
67
+ return {
68
+ focused: {
69
+ ...renderable === void 0 ? {} : { renderable },
70
+ editor: Boolean(harness.renderer.currentFocusedEditor)
71
+ },
72
+ elements: elements(harness.renderer)
73
+ };
74
+ }
75
+ export function snapshot(harness) {
76
+ const ids = new Set, visit = (renderable, parent) => {
77
+ if (!renderable.visible || renderable.isDestroyed)
78
+ return [];
79
+ const definition = SimulationSemantics.read(renderable)?.();
80
+ if (definition && ids.has(renderable.id))
81
+ throw Error(`duplicate semantic UI id: ${renderable.id}`);
82
+ if (definition)
83
+ ids.add(renderable.id);
84
+ const node = definition ? [{ id: renderable.id, ...definition, ...parent === void 0 ? {} : { parent }, element: renderable.num }] : [], ancestor = definition ? renderable.id : parent;
85
+ return [...node, ...children(renderable).flatMap((child) => visit(child, ancestor))];
86
+ };
87
+ return Schema.decodeUnknownSync(SimulationProtocol.Frontend.SemanticSnapshot)({
88
+ format: "opencode-ui-snapshot-v1",
89
+ nodes: visit(harness.renderer.root)
90
+ });
91
+ }
92
+ export function matches(harness, text) {
93
+ return harness.screen().includes(text);
94
+ }
95
+ export const capture = Effect.fn("SimulationActions.capture")(function* (harness) {
96
+ yield* Effect.tryPromise(() => harness.renderOnce());
97
+ const buffer = harness.renderer.currentRenderBuffer;
98
+ return {
99
+ cols: buffer.width,
100
+ rows: buffer.height,
101
+ cursor: [0, 0],
102
+ lines: buffer.getSpanLines().map((line) => ({
103
+ spans: line.spans.map((span) => ({
104
+ text: span.text,
105
+ fg: span.fg.toInts(),
106
+ bg: span.bg.toInts(),
107
+ attributes: span.attributes,
108
+ width: span.width
109
+ }))
110
+ }))
111
+ };
112
+ }), execute = Effect.fn("SimulationActions.execute")(function* (harness, action) {
113
+ switch (action.type) {
114
+ case "ui.type":
115
+ yield* Effect.tryPromise(() => harness.mockInput.typeText(action.text));
116
+ break;
117
+ case "ui.press":
118
+ harness.mockInput.pressKey(keyInput(action.key), action.modifiers);
119
+ break;
120
+ case "ui.enter":
121
+ harness.mockInput.pressEnter();
122
+ break;
123
+ case "ui.arrow":
124
+ harness.mockInput.pressArrow(action.direction);
125
+ break;
126
+ case "ui.focus":
127
+ all(harness.renderer.root).find((item) => item.num === action.target)?.focus();
128
+ break;
129
+ case "ui.click": {
130
+ const target = all(harness.renderer.root).find((item) => item.num === action.target);
131
+ if (!target || !target.visible || target.isDestroyed)
132
+ return yield* Effect.fail(Error(`click target is stale or unavailable: ${action.target}`));
133
+ if (action.semantic) {
134
+ const current = snapshot(harness).nodes.find((node) => node.element === action.target);
135
+ if (current?.id !== action.semantic.id || current.instance !== action.semantic.instance || current.element !== action.semantic.element)
136
+ return yield* Effect.fail(Error(`semantic click target is stale or unavailable: ${action.semantic.id}`));
137
+ }
138
+ if (!Number.isFinite(action.x) || action.x < 0 || action.x >= target.width || !Number.isFinite(action.y) || action.y < 0 || action.y >= target.height)
139
+ return yield* Effect.fail(Error("click position must be within the target element"));
140
+ yield* Effect.tryPromise(() => harness.mockMouse.click(target.screenX + action.x, target.screenY + action.y));
141
+ break;
142
+ }
143
+ case "ui.resize":
144
+ if (!Number.isSafeInteger(action.cols) || action.cols <= 0 || !Number.isSafeInteger(action.rows) || action.rows <= 0)
145
+ return yield* Effect.fail(Error("resize cols and rows must be positive integers"));
146
+ harness.resize(action.cols, action.rows);
147
+ SimulationRenderer.recordResize(harness.renderer, action.cols, action.rows);
148
+ break;
149
+ }
150
+ yield* Effect.tryPromise(() => harness.renderOnce());
151
+ return state(harness);
152
+ });
153
+
154
+ export * as SimulationActions from "./actions";
@@ -0,0 +1,18 @@
1
+ import type { CliRenderer, CliRendererConfig } from "@opentui/core";
2
+ import { type TestRendererSetup } from "@opentui/core/testing";
3
+ import { Effect } from "effect";
4
+ /**
5
+ * Creates a headless renderer with optional recording: a real CliRenderer
6
+ * backed by an in-memory screen buffer. The TestRendererSetup is kept
7
+ * module-side so the harness can use supported testing APIs without app
8
+ * code carrying it around.
9
+ */
10
+ export interface Viewport {
11
+ readonly cols: number;
12
+ readonly rows: number;
13
+ }
14
+ export declare const create: (options: CliRendererConfig, path?: string | undefined, viewport?: Viewport | undefined) => Effect.Effect<CliRenderer, import("effect/Cause").UnknownError, import("effect/Scope").Scope>;
15
+ export declare function recordResize(renderer: CliRenderer, cols: number, rows: number): void;
16
+ export declare function setupFor(renderer: CliRenderer): TestRendererSetup | undefined;
17
+ export declare function finish(renderer: CliRenderer): Effect.Effect<never, Error, never> | Effect.Effect<string, import("effect/Cause").UnknownError, never>;
18
+ export * as SimulationRenderer from "./renderer";
@@ -0,0 +1,38 @@
1
+ import { createTestRenderer } from "@opentui/core/testing";
2
+ import { Effect } from "effect";
3
+ import { Timeline } from "../recording";
4
+ const setups = new WeakMap, recordings = new WeakMap;
5
+ export const create = Effect.fn("SimulationRenderer.create")(function* (options, path, viewport) {
6
+ const cols = viewport?.cols ?? 100, rows = viewport?.rows ?? 40, recording = path ? yield* Effect.acquireRelease(Effect.tryPromise(() => Timeline.create(path, cols, rows)), (recording) => Effect.tryPromise(() => recording.finish()).pipe(Effect.catch((error) => Effect.sync(() => process.stderr.write(`Failed to finish UI recording: ${error}
7
+ `))))) : void 0, setup = yield* Effect.acquireRelease(Effect.tryPromise(() => createTestRenderer({
8
+ ...options,
9
+ width: cols,
10
+ height: rows,
11
+ kittyKeyboard: Boolean(options.useKittyKeyboard),
12
+ ...recording ? {
13
+ stdout: recording,
14
+ bufferedOutput: "stdout"
15
+ } : {}
16
+ })), (setup) => Effect.sync(() => {
17
+ if (!setup.renderer.isDestroyed)
18
+ setup.renderer.destroy();
19
+ }));
20
+ setups.set(setup.renderer, setup);
21
+ if (recording)
22
+ recordings.set(setup.renderer, recording);
23
+ return setup.renderer;
24
+ });
25
+ export function recordResize(renderer, cols, rows) {
26
+ recordings.get(renderer)?.resize(cols, rows);
27
+ }
28
+ export function setupFor(renderer) {
29
+ return setups.get(renderer);
30
+ }
31
+ export function finish(renderer) {
32
+ const recording = recordings.get(renderer);
33
+ if (!recording)
34
+ return Effect.fail(Error("UI recording is not available"));
35
+ return Effect.tryPromise(() => recording.finish());
36
+ }
37
+
38
+ export * as SimulationRenderer from "./renderer";
@@ -0,0 +1,8 @@
1
+ import type { Renderable } from "@opentui/core";
2
+ import type { SimulationProtocol } from "../protocol";
3
+ export type Definition = Omit<SimulationProtocol.Frontend.SemanticNode, "id" | "element" | "parent">;
4
+ export declare const read: (renderable: Renderable) => (() => Definition) | undefined;
5
+ export declare const SimulationSemantics: {
6
+ bind: (definition: () => Definition) => (renderable: Renderable) => void;
7
+ read: (renderable: Renderable) => (() => Definition) | undefined;
8
+ };
@@ -0,0 +1,7 @@
1
+ const key = Symbol.for("opencode.simulation.semantics"), bind = (definition) => (renderable) => {
2
+ Object.defineProperty(renderable, key, { value: definition, configurable: !0 });
3
+ };
4
+ export const read = (renderable) => {
5
+ const definition = Reflect.get(renderable, key);
6
+ return typeof definition === "function" ? definition : void 0;
7
+ }, SimulationSemantics = { bind, read };
@@ -0,0 +1,6 @@
1
+ import { Effect } from "effect";
2
+ import { SimulationActions } from "./actions";
3
+ export declare const start: (harness: SimulationActions.Harness, endpoint: string, version?: any) => Effect.Effect<{
4
+ url: string;
5
+ }, unknown, import("effect/Scope").Scope>;
6
+ export * as SimulationServer from "./server";
@@ -0,0 +1,64 @@
1
+ import { Effect } from "effect";
2
+ import { SimulationControlServer } from "../control-server";
3
+ import { SimulationProtocol } from "../protocol";
4
+ import { SimulationActions } from "./actions";
5
+ import { SimulationRenderer } from "./renderer";
6
+ function handle(harness, request, version) {
7
+ switch (request.method) {
8
+ case "simulation.handshake":
9
+ return SimulationProtocol.Handshake.dispatch({
10
+ role: "ui",
11
+ server: { name: "opencode", version },
12
+ capabilities: SimulationProtocol.Frontend.Capabilities
13
+ }, request.params);
14
+ case "ui.capture":
15
+ return SimulationActions.capture(harness);
16
+ case "ui.state":
17
+ return Effect.sync(() => SimulationActions.state(harness));
18
+ case "ui.snapshot":
19
+ return Effect.sync(() => SimulationActions.snapshot(harness));
20
+ case "ui.matches":
21
+ return Effect.sync(() => SimulationActions.matches(harness, request.params.text));
22
+ case "ui.recording.finish":
23
+ return SimulationRenderer.finish(harness.renderer);
24
+ case "ui.type":
25
+ return SimulationActions.execute(harness, { type: "ui.type", text: request.params.text });
26
+ case "ui.enter":
27
+ return SimulationActions.execute(harness, { type: "ui.enter" });
28
+ case "ui.press":
29
+ return SimulationActions.execute(harness, {
30
+ type: "ui.press",
31
+ key: request.params.key,
32
+ modifiers: request.params.modifiers
33
+ });
34
+ case "ui.arrow":
35
+ return SimulationActions.execute(harness, { type: "ui.arrow", direction: request.params.direction });
36
+ case "ui.focus":
37
+ return SimulationActions.execute(harness, { type: "ui.focus", target: request.params.target });
38
+ case "ui.click":
39
+ return SimulationActions.execute(harness, {
40
+ type: "ui.click",
41
+ target: request.params.target,
42
+ x: request.params.x,
43
+ y: request.params.y,
44
+ semantic: request.params.semantic
45
+ });
46
+ case "ui.resize":
47
+ return SimulationActions.execute(harness, {
48
+ type: "ui.resize",
49
+ cols: request.params.cols,
50
+ rows: request.params.rows
51
+ });
52
+ }
53
+ }
54
+ export const start = Effect.fn("SimulationServer.start")(function* (harness, endpoint, version = "unknown") {
55
+ return yield* SimulationControlServer.start({
56
+ endpoint,
57
+ label: "opencode drive ui websocket",
58
+ data: () => ({ drive: !0 }),
59
+ decode: SimulationProtocol.Frontend.decodeRequestEffect,
60
+ handle: (_socket, request) => handle(harness, request, version)
61
+ });
62
+ });
63
+
64
+ export * as SimulationServer from "./server";
@@ -0,0 +1,5 @@
1
+ import { type CliRendererConfig } from "@opentui/core";
2
+ import { Effect } from "effect";
3
+ /** Drive-mode renderer and control-server acquisition. */
4
+ export declare const create: (options: CliRendererConfig, version: string) => Effect.Effect<import("@opentui/core").CliRenderer, unknown, import("effect/Scope").Scope | import("effect/FileSystem").FileSystem>;
5
+ export * as Drive from "./simulation";
@@ -0,0 +1,20 @@
1
+ import { createCliRenderer } from "@opentui/core";
2
+ import { Config, Effect } from "effect";
3
+ import { DriveManifest } from "../manifest";
4
+ import { SimulationActions } from "./actions";
5
+ import { SimulationRenderer } from "./renderer";
6
+ import { SimulationServer } from "./server";
7
+ export const create = Effect.fn("Drive.create")(function* (options, version) {
8
+ const headless = (yield* Config.string("OPENCODE_DRIVE_RENDERER").pipe(Config.withDefault("visible"))) === "headless", manifest = yield* DriveManifest.resolve(), renderer = headless ? yield* SimulationRenderer.create(options, manifest.recording?.timeline, manifest.viewport) : yield* Effect.acquireRelease(Effect.tryPromise(() => createCliRenderer(options)), (renderer) => Effect.sync(() => {
9
+ if (!renderer.isDestroyed)
10
+ renderer.destroy();
11
+ }));
12
+ if (!headless && manifest.viewport)
13
+ renderer.resize(manifest.viewport.cols, manifest.viewport.rows);
14
+ const server = yield* SimulationServer.start(SimulationActions.createHarness(renderer), manifest.endpoints.ui, version);
15
+ yield* Effect.sync(() => process.stderr.write(`opencode drive ui websocket: ${server.url}
16
+ `));
17
+ return renderer;
18
+ });
19
+
20
+ export * as Drive from "./simulation";
@@ -0,0 +1,27 @@
1
+ import { Effect, FileSystem, Schema } from "effect";
2
+ export declare const Manifest: Schema.Struct<{
3
+ readonly endpoints: Schema.Struct<{
4
+ readonly ui: Schema.String;
5
+ readonly backend: Schema.String;
6
+ }>;
7
+ readonly viewport: Schema.optionalKey<Schema.Struct<{
8
+ readonly cols: Schema.Int;
9
+ readonly rows: Schema.Int;
10
+ }>>;
11
+ readonly recording: Schema.optionalKey<Schema.Struct<{
12
+ readonly timeline: Schema.String;
13
+ }>>;
14
+ }>;
15
+ export interface Manifest extends Schema.Schema.Type<typeof Manifest> {
16
+ }
17
+ declare const ResolveError_base: Schema.Class<ResolveError, Schema.TaggedStruct<"DriveManifest.ResolveError", {
18
+ readonly reason: Schema.Literals<readonly ["config", "not-found", "read", "decode"]>;
19
+ readonly path: Schema.optionalKey<Schema.String>;
20
+ readonly message: Schema.String;
21
+ readonly cause: Schema.Defect;
22
+ }>, import("effect/Cause").YieldableError>;
23
+ export declare class ResolveError extends ResolveError_base {
24
+ }
25
+ export declare const defaults: Manifest;
26
+ export declare const resolve: () => Effect.Effect<Manifest, ResolveError, FileSystem.FileSystem>;
27
+ export * as DriveManifest from "./manifest";
@@ -0,0 +1,61 @@
1
+ import { homedir } from "node:os";
2
+ import { isAbsolute, join } from "node:path";
3
+ import { Config, Effect, FileSystem, Schema } from "effect";
4
+ import { PositiveInt } from "@opencode-ai/core/schema";
5
+ const InstanceName = Schema.String.check(Schema.makeFilter((value) => /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(value) ? void 0 : "a valid Drive instance name")), Endpoint = Schema.String.check(Schema.makeFilter((value) => {
6
+ if (!URL.canParse(value))
7
+ return "a loopback WebSocket endpoint with an explicit port";
8
+ const endpoint = new URL(value), port = Number(endpoint.port);
9
+ return endpoint.protocol === "ws:" && endpoint.hostname === "127.0.0.1" && Number.isInteger(port) && port >= 1 ? void 0 : "a loopback WebSocket endpoint with an explicit port";
10
+ })), AbsolutePath = Schema.String.check(Schema.makeFilter((value) => isAbsolute(value) ? void 0 : "an absolute path"));
11
+ export const Manifest = Schema.Struct({
12
+ endpoints: Schema.Struct({
13
+ ui: Endpoint,
14
+ backend: Endpoint
15
+ }),
16
+ viewport: Schema.optionalKey(Schema.Struct({
17
+ cols: PositiveInt,
18
+ rows: PositiveInt
19
+ })),
20
+ recording: Schema.optionalKey(Schema.Struct({
21
+ timeline: AbsolutePath
22
+ }))
23
+ });
24
+
25
+ export class ResolveError extends Schema.TaggedError()("DriveManifest.ResolveError", {
26
+ reason: Schema.Literals(["config", "not-found", "read", "decode"]),
27
+ path: Schema.optionalKey(Schema.String),
28
+ message: Schema.String,
29
+ cause: Schema.Defect()
30
+ }) {
31
+ }
32
+ export const defaults = {
33
+ endpoints: {
34
+ ui: "ws://127.0.0.1:40900",
35
+ backend: "ws://127.0.0.1:40950"
36
+ }
37
+ };
38
+ const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest)), configError = (cause) => new ResolveError({
39
+ reason: "config",
40
+ message: `Invalid Drive configuration: ${String(cause)}`,
41
+ cause
42
+ });
43
+ export const resolve = Effect.fn("DriveManifest.resolve")(function* () {
44
+ const name = yield* Config.schema(InstanceName, "OPENCODE_DRIVE").pipe(Effect.mapError(configError));
45
+ if (name === "1")
46
+ return defaults;
47
+ const state = yield* Config.string("XDG_STATE_HOME").pipe(Config.withDefault(join(homedir(), ".local", "state")), Effect.mapError(configError)), directory = yield* Config.string("DRIVE_REGISTRY_DIR").pipe(Config.withDefault(join(state, "opencode-drive", "instances")), Effect.mapError(configError)), file = join(directory, `${name}.json`), contents = yield* (yield* FileSystem.FileSystem).readFileString(file).pipe(Effect.mapError((cause) => new ResolveError({
48
+ reason: cause.reason._tag === "NotFound" ? "not-found" : "read",
49
+ path: file,
50
+ message: cause.reason._tag === "NotFound" ? `Drive manifest not found: ${file}` : `Failed to read Drive manifest: ${file}: ${cause.message}`,
51
+ cause
52
+ })));
53
+ return yield* decode(contents).pipe(Effect.mapError((cause) => new ResolveError({
54
+ reason: "decode",
55
+ path: file,
56
+ message: `Invalid Drive manifest: ${file}: ${cause.message}`,
57
+ cause
58
+ })));
59
+ });
60
+
61
+ export * as DriveManifest from "./manifest";
@@ -0,0 +1,2 @@
1
+ export { Backend, BackendRpcs, Frontend, Handshake, JsonRpc, SimulationRequestError, UiRpcs, } from "@opencode-ai/protocol/simulation";
2
+ export * as SimulationProtocol from "@opencode-ai/protocol/simulation";
@@ -0,0 +1,10 @@
1
+ export {
2
+ Backend,
3
+ BackendRpcs,
4
+ Frontend,
5
+ Handshake,
6
+ JsonRpc,
7
+ SimulationRequestError,
8
+ UiRpcs
9
+ } from "@opencode-ai/protocol/simulation";
10
+ export * as SimulationProtocol from "@opencode-ai/protocol/simulation";
@@ -0,0 +1,65 @@
1
+ import { Writable } from "node:stream";
2
+ import { Schema } from "effect";
3
+ export declare const Header: Schema.Struct<{
4
+ readonly type: Schema.Literal<"header">;
5
+ readonly version: Schema.Literal<1>;
6
+ readonly cols: Schema.Number;
7
+ readonly rows: Schema.Number;
8
+ readonly encoding: Schema.Literal<"base64">;
9
+ }>;
10
+ export interface Header extends Schema.Schema.Type<typeof Header> {
11
+ }
12
+ export declare const Output: Schema.Struct<{
13
+ readonly type: Schema.Literal<"output">;
14
+ readonly at_ms: Schema.Number;
15
+ readonly data: Schema.String;
16
+ }>;
17
+ export interface Output extends Schema.Schema.Type<typeof Output> {
18
+ }
19
+ export declare const Resize: Schema.Struct<{
20
+ readonly type: Schema.Literal<"resize">;
21
+ readonly at_ms: Schema.Number;
22
+ readonly cols: Schema.Number;
23
+ readonly rows: Schema.Number;
24
+ }>;
25
+ export interface Resize extends Schema.Schema.Type<typeof Resize> {
26
+ }
27
+ export declare const Event: Schema.Union<readonly [Schema.Struct<{
28
+ readonly type: Schema.Literal<"header">;
29
+ readonly version: Schema.Literal<1>;
30
+ readonly cols: Schema.Number;
31
+ readonly rows: Schema.Number;
32
+ readonly encoding: Schema.Literal<"base64">;
33
+ }>, Schema.Struct<{
34
+ readonly type: Schema.Literal<"output">;
35
+ readonly at_ms: Schema.Number;
36
+ readonly data: Schema.String;
37
+ }>, Schema.Struct<{
38
+ readonly type: Schema.Literal<"resize">;
39
+ readonly at_ms: Schema.Number;
40
+ readonly cols: Schema.Number;
41
+ readonly rows: Schema.Number;
42
+ }>]>;
43
+ export type Event = Schema.Schema.Type<typeof Event>;
44
+ export declare class Timeline extends Writable {
45
+ readonly isTTY = true;
46
+ readonly path: string;
47
+ readonly columns: number;
48
+ readonly rows: number;
49
+ private readonly output;
50
+ private readonly started;
51
+ private readonly timestamps;
52
+ private done?;
53
+ private constructor();
54
+ static create(path: string, cols: number, rows: number): Promise<Timeline>;
55
+ getColorDepth(): number;
56
+ write(chunk: unknown, callback?: (error?: Error | null) => void): boolean;
57
+ write(chunk: unknown, encoding: BufferEncoding, callback?: (error?: Error | null) => void): boolean;
58
+ _write(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
59
+ _final(callback: (error?: Error | null) => void): void;
60
+ finish(): Promise<string>;
61
+ resize(cols: number, rows: number): void;
62
+ private elapsed;
63
+ private writeOutput;
64
+ }
65
+ export * as SimulationRecording from "./recording";